Compare commits
2 commits
c66026b938
...
c1acd3de87
Author | SHA1 | Date | |
---|---|---|---|
c1acd3de87 | |||
c83cadbb5d |
10
.gitignore
vendored
|
@ -162,3 +162,13 @@ cython_debug/
|
|||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
components
|
||||
|
||||
.vscode/
|
||||
whoosh_index/
|
||||
|
||||
*.pdf
|
||||
*.seg
|
||||
*.sql
|
||||
*.xls
|
||||
*.xlsx
|
||||
|
|
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "ntuh/submodule/ntuhgov"]
|
||||
path = ntuh/submodule/ntuhgov
|
||||
url = ssh://git@forgejo.mdi.bar:2401/xfr/ntuhgov.git
|
29
README
Normal file
|
@ -0,0 +1,29 @@
|
|||
Command line instructions
|
||||
|
||||
Git global setup
|
||||
git config --global user.name "Furen Xiao"
|
||||
git config --global user.email "xfuren@gmail.com"
|
||||
|
||||
Create a new repository
|
||||
git clone git@git126.ntuh.net:xfuren/test.git
|
||||
cd test
|
||||
touch README.md
|
||||
git add README.md
|
||||
git commit -m "add README"
|
||||
git push -u origin master
|
||||
|
||||
Existing folder
|
||||
cd existing_folder
|
||||
git init
|
||||
git remote add origin git@git126.ntuh.net:xfuren/test.git
|
||||
git add .
|
||||
git commit -m "Initial commit"
|
||||
git push -u origin master
|
||||
|
||||
Existing Git repository
|
||||
cd existing_repo
|
||||
git remote rename origin old-origin
|
||||
git remote add origin git@git126.ntuh.net:xfuren/test.git
|
||||
git push -u origin --all
|
||||
git push -u origin --tags
|
||||
|
435
forteo/Untitled.ipynb
Normal file
758
forteo/analysis.ipynb
Normal file
|
@ -0,0 +1,758 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from collections import Counter\n",
|
||||
"\n",
|
||||
"import math\n",
|
||||
"import re\n",
|
||||
"\n",
|
||||
"from pandas import read_excel\n",
|
||||
"from pymongo import MongoClient\n",
|
||||
"from pyquery import PyQuery as pq\n",
|
||||
"from scipy import stats\n",
|
||||
"\n",
|
||||
"import matplotlib\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import pandas as pd"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"SHEETS = (\n",
|
||||
" (\"台灣大學醫學院附設醫院_201601-201809.xls\", \"Sheet1\"), \n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"frames = []"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"for file_name, sheet_name in SHEETS:\n",
|
||||
" data = read_excel(file_name, sheet_name)\n",
|
||||
" frames.append(data)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"df=pd.concat(frames, ignore_index=True, sort=False)\n",
|
||||
"df.to_excel('concat2.xls')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<div>\n",
|
||||
"<style scoped>\n",
|
||||
" .dataframe tbody tr th:only-of-type {\n",
|
||||
" vertical-align: middle;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe tbody tr th {\n",
|
||||
" vertical-align: top;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead th {\n",
|
||||
" text-align: right;\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr style=\"text-align: right;\">\n",
|
||||
" <th></th>\n",
|
||||
" <th>醫院</th>\n",
|
||||
" <th>醫師</th>\n",
|
||||
" <th>系統編號</th>\n",
|
||||
" <th>病患姓名</th>\n",
|
||||
" <th>簽署日</th>\n",
|
||||
" <th>key-in日</th>\n",
|
||||
" <th>患者出生年月日</th>\n",
|
||||
" <th>患者狀況</th>\n",
|
||||
" <th>流失/停藥日期</th>\n",
|
||||
" <th>用藥時間</th>\n",
|
||||
" <th>病患是否參加P1NP</th>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>是否自費</th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>健保給付</th>\n",
|
||||
" <td>80</td>\n",
|
||||
" <td>80</td>\n",
|
||||
" <td>80</td>\n",
|
||||
" <td>80</td>\n",
|
||||
" <td>80</td>\n",
|
||||
" <td>80</td>\n",
|
||||
" <td>80</td>\n",
|
||||
" <td>80</td>\n",
|
||||
" <td>43</td>\n",
|
||||
" <td>80</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>自費</th>\n",
|
||||
" <td>271</td>\n",
|
||||
" <td>271</td>\n",
|
||||
" <td>271</td>\n",
|
||||
" <td>271</td>\n",
|
||||
" <td>271</td>\n",
|
||||
" <td>271</td>\n",
|
||||
" <td>271</td>\n",
|
||||
" <td>271</td>\n",
|
||||
" <td>187</td>\n",
|
||||
" <td>271</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"</div>"
|
||||
],
|
||||
"text/plain": [
|
||||
" 醫院 醫師 系統編號 病患姓名 簽署日 key-in日 患者出生年月日 患者狀況 流失/停藥日期 用藥時間 \\\n",
|
||||
"是否自費 \n",
|
||||
"健保給付 80 80 80 80 80 80 80 80 43 80 \n",
|
||||
"自費 271 271 271 271 271 271 271 271 187 271 \n",
|
||||
"\n",
|
||||
" 病患是否參加P1NP \n",
|
||||
"是否自費 \n",
|
||||
"健保給付 0 \n",
|
||||
"自費 1 "
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# print(df['是否自費'])\n",
|
||||
"df.groupby('是否自費').count()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<div>\n",
|
||||
"<style scoped>\n",
|
||||
" .dataframe tbody tr th:only-of-type {\n",
|
||||
" vertical-align: middle;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe tbody tr th {\n",
|
||||
" vertical-align: top;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead tr th {\n",
|
||||
" text-align: left;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead tr:last-of-type th {\n",
|
||||
" text-align: right;\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr>\n",
|
||||
" <th></th>\n",
|
||||
" <th colspan=\"8\" halign=\"left\">用藥時間</th>\n",
|
||||
" <th colspan=\"8\" halign=\"left\">系統編號</th>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th></th>\n",
|
||||
" <th>count</th>\n",
|
||||
" <th>mean</th>\n",
|
||||
" <th>std</th>\n",
|
||||
" <th>min</th>\n",
|
||||
" <th>25%</th>\n",
|
||||
" <th>50%</th>\n",
|
||||
" <th>75%</th>\n",
|
||||
" <th>max</th>\n",
|
||||
" <th>count</th>\n",
|
||||
" <th>mean</th>\n",
|
||||
" <th>std</th>\n",
|
||||
" <th>min</th>\n",
|
||||
" <th>25%</th>\n",
|
||||
" <th>50%</th>\n",
|
||||
" <th>75%</th>\n",
|
||||
" <th>max</th>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>是否自費</th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>健保給付</th>\n",
|
||||
" <td>80.0</td>\n",
|
||||
" <td>12.334821</td>\n",
|
||||
" <td>7.326534</td>\n",
|
||||
" <td>0.142857</td>\n",
|
||||
" <td>6.455357</td>\n",
|
||||
" <td>12.392857</td>\n",
|
||||
" <td>18.651786</td>\n",
|
||||
" <td>24.0</td>\n",
|
||||
" <td>80.0</td>\n",
|
||||
" <td>40738.875000</td>\n",
|
||||
" <td>3719.022181</td>\n",
|
||||
" <td>35494.0</td>\n",
|
||||
" <td>37584.0</td>\n",
|
||||
" <td>40289.5</td>\n",
|
||||
" <td>44118.75</td>\n",
|
||||
" <td>47796.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>自費</th>\n",
|
||||
" <td>271.0</td>\n",
|
||||
" <td>7.450053</td>\n",
|
||||
" <td>6.200420</td>\n",
|
||||
" <td>0.214286</td>\n",
|
||||
" <td>2.732143</td>\n",
|
||||
" <td>5.642857</td>\n",
|
||||
" <td>10.160714</td>\n",
|
||||
" <td>24.0</td>\n",
|
||||
" <td>271.0</td>\n",
|
||||
" <td>41558.937269</td>\n",
|
||||
" <td>3793.111387</td>\n",
|
||||
" <td>35258.0</td>\n",
|
||||
" <td>38007.0</td>\n",
|
||||
" <td>41723.0</td>\n",
|
||||
" <td>44780.00</td>\n",
|
||||
" <td>47792.0</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"</div>"
|
||||
],
|
||||
"text/plain": [
|
||||
" 用藥時間 \\\n",
|
||||
" count mean std min 25% 50% 75% \n",
|
||||
"是否自費 \n",
|
||||
"健保給付 80.0 12.334821 7.326534 0.142857 6.455357 12.392857 18.651786 \n",
|
||||
"自費 271.0 7.450053 6.200420 0.214286 2.732143 5.642857 10.160714 \n",
|
||||
"\n",
|
||||
" 系統編號 \\\n",
|
||||
" max count mean std min 25% 50% \n",
|
||||
"是否自費 \n",
|
||||
"健保給付 24.0 80.0 40738.875000 3719.022181 35494.0 37584.0 40289.5 \n",
|
||||
"自費 24.0 271.0 41558.937269 3793.111387 35258.0 38007.0 41723.0 \n",
|
||||
"\n",
|
||||
" \n",
|
||||
" 75% max \n",
|
||||
"是否自費 \n",
|
||||
"健保給付 44118.75 47796.0 \n",
|
||||
"自費 44780.00 47792.0 "
|
||||
]
|
||||
},
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"df.groupby('是否自費').describe()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<div>\n",
|
||||
"<style scoped>\n",
|
||||
" .dataframe tbody tr th:only-of-type {\n",
|
||||
" vertical-align: middle;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe tbody tr th {\n",
|
||||
" vertical-align: top;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead th {\n",
|
||||
" text-align: right;\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr style=\"text-align: right;\">\n",
|
||||
" <th></th>\n",
|
||||
" <th>醫院</th>\n",
|
||||
" <th>醫師</th>\n",
|
||||
" <th>系統編號</th>\n",
|
||||
" <th>病患姓名</th>\n",
|
||||
" <th>簽署日</th>\n",
|
||||
" <th>key-in日</th>\n",
|
||||
" <th>患者出生年月日</th>\n",
|
||||
" <th>流失/停藥日期</th>\n",
|
||||
" <th>是否自費</th>\n",
|
||||
" <th>用藥時間</th>\n",
|
||||
" <th>病患是否參加P1NP</th>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>患者狀況</th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>10-成功問卷</th>\n",
|
||||
" <td>111</td>\n",
|
||||
" <td>111</td>\n",
|
||||
" <td>111</td>\n",
|
||||
" <td>111</td>\n",
|
||||
" <td>111</td>\n",
|
||||
" <td>111</td>\n",
|
||||
" <td>111</td>\n",
|
||||
" <td>18</td>\n",
|
||||
" <td>111</td>\n",
|
||||
" <td>111</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>11-成功問卷-次回拒訪</th>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>12-成功問卷-已停藥</th>\n",
|
||||
" <td>210</td>\n",
|
||||
" <td>210</td>\n",
|
||||
" <td>210</td>\n",
|
||||
" <td>210</td>\n",
|
||||
" <td>210</td>\n",
|
||||
" <td>210</td>\n",
|
||||
" <td>210</td>\n",
|
||||
" <td>210</td>\n",
|
||||
" <td>210</td>\n",
|
||||
" <td>210</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>20-電話錯誤/無此人</th>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>24-拒訪</th>\n",
|
||||
" <td>8</td>\n",
|
||||
" <td>8</td>\n",
|
||||
" <td>8</td>\n",
|
||||
" <td>8</td>\n",
|
||||
" <td>8</td>\n",
|
||||
" <td>8</td>\n",
|
||||
" <td>8</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" <td>8</td>\n",
|
||||
" <td>8</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>26-往生</th>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2B-五次聯絡不到</th>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2D-暫時停藥-觀察中</th>\n",
|
||||
" <td>3</td>\n",
|
||||
" <td>3</td>\n",
|
||||
" <td>3</td>\n",
|
||||
" <td>3</td>\n",
|
||||
" <td>3</td>\n",
|
||||
" <td>3</td>\n",
|
||||
" <td>3</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>3</td>\n",
|
||||
" <td>3</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2E-暫時停藥-住院中</th>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2G-連續三個月連絡不到</th>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>6</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>31-無人接聽</th>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>33-語音信箱/答錄機</th>\n",
|
||||
" <td>2</td>\n",
|
||||
" <td>2</td>\n",
|
||||
" <td>2</td>\n",
|
||||
" <td>2</td>\n",
|
||||
" <td>2</td>\n",
|
||||
" <td>2</td>\n",
|
||||
" <td>2</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>2</td>\n",
|
||||
" <td>2</td>\n",
|
||||
" <td>0</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"</div>"
|
||||
],
|
||||
"text/plain": [
|
||||
" 醫院 醫師 系統編號 病患姓名 簽署日 key-in日 患者出生年月日 流失/停藥日期 是否自費 \\\n",
|
||||
"患者狀況 \n",
|
||||
"10-成功問卷 111 111 111 111 111 111 111 18 111 \n",
|
||||
"11-成功問卷-次回拒訪 1 1 1 1 1 1 1 0 1 \n",
|
||||
"12-成功問卷-已停藥 210 210 210 210 210 210 210 210 210 \n",
|
||||
"20-電話錯誤/無此人 1 1 1 1 1 1 1 0 1 \n",
|
||||
"24-拒訪 8 8 8 8 8 8 8 0 8 \n",
|
||||
"26-往生 6 6 6 6 6 6 6 0 6 \n",
|
||||
"2B-五次聯絡不到 1 1 1 1 1 1 1 0 1 \n",
|
||||
"2D-暫時停藥-觀察中 3 3 3 3 3 3 3 1 3 \n",
|
||||
"2E-暫時停藥-住院中 1 1 1 1 1 1 1 0 1 \n",
|
||||
"2G-連續三個月連絡不到 6 6 6 6 6 6 6 0 6 \n",
|
||||
"31-無人接聽 1 1 1 1 1 1 1 0 1 \n",
|
||||
"33-語音信箱/答錄機 2 2 2 2 2 2 2 1 2 \n",
|
||||
"\n",
|
||||
" 用藥時間 病患是否參加P1NP \n",
|
||||
"患者狀況 \n",
|
||||
"10-成功問卷 111 0 \n",
|
||||
"11-成功問卷-次回拒訪 1 0 \n",
|
||||
"12-成功問卷-已停藥 210 1 \n",
|
||||
"20-電話錯誤/無此人 1 0 \n",
|
||||
"24-拒訪 8 0 \n",
|
||||
"26-往生 6 0 \n",
|
||||
"2B-五次聯絡不到 1 0 \n",
|
||||
"2D-暫時停藥-觀察中 3 0 \n",
|
||||
"2E-暫時停藥-住院中 1 0 \n",
|
||||
"2G-連續三個月連絡不到 6 0 \n",
|
||||
"31-無人接聽 1 0 \n",
|
||||
"33-語音信箱/答錄機 2 0 "
|
||||
]
|
||||
},
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"df.groupby('患者狀況').count()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<div>\n",
|
||||
"<style scoped>\n",
|
||||
" .dataframe tbody tr th:only-of-type {\n",
|
||||
" vertical-align: middle;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe tbody tr th {\n",
|
||||
" vertical-align: top;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead tr th {\n",
|
||||
" text-align: left;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead tr:last-of-type th {\n",
|
||||
" text-align: right;\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr>\n",
|
||||
" <th></th>\n",
|
||||
" <th colspan=\"8\" halign=\"left\">用藥時間</th>\n",
|
||||
" <th colspan=\"8\" halign=\"left\">系統編號</th>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th></th>\n",
|
||||
" <th>count</th>\n",
|
||||
" <th>mean</th>\n",
|
||||
" <th>std</th>\n",
|
||||
" <th>min</th>\n",
|
||||
" <th>25%</th>\n",
|
||||
" <th>50%</th>\n",
|
||||
" <th>75%</th>\n",
|
||||
" <th>max</th>\n",
|
||||
" <th>count</th>\n",
|
||||
" <th>mean</th>\n",
|
||||
" <th>std</th>\n",
|
||||
" <th>min</th>\n",
|
||||
" <th>25%</th>\n",
|
||||
" <th>50%</th>\n",
|
||||
" <th>75%</th>\n",
|
||||
" <th>max</th>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>是否自費</th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" <th></th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>健保給付</th>\n",
|
||||
" <td>36.0</td>\n",
|
||||
" <td>13.112103</td>\n",
|
||||
" <td>6.840638</td>\n",
|
||||
" <td>0.750000</td>\n",
|
||||
" <td>7.678571</td>\n",
|
||||
" <td>16.196429</td>\n",
|
||||
" <td>18.651786</td>\n",
|
||||
" <td>20.857143</td>\n",
|
||||
" <td>36.0</td>\n",
|
||||
" <td>38853.555556</td>\n",
|
||||
" <td>3038.409645</td>\n",
|
||||
" <td>35494.0</td>\n",
|
||||
" <td>37015.75</td>\n",
|
||||
" <td>37777.0</td>\n",
|
||||
" <td>40363.25</td>\n",
|
||||
" <td>47017.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>自費</th>\n",
|
||||
" <td>174.0</td>\n",
|
||||
" <td>7.182677</td>\n",
|
||||
" <td>5.494101</td>\n",
|
||||
" <td>0.678571</td>\n",
|
||||
" <td>2.758929</td>\n",
|
||||
" <td>5.678571</td>\n",
|
||||
" <td>9.705357</td>\n",
|
||||
" <td>23.714286</td>\n",
|
||||
" <td>174.0</td>\n",
|
||||
" <td>40316.954023</td>\n",
|
||||
" <td>3181.145216</td>\n",
|
||||
" <td>35335.0</td>\n",
|
||||
" <td>37448.50</td>\n",
|
||||
" <td>40391.5</td>\n",
|
||||
" <td>42677.75</td>\n",
|
||||
" <td>46896.0</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"</div>"
|
||||
],
|
||||
"text/plain": [
|
||||
" 用藥時間 \\\n",
|
||||
" count mean std min 25% 50% 75% \n",
|
||||
"是否自費 \n",
|
||||
"健保給付 36.0 13.112103 6.840638 0.750000 7.678571 16.196429 18.651786 \n",
|
||||
"自費 174.0 7.182677 5.494101 0.678571 2.758929 5.678571 9.705357 \n",
|
||||
"\n",
|
||||
" 系統編號 \\\n",
|
||||
" max count mean std min 25% 50% \n",
|
||||
"是否自費 \n",
|
||||
"健保給付 20.857143 36.0 38853.555556 3038.409645 35494.0 37015.75 37777.0 \n",
|
||||
"自費 23.714286 174.0 40316.954023 3181.145216 35335.0 37448.50 40391.5 \n",
|
||||
"\n",
|
||||
" \n",
|
||||
" 75% max \n",
|
||||
"是否自費 \n",
|
||||
"健保給付 40363.25 47017.0 \n",
|
||||
"自費 42677.75 46896.0 "
|
||||
]
|
||||
},
|
||||
"execution_count": 20,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"df2 = df.query('患者狀況 == \"12-成功問卷-已停藥\"')\n",
|
||||
"df2.groupby('是否自費').describe()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.6.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
BIN
forteo/cAR.png
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
forteo/cAR3.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
forteo/drug.png
Normal file
After Width: | Height: | Size: 200 KiB |
1
forteo/ntuhgov
Symbolic link
|
@ -0,0 +1 @@
|
|||
../ntuh/submodule/ntuhgov/
|
50
forteo/scrape.py
Executable file
|
@ -0,0 +1,50 @@
|
|||
import re
|
||||
|
||||
# from openpyxl import load_workbook
|
||||
from pandas import read_excel
|
||||
|
||||
from pymongo import MongoClient
|
||||
|
||||
|
||||
SHEETS = (
|
||||
("神外-總院_1070920.xlsx", "總院"),
|
||||
# ("(骨穩)總院_分科1070928.xlsx", "工作表1")
|
||||
("(骨穩)總院_分科1071002.xlsx", "工作表1")
|
||||
)
|
||||
|
||||
client = MongoClient("mongodb.xiao.tw", 27017)
|
||||
db = client.forteo
|
||||
posts = db.posts
|
||||
|
||||
# print(posts.find_one())
|
||||
# exit()
|
||||
|
||||
from ntuhgov.portal_selenium import *
|
||||
from ntuhgov.myutil import *
|
||||
|
||||
|
||||
for file_name, sheet_name in SHEETS:
|
||||
data = read_excel(file_name, sheet_name)
|
||||
|
||||
for chartno in data.病歷號:
|
||||
if not re.search("\d+", str(chartno)):
|
||||
continue
|
||||
|
||||
print(chartno)
|
||||
post = posts.find_one({"_id": chartno})
|
||||
|
||||
if post is None:
|
||||
post = {"_id": chartno}
|
||||
|
||||
if 'drug' not in post:
|
||||
post['drug'] = ShowMedicalRecordDrug(chartno)
|
||||
|
||||
|
||||
if 'report' not in post:
|
||||
post['report'] = ElectronicMedicalReportViewer(chartno)
|
||||
|
||||
if 'op' not in post:
|
||||
post['op'] = OPNoteList(chartno)
|
||||
|
||||
posts.update({"_id": chartno}, {"$set": post}, upsert=True)
|
||||
|
16
forteo/split_ttc_font_to_ttf.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
"""Convert TTC font to TTF using fontforge with python extension.
|
||||
**Warning** The scripts saves splitted fonts in the current working directory.
|
||||
|
||||
Usage:
|
||||
split_ttc_font_to_ttf.py Droid.ttc
|
||||
"""
|
||||
import sys
|
||||
|
||||
import fontforge
|
||||
|
||||
fonts = fontforge.fontsInFile(sys.argv[1])
|
||||
|
||||
for fontName in fonts:
|
||||
font = fontforge.open('%s(%s)'%(sys.argv[1], fontName))
|
||||
font.generate('%s.ttf'%fontName)
|
||||
font.close()
|
18
ntuh/.project
Executable file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ntuh</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.python.pydev.PyDevBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.python.pydev.pythonNature</nature>
|
||||
<nature>org.python.pydev.django.djangoNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
7
ntuh/.pydevproject
Executable file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?eclipse-pydev version="1.0"?>
|
||||
|
||||
<pydev_project>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">/home/xfr/myenv/bin/python</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
|
||||
</pydev_project>
|
17
ntuh/.settings/org.eclipse.core.resources.prefs
Executable file
|
@ -0,0 +1,17 @@
|
|||
eclipse.preferences.version=1
|
||||
encoding//ck/views.py=utf-8
|
||||
encoding//fileupload/views.py=utf-8
|
||||
encoding//nss/quarter.py=utf-8
|
||||
encoding//ntuh/settings.py=utf-8
|
||||
encoding//ntuhgov/portal.py=utf-8
|
||||
encoding//ntuhgov/portal_spynner.py=utf-8
|
||||
encoding//ntuhgov/xportal.py=utf-8
|
||||
encoding//registry/models.py=utf-8
|
||||
encoding//registry/utils.py=utf-8
|
||||
encoding//registry/views.py=utf-8
|
||||
encoding//research/x_classifier.py=utf-8
|
||||
encoding/assistant.py=utf-8
|
||||
encoding/context_processors.py=utf-8
|
||||
encoding/get_inpatient.py=utf-8
|
||||
encoding/getop.py=utf-8
|
||||
encoding/getpatho.py=utf-8
|
0
ntuh/FILE
Normal file
1
ntuh/README.txt
Executable file
|
@ -0,0 +1 @@
|
|||
1234
|
0
ntuh/__init__.py
Executable file
14
ntuh/apache/django.wsgi
Executable file
|
@ -0,0 +1,14 @@
|
|||
import os, sys
|
||||
|
||||
path = os.path.abspath(os.path.dirname(__file__)+'/../../')
|
||||
|
||||
#path = '/home/ntuh/domains/adm.ntuh.net/ntuh'
|
||||
|
||||
sys.path.append(path)
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'ntuh.settings'
|
||||
import django.core.handlers.wsgi
|
||||
application = django.core.handlers.wsgi.WSGIHandler()
|
||||
|
||||
path2 = '%s/ntuh/' % path
|
||||
if path2 not in sys.path:
|
||||
sys.path.append(path2)
|
72
ntuh/assistant.py
Executable file
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 每個月的助手列表
|
||||
|
||||
FILENAME = '/Public/2012/09/2012年9月各月份外科手術主治及住院醫師資料.xls'
|
||||
FILENAME = '/Public/2013/03/10203_[MonlyReport_M387]_T0_前月份外科手術主治及住院醫師資料.xls'
|
||||
FILENAME = u'/shares/Public/2020/外科手術主治及住院醫師資料/10904_[MonlyReport_M387]_T0_前月份外科手術主治及住院醫師資料.xls'
|
||||
|
||||
from datetime import *
|
||||
from xlrd import *
|
||||
|
||||
#from tempfile import TemporaryFile
|
||||
from xlwt import Workbook
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
def Tokenization(s):
|
||||
s = s.replace(u'、', u' ')
|
||||
s = s.replace(u',', u' ')
|
||||
s = s.replace(u'.', u' ')
|
||||
tlist = re.split('[a-zA-Z0-9_ ,;/\(\)!]+', s)
|
||||
b = []
|
||||
for t in tlist:
|
||||
if t:
|
||||
b.append(t)
|
||||
print (b)
|
||||
return b
|
||||
|
||||
def Sum(s):
|
||||
adict = {}
|
||||
print (s.name)
|
||||
for row in range(s.nrows):
|
||||
try:
|
||||
code = s.cell(row,0).value
|
||||
date_value = xldate_as_tuple(s.cell(row,1).value, wb.datemode)
|
||||
d = datetime(*date_value)
|
||||
physician = s.cell(row,2).value
|
||||
assistants = s.cell(row,3).value
|
||||
except:
|
||||
continue
|
||||
|
||||
for a in Tokenization(assistants):
|
||||
try:
|
||||
adict[a] += 1
|
||||
except:
|
||||
adict[a] = 1
|
||||
|
||||
book = Workbook()
|
||||
sheet1 = book.add_sheet('Sheet 1')
|
||||
row = 0
|
||||
for k in sorted(adict.keys()):
|
||||
if adict[k] == 1:
|
||||
continue
|
||||
sheet1.write(row,0,k)
|
||||
sheet1.write(row,1,adict[k])
|
||||
row+=1
|
||||
print (' %s,%s' % (k, adict[k]))
|
||||
|
||||
print ('\n\n\n')
|
||||
out = '%s/%s.xls' % (os.path.dirname(FILENAME), s.name)
|
||||
book.save(out)
|
||||
|
||||
|
||||
wb = open_workbook(FILENAME)
|
||||
for s in wb.sheets():
|
||||
Sum(s)
|
||||
|
||||
# exit()
|
80
ntuh/categories.txt
Executable file
|
@ -0,0 +1,80 @@
|
|||
手術
|
||||
Brain tumor
|
||||
Glioma
|
||||
High grade
|
||||
Low grade
|
||||
Meningioma
|
||||
Pituitary tumor
|
||||
Acoustic neuroma
|
||||
brain metastasis
|
||||
Others
|
||||
Vascular
|
||||
Aneurysm (Microsurgery)
|
||||
AVM (Excision)
|
||||
EC-IC bypass
|
||||
Endarterectomy
|
||||
Cavernoma (Excision)
|
||||
Dural AVF (Microsurgery)
|
||||
CVA
|
||||
Spontaneous ICH
|
||||
Decompression for Infarction
|
||||
Spine
|
||||
高位頸椎(C1,2)手術
|
||||
Tumor
|
||||
Malignant
|
||||
Benign
|
||||
HIVD
|
||||
Cervical
|
||||
Lumber
|
||||
Stenosis
|
||||
Cervical
|
||||
Lumber
|
||||
Other Instrumentation
|
||||
Cervical
|
||||
Lumber
|
||||
Others
|
||||
Head injury
|
||||
EDH
|
||||
Acute SDH
|
||||
Chronic SDH
|
||||
Traumatic ICH
|
||||
Cranioplasty
|
||||
Infection (abscess,empyema)
|
||||
Aspiration
|
||||
Drainage
|
||||
Excision
|
||||
VP Shunt
|
||||
Functional
|
||||
DBS之顱內晶片
|
||||
電池置入
|
||||
脊椎刺激(SCS)療法
|
||||
脊椎腔內Baclofen (ITB)療法/脊椎腔內Mophine (ITM)療法)
|
||||
功能性腦部燒灼術(含(1)動作障礙(2)疼痛治療等)
|
||||
trigeminal neuralgia, Hemifacial spasm
|
||||
MVD
|
||||
percutaneous rhizotomy
|
||||
Hyperhidrosis
|
||||
Peripheral nerves
|
||||
Carpal tunnel syndrome
|
||||
PNS
|
||||
Epilepsy surgery
|
||||
Endovascular surgery
|
||||
Aneurysym (Coiling)
|
||||
AVM
|
||||
Dural AVF
|
||||
Carotid angioplasty / stent
|
||||
Stereotactic Radiosurgery (SRS)
|
||||
Vascular
|
||||
AVM
|
||||
Cavernoma
|
||||
Dural AVF
|
||||
Tumors
|
||||
Malignant
|
||||
Metastases
|
||||
Primary
|
||||
Benign
|
||||
Meningiomas
|
||||
Schwannomas
|
||||
Pituitary
|
||||
Trigeminal neuralgia
|
||||
Others
|
6
ntuh/ck/0.py
Executable file
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from intra import *
|
||||
|
||||
print HeightWeight('D120264406')
|
||||
#print ReportResult('D120264406')
|
0
ntuh/ck/__init__.py
Executable file
25
ntuh/ck/admin.py
Executable file
|
@ -0,0 +1,25 @@
|
|||
from .models import *
|
||||
from django.contrib import admin
|
||||
|
||||
class PatientAdmin(admin.ModelAdmin):
|
||||
# fields = ['pub_date', 'question']
|
||||
list_display = ('name', 'medical_records')
|
||||
|
||||
#admin.site.register(Patient, PatientAdmin)
|
||||
|
||||
class TreatmentAdmin(admin.ModelAdmin):
|
||||
# fields = ['pub_date', 'question']
|
||||
list_display = ('patient', 'icd9', 'oncologist', 'surgeon', 'date_completed', 'memo')
|
||||
list_filter = ['oncologist', 'surgeon', 'date_completed', 'accounting']
|
||||
# search_fields = ['date_started', 'date_completed']
|
||||
date_hierarchy = 'date_completed'
|
||||
|
||||
admin.site.register(Treatment, TreatmentAdmin)
|
||||
|
||||
class LesionAdmin(admin.ModelAdmin):
|
||||
list_display = ('treatment_id', 'sub_location_target_location', 'pathology')
|
||||
list_filter = ['pathology']
|
||||
|
||||
|
||||
admin.site.register(Lesion, LesionAdmin)
|
||||
|
20
ntuh/ck/forms.py
Executable file
|
@ -0,0 +1,20 @@
|
|||
#coding=utf-8
|
||||
|
||||
#from django import newforms as forms
|
||||
from django import forms
|
||||
#from django.newforms import form_for_model
|
||||
from django.forms import ModelForm
|
||||
from models import *
|
||||
|
||||
#PatientForm = form_for_model(Patient)
|
||||
|
||||
class PatientForm(forms.Form):
|
||||
ChartNo = forms.CharField()
|
||||
Name = forms.CharField()
|
||||
idcode = forms.CharField()
|
||||
|
||||
#TreatmentForm = form_for_model(Treatment)
|
||||
|
||||
class TreatmentForm(ModelForm):
|
||||
class Meta:
|
||||
model = Treatment
|
365
ntuh/ck/models.py
Executable file
|
@ -0,0 +1,365 @@
|
|||
# coding=utf-8
|
||||
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class ICD9Diag(models.Model):
|
||||
code = models.CharField(max_length=5, primary_key=True)
|
||||
desc = models.CharField(max_length=50)
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s.%s %s" % (self.code[0:3], self.code[3:], self.desc)
|
||||
|
||||
|
||||
class Activity(models.Model):
|
||||
title = models.CharField(max_length=200)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
class Patient(models.Model):
|
||||
GENDER_CHOICES = (
|
||||
(1, 'Male'),
|
||||
(2, 'Female'),
|
||||
)
|
||||
# STATUS_CHOICES = (
|
||||
# ( 0, 'Male'),
|
||||
# (10, 'Female'),
|
||||
# )
|
||||
name = models.CharField(max_length=200, verbose_name='姓名')
|
||||
medical_records = models.CharField(max_length=200, unique=True)
|
||||
gender = models.IntegerField(choices=GENDER_CHOICES)
|
||||
birthday = models.DateField()
|
||||
address = models.CharField(max_length=200)
|
||||
phone = models.CharField(max_length=200)
|
||||
id_cards = models.CharField(max_length=200, unique=True)
|
||||
memo = models.CharField(max_length=200, blank=True, null=True)
|
||||
dead = models.DateField(blank=True, null=True)
|
||||
|
||||
height = models.DecimalField(max_digits=4, decimal_places=1, null=True)
|
||||
weight = models.DecimalField(max_digits=6, decimal_places=3, null=True)
|
||||
native = models.CharField(max_length=200, blank=True, null=True)
|
||||
past_and_family_history = models.TextField(blank=True, null=True)
|
||||
|
||||
# last_followup = models.DateField(blank=True, null=True)
|
||||
# next_followup = models.DateField(blank=True, null=True)
|
||||
|
||||
timestamp = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
# return self.name+' '+str(self.medical_records)
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
def get_absolute_url(self):
|
||||
return "/patient/detail/%i/" % self.id
|
||||
|
||||
|
||||
class Oncologist(models.Model):
|
||||
name = models.CharField(max_length=200)
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
class Surgeon(models.Model):
|
||||
name = models.CharField(max_length=200)
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
class DiseaseStage(models.Model):
|
||||
stage = models.CharField(max_length=200)
|
||||
def __unicode__(self):
|
||||
return self.stage
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
|
||||
class PrimaryTumorSite(models.Model):
|
||||
# id = models.IntegerField('ID', primary_key=True)
|
||||
site = models.CharField(max_length=200)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.site
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
ACCOUNTING = (
|
||||
(10, '健保'),
|
||||
(20, '自費'),
|
||||
(30, '內含'),
|
||||
)
|
||||
|
||||
class Treatment(models.Model):
|
||||
KARNOFSKY_SCORING = (
|
||||
(100, '100% - normal, no complaints, no signs of disease'),
|
||||
( 90, ' 90% - capable of normal activity, few symptoms or signs of disease'),
|
||||
( 80, ' 80% - normal activity with some difficulty, some symptoms or signs'),
|
||||
( 70, ' 70% - caring for self, not capable of normal activity or work'),
|
||||
( 60, ' 60% - requiring some help, can take care of most personal requirements'),
|
||||
( 50, ' 50% - requires help often, requires frequent medical care'),
|
||||
( 40, ' 40% - disabled, requires special care and help'),
|
||||
( 30, ' 30% - severely disabled, hospital admission indicated but no risk of death'),
|
||||
( 20, ' 20% - very ill, urgently requiring admission, requires supportive measures or treatment'),
|
||||
( 10, ' 10% - moribund, rapidly progressive fatal disease processes'),
|
||||
( 0, ' 0% - death'),
|
||||
)
|
||||
|
||||
IMAGE_GUIDANCE = (
|
||||
(100, '6D Skull'),
|
||||
(200, 'Xsight-Spine'),
|
||||
(210, 'Xsight-Lung'),
|
||||
(300, 'Fiducial'),
|
||||
(400, 'Synchrony'),
|
||||
)
|
||||
|
||||
patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
|
||||
bed = models.CharField(max_length=200, blank=True, null=True, verbose_name='病床號')
|
||||
icd9 = models.ForeignKey(ICD9Diag, blank=True, null=True, verbose_name='ICD9診斷', on_delete=models.SET_NULL)
|
||||
other_diagnosis = models.CharField(max_length=200, blank=True, null=True, verbose_name='其他診斷')
|
||||
tracking_mode = models.IntegerField(choices=IMAGE_GUIDANCE, blank=True, null=True)
|
||||
referral = models.CharField(max_length=200, blank=True, null=True, verbose_name='轉介醫師')
|
||||
oncologist = models.ForeignKey(Oncologist, blank=True, null=True, on_delete=models.SET_NULL)
|
||||
surgeon = models.ForeignKey(Surgeon, blank=True, null=True, on_delete=models.SET_NULL)
|
||||
date_started = models.DateField(blank=True, null=True)
|
||||
date_completed = models.DateField(blank=True, null=True)
|
||||
accounting = models.IntegerField(choices=ACCOUNTING, blank=True, null=True, verbose_name='記帳別')
|
||||
karnofsky_score = models.IntegerField(choices=KARNOFSKY_SCORING, blank=True, null=True)
|
||||
disease_stage = models.ForeignKey(DiseaseStage, blank=True, null=True, on_delete=models.SET_NULL)
|
||||
primary_tumor_site = models.ForeignKey(PrimaryTumorSite, blank=True, null=True, on_delete=models.SET_NULL)
|
||||
input = models.ForeignKey(Activity, related_name='input', blank=True, null=True, verbose_name='已', on_delete=models.SET_NULL)
|
||||
output = models.ForeignKey(Activity, related_name='output', blank=True, null=True, verbose_name='待', on_delete=models.SET_NULL)
|
||||
complications = models.CharField(max_length=200, blank=True, null=True, verbose_name='併發症')
|
||||
chief_complaint = models.TextField(blank=True, null=True)
|
||||
|
||||
memo = models.CharField(max_length=200, blank=True, null=True)
|
||||
timestamp = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "/treatment/detail/%i/" % self.id
|
||||
|
||||
class Price(models.Model):
|
||||
code = models.CharField(max_length=200, blank=True, null=True)
|
||||
identity = models.IntegerField(choices=ACCOUNTING)
|
||||
name = models.CharField(max_length=200)
|
||||
unit = models.CharField(max_length=200, blank=True, null=True)
|
||||
address = models.IntegerField(blank=True, null=True)
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
def __unicode__(self):
|
||||
return self.code + '-' + self.get_identity_display() + '-' + self.name
|
||||
|
||||
class VEVENT(models.Model):
|
||||
# iCalendar
|
||||
DTSTAMP = models.DateTimeField(auto_now=True)
|
||||
DTSTART = models.DateTimeField()
|
||||
DTEND = models.TimeField(blank=True)
|
||||
DURATION = models.TimeField()
|
||||
SUMMARY = models.CharField(max_length=200, blank=True, null=True)
|
||||
CLASS = models.CharField(max_length=200, blank=True, null=True)
|
||||
CATEGORIES = models.CharField(max_length=200, blank=True, null=True)
|
||||
TRANSP = models.CharField(max_length=200, blank=True, null=True)
|
||||
RRULE = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
||||
DESCRIPTION = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
||||
MODE_CHOICES = (
|
||||
(110, 'Fiducial'),
|
||||
(200, '固定器'),
|
||||
(210, 'CT'),
|
||||
(220, 'MRI'),
|
||||
(230, 'Angio'),
|
||||
(310, '治療'),
|
||||
)
|
||||
treatment = models.ForeignKey(Treatment, on_delete=models.CASCADE)
|
||||
mode = models.IntegerField(choices=MODE_CHOICES)
|
||||
price = models.ForeignKey(Price, blank=True, null=True, on_delete=models.SET_NULL)
|
||||
|
||||
break_frequency = models.IntegerField(blank=True)
|
||||
system_err = models.IntegerField(blank=True)
|
||||
shift = models.IntegerField(blank=True)
|
||||
cone = models.IntegerField(blank=True)
|
||||
path = models.IntegerField(blank=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "/treatment/detail/%i/" % self.treatment.id
|
||||
|
||||
class TargetLocation(models.Model):
|
||||
location = models.CharField(max_length=200)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.location
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
|
||||
class Pathology(models.Model):
|
||||
pathology = models.CharField(max_length=200, unique=True)
|
||||
stage = models.ForeignKey(DiseaseStage, on_delete=models.CASCADE)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.pathology
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
|
||||
class SubLocation(models.Model):
|
||||
target_location = models.ForeignKey(TargetLocation, on_delete=models.CASCADE)
|
||||
group = models.IntegerField()
|
||||
sub_location = models.CharField(max_length=200)
|
||||
pathology = models.ManyToManyField(Pathology)
|
||||
|
||||
def __unicode__(self):
|
||||
# return TargetLocation.objects.get(id=self.target_location)+' - '+self.sub_location
|
||||
return self.sub_location
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
|
||||
class Lesion(models.Model):
|
||||
treatment = models.ForeignKey(Treatment, on_delete=models.CASCADE)
|
||||
sub_location = models.ForeignKey(SubLocation, on_delete=models.CASCADE)
|
||||
pathology = models.ForeignKey(Pathology, on_delete=models.CASCADE)
|
||||
|
||||
dimensions = models.CharField(max_length=200)
|
||||
volume = models.DecimalField(max_digits=9, decimal_places=2)
|
||||
|
||||
plan_name = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
||||
collimator = models.CharField(max_length=200)
|
||||
path_no = models.IntegerField()
|
||||
beam_no = models.IntegerField()
|
||||
|
||||
mu_max = models.DecimalField(max_digits=9, decimal_places=2)
|
||||
mu_min = models.DecimalField(max_digits=9, decimal_places=2)
|
||||
|
||||
dose = models.IntegerField()
|
||||
fractions = models.IntegerField()
|
||||
iso_dose_curve = models.IntegerField()
|
||||
|
||||
dmin = models.DecimalField(max_digits=9, decimal_places=2)
|
||||
dmax = models.DecimalField(max_digits=9, decimal_places=2)
|
||||
|
||||
coverage = models.DecimalField(max_digits=9, decimal_places=2)
|
||||
ci = models.DecimalField(max_digits=9, decimal_places=2)
|
||||
nci = models.DecimalField(max_digits=9, decimal_places=2)
|
||||
|
||||
start_date = models.DateField(blank=True, null=True)
|
||||
end_date = models.DateField(blank=True, null=True)
|
||||
|
||||
memo = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "/treatment/detail/%i/" % self.treatment.id
|
||||
|
||||
def sub_location_target_location(self):
|
||||
return self.sub_location.target_location
|
||||
|
||||
def treatment_id(self):
|
||||
return treatment.id
|
||||
|
||||
class PriorTreatment(models.Model):
|
||||
TREATMENT_CHOICES = (
|
||||
(1, 'Surgery'),
|
||||
(2, 'Biopsy'),
|
||||
(3, 'RT'),
|
||||
(4, 'Radiosurgery'),
|
||||
(5, 'Chemotherapy'),
|
||||
)
|
||||
PERIOD_CHOICES = (
|
||||
(1, 'Before'),
|
||||
(2, 'Concurrent'),
|
||||
(3, 'None'),
|
||||
)
|
||||
patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
|
||||
date = models.DateField()
|
||||
treatment = models.IntegerField(choices=TREATMENT_CHOICES)
|
||||
period = models.IntegerField(choices=PERIOD_CHOICES, blank=True, null=True)
|
||||
dose = models.IntegerField(blank=True, null=True)
|
||||
memo = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "/patient/detail/%i/" % self.patient.id
|
||||
|
||||
|
||||
class PathExam(models.Model):
|
||||
patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
|
||||
path_code = models.CharField(max_length=200, unique=True, verbose_name='病理號')
|
||||
specimen_code = models.CharField(max_length=200, verbose_name='檢體')
|
||||
specimen_get_date = models.DateField(verbose_name='收件日')
|
||||
report_date = models.DateField(verbose_name='報告日')
|
||||
division = models.CharField(max_length=200, verbose_name='科別')
|
||||
bed = models.CharField(max_length=200, blank=True, null=True, verbose_name='病床')
|
||||
report = models.TextField(blank=True, null=True, verbose_name='檢查報告')
|
||||
|
||||
|
||||
class Followup(models.Model):
|
||||
patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
|
||||
# date = models.DateField(auto_now_add=True)
|
||||
date = models.DateField()
|
||||
memo = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "/patient/detail/%i/" % self.patient.id
|
||||
|
||||
class PACSImage(models.Model):
|
||||
patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
|
||||
|
||||
PatChartNo = models.CharField(max_length=200, verbose_name='病歷號')
|
||||
RequestSheetNo = models.CharField(max_length=200, verbose_name='單號')
|
||||
ExamDate = models.DateField( verbose_name='檢查日')
|
||||
LinkOrderName = models.CharField(max_length=200, verbose_name='檢查名稱')
|
||||
Modality = models.CharField(max_length=200, verbose_name='儀器')
|
||||
VerifiedStateString = models.CharField(max_length=200, verbose_name='狀態')
|
||||
|
||||
SAVED_CHOICES = (
|
||||
(0 , '待處理'),
|
||||
(10, '有輸入'),
|
||||
(15, '已確認'),
|
||||
(20, '不適用'),
|
||||
)
|
||||
# Saved = models.BooleanField()
|
||||
Saved = models.IntegerField(choices=SAVED_CHOICES, verbose_name='保存')
|
||||
|
||||
|
||||
class LesionFollow(models.Model):
|
||||
Lesion = models.ForeignKey(Lesion, on_delete=models.CASCADE)
|
||||
Date = models.DateField(null=False, verbose_name='追蹤日期')
|
||||
Volume = models.FloatField(null=True, verbose_name='體積(mm3)')
|
||||
A = models.FloatField(null=True, verbose_name='長(mm)')
|
||||
B = models.FloatField(null=True, verbose_name='寬(mm)')
|
||||
C = models.FloatField(null=True, verbose_name='高(mm)')
|
||||
Memo = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
||||
|
||||
class MedicalRecord(models.Model):
|
||||
patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
|
||||
|
||||
Record = models.CharField(max_length=200, null=True) # 住急門
|
||||
|
||||
HospName = models.CharField(max_length=200, null=True)
|
||||
DeptName = models.CharField(max_length=200, null=True, verbose_name='科')
|
||||
InDate = models.DateField( null=False, verbose_name='入')
|
||||
OutDate = models.DateField( null=True, verbose_name='出')
|
||||
WardName = models.CharField(max_length=200, null=True, verbose_name='房')
|
||||
RoomName = models.CharField(max_length=200, null=True, verbose_name='室')
|
||||
BedName = models.CharField(max_length=200, null=True, verbose_name='床')
|
||||
MainDrName = models.CharField(max_length=200, null=True, verbose_name='主治')
|
||||
MainDiagnosisName = models.CharField(max_length=200, null=True, verbose_name='診斷')
|
||||
StatusName = models.CharField(max_length=200, null=True, verbose_name='狀態')
|
||||
|
||||
SpecialCureName = models.CharField(max_length=200, null=True, verbose_name='行為')
|
2071
ntuh/ck/selenium.py
Executable file
0
ntuh/ck/templatetags/__init__.py
Executable file
12
ntuh/ck/templatetags/calculate_age.py
Executable file
|
@ -0,0 +1,12 @@
|
|||
from django import template
|
||||
import datetime
|
||||
|
||||
register = template.Library()
|
||||
|
||||
def age(bday, d=None):
|
||||
if d is None:
|
||||
d = datetime.date.today()
|
||||
return (d.year - bday.year) - int((d.month, d.day) < (bday.month, bday.day))
|
||||
|
||||
register.filter('age', age)
|
||||
|
109
ntuh/ck/templatetags/stack.py
Executable file
|
@ -0,0 +1,109 @@
|
|||
from django import template
|
||||
|
||||
import math
|
||||
|
||||
register = template.Library()
|
||||
|
||||
class stack:
|
||||
def __init__(self):
|
||||
self.stack = []
|
||||
|
||||
def push(self, o):
|
||||
self.stack.append(o)
|
||||
#print 'push', self.stack
|
||||
|
||||
def pop(self):
|
||||
if len(self.stack) == 0:
|
||||
# raise KeyError, "Stack is empty"
|
||||
raise KeyError("Stack is empty")
|
||||
o = self.stack[-1]
|
||||
#print 'pop', self.stack
|
||||
del self.stack[-1]
|
||||
return o
|
||||
|
||||
def is_empty(self):
|
||||
return len(self.stack) == 0
|
||||
|
||||
def __len__(self):
|
||||
return len(self.stack)
|
||||
|
||||
# truncate a floating point number only if it has no decimal part (convert from string if necessary)
|
||||
def number(num):
|
||||
f = float(num)
|
||||
i = int(f)
|
||||
if i == f: #FIXME: floating point equality?
|
||||
return i
|
||||
return f
|
||||
|
||||
stacks = {}
|
||||
|
||||
@register.filter
|
||||
def stnew(value):
|
||||
#print 'stnew'
|
||||
stacks[value] = stack()
|
||||
return value
|
||||
|
||||
@register.filter
|
||||
def stpush(value, arg):
|
||||
#print 'stpush:',
|
||||
stacks[value].push(number(arg))
|
||||
return value
|
||||
|
||||
@register.filter
|
||||
def stpop(value):
|
||||
#print 'stpop:',
|
||||
if value in stacks:
|
||||
stacks[value].pop()
|
||||
return value
|
||||
|
||||
@register.filter
|
||||
def stget(value):
|
||||
#print 'stget:',
|
||||
if value in stacks:
|
||||
return stacks[value].pop()
|
||||
|
||||
@register.filter
|
||||
def stadd(value):
|
||||
#print 'stadd:',
|
||||
two = stacks[value].pop()
|
||||
one = stacks[value].pop()
|
||||
stacks[value].push(one + two)
|
||||
return value
|
||||
|
||||
@register.filter
|
||||
def stsub(value):
|
||||
#print 'stsub:',
|
||||
two = stacks[value].pop()
|
||||
one = stacks[value].pop()
|
||||
stacks[value].push(one - two)
|
||||
return value
|
||||
|
||||
@register.filter
|
||||
def stmult(value):
|
||||
#print 'stmult:',
|
||||
two = stacks[value].pop()
|
||||
one = stacks[value].pop()
|
||||
stacks[value].push(one * two)
|
||||
return value
|
||||
|
||||
@register.filter
|
||||
def stdiv(value):
|
||||
#print 'stdiv:',
|
||||
two = stacks[value].pop()
|
||||
one = stacks[value].pop()
|
||||
stacks[value].push(number(float(one) / float(two)))
|
||||
return value
|
||||
|
||||
@register.filter
|
||||
def stmod(value):
|
||||
two = stacks[value].pop()
|
||||
one = stacks[value].pop()
|
||||
stacks[value].push(one % two)
|
||||
return value
|
||||
|
||||
@register.filter
|
||||
def stsqrt(value):
|
||||
one = stacks[value].pop()
|
||||
stacks[value].push(math.sqrt(one))
|
||||
return value
|
||||
|
23
ntuh/ck/tests.py
Executable file
|
@ -0,0 +1,23 @@
|
|||
"""
|
||||
This file demonstrates two different styles of tests (one doctest and one
|
||||
unittest). These will both pass when you run "manage.py test".
|
||||
|
||||
Replace these with more appropriate tests for your application.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
class SimpleTest(TestCase):
|
||||
def test_basic_addition(self):
|
||||
"""
|
||||
Tests that 1 + 1 always equals 2.
|
||||
"""
|
||||
self.failUnlessEqual(1 + 1, 2)
|
||||
|
||||
__test__ = {"doctest": """
|
||||
Another way to test that 1 + 1 is equal to 2.
|
||||
|
||||
>>> 1 + 1 == 2
|
||||
True
|
||||
"""}
|
||||
|
269
ntuh/ck/unf.py
Executable file
|
@ -0,0 +1,269 @@
|
|||
#!/usr/bin/python
|
||||
# coding=utf-8
|
||||
|
||||
from intra import *
|
||||
|
||||
import datetime
|
||||
import time
|
||||
|
||||
|
||||
def unf():
|
||||
unf1 = unf_sort(-30,-7)
|
||||
dr1 = unf1['dr']
|
||||
resident1 = unf1['resident']
|
||||
division1 = unf1['division']
|
||||
|
||||
unf2 = unf_sort(-365,-31)
|
||||
dr2 = unf2['dr']
|
||||
resident2 = unf2['resident']
|
||||
division2 = unf2['division']
|
||||
|
||||
length = max(len(dr1),
|
||||
len(resident1),
|
||||
len(dr2),
|
||||
len(resident2))
|
||||
|
||||
result=[]
|
||||
|
||||
result.append(['主治醫師','科別','份數','住院醫師','份數','主治醫師','科別','份數','住院醫師','份數'])
|
||||
|
||||
d1 = 0
|
||||
r1 = 0
|
||||
d2 = 0
|
||||
r2 = 0
|
||||
for i in range(length):
|
||||
r = []
|
||||
if i < len(dr1):
|
||||
r.append(dr1[i][0])
|
||||
if vs.has_key(dr1[i][0]):
|
||||
r.append(vs[dr1[i][0]])
|
||||
else:
|
||||
r.append('')
|
||||
r.append(dr1[i][1])
|
||||
d1 += dr1[i][1]
|
||||
else:
|
||||
r.append('')
|
||||
r.append('')
|
||||
r.append('')
|
||||
if i < len(resident1):
|
||||
r.append(resident1[i][0])
|
||||
r.append(resident1[i][1])
|
||||
r1 += resident1[i][1]
|
||||
else:
|
||||
r.append('')
|
||||
r.append('')
|
||||
if i < len(dr2):
|
||||
r.append(dr2[i][0])
|
||||
if vs.has_key(dr2[i][0]):
|
||||
r.append(vs[dr2[i][0]])
|
||||
else:
|
||||
r.append('')
|
||||
r.append(dr2[i][1])
|
||||
d2 += dr2[i][1]
|
||||
else:
|
||||
r.append('')
|
||||
r.append('')
|
||||
r.append('')
|
||||
if i < len(resident2):
|
||||
r.append(resident2[i][0])
|
||||
r.append(resident2[i][1])
|
||||
r2 += resident2[i][1]
|
||||
else:
|
||||
r.append('')
|
||||
r.append('')
|
||||
|
||||
result.append(r)
|
||||
|
||||
# output = open('/home/xfr/mysite/site_media/unf.html','w')
|
||||
output = open('/SharedDocs/html/media.ntuh.net/unf.html','w')
|
||||
|
||||
print >> output, """
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv=Content-Type content="text/html; charset=utf-8">
|
||||
<title>外科部病歷未完成</title>
|
||||
</head>
|
||||
<body>
|
||||
"""
|
||||
|
||||
print >> output, "<table><tbody align='center'>"
|
||||
print >> output, "<tr><td colspan='10'>%s</td></tr>" % time.asctime()
|
||||
print >> output, "<tr><td colspan='5'>%s</td><td colspan='5'>%s</td></tr>" % ('7至30日','超過30日')
|
||||
for r in result:
|
||||
print >> output ,"<tr>"
|
||||
for c in r:
|
||||
print >> output, "<td>%s</td>" % c
|
||||
print >> output ,"</tr>"
|
||||
|
||||
print >> output, "<tr><td>---</td></tr>"
|
||||
print >> output, "<tr>"
|
||||
print >> output, "<td>總計</td><td></td><td>%i</td>" % d1
|
||||
print >> output, "<td>總計</td><td>%i</td>" % r1
|
||||
print >> output, "<td>總計</td><td></td><td>%i</td>" % d2
|
||||
print >> output, "<td>總計</td><td>%i</td>" % r2
|
||||
print >> output, "</tr>"
|
||||
|
||||
print >> output, "</tbody></table>"
|
||||
print >> output, "<hr/>"
|
||||
print >> output, "<table><tbody align='center'>"
|
||||
|
||||
|
||||
div1 = dict(division1)
|
||||
div2 = dict(division2)
|
||||
|
||||
for div in div1.keys():
|
||||
if not div2.has_key(div):
|
||||
div2[div] = 0
|
||||
|
||||
for div in div2.keys():
|
||||
if not div1.has_key(div):
|
||||
div1[div] = 0
|
||||
|
||||
div3 = {}
|
||||
for div in div1.keys():
|
||||
div3[div] = div1[div] + div2[div]
|
||||
|
||||
|
||||
# print div1
|
||||
# print div2
|
||||
# print div3
|
||||
|
||||
division_sort = sorted(list(div3), key=lambda x: -div3[x])
|
||||
# print division_sort
|
||||
|
||||
print >> output, "<tr><td></td>"
|
||||
for div in division_sort:
|
||||
print >> output, "<td>%s</td>" % div
|
||||
print >> output, "</tr>"
|
||||
|
||||
print >> output, "<tr><td>7至30日</td>"
|
||||
for div in division_sort:
|
||||
print >> output, "<td>%s</td>" % div1[div]
|
||||
print >> output, "</tr>"
|
||||
|
||||
print >> output, "<tr><td>超過30日</td>"
|
||||
for div in division_sort:
|
||||
print >> output, "<td>%s</td>" % div2[div]
|
||||
print >> output, "</tr>"
|
||||
|
||||
print >> output, "<tr><td>合計</td>"
|
||||
for div in division_sort:
|
||||
print >> output, "<td>%s</td>" % div3[div]
|
||||
print >> output, "</tr>"
|
||||
|
||||
|
||||
print >> output, "</tbody></table>"
|
||||
|
||||
print >> output, "</body></html>"
|
||||
|
||||
|
||||
|
||||
output.close()
|
||||
|
||||
return result
|
||||
|
||||
def unf_month():
|
||||
day = datetime.date.today().day
|
||||
EndDate = datetime.date.today() + datetime.timedelta(days=-day)
|
||||
|
||||
unf1 = unf_sort(-365,-day)
|
||||
dr1 = unf1['dr']
|
||||
resident1 = unf1['resident']
|
||||
division1 = unf1['division']
|
||||
|
||||
length = max(len(dr1),
|
||||
len(resident1))
|
||||
|
||||
result=[]
|
||||
|
||||
result.append(['主治醫師','科別','份數','住院醫師','份數'])
|
||||
|
||||
d1 = 0
|
||||
r1 = 0
|
||||
for i in range(length):
|
||||
r = []
|
||||
if i < len(dr1):
|
||||
r.append(dr1[i][0])
|
||||
if vs.has_key(dr1[i][0]):
|
||||
r.append(vs[dr1[i][0]])
|
||||
else:
|
||||
r.append('')
|
||||
r.append(dr1[i][1])
|
||||
d1 += dr1[i][1]
|
||||
else:
|
||||
r.append('')
|
||||
r.append('')
|
||||
r.append('')
|
||||
if i < len(resident1):
|
||||
r.append(resident1[i][0])
|
||||
r.append(resident1[i][1])
|
||||
r1 += resident1[i][1]
|
||||
else:
|
||||
r.append('')
|
||||
r.append('')
|
||||
|
||||
result.append(r)
|
||||
|
||||
# output = open('/home/xfr/mysite/site_media/unf.html','w')
|
||||
output = open('/SharedDocs/html/media.ntuh.net/unf_month.html','w')
|
||||
|
||||
print >> output, """
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv=Content-Type content="text/html; charset=utf-8">
|
||||
<title>上月外科部病歷未完成</title>
|
||||
</head>
|
||||
<body>
|
||||
"""
|
||||
|
||||
print >> output, "<table><tbody align='center'>"
|
||||
print >> output, "<tr><td colspan='5'>%s</td></tr>" % time.asctime()
|
||||
print >> output, "<tr><td colspan='5'>%s前</td></tr>" % EndDate
|
||||
for r in result:
|
||||
print >> output ,"<tr>"
|
||||
for c in r:
|
||||
print >> output, "<td>%s</td>" % c
|
||||
print >> output ,"</tr>"
|
||||
|
||||
print >> output, "<tr><td>---</td></tr>"
|
||||
print >> output, "<tr>"
|
||||
print >> output, "<td>總計</td><td></td><td>%i</td>" % d1
|
||||
print >> output, "<td>總計</td><td>%i</td>" % r1
|
||||
print >> output, "</tr>"
|
||||
print >> output, "</tbody></table>"
|
||||
|
||||
print >> output, "<hr/>"
|
||||
print >> output, "<table><tbody align='center'>"
|
||||
|
||||
|
||||
div1 = dict(division1)
|
||||
|
||||
# print div1
|
||||
# print div2
|
||||
# print div3
|
||||
|
||||
division_sort = sorted(list(div1), key=lambda x: -div1[x])
|
||||
# print division_sort
|
||||
|
||||
print >> output, "<tr><td></td>"
|
||||
for div in division_sort:
|
||||
print >> output, "<td>%s</td>" % div
|
||||
print >> output, "</tr>"
|
||||
|
||||
print >> output, "<tr><td>合計</td>"
|
||||
for div in division_sort:
|
||||
print >> output, "<td>%s</td>" % div1[div]
|
||||
print >> output, "</tr>"
|
||||
|
||||
|
||||
print >> output, "</tbody></table>"
|
||||
|
||||
print >> output, "</body></html>"
|
||||
|
||||
output.close()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
unf()
|
||||
unf_month()
|
1380
ntuh/ck/views.py
Executable file
62
ntuh/ck/vs.csv
Executable file
|
@ -0,0 +1,62 @@
|
|||
CS,徐紹勛
|
||||
CS,李元麒
|
||||
CS,李章銘
|
||||
CS,陳晉興
|
||||
CS,黃培銘
|
||||
CVS,吳毅暉
|
||||
CVS,周迺寬
|
||||
CVS,張重義
|
||||
CVS,王植賢
|
||||
CVS,王水深
|
||||
CVS,紀乃新
|
||||
CVS,虞希禹
|
||||
CVS,許榮彬
|
||||
CVS,邱英世
|
||||
CVS,陳益祥
|
||||
CVS,黃書健
|
||||
GS,何承懋
|
||||
GS,何明志
|
||||
GS,吳耀銘
|
||||
GS,張金堅
|
||||
GS,李伯皇
|
||||
GS,林明燦
|
||||
GS,林本仁
|
||||
GS,梁金銅
|
||||
GS,楊卿堯
|
||||
GS,王明暘
|
||||
GS,田郁文
|
||||
GS,胡瑞恆
|
||||
GS,蔡孟昆
|
||||
GS,袁瑞晃
|
||||
GS,賴逸儒
|
||||
GS,郭文宏
|
||||
GS,陳坤源
|
||||
GS,陳炯年
|
||||
GS,黃俊升
|
||||
GS,黃凱文
|
||||
GS,黃約翰
|
||||
ICU,柯文哲
|
||||
NS,曾勝弘
|
||||
NS,曾漢民
|
||||
NS,杜永光
|
||||
NS,楊士弘
|
||||
NS,王國川
|
||||
NS,蔡瑞章
|
||||
NS,蕭輔仁
|
||||
NS,賴達明
|
||||
NS,郭夢菲
|
||||
NS,陳敞牧
|
||||
NS,黃勝堅
|
||||
PED,林文熙
|
||||
PED,蔡明憲
|
||||
PED,許文明
|
||||
PED,賴鴻緒
|
||||
PS,戴浩志
|
||||
PS,楊永健
|
||||
PS,洪學義
|
||||
PS,湯月碧
|
||||
PS,簡雄飛
|
||||
PS,謝孟祥
|
||||
PS,謝榮賢
|
||||
PS,郭源松
|
||||
PS,鄭乃禎
|
|
62
ntuh/ck/vs0.csv
Executable file
|
@ -0,0 +1,62 @@
|
|||
GS,<2C><><EFBFBD>
|
||||
PED,苦翬狐
|
||||
GS,癒风<E79992>
|
||||
GS,独玊ど
|
||||
GS,狶<>篱
|
||||
PS,傣る貉
|
||||
ICU,琠ゅ<E790A0>
|
||||
CS,<2C>じ腝
|
||||
GS,璊风<E7928A>
|
||||
NS,<2C>ッ<EFBFBD>
|
||||
PED,狶ゅ撼
|
||||
CS,畗残吃
|
||||
NS,朝疮<E69C9D>
|
||||
PS,尝方猀
|
||||
GS,朝<>方
|
||||
GS,<2C><>в
|
||||
CVS,㏄癷糴
|
||||
PS,虏动<E8998F>
|
||||
GS,バ<>ゅ
|
||||
NS,苦笷<E88BA6>
|
||||
CVS,<2C>璣<EFBFBD>
|
||||
GS,辩<>簧
|
||||
CVS,朝痲不
|
||||
PS,瑇厩竡
|
||||
GS,朝<><E69C9D>
|
||||
PS,拦疎в
|
||||
GS,尝ゅЩ
|
||||
CVS,砛篴眑
|
||||
CS,<2C>彻皇
|
||||
PED,讲<>舅
|
||||
NS,<2C>瓣<EFBFBD>
|
||||
PS,綠<>赫
|
||||
NS,独秤绊
|
||||
CS,朝<>砍
|
||||
GS,<2C>模皇
|
||||
PS,法ッ胺
|
||||
NS,纯簙チ
|
||||
NS,纯秤グ
|
||||
CVS,<2C><>瞏
|
||||
CVS,眎<>竡
|
||||
PS,谅﹕不
|
||||
GS,苦秇晶
|
||||
GS,讲﹕<E8AEB2>
|
||||
CVS,阜<><E9989C>
|
||||
PED,砛ゅ<E7A09B>
|
||||
CS,独蚌皇
|
||||
CVS,独<>胺
|
||||
GS,法<>丑
|
||||
NS,讲风彻
|
||||
NS,尝冠滇
|
||||
GS,狶セく
|
||||
NS,法<>グ
|
||||
PS,谅篴藉
|
||||
NS,拷徊く
|
||||
GS,<2C>┯婪
|
||||
CVS,<2C>从藉
|
||||
CVS,<2C>驾穟
|
||||
GS,眎<>绊
|
||||
GS,独<>揩
|
||||
GS,独惩ゅ
|
||||
GS,<2C><>穤
|
||||
CVS,<2C><>穝
|
|
14
ntuh/context_processors.py
Executable file
|
@ -0,0 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ntuh import settings
|
||||
|
||||
import os
|
||||
|
||||
ABSPATH = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
def context_processors(request):
|
||||
|
||||
return {
|
||||
'open_icon_library': 'http://www.ntuh.net/open_icon_library-full/',
|
||||
'specialty': settings.SPECIALTY,
|
||||
}
|
16
ntuh/cron.sh
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
PATH=$PATH:/usr/local/bin
|
||||
|
||||
#. /home/ntuh/d111/bin/activate
|
||||
#conda init bash
|
||||
. /opt/conda/etc/profile.d/conda.sh
|
||||
conda activate django2
|
||||
|
||||
locale
|
||||
|
||||
cd $(dirname $0)
|
||||
python getop.py
|
||||
python getpatho.py
|
||||
#python get_inpatient.py
|
||||
|
8
ntuh/cron_hourly.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
. /home/ntuh/d18env/bin/activate
|
||||
|
||||
locale
|
||||
|
||||
cd $(dirname $0)
|
||||
python getop.py
|
80
ntuh/doc/plastic_category.txt
Executable file
|
@ -0,0 +1,80 @@
|
|||
手術
|
||||
A. Congenital Deformity
|
||||
1.Cleft lip and palate
|
||||
2.Ear reconstruction
|
||||
3.Hand
|
||||
4.Foot
|
||||
5.Craniofacial surgery
|
||||
6.Others
|
||||
B. Traumatic
|
||||
1.Soft tissue repair
|
||||
2.Facial fracture repair
|
||||
Nasal bone fracture
|
||||
Mandible fracture
|
||||
Zygomatic fracture
|
||||
Maxillary fracture
|
||||
Orbital blow out fracture
|
||||
Panfacial fracture
|
||||
C. Hand surgery
|
||||
1.Tendon
|
||||
2.Nerve
|
||||
3.Vessel repair
|
||||
4.Fracture reduction
|
||||
5.Soft tissue repair
|
||||
6.Replantation
|
||||
7.Others
|
||||
D. Aesthetic
|
||||
1.Face lift
|
||||
2.Blepharoplasty
|
||||
3.Rhinoplasty
|
||||
4.Facial contouring and genioplasty
|
||||
5.Fat grafting
|
||||
6.Body contouring and liposuction
|
||||
7.Breast
|
||||
Augmentation
|
||||
Reduction
|
||||
Gynecomastia
|
||||
Nipple-areolar complex
|
||||
8.Scar revision
|
||||
9.Osmidrosis
|
||||
10.Others
|
||||
E. Microvascular and reconstructive Surgery
|
||||
1.Free flap transfer
|
||||
Breast reconstruction
|
||||
Head and neck
|
||||
Functional muscle transfer
|
||||
Extremity
|
||||
2.Replantation of digit
|
||||
3.Replantation of extremity
|
||||
4.Others (flap revision /commissuroplasty)
|
||||
5.Breast Implant
|
||||
F. Benign and malignant tumors
|
||||
1.Skin and Soft tissue tumor, benign
|
||||
Hemangioma or vascular malformation
|
||||
Neurofibroma
|
||||
Others
|
||||
2.Skin, malignancy
|
||||
Basal cell carcinoma
|
||||
Squamous cell carcinoma
|
||||
Malignant melanoma
|
||||
Others
|
||||
3.Soft tissue, malignancy
|
||||
4.Salivary Gland Tumor
|
||||
5.Others
|
||||
G. Miscellaneous
|
||||
1.Debridement
|
||||
2.STSG
|
||||
3.FTSG
|
||||
4.Local flap
|
||||
5.Pedicle flap / division
|
||||
6.Varicose vein
|
||||
7.Nerve graft
|
||||
8.Fat graft
|
||||
9.Allograft
|
||||
10.Amputation
|
||||
11.Others
|
||||
H. Burn (acute)
|
||||
1.Debridement
|
||||
H. Burn (late effect)
|
||||
1.Tissue expander
|
||||
2.Others
|
16
ntuh/dojango/__init__.py
Executable file
|
@ -0,0 +1,16 @@
|
|||
VERSION = (0, 5, 2, 'final', 0)
|
||||
|
||||
def get_version():
|
||||
version = '%s.%s' % (VERSION[0], VERSION[1])
|
||||
if VERSION[2]:
|
||||
version = '%s.%s' % (version, VERSION[2])
|
||||
if VERSION[3:] == ('alpha', 0):
|
||||
version = '%s pre-alpha' % version
|
||||
else:
|
||||
if VERSION[3] != 'final':
|
||||
version = '%s %s %s' % (version, VERSION[3], VERSION[4])
|
||||
#from django.utils.version import get_svn_revision
|
||||
#svn_rev = get_svn_revision()
|
||||
#if svn_rev != u'SVN-unknown':
|
||||
# version = "%s %s" % (version, svn_rev)
|
||||
return version
|
20
ntuh/dojango/appengine/README
Executable file
|
@ -0,0 +1,20 @@
|
|||
This directory contains some helpers for running dojango on appengine.
|
||||
|
||||
memcache_zipserve.py:
|
||||
|
||||
Part of http://code.google.com/p/google-app-engine-samples/:
|
||||
Using zipserve to serve the media-files. After the first use they'll be
|
||||
cached in memcache. Modified to support last-modified-headers (so we have
|
||||
a real CDN!)
|
||||
|
||||
dojo_serve.py:
|
||||
|
||||
Helper for serving the whole dojo release folder, that holds the dojo
|
||||
modules as zipfiles.
|
||||
It can be used within app.yaml (AppEngine configuration file) like this:
|
||||
|
||||
- url: /dojango/media/release/.*
|
||||
script: dojango/appengine/dojo_serve.py
|
||||
|
||||
Afterwards all zip-files within /dojango/media/release/DOJANGO_DOJO_VERSION/
|
||||
will be served and cached.
|
0
ntuh/dojango/appengine/__init__.py
Executable file
51
ntuh/dojango/appengine/dojo_serve.py
Executable file
|
@ -0,0 +1,51 @@
|
|||
import os
|
||||
import wsgiref.handlers
|
||||
|
||||
from dojango.appengine import memcache_zipserve
|
||||
|
||||
from google.appengine.ext import webapp
|
||||
|
||||
# setup the environment
|
||||
from common.appenginepatch.aecmd import setup_env
|
||||
setup_env(manage_py_env=True)
|
||||
from dojango.conf import settings
|
||||
|
||||
# creating a handler structure for the zip-files within the release folder
|
||||
release_dir = '%s/release/%s' % (settings.BASE_MEDIA_ROOT, settings.DOJO_VERSION)
|
||||
handlers = []
|
||||
for zip_file in os.listdir(release_dir):
|
||||
if zip_file.endswith(".zip"):
|
||||
module = os.path.splitext(zip_file)[0]
|
||||
handler = [os.path.join(release_dir, zip_file)]
|
||||
handlers.append(handler)
|
||||
|
||||
class FlushCache(webapp.RequestHandler):
|
||||
"""
|
||||
Handler for flushing the whole memcache instance.
|
||||
"""
|
||||
from google.appengine.ext.webapp.util import login_required
|
||||
@login_required
|
||||
def get(self):
|
||||
from google.appengine.api import memcache
|
||||
from google.appengine.api import users
|
||||
if users.is_current_user_admin():
|
||||
stats = memcache.get_stats()
|
||||
memcache.flush_all()
|
||||
self.response.out.write("Memcache successfully flushed!<br/>")
|
||||
if stats:
|
||||
self.response.out.write("<p>Memcache stats:</p><p>")
|
||||
for key in stats.keys():
|
||||
self.response.out.write("%s: %s<br/>" % (key, stats[key]))
|
||||
self.response.out.write("</p>")
|
||||
|
||||
def main():
|
||||
application = webapp.WSGIApplication([
|
||||
('%s/%s/(.*)' % (settings.BUILD_MEDIA_URL, settings.DOJO_VERSION),
|
||||
memcache_zipserve.create_handler(handlers, max_age=31536000)
|
||||
),
|
||||
('%s/_flushcache[/]{0,1}' % settings.BUILD_MEDIA_URL, FlushCache)
|
||||
], debug=False)
|
||||
wsgiref.handlers.CGIHandler().run(application)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
435
ntuh/dojango/appengine/memcache_zipserve.py
Executable file
|
@ -0,0 +1,435 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2008 Google Inc.
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
"""A class to serve pages from zip files and use memcache for performance.
|
||||
|
||||
This contains a class and a function to create an anonymous instance of the
|
||||
class to serve HTTP GET requests. Memcache is used to increase response speed
|
||||
and lower processing cycles used in serving. Credit to Guido van Rossum and
|
||||
his implementation of zipserve which served as a reference as I wrote this.
|
||||
|
||||
NOTE: THIS FILE WAS MODIFIED TO SUPPORT CLIENT CACHING
|
||||
|
||||
MemcachedZipHandler: Class that serves request
|
||||
create_handler: method to create instance of MemcachedZipHandler
|
||||
"""
|
||||
|
||||
__author__ = 'j.c@google.com (Justin Mattson)'
|
||||
|
||||
import email.Utils
|
||||
import datetime
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import time
|
||||
import zipfile
|
||||
|
||||
from google.appengine.api import memcache
|
||||
from google.appengine.ext import webapp
|
||||
from google.appengine.ext.webapp import util
|
||||
|
||||
from django.utils.hashcompat import md5_constructor
|
||||
|
||||
def create_handler(zip_files, max_age=None, public=None, client_caching=None):
|
||||
"""Factory method to create a MemcachedZipHandler instance.
|
||||
|
||||
Args:
|
||||
zip_files: A list of file names, or a list of lists of file name, first
|
||||
member of file mappings. See MemcachedZipHandler documentation for
|
||||
more information about using the list of lists format
|
||||
max_age: The maximum client-side cache lifetime
|
||||
public: Whether this should be declared public in the client-side cache
|
||||
Returns:
|
||||
A MemcachedZipHandler wrapped in a pretty, anonymous bow for use with App
|
||||
Engine
|
||||
|
||||
Raises:
|
||||
ValueError: if the zip_files argument is not a list
|
||||
"""
|
||||
# verify argument integrity. If the argument is passed in list format,
|
||||
# convert it to list of lists format
|
||||
|
||||
if zip_files and type(zip_files).__name__ == 'list':
|
||||
num_items = len(zip_files)
|
||||
while num_items > 0:
|
||||
if type(zip_files[num_items - 1]).__name__ != 'list':
|
||||
zip_files[num_items - 1] = [zip_files[num_items-1]]
|
||||
num_items -= 1
|
||||
else:
|
||||
raise ValueError('File name arguments must be a list')
|
||||
|
||||
class HandlerWrapper(MemcachedZipHandler):
|
||||
"""Simple wrapper for an instance of MemcachedZipHandler.
|
||||
|
||||
I'm still not sure why this is needed
|
||||
"""
|
||||
|
||||
def get(self, name):
|
||||
self.zipfilenames = zip_files
|
||||
if max_age is not None:
|
||||
self.MAX_AGE = max_age
|
||||
if public is not None:
|
||||
self.PUBLIC = public
|
||||
if client_caching is not None:
|
||||
self.CLIENT_CACHING = client_caching
|
||||
self.TrueGet(name)
|
||||
|
||||
return HandlerWrapper
|
||||
|
||||
|
||||
class CacheFile(object):
|
||||
pass
|
||||
|
||||
class MemcachedZipHandler(webapp.RequestHandler):
|
||||
"""Handles get requests for a given URL.
|
||||
|
||||
Serves a GET request from a series of zip files. As files are served they are
|
||||
put into memcache, which is much faster than retreiving them from the zip
|
||||
source file again. It also uses considerably fewer CPU cycles.
|
||||
"""
|
||||
zipfile_cache = {} # class cache of source zip files
|
||||
current_last_modified = None # where we save the current last modified datetime
|
||||
current_etag = None # the current ETag of a file served
|
||||
CLIENT_CACHING = True # is client caching enabled? (sending Last-Modified and ETag within response!)
|
||||
MAX_AGE = 600 # max client-side cache lifetime
|
||||
PUBLIC = True # public cache setting
|
||||
CACHE_PREFIX = "cache://" # memcache key prefix for actual URLs
|
||||
NEG_CACHE_PREFIX = "noncache://" # memcache key prefix for non-existant URL
|
||||
|
||||
def TrueGet(self, name):
|
||||
"""The top-level entry point to serving requests.
|
||||
|
||||
Called 'True' get because it does the work when called from the wrapper
|
||||
class' get method
|
||||
|
||||
Args:
|
||||
name: URL requested
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
name = self.PreprocessUrl(name)
|
||||
|
||||
# see if we have the page in the memcache
|
||||
resp_data = self.GetFromCache(name)
|
||||
if resp_data is None:
|
||||
logging.info('Cache miss for %s', name)
|
||||
resp_data = self.GetFromNegativeCache(name)
|
||||
if resp_data is None or resp_data == -1:
|
||||
resp_data = self.GetFromStore(name)
|
||||
# IF we have the file, put it in the memcache
|
||||
# ELSE put it in the negative cache
|
||||
if resp_data is not None:
|
||||
self.StoreOrUpdateInCache(name, resp_data)
|
||||
else:
|
||||
logging.info('Adding %s to negative cache, serving 404', name)
|
||||
self.StoreInNegativeCache(name)
|
||||
self.Write404Error()
|
||||
return
|
||||
else:
|
||||
self.Write404Error()
|
||||
return
|
||||
|
||||
content_type, encoding = mimetypes.guess_type(name)
|
||||
if content_type:
|
||||
self.response.headers['Content-Type'] = content_type
|
||||
self.current_last_modified = resp_data.lastmod
|
||||
self.current_etag = resp_data.etag
|
||||
self.SetCachingHeaders()
|
||||
# if the received ETag matches
|
||||
if resp_data.etag == self.request.headers.get('If-None-Match'):
|
||||
self.error(304)
|
||||
return
|
||||
# if-modified-since was passed by the browser
|
||||
if self.request.headers.has_key('If-Modified-Since'):
|
||||
dt = self.request.headers.get('If-Modified-Since').split(';')[0]
|
||||
modsince = datetime.datetime.strptime(dt, "%a, %d %b %Y %H:%M:%S %Z")
|
||||
if modsince >= self.current_last_modified:
|
||||
# The file is older than the cached copy (or exactly the same)
|
||||
self.error(304)
|
||||
return
|
||||
self.response.out.write(resp_data.file)
|
||||
|
||||
def PreprocessUrl(self, name):
|
||||
"""Any preprocessing work on the URL when it comes it.
|
||||
|
||||
Put any work related to interpretting the incoming URL here. For example,
|
||||
this is used to redirect requests for a directory to the index.html file
|
||||
in that directory. Subclasses should override this method to do different
|
||||
preprocessing.
|
||||
|
||||
Args:
|
||||
name: The incoming URL
|
||||
|
||||
Returns:
|
||||
The processed URL
|
||||
"""
|
||||
if name[len(name) - 1:] == '/':
|
||||
return "%s%s" % (name, 'index.html')
|
||||
else:
|
||||
return name
|
||||
|
||||
def GetFromStore(self, file_path):
|
||||
"""Retrieve file from zip files.
|
||||
|
||||
Get the file from the source, it must not have been in the memcache. If
|
||||
possible, we'll use the zip file index to quickly locate where the file
|
||||
should be found. (See MapToFileArchive documentation for assumptions about
|
||||
file ordering.) If we don't have an index or don't find the file where the
|
||||
index says we should, look through all the zip files to find it.
|
||||
|
||||
Args:
|
||||
file_path: the file that we're looking for
|
||||
|
||||
Returns:
|
||||
The contents of the requested file
|
||||
"""
|
||||
resp_data = None
|
||||
file_itr = iter(self.zipfilenames)
|
||||
|
||||
# check the index, if we have one, to see what archive the file is in
|
||||
archive_name = self.MapFileToArchive(file_path)
|
||||
if not archive_name:
|
||||
archive_name = file_itr.next()[0]
|
||||
|
||||
while resp_data is None and archive_name:
|
||||
zip_archive = self.LoadZipFile(archive_name)
|
||||
if zip_archive:
|
||||
|
||||
# we expect some lookups will fail, and that's okay, 404s will deal
|
||||
# with that
|
||||
try:
|
||||
resp_data = CacheFile()
|
||||
info = os.stat(archive_name)
|
||||
#lastmod = datetime.datetime.fromtimestamp(info[8])
|
||||
lastmod = datetime.datetime(*zip_archive.getinfo(file_path).date_time)
|
||||
resp_data.file = zip_archive.read(file_path)
|
||||
resp_data.lastmod = lastmod
|
||||
resp_data.etag = '"%s"' % md5_constructor(resp_data.file).hexdigest()
|
||||
except (KeyError, RuntimeError), err:
|
||||
# no op
|
||||
x = False
|
||||
resp_data = None
|
||||
if resp_data is not None:
|
||||
logging.info('%s read from %s', file_path, archive_name)
|
||||
|
||||
try:
|
||||
archive_name = file_itr.next()[0]
|
||||
except (StopIteration), err:
|
||||
archive_name = False
|
||||
|
||||
return resp_data
|
||||
|
||||
def LoadZipFile(self, zipfilename):
|
||||
"""Convenience method to load zip file.
|
||||
|
||||
Just a convenience method to load the zip file from the data store. This is
|
||||
useful if we ever want to change data stores and also as a means of
|
||||
dependency injection for testing. This method will look at our file cache
|
||||
first, and then load and cache the file if there's a cache miss
|
||||
|
||||
Args:
|
||||
zipfilename: the name of the zip file to load
|
||||
|
||||
Returns:
|
||||
The zip file requested, or None if there is an I/O error
|
||||
"""
|
||||
zip_archive = None
|
||||
zip_archive = self.zipfile_cache.get(zipfilename)
|
||||
if zip_archive is None:
|
||||
try:
|
||||
zip_archive = zipfile.ZipFile(zipfilename)
|
||||
self.zipfile_cache[zipfilename] = zip_archive
|
||||
except (IOError, RuntimeError), err:
|
||||
logging.error('Can\'t open zipfile %s, cause: %s' % (zipfilename,
|
||||
err))
|
||||
return zip_archive
|
||||
|
||||
def MapFileToArchive(self, file_path):
|
||||
"""Given a file name, determine what archive it should be in.
|
||||
|
||||
This method makes two critical assumptions.
|
||||
(1) The zip files passed as an argument to the handler, if concatenated
|
||||
in that same order, would result in a total ordering
|
||||
of all the files. See (2) for ordering type.
|
||||
(2) Upper case letters before lower case letters. The traversal of a
|
||||
directory tree is depth first. A parent directory's files are added
|
||||
before the files of any child directories
|
||||
|
||||
Args:
|
||||
file_path: the file to be mapped to an archive
|
||||
|
||||
Returns:
|
||||
The name of the archive where we expect the file to be
|
||||
"""
|
||||
num_archives = len(self.zipfilenames)
|
||||
while num_archives > 0:
|
||||
target = self.zipfilenames[num_archives - 1]
|
||||
if len(target) > 1:
|
||||
if self.CompareFilenames(target[1], file_path) >= 0:
|
||||
return target[0]
|
||||
num_archives -= 1
|
||||
|
||||
return None
|
||||
|
||||
def CompareFilenames(self, file1, file2):
|
||||
"""Determines whether file1 is lexigraphically 'before' file2.
|
||||
|
||||
WARNING: This method assumes that paths are output in a depth-first,
|
||||
with parent directories' files stored before childs'
|
||||
|
||||
We say that file1 is lexigraphically before file2 if the last non-matching
|
||||
path segment of file1 is alphabetically before file2.
|
||||
|
||||
Args:
|
||||
file1: the first file path
|
||||
file2: the second file path
|
||||
|
||||
Returns:
|
||||
A positive number if file1 is before file2
|
||||
A negative number if file2 is before file1
|
||||
0 if filenames are the same
|
||||
"""
|
||||
f1_segments = file1.split('/')
|
||||
f2_segments = file2.split('/')
|
||||
|
||||
segment_ptr = 0
|
||||
while (segment_ptr < len(f1_segments) and
|
||||
segment_ptr < len(f2_segments) and
|
||||
f1_segments[segment_ptr] == f2_segments[segment_ptr]):
|
||||
segment_ptr += 1
|
||||
|
||||
if len(f1_segments) == len(f2_segments):
|
||||
|
||||
# we fell off the end, the paths much be the same
|
||||
if segment_ptr == len(f1_segments):
|
||||
return 0
|
||||
|
||||
# we didn't fall of the end, compare the segments where they differ
|
||||
if f1_segments[segment_ptr] < f2_segments[segment_ptr]:
|
||||
return 1
|
||||
elif f1_segments[segment_ptr] > f2_segments[segment_ptr]:
|
||||
return -1
|
||||
else:
|
||||
return 0
|
||||
|
||||
# the number of segments differs, we either mismatched comparing
|
||||
# directories, or comparing a file to a directory
|
||||
else:
|
||||
|
||||
# IF we were looking at the last segment of one of the paths,
|
||||
# the one with fewer segments is first because files come before
|
||||
# directories
|
||||
# ELSE we just need to compare directory names
|
||||
if (segment_ptr + 1 == len(f1_segments) or
|
||||
segment_ptr + 1 == len(f2_segments)):
|
||||
return len(f2_segments) - len(f1_segments)
|
||||
else:
|
||||
if f1_segments[segment_ptr] < f2_segments[segment_ptr]:
|
||||
return 1
|
||||
elif f1_segments[segment_ptr] > f2_segments[segment_ptr]:
|
||||
return -1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def SetCachingHeaders(self):
|
||||
"""Set caching headers for the request."""
|
||||
max_age = self.MAX_AGE
|
||||
self.response.headers['Expires'] = email.Utils.formatdate(
|
||||
time.time() + max_age, usegmt=True)
|
||||
cache_control = []
|
||||
if self.PUBLIC:
|
||||
cache_control.append('public')
|
||||
cache_control.append('max-age=%d' % max_age)
|
||||
self.response.headers['Cache-Control'] = ', '.join(cache_control)
|
||||
# adding caching headers for the client
|
||||
if self.CLIENT_CACHING:
|
||||
if self.current_last_modified:
|
||||
self.response.headers['Last-Modified'] = self.current_last_modified.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
if self.current_etag:
|
||||
self.response.headers['ETag'] = self.current_etag
|
||||
|
||||
def GetFromCache(self, filename):
|
||||
"""Get file from memcache, if available.
|
||||
|
||||
Args:
|
||||
filename: The URL of the file to return
|
||||
|
||||
Returns:
|
||||
The content of the file
|
||||
"""
|
||||
return memcache.get("%s%s" % (self.CACHE_PREFIX, filename))
|
||||
|
||||
def StoreOrUpdateInCache(self, filename, data):
|
||||
"""Store data in the cache.
|
||||
|
||||
Store a piece of data in the memcache. Memcache has a maximum item size of
|
||||
1*10^6 bytes. If the data is too large, fail, but log the failure. Future
|
||||
work will consider compressing the data before storing or chunking it
|
||||
|
||||
Args:
|
||||
filename: the name of the file to store
|
||||
data: the data of the file
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
try:
|
||||
if not memcache.add("%s%s" % (self.CACHE_PREFIX, filename), data):
|
||||
memcache.replace("%s%s" % (self.CACHE_PREFIX, filename), data)
|
||||
except (ValueError), err:
|
||||
logging.warning("Data size too large to cache\n%s" % err)
|
||||
|
||||
def Write404Error(self):
|
||||
"""Ouptut a simple 404 response."""
|
||||
self.error(404)
|
||||
self.response.out.write('Error 404, file not found')
|
||||
|
||||
def StoreInNegativeCache(self, filename):
|
||||
"""If a non-existant URL is accessed, cache this result as well.
|
||||
|
||||
Future work should consider setting a maximum negative cache size to
|
||||
prevent it from from negatively impacting the real cache.
|
||||
|
||||
Args:
|
||||
filename: URL to add ot negative cache
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
memcache.add("%s%s" % (self.NEG_CACHE_PREFIX, filename), -1)
|
||||
|
||||
def GetFromNegativeCache(self, filename):
|
||||
"""Retrieve from negative cache.
|
||||
|
||||
Args:
|
||||
filename: URL to retreive
|
||||
|
||||
Returns:
|
||||
The file contents if present in the negative cache.
|
||||
"""
|
||||
return memcache.get("%s%s" % (self.NEG_CACHE_PREFIX, filename))
|
||||
|
||||
|
||||
def main():
|
||||
application = webapp.WSGIApplication([('/([^/]+)/(.*)',
|
||||
MemcachedZipHandler)])
|
||||
util.run_wsgi_app(application)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
31
ntuh/dojango/bin/dojobuild.py
Executable file
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env python
|
||||
# This is the alternate dojo build command so it can be used
|
||||
# with older versions of django (mainly because of AppEngine, it uses version 0.96)
|
||||
import os
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
def setup_environ():
|
||||
# we assume, that dojango is installed within your django's project dir
|
||||
project_directory = os.path.abspath(os.path.dirname(__file__)+'/../../')
|
||||
settings_filename = "settings.py"
|
||||
if not project_directory:
|
||||
project_directory = os.getcwd()
|
||||
project_name = os.path.basename(project_directory)
|
||||
settings_name = os.path.splitext(settings_filename)[0]
|
||||
sys.path.append(project_directory)
|
||||
sys.path.append(os.path.abspath(project_directory + "/.."))
|
||||
project_module = __import__(project_name, {}, {}, [''])
|
||||
sys.path.pop()
|
||||
# Set DJANGO_SETTINGS_MODULE appropriately.
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name, settings_name)
|
||||
return project_directory
|
||||
|
||||
project_dir = setup_environ()
|
||||
from dojango.management.commands.dojobuild import Command
|
||||
|
||||
if __name__ == "__main__":
|
||||
my_build = Command()
|
||||
parser = OptionParser(option_list=my_build.option_list)
|
||||
options, args = parser.parse_args(sys.argv)
|
||||
my_build.handle(*args[1:], **options.__dict__)
|
0
ntuh/dojango/conf/__init__.py
Executable file
97
ntuh/dojango/conf/settings.py
Executable file
|
@ -0,0 +1,97 @@
|
|||
import os
|
||||
from django.conf import settings
|
||||
|
||||
DEBUG = getattr(settings, "DEBUG", False)
|
||||
DEFAULT_CHARSET = getattr(settings, 'DEFAULT_CHARSET', 'utf-8')
|
||||
|
||||
DOJO_VERSION = getattr(settings, "DOJANGO_DOJO_VERSION", "1.6.0")
|
||||
DOJO_PROFILE = getattr(settings, "DOJANGO_DOJO_PROFILE", "google")
|
||||
|
||||
DOJO_MEDIA_URL = getattr(settings, "DOJANGO_DOJO_MEDIA_URL", 'dojo-media')
|
||||
BASE_MEDIA_URL = getattr(settings, "DOJANGO_BASE_MEDIA_URL", '/dojango/%s' % DOJO_MEDIA_URL)
|
||||
BUILD_MEDIA_URL = getattr(settings, "DOJANGO_BUILD_MEDIA_URL", '%s/release' % BASE_MEDIA_URL)
|
||||
BASE_MEDIA_ROOT = getattr(settings, "DOJANGO_BASE_MEDIA_ROOT", os.path.abspath(os.path.dirname(__file__)+'/../dojo-media/'))
|
||||
BASE_DOJO_ROOT = getattr(settings, "DOJANGO_BASE_DOJO_ROOT", BASE_MEDIA_ROOT + "/src")
|
||||
# as default the dijit theme folder is used
|
||||
DOJO_THEME_URL = getattr(settings, "DOJANGO_DOJO_THEME_URL", False)
|
||||
DOJO_THEME = getattr(settings, "DOJANGO_DOJO_THEME", "claro")
|
||||
DOJO_DEBUG = getattr(settings, "DOJANGO_DOJO_DEBUG", DEBUG) # using the default django DEBUG setting
|
||||
DOJO_SECURE_JSON = getattr(settings, "DOJANGO_DOJO_SECURE_JSON", True) # if you are using dojo version < 1.2.0 you have set it to False
|
||||
CDN_USE_SSL = getattr(settings, "DOJANGO_CDN_USE_SSL", False) # is dojo served via https from google? doesn't work for aol!
|
||||
|
||||
# set the urls for actual possible paths for dojo
|
||||
# one dojo profile must at least contain a path that defines the base url of a dojo installation
|
||||
# the following settings can be set for each dojo profile:
|
||||
# - base_url: where do the dojo files reside (without the version folder!)
|
||||
# - use_xd: use the crossdomain-build? used to build the correct filename (e.g. dojo.xd.js)
|
||||
# - versions: this list defines all possible versions that are available in the defined profile
|
||||
# - uncompressed: use the uncompressed version of dojo (dojo.xd.js.uncompressed.js)
|
||||
# - use_gfx: there is a special case, when using dojox.gfx from aol (see http://dev.aol.com/dojo)
|
||||
# - is_local: marks a profile being local. this is needed when using the dojo module loader
|
||||
# - is_local_build: profile being a locally builded version
|
||||
_aol_versions = ('0.9.0', '1.0.0', '1.0.2', '1.1.0', '1.1.1', '1.2.0', '1.2.3', '1.3', '1.3.0', '1.3.1', '1.3.2', '1.4', '1.4.0', '1.4.1', '1.4.3', '1.5', '1.5.0', '1.6', '1.6.0')
|
||||
_aol_gfx_versions = ('0.9.0', '1.0.0', '1.0.2', '1.1.0', '1.1.1',)
|
||||
_google_versions = ('1.1.1', '1.2', '1.2.0', '1.2.3', '1.3', '1.3.0', '1.3.1', '1.3.2', '1.4', '1.4.0', '1.4.1', '1.4.3', '1.5', '1.5.0', '1.6', '1.6.0')
|
||||
DOJO_PROFILES = {
|
||||
'google': {'base_url':(CDN_USE_SSL and 'https' or 'http') + '://ajax.googleapis.com/ajax/libs/dojo', 'use_xd':True, 'versions':_google_versions}, # google just supports version >= 1.1.1
|
||||
'google_uncompressed': {'base_url':(CDN_USE_SSL and 'https' or 'http') + '://ajax.googleapis.com/ajax/libs/dojo', 'use_xd':True, 'uncompressed':True, 'versions':_google_versions},
|
||||
'aol': {'base_url':'http://o.aolcdn.com/dojo', 'use_xd':True, 'versions':_aol_versions},
|
||||
'aol_uncompressed': {'base_url':'http://o.aolcdn.com/dojo', 'use_xd':True, 'uncompressed':True, 'versions':_aol_versions},
|
||||
'aol_gfx': {'base_url':'http://o.aolcdn.com/dojo', 'use_xd':True, 'use_gfx':True, 'versions':_aol_gfx_versions},
|
||||
'aol_gfx-uncompressed': {'base_url':'http://o.aolcdn.com/dojo', 'use_xd':True, 'use_gfx':True, 'uncompressed':True, 'versions':_aol_gfx_versions},
|
||||
'local': {'base_url': '%(BASE_MEDIA_URL)s', 'is_local':True}, # we don't have a restriction on version names, name them as you like
|
||||
'local_release': {'base_url': '%(BUILD_MEDIA_URL)s', 'is_local':True, 'is_local_build':True}, # this will be available after the first dojo build!
|
||||
'local_release_uncompressed': {'base_url': '%(BUILD_MEDIA_URL)s', 'uncompressed':True, 'is_local':True, 'is_local_build':True} # same here
|
||||
}
|
||||
|
||||
# we just want users to append/overwrite own profiles
|
||||
DOJO_PROFILES.update(getattr(settings, "DOJANGO_DOJO_PROFILES", {}))
|
||||
|
||||
# =============================================================================================
|
||||
# =================================== NEEDED FOR DOJO BUILD ===================================
|
||||
# =============================================================================================
|
||||
# general doc: http://dojotoolkit.org/book/dojo-book-0-9/part-4-meta-dojo/package-system-and-custom-builds
|
||||
# see http://www.sitepen.com/blog/2008/04/02/dojo-mini-optimization-tricks-with-the-dojo-toolkit/ for details
|
||||
DOJO_BUILD_VERSION = getattr(settings, "DOJANGO_DOJO_BUILD_VERSION", '1.6.0')
|
||||
# this is the default build profile, that is used, when calling "./manage.py dojobuild"
|
||||
# "./manage.py dojobuild dojango" would have the same effect
|
||||
DOJO_BUILD_PROFILE = getattr(settings, "DOJANGO_DOJO_BUILD_PROFILE", "dojango")
|
||||
# This dictionary defines your build profiles you can use within the custom command "./manage.py dojobuild
|
||||
# You can set your own build profile within the main settings.py of the project by defining a dictionary
|
||||
# DOJANGO_DOJO_BUILD_PROFILES, that sets the following key/value pairs for each defined profile name:
|
||||
# profile_file: which dojo profile file is used for the build (see dojango.profile.js how it has to look)
|
||||
# options: these are the options that are passed to the build command (see the dojo doc for details)
|
||||
# OPTIONAL SETTINGS (see DOJO_BUILD_PROFILES_DEFAULT):
|
||||
# base_root: in which directory will the dojo version be builded to?
|
||||
# used_src_version: which version should be used for the dojo build (e.g. 1.1.1)
|
||||
# build_version: what is the version name of the builded release (e.g. dojango1.1.1) - this option can be overwritten by the commandline parameter --build_version=...
|
||||
# minify_extreme_skip_files: a tupel of files/folders (each expressed as regular expression) that should be kept when doing a minify extreme (useful when you have several layers and don't want some files)
|
||||
# this tupel will be appended to the default folders/files that are skipped: see SKIP_FILES in management/commands/dojobuild.py
|
||||
DOJO_BUILD_PROFILES = {
|
||||
'dojango': {
|
||||
'options': 'profileFile="%(BASE_MEDIA_ROOT)s/dojango.profile.js" action=release optimize=shrinksafe.keepLines cssOptimize=comments.keepLines',
|
||||
},
|
||||
'dojango_optimized': {
|
||||
'options': 'profileFile="%(BASE_MEDIA_ROOT)s/dojango_optimized.profile.js" action=release optimize=shrinksafe.keepLines cssOptimize=comments.keepLines',
|
||||
'build_version': '%(DOJO_BUILD_VERSION)s-dojango-optimized-with-dojo',
|
||||
},
|
||||
}
|
||||
|
||||
# these defaults are mixed into each DOJO_BUILD_PROFILES element
|
||||
# but you can overwrite each attribute within your own build profile element
|
||||
# e.g. DOJANGO_BUILD_PROFILES = {'used_src_version': '1.2.2', ....}
|
||||
DOJO_BUILD_PROFILES_DEFAULT = getattr(settings, "DOJANGO_DOJO_BUILD_PROFILES_DEFAULT", {
|
||||
# build the release in the media directory of dojango
|
||||
# use a formatting string, so this can be set in the project's settings.py without getting the dojango settings
|
||||
'base_root': '%(BASE_MEDIA_ROOT)s/release',
|
||||
'used_src_version': '%(DOJO_BUILD_VERSION)s',
|
||||
'build_version': '%(DOJO_BUILD_VERSION)s-dojango-with-dojo',
|
||||
})
|
||||
# TODO: we should also enable the already pre-delivered dojo default profiles
|
||||
|
||||
# you can add/overwrite your own build profiles
|
||||
DOJO_BUILD_PROFILES.update(getattr(settings, "DOJANGO_DOJO_BUILD_PROFILES", {}))
|
||||
DOJO_BUILD_JAVA_EXEC = getattr(settings, 'DOJANGO_DOJO_BUILD_JAVA_EXEC', 'java')
|
||||
# a version string that must have the following form: '1.0.0', '1.2.1', ....
|
||||
# this setting is used witin the dojobuild, because the build process changed since version 1.2.0
|
||||
DOJO_BUILD_USED_VERSION = getattr(settings, 'DOJANGO_DOJO_BUILD_USED_VERSION', DOJO_BUILD_VERSION)
|
25
ntuh/dojango/context_processors.py
Executable file
|
@ -0,0 +1,25 @@
|
|||
from dojango.util.config import Config
|
||||
|
||||
def config(request):
|
||||
'''Make several dojango constants available in the template, like:
|
||||
|
||||
{{ DOJANGO.DOJO_BASE_URL }}, {{ DOJANGO.DOJO_URL }}, ...
|
||||
|
||||
You can also use the templatetag 'set_dojango_context' in your templates.
|
||||
Just set the following at the top of your template to set these context
|
||||
contants:
|
||||
|
||||
If you want to use the default DOJANGO_DOJO_VERSION/DOJANGO_DOJO_PROFILE:
|
||||
|
||||
{% load dojango_base %}
|
||||
{% set_dojango_context %}
|
||||
|
||||
Using a difernet profile set the following:
|
||||
|
||||
{% load dojango_base %}
|
||||
{% set_dojango_context "google" "1.1.1" %}
|
||||
'''
|
||||
context_extras = {'DOJANGO': {}}
|
||||
config = Config()
|
||||
context_extras['DOJANGO'] = config.get_context_dict()
|
||||
return context_extras
|
182
ntuh/dojango/data/__init__.py
Executable file
|
@ -0,0 +1,182 @@
|
|||
import re
|
||||
|
||||
__all__ = ('QueryInfo', 'QueryReadStoreInfo',
|
||||
'JsonRestStoreInfo', 'JsonQueryRestStoreInfo',)
|
||||
|
||||
class QueryInfoFeatures(object):
|
||||
sorting = True
|
||||
paging = False
|
||||
|
||||
class QueryInfo(object):
|
||||
'''Usage (is that the right solution?):
|
||||
info = QueryInfo(request)
|
||||
info.extract()
|
||||
queryset = extract.process(Object.objects.all())
|
||||
'''
|
||||
start = 0
|
||||
end = 25
|
||||
filters = {}
|
||||
sorting = [] # key=field // value=descending(True/False)
|
||||
|
||||
request = None
|
||||
max_count = 25
|
||||
|
||||
def __init__(self, request, max_count=None, **kwargs):
|
||||
self.request = request
|
||||
if max_count is not None:
|
||||
self.max_count = max_count
|
||||
|
||||
def extract(self):
|
||||
self.set_paging()
|
||||
self.set_sorting()
|
||||
self.set_filters()
|
||||
|
||||
def set_paging(self):
|
||||
"""Needs to be implemented in a subclass"""
|
||||
pass
|
||||
|
||||
def set_sorting(self):
|
||||
pass
|
||||
|
||||
def set_filters(self):
|
||||
"""Needs to be implemented in a subclass"""
|
||||
pass
|
||||
|
||||
def process(self, queryset):
|
||||
# maybe using Django's paginator
|
||||
return queryset.filter(**self.filters).order_by(*self.sorting)[self.start:self.end]
|
||||
|
||||
class QueryReadStoreInfo(QueryInfo):
|
||||
"""
|
||||
A helper to evaluate a request from a dojox.data.QueryReadStore
|
||||
and extracting the following information from it:
|
||||
|
||||
- paging
|
||||
- sorting
|
||||
- filters
|
||||
|
||||
Parameters could be passed within GET or POST.
|
||||
"""
|
||||
def set_paging(self):
|
||||
start = self.request[self.request.method].pop('start', 0)
|
||||
# TODO: start = 1???
|
||||
count = self.request[self.request.method].pop('count', 25)
|
||||
#if not is_number(end): # The dojo combobox may return "Infinity" tsss
|
||||
if not is_number(count) or int(count) > self.max_count:
|
||||
count = self.max_count
|
||||
self.start = int(start)
|
||||
self.end = int(start)+int(count)
|
||||
|
||||
def set_sorting(self):
|
||||
# REQUEST['sort']:
|
||||
# value: -sort_field (descending) / sort_field (ascending)
|
||||
sort_attr = self.request[self.request.method].pop('sort', None)
|
||||
if sort_attr:
|
||||
self.sorting.append(sort_attr)
|
||||
|
||||
def set_filters(self):
|
||||
query_dict = {}
|
||||
for k,v in self.request[self.request.method].items():
|
||||
query_dict[k] = v
|
||||
|
||||
class JsonRestStoreInfo(QueryReadStoreInfo):
|
||||
"""
|
||||
A helper to evaluate a request from a dojox.data.JsonRestStoreInfo
|
||||
and extracting the following information:
|
||||
|
||||
- paging
|
||||
- filters
|
||||
|
||||
The paging parameter is passed within the request header "Range".
|
||||
Filters are passed via GET (equal to QueryReadStoreInfo).
|
||||
|
||||
Sorting is just possible with JsonQueryReadStoreInfo.
|
||||
"""
|
||||
def set_paging(self):
|
||||
# Receiving the following header:
|
||||
# Range: items=0-24
|
||||
# Returning: Content-Range: items 0-24/66
|
||||
if 'RANGE' in self.META:
|
||||
regexp = re.compile(r"^\s*items=(\d+)-(\d+)", re.I)
|
||||
match = regexp.match(self.META['RANGE'])
|
||||
if match:
|
||||
start, end = match.groups()
|
||||
start, end = int(start), int(end)+1 # range-end means including that element!
|
||||
self.start = start
|
||||
count = self.max_count
|
||||
if end-start < self.max_count:
|
||||
count = end-start
|
||||
self.end = start+count
|
||||
|
||||
def set_sorting(self):
|
||||
# sorting is not available in the normal JsonRestStore
|
||||
pass
|
||||
|
||||
class JsonQueryRestStoreInfo(QueryInfo):
|
||||
jsonpath = None
|
||||
jsonpath_filters = None
|
||||
jsonpath_sorting = None
|
||||
jsonpath_paging = None
|
||||
|
||||
def __init__(self, request, **kwargs):
|
||||
"""
|
||||
Matching the following example jsonpath:
|
||||
|
||||
/path/[?(@.field1='searchterm*'&@.field2='*search*')][/@['field1'],/@['field2']][0:24]
|
||||
|
||||
The last part of the URL will contain a JSONPath-query:
|
||||
|
||||
[filter][sort][start:end:step]
|
||||
"""
|
||||
path = request.path
|
||||
if not path.endswith("/"):
|
||||
path = path + "/"
|
||||
# assuming that a least one /path/ will be before the jsonpath query
|
||||
# and that the character [ initiates and ] ends the jsonpath
|
||||
# [ will be removed from the start and ] from the end
|
||||
match = re.match(r'^/.*/(\[.*\])/$', path)
|
||||
if match:
|
||||
self.jsonpath = match.groups()[0]
|
||||
if self.jsonpath:
|
||||
# now we remove the starting [ and ending ] and also splitting it via ][
|
||||
parts = self.jsonpath[1:-1].split("][")
|
||||
for part in parts:
|
||||
if part.startswith("?"):
|
||||
self.jsonpath_filters = part
|
||||
elif re.match(r'^[/\\].*$', part):
|
||||
self.jsonpath_sorting = part
|
||||
# [start:end:step]
|
||||
elif re.match(r'^\d*:\d*:{0,1}\d*$', part):
|
||||
self.jsonpath_paging = part
|
||||
super(JsonQueryRestStoreInfo, self).__init__(request, **kwargs)
|
||||
|
||||
def set_paging(self):
|
||||
# handling 0:24
|
||||
match = re.match(r'^(\d*):(\d*):{0,1}\d*$', self.jsonpath_paging)
|
||||
if match:
|
||||
start, end = match.groups()
|
||||
if(start.length == 0):
|
||||
start = 0
|
||||
if(end.length == 0):
|
||||
end = int(start) + self.max_count
|
||||
start, end = int(start), int(end)+1 # second argument means the element should be included!
|
||||
self.start = start
|
||||
count = self.max_count
|
||||
if end-start < self.max_count:
|
||||
count = end-start
|
||||
self.end = start+count
|
||||
|
||||
def set_sorting(self):
|
||||
# handling /@['field1'],/@['field2']
|
||||
for f in self.jsonpath_sorting.split(",/"):
|
||||
m = re.match(r"([\\/])@\['(.*)'\]", f)
|
||||
if m:
|
||||
sort_prefix = "-"
|
||||
direction, field = m.groups()
|
||||
if direction == "/":
|
||||
descending = ""
|
||||
self.sorting.append(sort_prefix + field)
|
||||
|
||||
def set_filters(self):
|
||||
# handling ?(@.field1='searchterm*'&@.field2~'*search*')
|
||||
pass
|
29
ntuh/dojango/data/modelstore/__init__.py
Executable file
|
@ -0,0 +1,29 @@
|
|||
""" Django ModelStore
|
||||
"""
|
||||
|
||||
from stores import Store, ModelQueryStore
|
||||
|
||||
from methods import Method, ModelMethod, \
|
||||
ObjectMethod, StoreMethod, FieldMethod, ValueMethod, \
|
||||
RequestArg, ModelArg, ObjectArg, FieldArg, StoreArg
|
||||
|
||||
from fields import StoreField, ReferenceField
|
||||
|
||||
from services import BaseService, JsonService, servicemethod
|
||||
|
||||
from utils import get_object_from_identifier
|
||||
|
||||
__all__ = (
|
||||
'Store', 'ModelQueryStore',
|
||||
|
||||
'Method', 'ModelMethod', 'ObjectMethod', 'StoreMethod',
|
||||
'FieldMethod', 'ValueMethod',
|
||||
|
||||
'RequestArg', 'ModelArg', 'ObjectArg', 'FieldArg', 'StoreArg',
|
||||
|
||||
'StoreField', 'ReferenceField',
|
||||
|
||||
'BaseService', 'JsonService', 'servicemethod',
|
||||
|
||||
'get_object_from_identifier'
|
||||
)
|
27
ntuh/dojango/data/modelstore/exceptions.py
Executable file
|
@ -0,0 +1,27 @@
|
|||
""" Django ModelStore exception classes
|
||||
"""
|
||||
|
||||
__all__ = ('MethodException', 'FieldException',
|
||||
'StoreException', 'ServiceException')
|
||||
|
||||
class MethodException(Exception):
|
||||
""" Raised when an error occurs related to a custom
|
||||
method (Method, ObjectMethod, etc.) call
|
||||
"""
|
||||
pass
|
||||
|
||||
class FieldException(Exception):
|
||||
""" Raised when an error occurs related to a custom
|
||||
StoreField definition
|
||||
"""
|
||||
pass
|
||||
|
||||
class StoreException(Exception):
|
||||
""" Raised when an error occurs related to a
|
||||
Store definition
|
||||
"""
|
||||
|
||||
class ServiceException(Exception):
|
||||
""" Raised when an error occurs related to a custom
|
||||
Service definition or servicemethod call
|
||||
"""
|
197
ntuh/dojango/data/modelstore/fields.py
Executable file
|
@ -0,0 +1,197 @@
|
|||
import utils
|
||||
from exceptions import FieldException
|
||||
import methods
|
||||
|
||||
__all__ = ('FieldException', 'StoreField'
|
||||
'ReferenceField', 'DojoDateField')
|
||||
|
||||
class StoreField(object):
|
||||
""" The base StoreField from which all ```StoreField```s derive
|
||||
"""
|
||||
|
||||
def __init__(self, model_field=None, store_field=None, get_value=None, sort_field=None, can_sort=True):
|
||||
""" A StoreField corresponding to a field on a model.
|
||||
|
||||
Arguments (all optional):
|
||||
|
||||
model_field
|
||||
The name of the field on the model. If omitted then
|
||||
it's assumed to be the attribute name given to this StoreField
|
||||
in the Store definition.
|
||||
|
||||
Example:
|
||||
|
||||
>>> class MyStore(Store):
|
||||
>>> field_1 = StoreField() # The model_field will be Model.field_1
|
||||
>>> field_2 = StoreField('my_field') # The model_field will be Model.my_field
|
||||
|
||||
store_field
|
||||
The name of the field in the final store. If omitted then
|
||||
it will be the attribute name given to this StoreField in the
|
||||
Store definition.
|
||||
|
||||
Example:
|
||||
|
||||
>>> class MyStore(Store):
|
||||
>>> field_1 = StoreField() # The store_field will be 'field_1'
|
||||
>>> field_2 = StoreField(store_field='my_store_field')
|
||||
|
||||
get_value
|
||||
An instance of modelstore.methods.BaseMethod (or any callable)
|
||||
used to get the final value from the field (or anywhere) that
|
||||
will go in the store.
|
||||
|
||||
Example:
|
||||
|
||||
def get_custom_value():
|
||||
return 'my custom value'
|
||||
|
||||
>>> class MyStore(Store):
|
||||
# get_custom_value will be called with no arguments
|
||||
>>> field_1 = StoreField(get_value=get_custom_value)
|
||||
|
||||
# Wrap your method in an instance of methods.BaseMethod if you want to pass
|
||||
# custom arguments -- see methods.BaseMethod (and it's derivatives) for full docs.
|
||||
>>> field_2 = StoreField(get_value=Method(get_custom_value, arg1, arg2, arg3))
|
||||
|
||||
sort_field
|
||||
Denotes the string used with QuerySet.order_by() to sort the objects
|
||||
by this field.
|
||||
|
||||
Either the value passed to 'order_by()' on Django
|
||||
QuerySets or an instance of modelstore.methods.BaseMethod
|
||||
(or any callable) which returns the value.
|
||||
|
||||
Requests to sort descending are handled automatically by prepending the sort field
|
||||
with '-'
|
||||
|
||||
Example:
|
||||
|
||||
>>> class MyStore(Store):
|
||||
# QuerySet.order_by() will be called like: QuerySet.order_by('my_model_field')
|
||||
>>> field_1 = StoreField('my_model_field')
|
||||
|
||||
# Sorting by dotted fields.
|
||||
>>> field_2 = StoreField('my.dotted.field', sort_field='my__dotted__field')
|
||||
|
||||
can_sort
|
||||
Whether or not this field can be order_by()'d -- Default is True.
|
||||
|
||||
If this is False, then attempts to sort by this field will be ignored.
|
||||
"""
|
||||
|
||||
self._model_field_name = model_field
|
||||
self._store_field_name = store_field
|
||||
self._store_attr_name = None # We don't know this yet
|
||||
self.can_sort = can_sort
|
||||
self._sort_field = sort_field
|
||||
self._get_value = get_value
|
||||
|
||||
# Attach a reference to this field to the get_value method
|
||||
# so it can access proxied_args
|
||||
if self._get_value:
|
||||
setattr(self._get_value, 'field', self)
|
||||
|
||||
# Proxied arguments (ie, RequestArg, ObjectArg etc.)
|
||||
self.proxied_args = {}
|
||||
|
||||
def _get_sort_field(self):
|
||||
""" Return the name of the field to be passed to
|
||||
QuerySet.order_by().
|
||||
|
||||
Either the name of the value passed to 'order_by()' on Django
|
||||
QuerySets or some method which returns the value.
|
||||
"""
|
||||
if (self._sort_field is None) or isinstance(self._sort_field, (str, unicode) ):
|
||||
return self._sort_field
|
||||
else:
|
||||
return self._sort_field()
|
||||
sort_field = property(_get_sort_field)
|
||||
|
||||
def _get_store_field_name(self):
|
||||
""" Return the name of the field in the final store.
|
||||
|
||||
If an explicit store_field is given in the constructor then that is
|
||||
used, otherwise it's the attribute name given to this field in the
|
||||
Store definition.
|
||||
"""
|
||||
return self._store_field_name or self._store_attr_name
|
||||
store_field_name = property(_get_store_field_name)
|
||||
|
||||
def _get_model_field_name(self):
|
||||
""" Return the name of the field on the Model that this field
|
||||
corresponds to.
|
||||
|
||||
If an explicit model_field (the first arg) is given in the constructor
|
||||
then that is used, otherwise it's assumed to be the attribute name
|
||||
given to this field in the Store definition.
|
||||
"""
|
||||
return self._model_field_name or self._store_attr_name
|
||||
model_field_name = property(_get_model_field_name)
|
||||
|
||||
def get_value(self):
|
||||
""" Returns the value for this field
|
||||
"""
|
||||
if not self._get_value:
|
||||
self._get_value = methods.ObjectMethod(self.model_field_name)
|
||||
self._get_value.field = self
|
||||
|
||||
return self._get_value()
|
||||
|
||||
class ReferenceField(StoreField):
|
||||
""" A StoreField that handles '_reference' items
|
||||
|
||||
Corresponds to model fields that refer to other models,
|
||||
ie, ForeignKey, ManyToManyField etc.
|
||||
"""
|
||||
|
||||
def get_value(self):
|
||||
""" Returns a list (if more than one) or dict
|
||||
of the form:
|
||||
|
||||
{'_reference': '<item identifier>'}
|
||||
"""
|
||||
|
||||
# The Store we're attached to
|
||||
store = self.proxied_args['StoreArg']
|
||||
|
||||
items = []
|
||||
|
||||
if not self._get_value:
|
||||
self._get_value = methods.ObjectMethod(self.model_field_name)
|
||||
self._get_value.field = self
|
||||
|
||||
related = self._get_value()
|
||||
|
||||
if not bool(related):
|
||||
return items
|
||||
|
||||
# Is this a model instance (ie from ForeignKey) ?
|
||||
if hasattr(related, '_get_pk_val'):
|
||||
return {'_reference': store.get_identifier(related)}
|
||||
|
||||
# Django Queryset or Manager
|
||||
if hasattr(related, 'iterator'):
|
||||
related = related.iterator()
|
||||
|
||||
try:
|
||||
for item in related:
|
||||
items.append({'_reference': store.get_identifier(item)})
|
||||
except TypeError:
|
||||
raise FieldException('Cannot iterate on field "%s"' % (
|
||||
self.model_field_name
|
||||
))
|
||||
|
||||
return items
|
||||
|
||||
###
|
||||
# Pre-built custom Fields
|
||||
###
|
||||
|
||||
class DojoDateField(StoreField):
|
||||
|
||||
def get_value(self):
|
||||
|
||||
self._get_value = methods.DojoDateMethod
|
||||
self._get_value.field = self
|
||||
return self._get_value()
|
301
ntuh/dojango/data/modelstore/methods.py
Executable file
|
@ -0,0 +1,301 @@
|
|||
import utils
|
||||
from exceptions import MethodException
|
||||
|
||||
class Arg(object):
|
||||
""" The base placeholder argument class
|
||||
|
||||
There is no reason to use this class directly and really
|
||||
only exists to do some type checking on classes that
|
||||
inherit from it
|
||||
"""
|
||||
pass
|
||||
|
||||
class RequestArg(Arg):
|
||||
""" Placeholder argument that represents the current
|
||||
Request object.
|
||||
"""
|
||||
pass
|
||||
|
||||
class ModelArg(Arg):
|
||||
""" Placeholder argument that represents the current
|
||||
Model object.
|
||||
|
||||
>>> user = User.objects.get(pk=1)
|
||||
>>>
|
||||
|
||||
In this case 'user' is the ObjectArg and
|
||||
and 'User' is the ModelArg.
|
||||
"""
|
||||
pass
|
||||
|
||||
class ObjectArg(Arg):
|
||||
""" Placeholder argument that represents the current
|
||||
Model object instance.
|
||||
|
||||
user = User.objects.get(pk=1)
|
||||
|
||||
'user' is the ObjectArg, 'User' is the ModelArg
|
||||
"""
|
||||
pass
|
||||
|
||||
class StoreArg(Arg):
|
||||
""" Placeholder argument that represents the current
|
||||
Store instance.
|
||||
"""
|
||||
pass
|
||||
|
||||
class FieldArg(Arg):
|
||||
""" Placeholder argument that represents the current
|
||||
Field instance.
|
||||
|
||||
This is the field specified on the Store object,
|
||||
not the Model object.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BaseMethod(object):
|
||||
""" The base class from which all proxied methods
|
||||
derive.
|
||||
"""
|
||||
|
||||
def __init__(self, method_or_methodname, *args, **kwargs):
|
||||
""" The first argument is either the name of a method
|
||||
or the method object itself (ie, pointer to the method)
|
||||
|
||||
The remaining arguments are passed to the given method
|
||||
substituting any proxied arguments as needed.
|
||||
|
||||
Usage:
|
||||
>>> method = Method('my_method', RequestArg, ObjectArg, 'my other arg', my_kwarg='Something')
|
||||
>>> method()
|
||||
'My Result'
|
||||
>>>
|
||||
|
||||
The method call looks like:
|
||||
>>> my_method(request, model_instance, 'my_other_arg', my_kwarg='Something')
|
||||
"""
|
||||
|
||||
self.method_or_methodname = method_or_methodname
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.field = None # Don't have a handle on the field yet
|
||||
|
||||
def __call__(self):
|
||||
""" Builds the arguments and returns the value of the method call
|
||||
"""
|
||||
|
||||
self._build_args()
|
||||
return self.get_value()
|
||||
|
||||
def _build_args(self):
|
||||
""" Builds the arguments to be passed to the given method
|
||||
|
||||
Substitutes placeholder args (ie RequestArg, ObjectArg etc.)
|
||||
with the actual objects.
|
||||
"""
|
||||
|
||||
args = []
|
||||
for arg in self.args:
|
||||
try:
|
||||
arg = self.field.proxied_args.get(arg.__name__, arg)
|
||||
except AttributeError: # No __name__ attr on the arg
|
||||
pass
|
||||
args.append(arg)
|
||||
self.args = args
|
||||
|
||||
for key, val in self.kwargs.items():
|
||||
self.kwargs.update({
|
||||
key: self.field.proxied_args.get(hasattr(val, '__name__') and val.__name__ or val, val)
|
||||
})
|
||||
|
||||
def get_value(self):
|
||||
""" Calls the given method with the requested arguments.
|
||||
"""
|
||||
raise NotImplementedError('get_value() not implemented in BaseMethod')
|
||||
|
||||
def get_method(self, obj=None):
|
||||
""" Resolves the given method into a callable object.
|
||||
|
||||
If 'obj' is provided, the method will be looked for as an
|
||||
attribute of the 'obj'
|
||||
|
||||
Supports dotted names.
|
||||
|
||||
Usage:
|
||||
>>> method = Method('obj.obj.method', RequestArg)
|
||||
>>> method()
|
||||
'Result of method called with: obj.obj.method(request)'
|
||||
>>>
|
||||
|
||||
Dotted attributes are most useful when using something like an
|
||||
an ObjectMethod:
|
||||
|
||||
(where 'user' is an instance of Django's 'User' model,
|
||||
the Object in this example is the 'user' instance)
|
||||
|
||||
>>> method = ObjectMethod('date_joined.strftime', '%Y-%m-%d %H:%M:%S')
|
||||
>>> method()
|
||||
2009-10-02 09:58:39
|
||||
>>>
|
||||
|
||||
The actual method call looks like:
|
||||
>>> user.date_joined.strftime('%Y-%m-%d %H:%M:%S')
|
||||
2009-10-02 09:58:39
|
||||
>>>
|
||||
|
||||
It also supports attributes which are not actually methods:
|
||||
|
||||
>>> method = ObjectMethod('first_name', 'ignored arguments', ...) # Arguments to a non-callable are ignored.
|
||||
>>> method()
|
||||
u'Bilbo'
|
||||
>>> method = ValueMethod('first_name', 'upper') # Called on the returned value
|
||||
>>> method()
|
||||
u'BILBO'
|
||||
>>>
|
||||
|
||||
The method call for the last one looks like:
|
||||
>>> user.first_name.upper()
|
||||
u'BILBO'
|
||||
>>>
|
||||
|
||||
"""
|
||||
|
||||
if callable(self.method_or_methodname):
|
||||
return self.method_or_methodname
|
||||
|
||||
if not isinstance(self.method_or_methodname, (str, unicode) ):
|
||||
raise MethodException('Method must a string or callable')
|
||||
|
||||
if obj is not None:
|
||||
|
||||
try:
|
||||
method = utils.resolve_dotted_attribute(obj, self.method_or_methodname)
|
||||
except AttributeError:
|
||||
raise MethodException('Cannot resolve method "%s" in object "%s"' % (
|
||||
self.method_or_methodname, type(obj)
|
||||
))
|
||||
|
||||
if not callable(method):
|
||||
|
||||
# Turn this into a callable
|
||||
m = method
|
||||
def _m(*args, **kwargs): return m
|
||||
method = _m
|
||||
|
||||
return method
|
||||
|
||||
try:
|
||||
return eval(self.method_or_methodname) # Just try to get it in current scope
|
||||
except NameError:
|
||||
raise MethodException('Cannot resolve method "%s"' % self.method_or_methodname)
|
||||
|
||||
class Method(BaseMethod):
|
||||
""" Basic method proxy class.
|
||||
|
||||
Usage:
|
||||
|
||||
>>> method = Method('my_global_method')
|
||||
>>> result = method()
|
||||
|
||||
>>> method = Method(my_method, RequestArg, ObjectArg)
|
||||
>>> result = method()
|
||||
|
||||
The real method call would look like:
|
||||
>>> my_method(request, model_object)
|
||||
|
||||
Notes:
|
||||
|
||||
If the method passed is the string name of a method,
|
||||
it is evaluated in the global scope to get the actual
|
||||
method, or MethodException is raised.
|
||||
|
||||
>>> method = Method('my_method')
|
||||
|
||||
Under the hood:
|
||||
>>> try:
|
||||
>>> method = eval('my_method')
|
||||
>>> except NameError:
|
||||
>>> ...
|
||||
|
||||
"""
|
||||
def get_value(self):
|
||||
return self.get_method()(*self.args, **self.kwargs)
|
||||
|
||||
class ModelMethod(BaseMethod):
|
||||
""" A method proxy that will look for the given method
|
||||
as an attribute on the Model.
|
||||
"""
|
||||
def get_value(self):
|
||||
obj = self.field.proxied_args['ModelArg']
|
||||
return self.get_method(obj)(*self.args, **self.kwargs)
|
||||
|
||||
class ObjectMethod(BaseMethod):
|
||||
""" A method proxy that will look for the given method
|
||||
as an attribute on the Model instance.
|
||||
|
||||
Example:
|
||||
|
||||
>>> method = ObjectMethod('get_full_name')
|
||||
>>> method()
|
||||
u'Bilbo Baggins'
|
||||
|
||||
Assuming this is used on an instance of Django's 'User' model,
|
||||
the method call looks like:
|
||||
>>> user.get_full_name()
|
||||
|
||||
"""
|
||||
def get_value(self):
|
||||
obj = self.field.proxied_args['ObjectArg']
|
||||
return self.get_method(obj)(*self.args, **self.kwargs)
|
||||
|
||||
class StoreMethod(BaseMethod):
|
||||
""" A method proxy that will look for the given method
|
||||
as an attribute on the Store.
|
||||
"""
|
||||
def get_value(self):
|
||||
obj = self.field.proxied_args['StoreArg']
|
||||
return self.get_method(obj)(*self.args, **self.kwargs)
|
||||
|
||||
class FieldMethod(BaseMethod):
|
||||
""" A method proxy that will look for the given method
|
||||
as an attribute on the Field.
|
||||
|
||||
Notes:
|
||||
Field is the field on the Store, not the Model.
|
||||
"""
|
||||
def get_value(self):
|
||||
obj = self.field.proxied_args['FieldArg']
|
||||
return self.get_method(obj)(*self.args, **self.kwargs)
|
||||
|
||||
class ValueMethod(BaseMethod):
|
||||
""" A method proxy that will look for the given method
|
||||
as an attribute on the value of a field.
|
||||
|
||||
Usage:
|
||||
>>> user = User.objects.get(pk=1)
|
||||
>>> user.date_joined
|
||||
datetime.datetime(..)
|
||||
>>>
|
||||
|
||||
A ValueMethod would look for the given method on
|
||||
the datetime object:
|
||||
|
||||
>>> method = ValueMethod('strftime', '%Y-%m-%d %H:%M:%S')
|
||||
>>> method()
|
||||
u'2009-10-02 12:32:12'
|
||||
>>>
|
||||
"""
|
||||
def get_value(self):
|
||||
obj = self.field.proxied_args['ObjectArg']
|
||||
val = utils.resolve_dotted_attribute(obj, self.field.model_field_name)
|
||||
|
||||
# Prevent throwing a MethodException if the value is None
|
||||
if val is None:
|
||||
return None
|
||||
return self.get_method(val)(*self.args, **self.kwargs)
|
||||
|
||||
###
|
||||
# Pre-built custom Methods
|
||||
###
|
||||
DojoDateMethod = ValueMethod('strftime', '%Y-%m-%dT%H:%M:%S')
|
266
ntuh/dojango/data/modelstore/services.py
Executable file
|
@ -0,0 +1,266 @@
|
|||
import sys, inspect
|
||||
from django.utils import simplejson
|
||||
from exceptions import ServiceException
|
||||
|
||||
def servicemethod(*args, **kwargs):
|
||||
""" The Service method decorator.
|
||||
|
||||
Decorate a function or method to expose it remotely
|
||||
via RPC (or other mechanism.)
|
||||
|
||||
Arguments:
|
||||
|
||||
name (optional):
|
||||
The name of this method as seen remotely.
|
||||
|
||||
store (required if not decorating a bound Store method):
|
||||
A reference to the Store this method operates on.
|
||||
|
||||
This is required if the method is a regular function,
|
||||
a staticmethod or otherwise defined outside a Store instance.
|
||||
(ie doesn't take a 'self' argument)
|
||||
|
||||
store_arg (optional):
|
||||
Specifies whether this method should be passed the Store instance
|
||||
as the first argument (default is True so that servicemethods bound to
|
||||
a store instance can get a proper 'self' reference.)
|
||||
|
||||
request_arg (optional):
|
||||
Specifies whether this method should be passed a reference to the current
|
||||
Request object. (Default is True)
|
||||
|
||||
If both store_arg and request_arg are True, the the store will be passed first,
|
||||
then the request (to appease bound store methods that need a 'self' as the first arg)
|
||||
|
||||
If only one is True then that one will be passed first. This is useful for using
|
||||
standard Django view functions as servicemethods since they require the 'request'
|
||||
as the first argument.
|
||||
"""
|
||||
# Default options
|
||||
options = {'name': None, 'store': None, 'request_arg': True, 'store_arg': True}
|
||||
|
||||
# Figure out if we were called with arguments
|
||||
# If we were called with args, ie:
|
||||
# @servicemethod(name='Foo')
|
||||
# Then the only argument here will be the pre-decorated function/method object.
|
||||
method = ( (len(args) == 1) and callable(args[0]) ) and args[0] or None
|
||||
|
||||
if method is None:
|
||||
# We were called with args, (or @servicemethod() )
|
||||
# so figure out what they were ...
|
||||
|
||||
# The method name should be either the first non-kwarg
|
||||
# or the kwarg 'name'
|
||||
# Example: @servicemethod('my_method', ...) or @servicemethod(name='my_method')
|
||||
options.update({
|
||||
'name': bool(args) and args[0] or kwargs.pop('name', None),
|
||||
'store': (len(args) >= 2) and args[1] or kwargs.pop('store', None),
|
||||
'request_arg': kwargs.pop('request_arg', True),
|
||||
'store_arg': kwargs.pop('store_arg', True),
|
||||
})
|
||||
else:
|
||||
options['name'] = method.__name__
|
||||
method.__servicemethod__ = options
|
||||
|
||||
def method_with_args_wrapper(method):
|
||||
""" Wrapper for a method decorated with decorator arguments
|
||||
"""
|
||||
if options['name'] is None:
|
||||
options['name'] = method.__name__
|
||||
method.__servicemethod__ = options
|
||||
|
||||
if options['store'] is not None:
|
||||
options['store'].service.add_method(method)
|
||||
|
||||
return method
|
||||
|
||||
return method or method_with_args_wrapper
|
||||
|
||||
class BaseService(object):
|
||||
""" The base Service class that manages servicemethods and
|
||||
service method descriptions
|
||||
"""
|
||||
def __init__(self):
|
||||
""" BaseService constructor
|
||||
"""
|
||||
self.methods = {}
|
||||
self._store = None
|
||||
|
||||
def _get_store(self):
|
||||
""" Property getter for the store this service is
|
||||
bound to
|
||||
"""
|
||||
return self._store
|
||||
|
||||
def _set_store(self, store):
|
||||
""" Property setter for the store this service is
|
||||
bound to. Automatically updates the store
|
||||
reference in all the __servicemethod__
|
||||
properties on servicemethods in this service
|
||||
"""
|
||||
for method in self.methods.values():
|
||||
method.__servicemethod__['store'] = store
|
||||
self._store = store
|
||||
store = property(_get_store, _set_store)
|
||||
|
||||
def _get_method_args(self, method, request, params):
|
||||
""" Decide if we should pass store_arg and/or request_arg
|
||||
to the servicemethod
|
||||
"""
|
||||
idx = 0
|
||||
|
||||
if method.__servicemethod__['store_arg']:
|
||||
params.insert(idx, method.__servicemethod__['store'])
|
||||
idx += 1
|
||||
|
||||
if method.__servicemethod__['request_arg']:
|
||||
params.insert(idx, request)
|
||||
|
||||
return params
|
||||
|
||||
def add_method(self, method, name=None, request_arg=True, store_arg=True):
|
||||
""" Adds a method as a servicemethod to this service.
|
||||
"""
|
||||
# Was this a decorated servicemethod?
|
||||
if hasattr(method, '__servicemethod__'):
|
||||
options = method.__servicemethod__
|
||||
else:
|
||||
options = {'name': name or method.__name__, 'store': self.store,
|
||||
'request_arg': request_arg, 'store_arg': store_arg}
|
||||
|
||||
method.__servicemethod__ = options
|
||||
self.methods[ options['name'] ] = method
|
||||
|
||||
def get_method(self, name):
|
||||
""" Returns the servicemethod given by name
|
||||
"""
|
||||
try:
|
||||
return self.methods[name]
|
||||
except KeyError:
|
||||
raise ServiceException('Service method "%s" not registered' % name)
|
||||
|
||||
def list_methods(self):
|
||||
""" Returns a list of all servicemethod names
|
||||
"""
|
||||
return self.methods.keys()
|
||||
|
||||
def process_request(self, request):
|
||||
""" Processes a request object --
|
||||
|
||||
This is generally the entry point for all
|
||||
servicemethod calls
|
||||
"""
|
||||
raise NotImplementedError('process_request not implemented in BaseService')
|
||||
|
||||
def process_response(self, id, result):
|
||||
""" Prepares a response from a servicemethod call
|
||||
"""
|
||||
raise NotImplementedError('process_response not implemented in BaseService')
|
||||
|
||||
def process_error(self, id, code, error):
|
||||
""" Prepares an error response from a servicemethod call
|
||||
"""
|
||||
raise NotImplementedError('process_error not implemented in BaseService')
|
||||
|
||||
def get_smd(self, url):
|
||||
""" Returns a service method description of all public servicemethods
|
||||
"""
|
||||
raise NotImplementedError('get_smd not implemented in BaseService')
|
||||
|
||||
class JsonService(BaseService):
|
||||
""" Implements a JSON-RPC version 1.1 service
|
||||
"""
|
||||
|
||||
def __call__(self, request):
|
||||
""" JSON-RPC method calls come in as POSTs
|
||||
--
|
||||
Requests for the SMD come in as GETs
|
||||
"""
|
||||
|
||||
if request.method == 'POST':
|
||||
response = self.process_request(request)
|
||||
|
||||
else:
|
||||
response = self.get_smd(request.get_full_path())
|
||||
|
||||
return simplejson.dumps(response)
|
||||
|
||||
def process_request(self, request):
|
||||
""" Handle the request
|
||||
"""
|
||||
try:
|
||||
data = simplejson.loads(request.raw_post_data)
|
||||
id, method_name, params = data["id"], data["method"], data["params"]
|
||||
|
||||
# Doing a blanket except here because God knows kind of crazy
|
||||
# POST data might come in.
|
||||
except:
|
||||
return self.process_error(0, 100, 'Invalid JSON-RPC request')
|
||||
|
||||
try:
|
||||
method = self.get_method(method_name)
|
||||
except ServiceException:
|
||||
return self.process_error(id, 100, 'Unknown method: "%s"' % method_name)
|
||||
|
||||
params = self._get_method_args(method, request, params)
|
||||
|
||||
try:
|
||||
result = method(*params)
|
||||
return self.process_response(id, result)
|
||||
|
||||
except BaseException:
|
||||
etype, eval, etb = sys.exc_info()
|
||||
return self.process_error(id, 100, '%s: %s' % (etype.__name__, eval) )
|
||||
|
||||
except:
|
||||
etype, eval, etb = sys.exc_info()
|
||||
return self.process_error(id, 100, 'Exception %s: %s' % (etype, eval) )
|
||||
|
||||
def process_response(self, id, result):
|
||||
""" Build a JSON-RPC 1.1 response dict
|
||||
"""
|
||||
return {
|
||||
'version': '1.1',
|
||||
'id': id,
|
||||
'result': result,
|
||||
'error': None,
|
||||
}
|
||||
|
||||
def process_error(self, id, code, error):
|
||||
""" Build a JSON-RPC 1.1 error dict
|
||||
"""
|
||||
return {
|
||||
'id': id,
|
||||
'version': '1.1',
|
||||
'error': {
|
||||
'name': 'JSONRPCError',
|
||||
'code': code,
|
||||
'message': error,
|
||||
},
|
||||
}
|
||||
|
||||
def get_smd(self, url):
|
||||
""" Generate a JSON-RPC 1.1 Service Method Description (SMD)
|
||||
"""
|
||||
smd = {
|
||||
'serviceType': 'JSON-RPC',
|
||||
'serviceURL': url,
|
||||
'methods': []
|
||||
}
|
||||
|
||||
for name, method in self.methods.items():
|
||||
|
||||
# Figure out what params to report --
|
||||
# we don't want to report the 'store' and 'request'
|
||||
# params to the remote method.
|
||||
idx = 0
|
||||
idx += method.__servicemethod__['store_arg'] and 1 or 0
|
||||
idx += method.__servicemethod__['request_arg'] and 1 or 0
|
||||
|
||||
sig = inspect.getargspec(method)
|
||||
smd['methods'].append({
|
||||
'name': name,
|
||||
'parameters': [ {'name': val} for val in sig.args[idx:] ]
|
||||
})
|
||||
|
||||
return smd
|
454
ntuh/dojango/data/modelstore/stores.py
Executable file
|
@ -0,0 +1,454 @@
|
|||
from django.utils import simplejson
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.core.paginator import Paginator
|
||||
|
||||
from utils import get_fields_and_servicemethods
|
||||
from exceptions import StoreException, ServiceException
|
||||
from services import JsonService, servicemethod
|
||||
|
||||
__all__ = ('Store', 'ModelQueryStore')
|
||||
|
||||
class StoreMetaclass(type):
|
||||
""" This class (mostly) came from django/forms/forms.py
|
||||
See the original class 'DeclarativeFieldsMetaclass' for doc and comments.
|
||||
"""
|
||||
def __new__(cls, name, bases, attrs):
|
||||
|
||||
# Get the declared StoreFields and service methods
|
||||
fields, servicemethods = get_fields_and_servicemethods(bases, attrs)
|
||||
|
||||
attrs['servicemethods'] = servicemethods
|
||||
|
||||
# Tell each field the name of the attribute used to reference it
|
||||
# in the Store
|
||||
for fieldname, field in fields.items():
|
||||
setattr(field, '_store_attr_name', fieldname)
|
||||
attrs['fields'] = fields
|
||||
|
||||
return super(StoreMetaclass, cls).__new__(cls, name, bases, attrs)
|
||||
|
||||
class BaseStore(object):
|
||||
""" The base Store from which all Stores derive
|
||||
"""
|
||||
|
||||
class Meta(object):
|
||||
""" Inner class to hold store options.
|
||||
|
||||
Same basic concept as Django's Meta class
|
||||
on Model definitions.
|
||||
"""
|
||||
pass
|
||||
|
||||
def __init__(self, objects=None, stores=None, identifier=None, label=None, is_nested=False):
|
||||
""" Store instance constructor.
|
||||
|
||||
Arguments (all optional):
|
||||
|
||||
objects:
|
||||
The list (or any iterable, ie QuerySet) of objects that will
|
||||
fill the store.
|
||||
|
||||
stores:
|
||||
One or more Store objects to combine together into a single
|
||||
store. Useful when using ReferenceFields to build a store
|
||||
with objects of more than one 'type' (like Django models
|
||||
via ForeignKeys, ManyToManyFields etc.)
|
||||
|
||||
identifier:
|
||||
The 'identifier' attribute used in the store.
|
||||
|
||||
label:
|
||||
The 'label' attribute used in the store.
|
||||
|
||||
is_nested:
|
||||
This is required, if we want to return the items as direct
|
||||
array and not as dictionary including
|
||||
{'identifier': "id", 'label', ...}
|
||||
It mainly is required, if children of a tree structure needs
|
||||
to be rendered (see TreeStore).
|
||||
"""
|
||||
|
||||
# Instantiate the inner Meta class
|
||||
self._meta = self.Meta()
|
||||
|
||||
# Move the fields into the _meta instance
|
||||
self.set_option('fields', self.fields)
|
||||
|
||||
# Set the identifier
|
||||
if identifier:
|
||||
self.set_option('identifier', identifier)
|
||||
elif not self.has_option('identifier'):
|
||||
self.set_option('identifier', 'id')
|
||||
|
||||
# Set the label
|
||||
if label:
|
||||
self.set_option('label', label)
|
||||
elif not self.has_option('label'):
|
||||
self.set_option('label', 'label')
|
||||
|
||||
# Is this a nested store? (indicating that it should be rendered as array)
|
||||
self.is_nested = is_nested
|
||||
|
||||
# Set the objects
|
||||
if objects != None:
|
||||
self.set_option('objects', objects)
|
||||
elif not self.has_option('objects'):
|
||||
self.set_option('objects', [])
|
||||
|
||||
# Set the stores
|
||||
if stores:
|
||||
self.set_option('stores', stores)
|
||||
elif not self.has_option('stores'):
|
||||
self.set_option('stores', [])
|
||||
|
||||
# Instantiate the stores (if required)
|
||||
self.set_option('stores', [ isinstance(s, Store) and s or s() for s in self.get_option('stores') ])
|
||||
|
||||
# Do we have service set?
|
||||
try:
|
||||
self.service = self.get_option('service')
|
||||
self.service.store = self
|
||||
|
||||
# Populate all the declared servicemethods
|
||||
for method in self.servicemethods.values():
|
||||
self.service.add_method(method)
|
||||
|
||||
except StoreException:
|
||||
self.service = None
|
||||
|
||||
self.request = None # Placeholder for the Request object (if used)
|
||||
self.data = self.is_nested and [] or {} # The serialized data in it's final form
|
||||
|
||||
def has_option(self, option):
|
||||
""" True/False whether the given option is set in the store
|
||||
"""
|
||||
try:
|
||||
self.get_option(option)
|
||||
except StoreException:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_option(self, option):
|
||||
""" Returns the given store option.
|
||||
Raises a StoreException if the option isn't set.
|
||||
"""
|
||||
try:
|
||||
return getattr(self._meta, option)
|
||||
except AttributeError:
|
||||
raise StoreException('Option "%s" not set in store' % option)
|
||||
|
||||
def set_option(self, option, value):
|
||||
""" Sets a store option.
|
||||
"""
|
||||
setattr(self._meta, option, value)
|
||||
|
||||
def __call__(self, request):
|
||||
""" Called when an instance of this store is called
|
||||
(ie as a Django 'view' function from a URLConf).
|
||||
|
||||
It accepts the Request object as it's only param, which
|
||||
it makes available to other methods at 'self.request'.
|
||||
|
||||
Returns the serialized store as Json.
|
||||
"""
|
||||
self.request = request
|
||||
|
||||
if self.service:
|
||||
self._merge_servicemethods()
|
||||
if not self.is_nested:
|
||||
self.data['SMD'] = self.service.get_smd( request.get_full_path() )
|
||||
|
||||
if request.method == 'POST':
|
||||
return self.service(request)
|
||||
|
||||
return self.to_json()
|
||||
|
||||
def __str__(self):
|
||||
""" Renders the store as Json.
|
||||
"""
|
||||
return self.to_json()
|
||||
|
||||
def __repr__(self):
|
||||
""" Renders the store as Json.
|
||||
"""
|
||||
count = getattr(self.get_option('objects'), 'count', '__len__')()
|
||||
return '<%s: identifier: %s, label: %s, objects: %d>' % (
|
||||
self.__class__.__name__, self.get_option('identifier'), self.get_option('label'), count)
|
||||
|
||||
def get_identifier(self, obj):
|
||||
""" Returns a (theoretically) unique key for a given
|
||||
object of the form: <appname>.<modelname>__<pk>
|
||||
"""
|
||||
return smart_unicode('%s__%s' % (
|
||||
obj._meta,
|
||||
obj._get_pk_val(),
|
||||
), strings_only=True)
|
||||
|
||||
def get_label(self, obj):
|
||||
""" Calls the object's __unicode__ method
|
||||
to get the label if available or just returns
|
||||
the identifier.
|
||||
"""
|
||||
try:
|
||||
return obj.__unicode__()
|
||||
except AttributeError:
|
||||
return self.get_identifier(obj)
|
||||
|
||||
def _merge_servicemethods(self):
|
||||
""" Merges the declared service methods from multiple
|
||||
stores into a single store. The store reference on each
|
||||
method will still point to the original store.
|
||||
"""
|
||||
# only run if we have a service set
|
||||
if self.service:
|
||||
|
||||
for store in self.get_option('stores'):
|
||||
if not store.service: # Ignore when no service is defined.
|
||||
continue
|
||||
|
||||
for name, method in store.service.methods.items():
|
||||
try:
|
||||
self.service.get_method(name)
|
||||
raise StoreException('Combined stores have conflicting service method name "%s"' % name)
|
||||
except ServiceException: # This is what we want
|
||||
|
||||
# Don't use service.add_method since we want the 'foreign' method to
|
||||
# stay attached to the original store
|
||||
self.service.methods[name] = method
|
||||
|
||||
def _merge_stores(self):
|
||||
""" Merge all the stores into one.
|
||||
"""
|
||||
for store in self.get_option('stores'):
|
||||
|
||||
# The other stores will (temporarily) take on this store's 'identifier' and
|
||||
# 'label' settings
|
||||
orig_identifier = store.get_option('identifier')
|
||||
orig_label = store.get_option('label')
|
||||
for attr in ('identifier', 'label'):
|
||||
store.set_option(attr, self.get_option(attr))
|
||||
|
||||
self.data['items'] += store.to_python()['items']
|
||||
|
||||
# Reset the old values for label and identifier
|
||||
store.set_option('identifier', orig_identifier)
|
||||
store.set_option('label', orig_label)
|
||||
|
||||
def add_store(self, *stores):
|
||||
""" Add one or more stores to this store.
|
||||
|
||||
Arguments (required):
|
||||
|
||||
stores:
|
||||
One or many Stores (or Store instances) to add to this store.
|
||||
|
||||
Usage:
|
||||
|
||||
>>> store.add_store(MyStore1, MyStore2(), ...)
|
||||
>>>
|
||||
"""
|
||||
# If a non-instance Store is given, instantiate it.
|
||||
stores = [ isinstance(s, Store) and s or s() for s in stores ]
|
||||
self.set_option('stores', list( self.get_option('stores') ) + stores )
|
||||
|
||||
def to_python(self, objects=None):
|
||||
""" Serialize the store into a Python dictionary.
|
||||
|
||||
Arguments (optional):
|
||||
|
||||
objects:
|
||||
The list (or any iterable, ie QuerySet) of objects that will
|
||||
fill the store -- the previous 'objects' setting will be restored
|
||||
after serialization is finished.
|
||||
"""
|
||||
|
||||
if objects is not None:
|
||||
# Save the previous objects setting
|
||||
old_objects = self.get_option('objects')
|
||||
self.set_option('objects', objects)
|
||||
self._serialize()
|
||||
self.set_option('objects', old_objects)
|
||||
else:
|
||||
self._serialize()
|
||||
|
||||
return self.data
|
||||
|
||||
def to_json(self, *args, **kwargs):
|
||||
""" Serialize the store as Json.
|
||||
|
||||
Arguments (all optional):
|
||||
|
||||
objects:
|
||||
(The kwarg 'objects')
|
||||
The list (or any iterable, ie QuerySet) of objects that will
|
||||
fill the store.
|
||||
|
||||
All other args and kwargs are passed to simplejson.dumps
|
||||
"""
|
||||
objects = kwargs.pop('objects', None)
|
||||
return simplejson.dumps( self.to_python(objects), *args, **kwargs )
|
||||
|
||||
def _start_serialization(self):
|
||||
""" Called when serialization of the store begins
|
||||
"""
|
||||
if not self.is_nested:
|
||||
self.data['identifier'] = self.get_option('identifier')
|
||||
|
||||
# Don't set a label field in the store if it's not wanted
|
||||
if bool( self.get_option('label') ) and not self.is_nested:
|
||||
self.data['label'] = self.get_option('label')
|
||||
|
||||
if self.is_nested:
|
||||
self.data = []
|
||||
else:
|
||||
self.data['items'] = []
|
||||
|
||||
def _start_object(self, obj):
|
||||
""" Called when starting to serialize each object in 'objects'
|
||||
|
||||
Requires an object as the only argument.
|
||||
"""
|
||||
# The current object in it's serialized state.
|
||||
self._item = {self.get_option('identifier'): self.get_identifier(obj)}
|
||||
|
||||
label = self.get_option('label')
|
||||
|
||||
# Do we have a 'label' and is it already the
|
||||
# name of one of the declared fields?
|
||||
if label and ( label not in self.get_option('fields').keys() ):
|
||||
|
||||
# Have we defined a 'get_label' method on the store?
|
||||
if callable( getattr(self, 'get_label', None) ):
|
||||
self._item[label] = self.get_label(obj)
|
||||
|
||||
def _handle_field(self, obj, field):
|
||||
""" Handle the given field in the Store
|
||||
"""
|
||||
# Fill the proxied_args on the field (for get_value methods that use them)
|
||||
field.proxied_args.update({
|
||||
'RequestArg': self.request,
|
||||
'ObjectArg': obj,
|
||||
'ModelArg': obj.__class__,
|
||||
'FieldArg': field,
|
||||
'StoreArg': self,
|
||||
})
|
||||
|
||||
# Get the value
|
||||
self._item[field.store_field_name] = field.get_value()
|
||||
|
||||
def _end_object(self, obj):
|
||||
""" Called when serializing an object ends.
|
||||
"""
|
||||
if self.is_nested:
|
||||
self.data.append(self._item)
|
||||
else:
|
||||
self.data['items'].append(self._item)
|
||||
self._item = None
|
||||
|
||||
def _end_serialization(self):
|
||||
""" Called when serialization of the store ends
|
||||
"""
|
||||
pass
|
||||
|
||||
def _serialize(self):
|
||||
""" Serialize the defined objects and stores into it's final form
|
||||
"""
|
||||
self._start_serialization()
|
||||
for obj in self.get_option('objects'):
|
||||
self._start_object(obj)
|
||||
|
||||
for field in self.get_option('fields').values():
|
||||
self._handle_field(obj, field)
|
||||
|
||||
self._end_object(obj)
|
||||
self._end_serialization()
|
||||
self._merge_stores()
|
||||
|
||||
class Store(BaseStore):
|
||||
""" Just defines the __metaclass__
|
||||
|
||||
All the real functionality is implemented in
|
||||
BaseStore
|
||||
"""
|
||||
__metaclass__ = StoreMetaclass
|
||||
|
||||
class ModelQueryStore(Store):
|
||||
""" A store designed to be used with dojox.data.QueryReadStore
|
||||
|
||||
Handles paging, sorting and filtering
|
||||
|
||||
At the moment it requires a custom subclass of QueryReadStore
|
||||
that implements the necessary mechanics to handle server queries
|
||||
the the exported Json RPC 'fetch' method. Soon it will support
|
||||
QueryReadStore itself.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
"""
|
||||
|
||||
objects_per_query = kwargs.pop('objects_per_query', None)
|
||||
|
||||
super(ModelQueryStore, self).__init__(*args, **kwargs)
|
||||
|
||||
if objects_per_query is not None:
|
||||
self.set_option('objects_per_query', objects_per_query)
|
||||
elif not self.has_option('objects_per_query'):
|
||||
self.set_option('objects_per_query', 25)
|
||||
|
||||
def filter_objects(self, request, objects, query):
|
||||
""" Overridable method used to filter the objects
|
||||
based on the query dict.
|
||||
"""
|
||||
return objects
|
||||
|
||||
def sort_objects(self, request, objects, sort_attr, descending):
|
||||
""" Overridable method used to sort the objects based
|
||||
on the attribute given by sort_attr
|
||||
"""
|
||||
return objects
|
||||
|
||||
def __call__(self, request):
|
||||
"""
|
||||
"""
|
||||
self.request = request
|
||||
|
||||
# We need the request.GET QueryDict to be mutable.
|
||||
query_dict = {}
|
||||
for k,v in request.GET.items():
|
||||
query_dict[k] = v
|
||||
|
||||
# dojox.data.QueryReadStore only handles sorting by a single field
|
||||
sort_attr = query_dict.pop('sort', None)
|
||||
descending = False
|
||||
if sort_attr and sort_attr.startswith('-'):
|
||||
descending = True
|
||||
sort_attr = sort_attr.lstrip('-')
|
||||
|
||||
# Paginator is 1-indexed
|
||||
start_index = int( query_dict.pop('start', 0) ) + 1
|
||||
|
||||
# Calculate the count taking objects_per_query into account
|
||||
objects_per_query = self.get_option('objects_per_query')
|
||||
count = query_dict.pop('count', objects_per_query)
|
||||
|
||||
# We don't want the client to be able to ask for a million records.
|
||||
# They can ask for less, but not more ...
|
||||
if count == 'Infinity' or count > objects_per_query:
|
||||
count = objects_per_query
|
||||
|
||||
objects = self.filter_objects(request, self.get_option('objects'), query_dict)
|
||||
objects = self.sort_objects(request, objects, sort_attr, descending)
|
||||
|
||||
paginator = Paginator(objects, count)
|
||||
|
||||
page_num = 1
|
||||
for i in xrange(1, paginator.num_pages + 1):
|
||||
if paginator.page(i).start_index() <= start_index <= paginator.page(i).end_index():
|
||||
page_num = i
|
||||
break
|
||||
|
||||
page = paginator.page(page_num)
|
||||
|
||||
data = self.to_python(objects=page.object_list)
|
||||
data['numRows'] = paginator.count
|
||||
return data
|
44
ntuh/dojango/data/modelstore/treestore.py
Executable file
|
@ -0,0 +1,44 @@
|
|||
from stores import Store
|
||||
from fields import StoreField
|
||||
from methods import BaseMethod
|
||||
|
||||
class ChildrenMethod(BaseMethod):
|
||||
""" A method proxy that will resolve the children
|
||||
of a model that has a tree structure.
|
||||
"django-treebeard" and "django-mptt" both attach a get_children method
|
||||
to the model.
|
||||
"""
|
||||
def get_value(self):
|
||||
store = self.field.proxied_args['StoreArg']
|
||||
obj = self.field.proxied_args['ObjectArg']
|
||||
ret = []
|
||||
# TODO: optimize using get_descendants()
|
||||
if hasattr(obj, "get_children"):
|
||||
ret = store.__class__(objects=obj.get_children(), is_nested=True).to_python()
|
||||
return ret
|
||||
|
||||
class ChildrenField(StoreField):
|
||||
""" A field that renders children items
|
||||
If your model provides a get_children method you can use that field
|
||||
to render all children recursively.
|
||||
(see "django-treebeard", "django-mptt")
|
||||
"""
|
||||
def get_value(self):
|
||||
self._get_value = ChildrenMethod(self.model_field_name)
|
||||
self._get_value.field = self
|
||||
return self._get_value()
|
||||
|
||||
class TreeStore(Store):
|
||||
""" A store that already includes the children field with no additional
|
||||
options. Just subclass that Store, add the to-be-rendered fields and
|
||||
attach a django-treebeard (or django-mptt) model to its Meta class:
|
||||
|
||||
class MyStore(TreeStore):
|
||||
username = StoreField()
|
||||
first_name = StoreField()
|
||||
|
||||
class Meta:
|
||||
objects = YourTreeModel.objects.filter(id=1) # using treebeard or mptt
|
||||
label = 'username'
|
||||
"""
|
||||
children = ChildrenField()
|
95
ntuh/dojango/data/modelstore/utils.py
Executable file
|
@ -0,0 +1,95 @@
|
|||
from django.utils.datastructures import SortedDict
|
||||
from django.db.models import get_model
|
||||
from fields import StoreField
|
||||
from exceptions import StoreException
|
||||
|
||||
def get_object_from_identifier(identifier, valid=None):
|
||||
""" Helper function to resolve an item identifier
|
||||
into a model instance.
|
||||
|
||||
Raises StoreException if the identifier is invalid
|
||||
or the requested Model could not be found
|
||||
|
||||
Raises <Model>.DoesNotExist if the object lookup fails
|
||||
|
||||
Arguments (optional):
|
||||
|
||||
valid
|
||||
One or more Django model classes to compare the
|
||||
returned model instance to.
|
||||
"""
|
||||
try:
|
||||
model_str, pk = identifier.split('__')
|
||||
except ValueError:
|
||||
raise StoreException('Invalid identifier string')
|
||||
|
||||
Model = get_model(*model_str.split('.'))
|
||||
if Model is None:
|
||||
raise StoreException('Model from identifier string "%s" not found' % model_str)
|
||||
|
||||
if valid is not None:
|
||||
if not isinstance(valid, (list, tuple) ):
|
||||
valid = (valid,)
|
||||
if Model not in valid:
|
||||
raise StoreException('Model type mismatch')
|
||||
|
||||
# This will raise Model.DoesNotExist if lookup fails
|
||||
return Model._default_manager.get(pk=pk)
|
||||
|
||||
def get_fields_and_servicemethods(bases, attrs, include_bases=True):
|
||||
""" This function was pilfered (and slightly modified) from django/forms/forms.py
|
||||
See the original function for doc and comments.
|
||||
"""
|
||||
fields = [ (field_name, attrs.pop(field_name)) for \
|
||||
field_name, obj in attrs.items() if isinstance(obj, StoreField)]
|
||||
|
||||
# Get the method name directly from the __servicemethod__ dict
|
||||
# as set by the decorator
|
||||
methods = [ (method.__servicemethod__['name'], method) for \
|
||||
method in attrs.values() if hasattr(method, '__servicemethod__') ]
|
||||
|
||||
if include_bases:
|
||||
for base in bases[::-1]:
|
||||
|
||||
# Grab the fields and servicemethods from the base classes
|
||||
try:
|
||||
fields = base.fields.items() + fields
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
methods = base.servicemethods.items() + methods
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return SortedDict(fields), SortedDict(methods)
|
||||
|
||||
def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
|
||||
""" resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
|
||||
|
||||
Resolves a dotted attribute name to an object. Raises
|
||||
an AttributeError if any attribute in the chain starts with a '_'
|
||||
|
||||
Modification Note:
|
||||
(unless it's the special '__unicode__' method)
|
||||
|
||||
If the optional allow_dotted_names argument is False, dots are not
|
||||
supported and this function operates similar to getattr(obj, attr).
|
||||
|
||||
NOTE:
|
||||
This method was (mostly) copied straight over from SimpleXMLRPCServer.py in the
|
||||
standard library
|
||||
"""
|
||||
if allow_dotted_names:
|
||||
attrs = attr.split('.')
|
||||
else:
|
||||
attrs = [attr]
|
||||
|
||||
for i in attrs:
|
||||
if i.startswith('_') and i != '__unicode__': # Allow the __unicode__ method to be called
|
||||
raise AttributeError(
|
||||
'attempt to access private attribute "%s"' % i
|
||||
)
|
||||
else:
|
||||
obj = getattr(obj,i)
|
||||
return obj
|
0
ntuh/dojango/data/piston/__init__.py
Executable file
76
ntuh/dojango/data/piston/emitters.py
Executable file
|
@ -0,0 +1,76 @@
|
|||
from django.core.serializers.json import DateTimeAwareJSONEncoder
|
||||
from django.db.models.query import QuerySet
|
||||
from django.utils import simplejson as json
|
||||
from piston.emitters import Emitter
|
||||
from piston.validate_jsonp import is_valid_jsonp_callback_value
|
||||
|
||||
|
||||
class DojoDataEmitter(Emitter):
|
||||
"""
|
||||
This emitter is designed to render dojo.data.ItemFileReadStore compatible
|
||||
data.
|
||||
|
||||
Requires your handler to expose the `id` field of your model, that Piston
|
||||
excludes in the default setting. The item's label is the unicode
|
||||
representation of your model unless it already has a field with the
|
||||
name `_unicode`.
|
||||
|
||||
Optional GET variables:
|
||||
`callback`: JSONP callback
|
||||
`indent`: Number of spaces for JSON indentation
|
||||
|
||||
If you serialize Django models and nest related models (which is a common
|
||||
case), make sure to set the `hierarchical` parameter of the
|
||||
ItemFileReadStore to false (which defaults to true).
|
||||
"""
|
||||
|
||||
def render(self, request):
|
||||
"""
|
||||
Renders dojo.data compatible JSON if self.data is a QuerySet, falls
|
||||
back to standard JSON.
|
||||
"""
|
||||
callback = request.GET.get('callback', None)
|
||||
try:
|
||||
indent = int(request.GET['indent'])
|
||||
except (KeyError, ValueError):
|
||||
indent = None
|
||||
|
||||
data = self.construct()
|
||||
|
||||
if isinstance(self.data, QuerySet):
|
||||
unicode_lookup_table = dict()
|
||||
|
||||
[unicode_lookup_table.__setitem__(item.pk, unicode(item)) \
|
||||
for item in self.data]
|
||||
|
||||
for dict_item in data:
|
||||
try:
|
||||
id = dict_item['id']
|
||||
except KeyError:
|
||||
raise KeyError('The handler of the model that you want '\
|
||||
'to emit as DojoData needs to expose the `id` field!')
|
||||
else:
|
||||
dict_item.setdefault('_unicode', unicode_lookup_table[id])
|
||||
|
||||
data = {
|
||||
'identifier': 'id',
|
||||
'items': data,
|
||||
'label': '_unicode',
|
||||
'numRows': self.data.count(),
|
||||
}
|
||||
|
||||
serialized_data = json.dumps(data, ensure_ascii=False,
|
||||
cls=DateTimeAwareJSONEncoder, indent=indent)
|
||||
|
||||
if callback and is_valid_jsonp_callback_value(callback):
|
||||
return '%s(%s)' % (callback, serialized_data)
|
||||
|
||||
return serialized_data
|
||||
|
||||
|
||||
def register_emitters():
|
||||
"""
|
||||
Registers the DojoDataEmitter with the name 'dojodata'.
|
||||
"""
|
||||
Emitter.register('dojodata', DojoDataEmitter,
|
||||
'application/json; charset=utf-8')
|
146
ntuh/dojango/decorators.py
Executable file
|
@ -0,0 +1,146 @@
|
|||
from django.http import HttpResponseNotAllowed, HttpResponseServerError
|
||||
from django.utils import simplejson as json
|
||||
|
||||
from util import to_json_response
|
||||
from util import to_dojo_data
|
||||
|
||||
try:
|
||||
from functools import wraps
|
||||
except ImportError:
|
||||
from django.utils.functional import wraps # Python 2.3, 2.4 fallback.
|
||||
|
||||
def expect_post_request(func):
|
||||
"""Allow only POST requests to come in, throw an exception otherwise.
|
||||
|
||||
This relieves from checking every time that the request is
|
||||
really a POST request, which it should be when using this
|
||||
decorator.
|
||||
"""
|
||||
def _ret(*args, **kwargs):
|
||||
ret = func(*args, **kwargs)
|
||||
request = args[0]
|
||||
if not request.method=='POST':
|
||||
return HttpResponseNotAllowed(['POST'])
|
||||
return ret
|
||||
return _ret
|
||||
|
||||
def add_request_getdict(func):
|
||||
"""Add the method getdict() to the request object.
|
||||
|
||||
This works just like getlist() only that it decodes any nested
|
||||
JSON encoded object structure.
|
||||
Since sending deep nested structures is not possible via
|
||||
GET/POST by default, this enables it. Of course you need to
|
||||
make sure that on the JavaScript side you are also sending
|
||||
the data properly, which dojango.send() automatically does.
|
||||
Example:
|
||||
this is being sent:
|
||||
one:1
|
||||
two:{"three":3, "four":4}
|
||||
using
|
||||
request.POST.getdict('two')
|
||||
returns a dict containing the values sent by the JavaScript.
|
||||
"""
|
||||
def _ret(*args, **kwargs):
|
||||
args[0].POST.__class__.getdict = __getdict
|
||||
ret = func(*args, **kwargs)
|
||||
return ret
|
||||
return _ret
|
||||
|
||||
def __getdict(self, key):
|
||||
ret = self.get(key)
|
||||
try:
|
||||
ret = json.loads(ret)
|
||||
except ValueError: # The value was not JSON encoded :-)
|
||||
raise Exception('"%s" was not JSON encoded as expected (%s).' % (key, str(ret)))
|
||||
return ret
|
||||
|
||||
def json_response(func):
|
||||
"""
|
||||
A simple json response decorator. Use it on views, where a python data object should be converted
|
||||
to a json response:
|
||||
|
||||
@json_response
|
||||
def my_view(request):
|
||||
my_data = {'foo': 'bar'}
|
||||
return my_data
|
||||
"""
|
||||
def inner(request, *args, **kwargs):
|
||||
ret = func(request, *args, **kwargs)
|
||||
return __prepare_json_ret(request, ret)
|
||||
return wraps(func)(inner)
|
||||
|
||||
def jsonp_response_custom(callback_param_name):
|
||||
"""
|
||||
A jsonp (JSON with Padding) response decorator, where you can define your own callbackParamName.
|
||||
It acts like the json_response decorator but with the difference, that it
|
||||
wraps the returned json string into a client-specified function name (that is the Padding).
|
||||
|
||||
You can add this decorator to a function like that:
|
||||
|
||||
@jsonp_response_custom("my_callback_param")
|
||||
def my_view(request):
|
||||
my_data = {'foo': 'bar'}
|
||||
return my_data
|
||||
|
||||
Your now can access this view from a foreign URL using JSONP.
|
||||
An example with Dojo looks like that:
|
||||
|
||||
dojo.io.script.get({ url:"http://example.com/my_url/",
|
||||
callbackParamName:"my_callback_param",
|
||||
load: function(response){
|
||||
console.log(response);
|
||||
}
|
||||
});
|
||||
|
||||
Note: the callback_param_name in the decorator and in your JavaScript JSONP call must be the same.
|
||||
"""
|
||||
def decorator(func):
|
||||
def inner(request, *args, **kwargs):
|
||||
ret = func(request, *args, **kwargs)
|
||||
return __prepare_json_ret(request, ret, callback_param_name=callback_param_name)
|
||||
return wraps(func)(inner)
|
||||
return decorator
|
||||
|
||||
jsonp_response = jsonp_response_custom("jsonp_callback")
|
||||
jsonp_response.__doc__ = "A predefined jsonp response decorator using 'jsoncallback' as a fixed callback_param_name."
|
||||
|
||||
def json_iframe_response(func):
|
||||
"""
|
||||
A simple json response decorator but wrapping the json response into a html page.
|
||||
It helps when doing a json request using an iframe (e.g. file up-/download):
|
||||
|
||||
@json_iframe
|
||||
def my_view(request):
|
||||
my_data = {'foo': 'bar'}
|
||||
return my_data
|
||||
"""
|
||||
def inner(request, *args, **kwargs):
|
||||
ret = func(request, *args, **kwargs)
|
||||
return __prepare_json_ret(request, ret, use_iframe=True)
|
||||
return wraps(func)(inner)
|
||||
|
||||
def __prepare_json_ret(request, ret, callback_param_name=None, use_iframe=False):
|
||||
if ret==False:
|
||||
ret = {'success':False}
|
||||
elif ret==None: # Sometimes there is no return.
|
||||
ret = {}
|
||||
# Add the 'ret'=True, since it was obviously no set yet and we got valid data, no exception.
|
||||
func_name = None
|
||||
if callback_param_name:
|
||||
func_name = request.GET.get(callback_param_name, "callbackParamName")
|
||||
try:
|
||||
if not ret.has_key('success'):
|
||||
ret['success'] = True
|
||||
except AttributeError, e:
|
||||
raise Exception("The returned data of your function must be a dictionary!")
|
||||
json_ret = ""
|
||||
try:
|
||||
# Sometimes the serialization fails, i.e. when there are too deeply nested objects or even classes inside
|
||||
json_ret = to_json_response(ret, func_name, use_iframe)
|
||||
except Exception, e:
|
||||
print '\n\n===============Exception=============\n\n'+str(e)+'\n\n'
|
||||
print ret
|
||||
print '\n\n'
|
||||
return HttpResponseServerError(content=str(e))
|
||||
return json_ret
|
18
ntuh/dojango/dojo-media/dojango.profile.js
Executable file
|
@ -0,0 +1,18 @@
|
|||
dependencies = {
|
||||
layers: [
|
||||
{
|
||||
name: "dojo.js",
|
||||
dependencies: [
|
||||
"dojango.dojango",
|
||||
"dojo.parser"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
prefixes: [
|
||||
[ "dijit", "../dijit" ],
|
||||
[ "dojox", "../dojox" ],
|
||||
[ "dojango", "../../../dojango" ] // relative to the directory, where the dojo.js source file resides
|
||||
]
|
||||
}
|
||||
|
5
ntuh/dojango/dojo-media/dojango/_base.js
Executable file
|
@ -0,0 +1,5 @@
|
|||
dojo.provide("dojango._base");
|
||||
|
||||
dojo.mixin(dojango, {
|
||||
// This where we can mixin functions that are directly accessbile via dojango.*
|
||||
});
|
39
ntuh/dojango/dojo-media/dojango/dojango.js
Executable file
|
@ -0,0 +1,39 @@
|
|||
dojo.provide("dojango.dojango");
|
||||
|
||||
dojango.registerModulePath = function(name, absoluteUrl, relativeUrl) {
|
||||
/*
|
||||
* This is an extended dojo.registerModulePath function. It sets the right
|
||||
* module path depending if you use a local, a builded local or a remote
|
||||
* xd build of dojo.
|
||||
*
|
||||
* If you don't register a path for a module, dojo assumes to find it in:
|
||||
*
|
||||
* ../moduleName
|
||||
*
|
||||
* This is utilized for a local builded version, where your own module will
|
||||
* reside next to dojo/dijit/dojox after it was built.
|
||||
*
|
||||
* An example on how to use an xd build and also loading local files can be found here:
|
||||
* http://jburke.dojotoolkit.org/demos/xdlocal/LocalAndXd.html
|
||||
*/
|
||||
//if (!(dojo.version.flag.length>0 && dojo.baseUrl.indexOf(dojo.version.flag)>-1)) { // what a dirty hack to recognize a locally builded version
|
||||
if (dojo.config.useXDomain) {
|
||||
// if we use an xd build located on another host, we have to use the absolute url of the called host
|
||||
dojo.registerModulePath(name, absoluteUrl.substring(1)); // because '/' is already set in dojo.baseUrl (this is needed!)
|
||||
}
|
||||
else if(dojangoConfig.isLocalBuild){
|
||||
// normally we don't have to set the module path like this.
|
||||
// this is the default module path resolution!
|
||||
// we just add it here because of documentation!
|
||||
dojo.registerModulePath(name, "../" + name)
|
||||
}
|
||||
else {
|
||||
// relative to the dojo/dojo.js-file
|
||||
dojo.registerModulePath(name, relativeUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// dojango.registerModulePath("dojango", dojangoConfig.baseUrl + "/dojango", "../../../dojango");
|
||||
|
||||
// all required dojango functions must be loaded after the module registration
|
||||
dojo.require("dojango._base"); // we always include the basic functionality
|
75
ntuh/dojango/dojo-media/dojango/form/Form.js
Executable file
|
@ -0,0 +1,75 @@
|
|||
dojo.provide("dojango.form.Form");
|
||||
|
||||
dojo.require("dijit.form.Form");
|
||||
dojo.require("dojo.date.stamp");
|
||||
|
||||
dojo.declare("dojango.form.Form", dijit.form.Form, {
|
||||
|
||||
_getDojangoValueAttr: function(){
|
||||
// summary:
|
||||
// Custom get-value-method.
|
||||
// myForm.attr('dojangoValue') returns all form field values
|
||||
// in a Django backend compatible format
|
||||
// myForm.attr("value") still can be used to retrieve the normal dijit.form.Form result
|
||||
var values = this.attr("value");
|
||||
return dojango.form.converter.convert_values(values);
|
||||
}
|
||||
});
|
||||
|
||||
dojango.form.converter = {
|
||||
convert_values:function(/* Array */values){
|
||||
// summary:
|
||||
// Converting a dictionary into valid values for the Django backend, e.g. JS Date Objects will
|
||||
// be converted to 2009-01-01T00:00:00. This is used for converting all values of a form to a
|
||||
// compatible format.
|
||||
//
|
||||
// values:Array
|
||||
// Containing the values that should be converted
|
||||
for(var i in values){
|
||||
if(values[i] && ! values[i].getDate && dojo.isObject(values[i]) && !dojo.isArray(values[i])){
|
||||
values[i] = dojo.toJson(this._convert_value(values[i]));
|
||||
}
|
||||
else {
|
||||
values[i] = this._convert_value(values[i]);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
},
|
||||
|
||||
_convert_value:function(/* String|Object|Integer|Date */value){
|
||||
// summary:
|
||||
// Returns a value that was converted into a django compatible format
|
||||
//
|
||||
// value: String|Object|Integer|Date
|
||||
// The value that should be converted.
|
||||
if(value && value.getDate){
|
||||
value = dojo.date.stamp.toISOString(value);
|
||||
if(typeof(value) != "undefined"){
|
||||
// strip the timezone information +01:00
|
||||
// django/python does not support timezones out of the box!
|
||||
value = value.substring(0, value.length-6);
|
||||
}
|
||||
}
|
||||
else if(dojo.isString(value)){
|
||||
value = value;
|
||||
value = value.replace("<br _moz_editor_bogus_node=\"TRUE\" />", ""); // if a dijit.Editor field is empty in FF it always returns that
|
||||
}
|
||||
else if(dojo.isArray(value)){
|
||||
for(var i=0,l=value.length;i<l;i++){
|
||||
value[i] = this._convert_value(value[i]);
|
||||
}
|
||||
}
|
||||
else if(typeof(value) == "number" && isNaN(value)){ // just matches NaN
|
||||
value = "";
|
||||
}
|
||||
else if(dojo.isObject(value)){
|
||||
for(var i in value){
|
||||
value[i] = this._convert_value(value[i]); // recursive call, if the widget contains several values
|
||||
}
|
||||
}
|
||||
else if(typeof(value) == "undefined"){
|
||||
value = "";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
BIN
ntuh/dojango/dojo-media/dojango/resources/arrowSmall.gif
Executable file
After Width: | Height: | Size: 54 B |
1
ntuh/dojango/dojo-media/dojango/resources/blank.html
Executable file
|
@ -0,0 +1 @@
|
|||
<html><head><script>isLoaded = true;</script></head><body></body></html>
|
BIN
ntuh/dojango/dojo-media/dojango/resources/dojango_logo.jpg
Executable file
After Width: | Height: | Size: 121 KiB |
BIN
ntuh/dojango/dojo-media/dojango/resources/note.gif
Executable file
After Width: | Height: | Size: 617 B |
BIN
ntuh/dojango/dojo-media/dojango/resources/tube.gif
Executable file
After Width: | Height: | Size: 147 B |
BIN
ntuh/dojango/dojo-media/dojango/resources/tubeTall.gif
Executable file
After Width: | Height: | Size: 256 B |
30
ntuh/dojango/dojo-media/dojango/widget/ThumbnailPicker.js
Executable file
|
@ -0,0 +1,30 @@
|
|||
dojo.provide("dojango.widget.ThumbnailPicker");
|
||||
|
||||
dojo.require("dojox.image.ThumbnailPicker");
|
||||
|
||||
dojango.widget._thumbNailPicker = {
|
||||
reset: dojox.image.ThumbnailPicker.prototype.reset // saving the reset method of the original picker
|
||||
}
|
||||
|
||||
dojo.declare("dojango.widget.ThumbnailPicker",
|
||||
dojox.image.ThumbnailPicker,
|
||||
{
|
||||
setDataStore: function(dataStore, request, paramNames){
|
||||
this._reset();
|
||||
this.inherited(arguments);
|
||||
},
|
||||
|
||||
reset: function(){
|
||||
// summary:
|
||||
// dijit.form._FormMixin.reset() is always calling the reset-method and it is
|
||||
// called everytime the tooltip is opened and is deleting all images that were loaded
|
||||
// previously!
|
||||
// we just call it, when a new data-store is set (see setDataStore)
|
||||
dojo.forEach(this._thumbs, function(item){
|
||||
dojo.removeClass(item, "imgSelected");
|
||||
});
|
||||
},
|
||||
|
||||
_reset: dojango.widget._thumbNailPicker.reset // using the reset implementation of the original picker
|
||||
}
|
||||
);
|
112
ntuh/dojango/dojo-media/dojango/widget/plugins/InsertImage.js
Executable file
|
@ -0,0 +1,112 @@
|
|||
dojo.provide("dojango.widget.plugins.InsertImage");
|
||||
|
||||
dojo.require("dijit._editor.plugins.LinkDialog");
|
||||
dojo.require("dojango.widget.ThumbnailPicker");
|
||||
|
||||
dojango.widget.plugins.InsertImageConfig = {
|
||||
// these values just can be overwritten, when the plugin is loaded synchronous
|
||||
size:400,
|
||||
thumbHeight:75,
|
||||
thumbWidth:100,
|
||||
isHorizontal:true
|
||||
}
|
||||
|
||||
dojo.declare("dojango.widget.plugins.InsertImage",
|
||||
dijit._editor.plugins.LinkDialog,
|
||||
{
|
||||
// summary:
|
||||
// An editor plugin that uses dojox.image.ThumbailPicker to select an image and inserting it into the editor.
|
||||
// Populates the ThumbnailPicker as the editor attribute "thumbnailPicker", where you can attach your store
|
||||
// via setDataStore. For store examples look at the dojox.image.ThumbnailPicker tests.
|
||||
// The InsertImage plugin simply extends the LinkDialog plugin which already contains the functionality
|
||||
// for inserting an image.
|
||||
//
|
||||
// example:
|
||||
// | // these css files are required:
|
||||
// | // <link rel="stylesheet" href="%{DOJOX_URL}/image/resources/image.css">
|
||||
// | // <link rel="stylesheet" href="%{DOJANGO_URL}/widget/resources/ThumbnailPicker.css">
|
||||
// | dojo.require("dijit.Editor");
|
||||
// | dojo.require("dojango.widget.plugins.InsertImage");
|
||||
// | dojo.require("dojox.data.FlickrRestStore");
|
||||
// |
|
||||
// | var flickrRestStore = new dojox.data.FlickrRestStore();
|
||||
// | var req = {
|
||||
// | query: {
|
||||
// | apikey: "8c6803164dbc395fb7131c9d54843627", tags: ["dojobeer"]
|
||||
// | },
|
||||
// | count: 20
|
||||
// | };
|
||||
// | var editor = new dijit.Editor({}, dojo.place(dojo.body()));
|
||||
// | editor.thumbnailPicker.setDataStore(flickrRestStore, req);
|
||||
|
||||
//size, thumbHeight, thumbWidth, isHorizontal <= setting these additional parameters
|
||||
command: "insertImage",
|
||||
linkDialogTemplate: [
|
||||
'<div id="${id}_thumbPicker" class="thumbPicker" dojoType="dojango.widget.ThumbnailPicker" size="${size}"',
|
||||
'thumbHeight="${thumbHeight}" thumbWidth="${thumbWidth}" isHorizontal="${isHorizontal}" isClickable="true"></div>',
|
||||
'<label for="${id}_textInput">${text}</label><input dojoType="dijit.form.ValidationTextBox" required="true" name="textInput" id="${id}_textInput"/>',
|
||||
'<div><button dojoType=dijit.form.Button type="submit">${set}</button></div>'
|
||||
].join(""),
|
||||
_picker: null,
|
||||
_textInput: null,
|
||||
_currentItem: null,
|
||||
_initButton: function(){
|
||||
this.linkDialogTemplate = dojo.string.substitute(this.linkDialogTemplate,
|
||||
dojango.widget.plugins.InsertImageConfig,
|
||||
function(value, key){
|
||||
return value ? value : "${" + key + "}"; // we keep the non-matching keys
|
||||
}
|
||||
);
|
||||
this.inherited(arguments);
|
||||
// creating a unique id should happen outside of _initButton (see LinkDialog), so accessing
|
||||
// the widgets in the linkDialog would be easier!
|
||||
this._picker = dijit.byNode(dojo.query("[widgetId]", this.dropDown.domNode)[0]);
|
||||
this._textInput = dijit.byNode(dojo.query("[widgetId]", this.dropDown.domNode)[1]);
|
||||
|
||||
dojo.subscribe(this._picker.getClickTopicName(), dojo.hitch(this, "_markSelected"));
|
||||
this.dropDown.execute = dojo.hitch(this, "_customSetValue");
|
||||
var _this=this;
|
||||
this.dropDown.onOpen = function(){
|
||||
_this._onOpenDialog();
|
||||
dijit.TooltipDialog.prototype.onOpen.apply(this, arguments);
|
||||
// resetting scroller (onOpen scrollLeft set to 0!)
|
||||
var p = _this._picker, a = p._thumbs[p._thumbIndex],
|
||||
b = p.thumbsNode;
|
||||
if(typeof(a) != "undefined" && typeof(b) != "undefined" ){
|
||||
var left = a[p._offsetAttr] - b[p._offsetAttr];
|
||||
p.thumbScroller[p._scrollAttr] = left;
|
||||
}
|
||||
}
|
||||
// the popup needs to be generated, so the ThumbnailPicker can align the images!
|
||||
dijit.popup.prepare(this.dropDown.domNode);
|
||||
// assigning the picker to the editor
|
||||
this.editor.thumbnailPicker = this._picker;
|
||||
},
|
||||
|
||||
_customSetValue: function(args){
|
||||
if(! this._currentItem) {
|
||||
return false;
|
||||
}
|
||||
// pass the url of the current selected image to the setValue method
|
||||
args.urlInput = this._currentItem['largeUrl'] ? this._currentItem['largeUrl'] : this._currentItem['url'];
|
||||
this.setValue(args);
|
||||
},
|
||||
|
||||
_markSelected: function(item){
|
||||
// url, largeUrl, title, link
|
||||
this._currentItem = item;
|
||||
this._textInput.attr("value", item.title ? item.title : "");
|
||||
this._picker.reset();
|
||||
dojo.addClass(this._picker._thumbs[item.index], "imgSelected");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Register this plugin.
|
||||
dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
|
||||
if(o.plugin){ return; }
|
||||
switch(o.args.name){
|
||||
case "dojangoInsertImage":
|
||||
o.plugin = new dojango.widget.plugins.InsertImage({command: "insertImage"});
|
||||
}
|
||||
});
|
5
ntuh/dojango/dojo-media/dojango/widget/resources/ThumbnailPicker.css
Executable file
|
@ -0,0 +1,5 @@
|
|||
.imgSelected {
|
||||
opacity:0.5;
|
||||
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
|
||||
filter: alpha(opacity=50);
|
||||
}
|
26
ntuh/dojango/dojo-media/dojango_optimized.profile.js
Executable file
|
@ -0,0 +1,26 @@
|
|||
dependencies = {
|
||||
layers: [
|
||||
{
|
||||
name: "dojo.js",
|
||||
layerDependencies: [
|
||||
"../dijit/dijit.js"
|
||||
],
|
||||
dependencies: [
|
||||
"dojango.dojango",
|
||||
"dojo.dnd.Source",
|
||||
"dojo.parser",
|
||||
"dojo.date.locale",
|
||||
"dojo.data.ItemFileReadStore",
|
||||
"dojox.data.QueryReadStore",
|
||||
"dijit.dijit-all",
|
||||
"dijit.form.TimeTextBox"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
prefixes: [
|
||||
[ "dijit", "../dijit" ],
|
||||
[ "dojox", "../dojox" ],
|
||||
[ "dojango", "../../../dojango" ] // relative to the directory, where the dojo.js source file resides
|
||||
]
|
||||
}
|
2
ntuh/dojango/dojo-media/src/README
Executable file
|
@ -0,0 +1,2 @@
|
|||
This is were all Dojo source releases are placed.
|
||||
For each Dojo version a new directory must be created and below that, the folders dojo/dijit/dojox/util must reside!
|
4
ntuh/dojango/forms/__init__.py
Executable file
|
@ -0,0 +1,4 @@
|
|||
from django.forms import *
|
||||
from widgets import *
|
||||
from fields import *
|
||||
from models import *
|
158
ntuh/dojango/forms/fields.py
Executable file
|
@ -0,0 +1,158 @@
|
|||
from django.forms import *
|
||||
from django.conf import settings as dj_settings
|
||||
from django.utils import formats
|
||||
|
||||
from dojango.forms import widgets
|
||||
from dojango.util import json_encode
|
||||
|
||||
__all__ = (
|
||||
'Field', 'DEFAULT_DATE_INPUT_FORMATS', 'DEFAULT_TIME_INPUT_FORMATS', # original django classes
|
||||
'DEFAULT_DATETIME_INPUT_FORMATS', 'MultiValueField', 'ComboField', # original django classes
|
||||
'DojoFieldMixin', 'CharField', 'ChoiceField', 'TypedChoiceField',
|
||||
'IntegerField', 'BooleanField', 'FileField', 'ImageField',
|
||||
'DateField', 'TimeField', 'DateTimeField', 'SplitDateTimeField',
|
||||
'RegexField', 'DecimalField', 'FloatField', 'FilePathField',
|
||||
'MultipleChoiceField', 'NullBooleanField', 'EmailField',
|
||||
'IPAddressField', 'URLField', 'SlugField',
|
||||
)
|
||||
|
||||
class DojoFieldMixin(object):
|
||||
"""
|
||||
A general mixin for all custom django/dojo form fields.
|
||||
It passes the field attributes in 'passed_attrs' to the form widget, so
|
||||
they can be used there. The widget itself then evaluates which of these
|
||||
fiels will be used.
|
||||
"""
|
||||
passed_attrs = [ # forwarded field->widget attributes
|
||||
'required',
|
||||
'help_text',
|
||||
'min_value',
|
||||
'max_value',
|
||||
'max_length',
|
||||
'max_digits',
|
||||
'decimal_places',
|
||||
'js_regex', # special key for some dojo widgets
|
||||
]
|
||||
|
||||
def widget_attrs(self, widget):
|
||||
"""Called, when the field is instanitating the widget. Here we collect
|
||||
all field attributes and pass it to the attributes of the widgets using
|
||||
the 'extra_field_attrs' key. These additional attributes will be
|
||||
evaluated by the widget and deleted within the 'DojoWidgetMixin'.
|
||||
"""
|
||||
ret = {'extra_field_attrs': {}}
|
||||
for field_attr in self.passed_attrs:
|
||||
field_val = getattr(self, field_attr, None)
|
||||
#print field_attr, widget, field_val
|
||||
if field_val is not None:
|
||||
ret['extra_field_attrs'][field_attr] = field_val
|
||||
return ret
|
||||
|
||||
###############################################
|
||||
# IMPLEMENTATION OF ALL EXISTING DJANGO FIELDS
|
||||
###############################################
|
||||
|
||||
class CharField(DojoFieldMixin, fields.CharField):
|
||||
widget = widgets.ValidationTextInput
|
||||
|
||||
class ChoiceField(DojoFieldMixin, fields.ChoiceField):
|
||||
widget = widgets.Select
|
||||
|
||||
class TypedChoiceField(DojoFieldMixin, fields.TypedChoiceField):
|
||||
widget = widgets.Select
|
||||
|
||||
class IntegerField(DojoFieldMixin, fields.IntegerField):
|
||||
widget = widgets.NumberTextInput
|
||||
decimal_places = 0
|
||||
|
||||
class BooleanField(DojoFieldMixin, fields.BooleanField):
|
||||
widget = widgets.CheckboxInput
|
||||
|
||||
class FileField(DojoFieldMixin, fields.FileField):
|
||||
widget = widgets.FileInput
|
||||
|
||||
class ImageField(DojoFieldMixin, fields.ImageField):
|
||||
widget = widgets.FileInput
|
||||
|
||||
class DateField(DojoFieldMixin, fields.DateField):
|
||||
widget = widgets.DateInput
|
||||
|
||||
def __init__(self, input_formats=None, min_value=None, max_value=None, *args, **kwargs):
|
||||
kwargs['input_formats'] = input_formats or \
|
||||
tuple(list(formats.get_format('DATE_INPUT_FORMATS')) + [
|
||||
'%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S' # also support dojo's default date-strings
|
||||
])
|
||||
self.max_value = max_value
|
||||
self.min_value = min_value
|
||||
super(DateField, self).__init__(*args, **kwargs)
|
||||
|
||||
class TimeField(DojoFieldMixin, fields.TimeField):
|
||||
widget = widgets.TimeInput
|
||||
|
||||
def __init__(self, input_formats=None, min_value=None, max_value=None, *args, **kwargs):
|
||||
kwargs['input_formats'] = input_formats or \
|
||||
tuple(list(formats.get_format('TIME_INPUT_FORMATS')) + [
|
||||
'%Y-%m-%dT%H:%M', '%Y-%m-%dT%H:%M:%S', 'T%H:%M:%S', 'T%H:%M' # also support dojo's default time-strings
|
||||
])
|
||||
self.max_value = max_value
|
||||
self.min_value = min_value
|
||||
super(TimeField, self).__init__(*args, **kwargs)
|
||||
|
||||
class SplitDateTimeField(DojoFieldMixin, fields.SplitDateTimeField):
|
||||
widget = widgets.DateTimeInput
|
||||
|
||||
def __init__(self, min_value=None, max_value=None, *args, **kwargs):
|
||||
self.max_value = max_value
|
||||
self.min_value = min_value
|
||||
super(SplitDateTimeField, self).__init__(*args, **kwargs)
|
||||
# Overwrite the SplitDateTimeField
|
||||
# copied from original SplitDateTimeField of django
|
||||
errors = self.default_error_messages.copy()
|
||||
if 'error_messages' in kwargs:
|
||||
errors.update(kwargs['error_messages'])
|
||||
fields = (
|
||||
DateField(error_messages={'invalid': errors['invalid_date']}),
|
||||
TimeField(error_messages={'invalid': errors['invalid_time']}),
|
||||
)
|
||||
# copied from original MultiValueField of django
|
||||
for f in fields:
|
||||
f.required = False
|
||||
self.fields = fields
|
||||
|
||||
DateTimeField = SplitDateTimeField # datetime-field is always splitted
|
||||
|
||||
class RegexField(DojoFieldMixin, fields.RegexField):
|
||||
widget = widgets.ValidationTextInput
|
||||
js_regex = None # we additionally have to define a custom javascript regexp, because the python one is not compatible to javascript
|
||||
|
||||
def __init__(self, js_regex=None, *args, **kwargs):
|
||||
self.js_regex = js_regex
|
||||
super(RegexField, self).__init__(*args, **kwargs)
|
||||
|
||||
class DecimalField(DojoFieldMixin, fields.DecimalField):
|
||||
widget = widgets.NumberTextInput
|
||||
|
||||
class FloatField(DojoFieldMixin, fields.FloatField):
|
||||
widget = widgets.ValidationTextInput
|
||||
|
||||
class FilePathField(DojoFieldMixin, fields.FilePathField):
|
||||
widget = widgets.Select
|
||||
|
||||
class MultipleChoiceField(DojoFieldMixin, fields.MultipleChoiceField):
|
||||
widget = widgets.SelectMultiple
|
||||
|
||||
class NullBooleanField(DojoFieldMixin, fields.NullBooleanField):
|
||||
widget = widgets.NullBooleanSelect
|
||||
|
||||
class EmailField(DojoFieldMixin, fields.EmailField):
|
||||
widget = widgets.EmailTextInput
|
||||
|
||||
class IPAddressField(DojoFieldMixin, fields.IPAddressField):
|
||||
widget = widgets.IPAddressTextInput
|
||||
|
||||
class URLField(DojoFieldMixin, fields.URLField):
|
||||
widget = widgets.URLTextInput
|
||||
|
||||
class SlugField(DojoFieldMixin, fields.SlugField):
|
||||
widget = widgets.ValidationTextInput
|
||||
js_regex = '^[-\w]+$' # we cannot extract the original regex input from the python regex
|
74
ntuh/dojango/forms/formsets.py
Executable file
|
@ -0,0 +1,74 @@
|
|||
from django.forms.formsets import *
|
||||
from django.forms.util import ValidationError
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.forms.formsets import TOTAL_FORM_COUNT
|
||||
from django.forms.formsets import INITIAL_FORM_COUNT
|
||||
from django.forms.formsets import DELETION_FIELD_NAME
|
||||
from django.forms.formsets import ORDERING_FIELD_NAME
|
||||
from django.forms.formsets import formset_factory as django_formset_factory
|
||||
from django.forms.forms import Form
|
||||
|
||||
from fields import IntegerField, BooleanField
|
||||
from widgets import Media, HiddenInput
|
||||
|
||||
from django.forms.formsets import BaseFormSet
|
||||
|
||||
__all__ = ('BaseFormSet', 'all_valid')
|
||||
|
||||
class ManagementForm(Form):
|
||||
"""
|
||||
Changed ManagementForm. It is using the dojango form fields.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
|
||||
self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
|
||||
Form.__init__(self, *args, **kwargs)
|
||||
|
||||
class BaseFormSet(BaseFormSet):
|
||||
"""
|
||||
Overwritten BaseFormSet. Basically using the form extension of dojango.
|
||||
"""
|
||||
def _dojango_management_form(self):
|
||||
"""Attaching our own ManagementForm"""
|
||||
if self.data or self.files:
|
||||
form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix)
|
||||
if not form.is_valid():
|
||||
raise ValidationError('ManagementForm data is missing or has been tampered with')
|
||||
else:
|
||||
is_dojo_1_0 = getattr(self, "_total_form_count", False)
|
||||
# this is for django versions before 1.1
|
||||
initial = {
|
||||
TOTAL_FORM_COUNT: is_dojo_1_0 and self._total_form_count or self.total_form_count(),
|
||||
INITIAL_FORM_COUNT: is_dojo_1_0 and self._initial_form_count or self.initial_form_count()
|
||||
}
|
||||
form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial=initial)
|
||||
return form
|
||||
dojango_management_form = property(_dojango_management_form)
|
||||
|
||||
def __getattribute__(self, anatt):
|
||||
"""This is the superhack for overwriting the management_form
|
||||
property of the super class using a newly defined ManagementForm.
|
||||
In Django this property should've be defined lazy:
|
||||
management_form = property(lambda self: self._management_form())
|
||||
"""
|
||||
if anatt == 'management_form':
|
||||
anatt = "dojango_management_form"
|
||||
return super(BaseFormSet, self).__getattribute__(anatt)
|
||||
|
||||
def add_fields(self, form, index):
|
||||
"""Using the dojango form fields instead of the django ones"""
|
||||
is_dojo_1_0 = getattr(self, "_total_form_count", False)
|
||||
if self.can_order:
|
||||
# Only pre-fill the ordering field for initial forms.
|
||||
# before django 1.1 _total_form_count was used!
|
||||
if index < (is_dojo_1_0 and self._total_form_count or self.total_form_count()):
|
||||
form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), initial=index+1, required=False)
|
||||
else:
|
||||
form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_(u'Order'), required=False)
|
||||
if self.can_delete:
|
||||
form.fields[DELETION_FIELD_NAME] = BooleanField(label=_(u'Delete'), required=False)
|
||||
|
||||
def formset_factory(*args, **kwargs):
|
||||
"""Formset factory function that uses the dojango BaseFormSet"""
|
||||
kwargs["formset"] = BaseFormSet
|
||||
return django_formset_factory(*args, **kwargs)
|
212
ntuh/dojango/forms/models.py
Executable file
|
@ -0,0 +1,212 @@
|
|||
from django.forms import *
|
||||
from django.forms.models import BaseModelFormSet
|
||||
from django.forms.models import BaseInlineFormSet
|
||||
from django.forms.models import ModelChoiceIterator
|
||||
from django.forms.models import InlineForeignKeyHiddenInput, InlineForeignKeyField
|
||||
|
||||
from django.utils.text import capfirst
|
||||
|
||||
from formsets import BaseFormSet
|
||||
|
||||
from django.db.models import fields
|
||||
|
||||
from dojango.forms.fields import *
|
||||
from dojango.forms.widgets import DojoWidgetMixin, Textarea, Select, SelectMultiple, HiddenInput
|
||||
|
||||
__all__ = (
|
||||
'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
|
||||
'save_instance', 'ModelChoiceField', 'ModelMultipleChoiceField',
|
||||
)
|
||||
|
||||
class ModelChoiceField(DojoFieldMixin, models.ModelChoiceField):
|
||||
"""
|
||||
Overwritten 'ModelChoiceField' using the 'DojoFieldMixin' functionality.
|
||||
"""
|
||||
widget = Select
|
||||
|
||||
class ModelMultipleChoiceField(DojoFieldMixin, models.ModelMultipleChoiceField):
|
||||
"""
|
||||
Overwritten 'ModelMultipleChoiceField' using the 'DojoFieldMixin' functonality.
|
||||
"""
|
||||
widget = SelectMultiple
|
||||
|
||||
# Fields #####################################################################
|
||||
|
||||
class InlineForeignKeyHiddenInput(DojoWidgetMixin, InlineForeignKeyHiddenInput):
|
||||
"""
|
||||
Overwritten InlineForeignKeyHiddenInput to use the dojango widget mixin
|
||||
"""
|
||||
dojo_type = 'dijit.form.TextBox' # otherwise dijit.form.Form can't get its values
|
||||
|
||||
class InlineForeignKeyField(DojoFieldMixin, InlineForeignKeyField, Field):
|
||||
"""
|
||||
Overwritten InlineForeignKeyField to use the dojango field mixin and passing
|
||||
the dojango InlineForeignKeyHiddenInput as widget.
|
||||
"""
|
||||
def __init__(self, parent_instance, *args, **kwargs):
|
||||
self.parent_instance = parent_instance
|
||||
self.pk_field = kwargs.pop("pk_field", False)
|
||||
self.to_field = kwargs.pop("to_field", None)
|
||||
if self.parent_instance is not None:
|
||||
if self.to_field:
|
||||
kwargs["initial"] = getattr(self.parent_instance, self.to_field)
|
||||
else:
|
||||
kwargs["initial"] = self.parent_instance.pk
|
||||
|
||||
kwargs["required"] = False
|
||||
kwargs["widget"] = InlineForeignKeyHiddenInput
|
||||
# don't call the the superclass of this one. Use the superclass of the
|
||||
# normal django InlineForeignKeyField
|
||||
Field.__init__(self, *args, **kwargs)
|
||||
|
||||
# our customized model field => form field map
|
||||
# here it is defined which form field is used by which model field, when creating a ModelForm
|
||||
MODEL_TO_FORM_FIELD_MAP = (
|
||||
# (model_field, form_field, [optional widget])
|
||||
# the order of these fields is very important for inherited model fields
|
||||
# e.g. the CharField must be checked at last, because several other
|
||||
# fields are a subclass of it.
|
||||
(fields.CommaSeparatedIntegerField, CharField),
|
||||
(fields.DateTimeField, DateTimeField), # must be in front of the DateField
|
||||
(fields.DateField, DateField),
|
||||
(fields.DecimalField, DecimalField),
|
||||
(fields.EmailField, EmailField),
|
||||
(fields.FilePathField, FilePathField),
|
||||
(fields.FloatField, FloatField),
|
||||
(fields.related.ForeignKey, ModelChoiceField),
|
||||
(fields.files.ImageField, ImageField),
|
||||
(fields.files.FileField, FileField),
|
||||
(fields.IPAddressField, IPAddressField),
|
||||
(fields.related.ManyToManyField, ModelMultipleChoiceField),
|
||||
(fields.NullBooleanField, CharField),
|
||||
(fields.BooleanField, BooleanField),
|
||||
(fields.PositiveSmallIntegerField, IntegerField),
|
||||
(fields.PositiveIntegerField, IntegerField),
|
||||
(fields.SlugField, SlugField),
|
||||
(fields.SmallIntegerField, IntegerField),
|
||||
(fields.IntegerField, IntegerField),
|
||||
(fields.TimeField, TimeField),
|
||||
(fields.URLField, URLField),
|
||||
(fields.TextField, CharField, Textarea),
|
||||
(fields.CharField, CharField),
|
||||
)
|
||||
|
||||
def formfield_function(field, **kwargs):
|
||||
"""
|
||||
Custom formfield function, so we can inject our own form fields. The
|
||||
mapping of model fields to form fields is defined in 'MODEL_TO_FORM_FIELD_MAP'.
|
||||
It uses the default django mapping as fallback, if there is no match in our
|
||||
custom map.
|
||||
|
||||
field -- a model field
|
||||
"""
|
||||
for field_map in MODEL_TO_FORM_FIELD_MAP:
|
||||
if isinstance(field, field_map[0]):
|
||||
defaults = {}
|
||||
if field.choices:
|
||||
# the normal django field forms.TypedChoiceField is wired hard
|
||||
# within the original db/models/fields.py.
|
||||
# If we use our custom Select widget, we also have to pass in
|
||||
# some additional validation field attributes.
|
||||
defaults['widget'] = Select(attrs={
|
||||
'extra_field_attrs':{
|
||||
'required':not field.blank,
|
||||
'help_text':field.help_text,
|
||||
}
|
||||
})
|
||||
elif len(field_map) == 3:
|
||||
defaults['widget']=field_map[2]
|
||||
defaults.update(kwargs)
|
||||
return field.formfield(form_class=field_map[1], **defaults)
|
||||
# return the default formfield, if there is no equivalent
|
||||
return field.formfield(**kwargs)
|
||||
|
||||
# ModelForms #################################################################
|
||||
|
||||
def fields_for_model(*args, **kwargs):
|
||||
"""Changed fields_for_model function, where we use our own formfield_callback"""
|
||||
kwargs["formfield_callback"] = formfield_function
|
||||
return models.fields_for_model(*args, **kwargs)
|
||||
|
||||
class ModelFormMetaclass(models.ModelFormMetaclass):
|
||||
"""
|
||||
Overwritten 'ModelFormMetaClass'. We attach our own formfield generation
|
||||
function.
|
||||
"""
|
||||
def __new__(cls, name, bases, attrs):
|
||||
# this is how we can replace standard django form fields with dojo ones
|
||||
attrs["formfield_callback"] = formfield_function
|
||||
return super(ModelFormMetaclass, cls).__new__(cls, name, bases, attrs)
|
||||
|
||||
class ModelForm(models.ModelForm):
|
||||
"""
|
||||
Overwritten 'ModelForm' using the metaclass defined above.
|
||||
"""
|
||||
__metaclass__ = ModelFormMetaclass
|
||||
|
||||
def modelform_factory(*args, **kwargs):
|
||||
"""Changed modelform_factory function, where we use our own formfield_callback"""
|
||||
kwargs["formfield_callback"] = formfield_function
|
||||
kwargs["form"] = ModelForm
|
||||
return models.modelform_factory(*args, **kwargs)
|
||||
|
||||
# ModelFormSets ##############################################################
|
||||
|
||||
class BaseModelFormSet(BaseModelFormSet, BaseFormSet):
|
||||
|
||||
def add_fields(self, form, index):
|
||||
"""Overwritten BaseModelFormSet using the dojango BaseFormSet and
|
||||
the ModelChoiceField.
|
||||
NOTE: This method was copied from django 1.3 beta 1"""
|
||||
from django.db.models import AutoField, OneToOneField, ForeignKey
|
||||
self._pk_field = pk = self.model._meta.pk
|
||||
def pk_is_not_editable(pk):
|
||||
return ((not pk.editable) or (pk.auto_created or isinstance(pk, AutoField))
|
||||
or (pk.rel and pk.rel.parent_link and pk_is_not_editable(pk.rel.to._meta.pk)))
|
||||
if pk_is_not_editable(pk) or pk.name not in form.fields:
|
||||
if form.is_bound:
|
||||
pk_value = form.instance.pk
|
||||
else:
|
||||
try:
|
||||
if index is not None:
|
||||
pk_value = self.get_queryset()[index].pk
|
||||
else:
|
||||
pk_value = None
|
||||
except IndexError:
|
||||
pk_value = None
|
||||
if isinstance(pk, OneToOneField) or isinstance(pk, ForeignKey):
|
||||
qs = pk.rel.to._default_manager.get_query_set()
|
||||
else:
|
||||
qs = self.model._default_manager.get_query_set()
|
||||
qs = qs.using(form.instance._state.db)
|
||||
form.fields[self._pk_field.name] = ModelChoiceField(qs, initial=pk_value, required=False, widget=HiddenInput)
|
||||
BaseFormSet.add_fields(self, form, index)
|
||||
|
||||
def modelformset_factory(*args, **kwargs):
|
||||
"""Changed modelformset_factory function, where we use our own formfield_callback"""
|
||||
kwargs["formfield_callback"] = formfield_function
|
||||
kwargs["formset"] = BaseModelFormSet
|
||||
return models.modelformset_factory(*args, **kwargs)
|
||||
|
||||
# InlineFormSets #############################################################
|
||||
|
||||
class BaseInlineFormSet(BaseInlineFormSet, BaseModelFormSet):
|
||||
"""Overwritten BaseInlineFormSet using the dojango InlineForeignKeyFields.
|
||||
NOTE: This method was copied from django 1.1"""
|
||||
def add_fields(self, form, index):
|
||||
super(BaseInlineFormSet, self).add_fields(form, index)
|
||||
if self._pk_field == self.fk:
|
||||
form.fields[self._pk_field.name] = InlineForeignKeyField(self.instance, pk_field=True)
|
||||
else:
|
||||
kwargs = {
|
||||
'label': getattr(form.fields.get(self.fk.name), 'label', capfirst(self.fk.verbose_name))
|
||||
}
|
||||
if self.fk.rel.field_name != self.fk.rel.to._meta.pk.name:
|
||||
kwargs['to_field'] = self.fk.rel.field_name
|
||||
form.fields[self.fk.name] = InlineForeignKeyField(self.instance, **kwargs)
|
||||
|
||||
def inlineformset_factory(*args, **kwargs):
|
||||
"""Changed inlineformset_factory function, where we use our own formfield_callback"""
|
||||
kwargs["formfield_callback"] = formfield_function
|
||||
kwargs["formset"] = BaseInlineFormSet
|
||||
return models.inlineformset_factory(*args, **kwargs)
|
565
ntuh/dojango/forms/widgets.py
Executable file
|
@ -0,0 +1,565 @@
|
|||
import datetime
|
||||
|
||||
from django.forms import *
|
||||
from django.utils import formats
|
||||
from django.utils.encoding import StrAndUnicode, force_unicode
|
||||
from django.utils.html import conditional_escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.forms.util import flatatt
|
||||
from django.utils import datetime_safe
|
||||
|
||||
from dojango.util import json_encode
|
||||
from dojango.util.config import Config
|
||||
|
||||
from dojango.util import dojo_collector
|
||||
|
||||
__all__ = (
|
||||
'Media', 'MediaDefiningClass', # original django classes
|
||||
'DojoWidgetMixin', 'Input', 'Widget', 'TextInput', 'PasswordInput',
|
||||
'HiddenInput', 'MultipleHiddenInput', 'FileInput', 'Textarea',
|
||||
'DateInput', 'DateTimeInput', 'TimeInput', 'CheckboxInput', 'Select',
|
||||
'NullBooleanSelect', 'SelectMultiple', 'RadioInput', 'RadioFieldRenderer',
|
||||
'RadioSelect', 'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
|
||||
'SplitHiddenDateTimeWidget', 'SimpleTextarea', 'EditorInput', 'HorizontalSliderInput',
|
||||
'VerticalSliderInput', 'ValidationTextInput', 'ValidationPasswordInput',
|
||||
'EmailTextInput', 'IPAddressTextInput', 'URLTextInput', 'NumberTextInput',
|
||||
'RangeBoundTextInput', 'NumberSpinnerInput', 'RatingInput', 'DateInputAnim',
|
||||
'DropDownSelect', 'CheckedMultiSelect', 'FilteringSelect', 'ComboBox',
|
||||
'ComboBoxStore', 'FilteringSelectStore', 'ListInput',
|
||||
)
|
||||
|
||||
dojo_config = Config() # initialize the configuration
|
||||
|
||||
class DojoWidgetMixin:
|
||||
"""A helper mixin, that is used by every custom dojo widget.
|
||||
Some dojo widgets can utilize the validation information of a field and here
|
||||
we mixin those attributes into the widget. Field attributes that are listed
|
||||
in the 'valid_extra_attrs' will be mixed into the attributes of a widget.
|
||||
|
||||
The 'default_field_attr_map' property contains the default mapping of field
|
||||
attributes to dojo widget attributes.
|
||||
|
||||
This mixin also takes care passing the required dojo modules to the collector.
|
||||
'dojo_type' defines the used dojo module type of this widget and adds this
|
||||
module to the collector, if no 'alt_require' property is defined. When
|
||||
'alt_require' is set, this module will be passed to the collector. By using
|
||||
'extra_dojo_require' it is possible to pass additional dojo modules to the
|
||||
collector.
|
||||
"""
|
||||
dojo_type = None # this is the dojoType definition of the widget. also used for generating the dojo.require call
|
||||
alt_require = None # alternative dojo.require call (not using the dojo_type)
|
||||
extra_dojo_require = [] # these dojo modules also needs to be loaded for this widget
|
||||
|
||||
default_field_attr_map = { # the default map for mapping field attributes to dojo attributes
|
||||
'required':'required',
|
||||
'help_text':'promptMessage',
|
||||
'min_value':'constraints.min',
|
||||
'max_value':'constraints.max',
|
||||
'max_length':'maxLength',
|
||||
#'max_digits':'maxDigits',
|
||||
'decimal_places':'constraints.places',
|
||||
'js_regex':'regExp',
|
||||
'multiple':'multiple',
|
||||
}
|
||||
field_attr_map = {} # used for overwriting the default attr-map
|
||||
valid_extra_attrs = [] # these field_attributes are valid for the current widget
|
||||
|
||||
def _mixin_attr(self, attrs, key, value):
|
||||
"""Mixes in the passed key/value into the passed attrs and returns that
|
||||
extended attrs dictionary.
|
||||
|
||||
A 'key', that is separated by a dot, e.g. 'constraints.min', will be
|
||||
added as:
|
||||
|
||||
{'constraints':{'min':value}}
|
||||
"""
|
||||
dojo_field_attr = key.split(".")
|
||||
inner_dict = attrs
|
||||
len_fields = len(dojo_field_attr)
|
||||
count = 0
|
||||
for i in dojo_field_attr:
|
||||
count = count+1
|
||||
if count == len_fields and inner_dict.get(i, None) is None:
|
||||
if isinstance(value, datetime.datetime):
|
||||
if isinstance(self, TimeInput):
|
||||
value = value.strftime('T%H:%M:%S')
|
||||
if isinstance(self, DateInput):
|
||||
value = value.strftime('%Y-%m-%d')
|
||||
value = str(value).replace(' ', 'T') # see dojo.date.stamp
|
||||
if isinstance(value, datetime.date):
|
||||
value = str(value)
|
||||
if isinstance(value, datetime.time):
|
||||
value = "T" + str(value) # see dojo.date.stamp
|
||||
inner_dict[i] = value
|
||||
elif not inner_dict.has_key(i):
|
||||
inner_dict[i] = {}
|
||||
inner_dict = inner_dict[i]
|
||||
return attrs
|
||||
|
||||
def build_attrs(self, extra_attrs=None, **kwargs):
|
||||
"""Overwritten helper function for building an attribute dictionary.
|
||||
This helper also takes care passing the used dojo modules to the
|
||||
collector. Furthermore it mixes in the used field attributes into the
|
||||
attributes of this widget.
|
||||
"""
|
||||
# gathering all widget attributes
|
||||
attrs = dict(self.attrs, **kwargs)
|
||||
field_attr = self.default_field_attr_map.copy() # use a copy of that object. otherwise changed field_attr_map would overwrite the default-map for all widgets!
|
||||
field_attr.update(self.field_attr_map) # the field-attribute-mapping can be customzied
|
||||
if extra_attrs:
|
||||
attrs.update(extra_attrs)
|
||||
# assigning dojoType to our widget
|
||||
dojo_type = getattr(self, "dojo_type", False)
|
||||
if dojo_type:
|
||||
attrs["dojoType"] = dojo_type # add the dojoType attribute
|
||||
|
||||
# fill the global collector object
|
||||
if getattr(self, "alt_require", False):
|
||||
dojo_collector.add_module(self.alt_require)
|
||||
elif dojo_type:
|
||||
dojo_collector.add_module(self.dojo_type)
|
||||
extra_requires = getattr(self, "extra_dojo_require", [])
|
||||
for i in extra_requires:
|
||||
dojo_collector.add_module(i)
|
||||
|
||||
# mixin those additional field attrs, that are valid for this widget
|
||||
extra_field_attrs = attrs.get("extra_field_attrs", False)
|
||||
if extra_field_attrs:
|
||||
for i in self.valid_extra_attrs:
|
||||
field_val = extra_field_attrs.get(i, None)
|
||||
new_attr_name = field_attr.get(i, None)
|
||||
if field_val is not None and new_attr_name is not None:
|
||||
attrs = self._mixin_attr(attrs, new_attr_name, field_val)
|
||||
del attrs["extra_field_attrs"]
|
||||
|
||||
# now encode several attributes, e.g. False = false, True = true
|
||||
for i in attrs:
|
||||
if isinstance(attrs[i], bool):
|
||||
attrs[i] = json_encode(attrs[i])
|
||||
return attrs
|
||||
|
||||
#############################################
|
||||
# ALL OVERWRITTEN DEFAULT DJANGO WIDGETS
|
||||
#############################################
|
||||
|
||||
class Widget(DojoWidgetMixin, widgets.Widget):
|
||||
dojo_type = 'dijit._Widget'
|
||||
|
||||
class Input(DojoWidgetMixin, widgets.Input):
|
||||
pass
|
||||
|
||||
class TextInput(DojoWidgetMixin, widgets.TextInput):
|
||||
dojo_type = 'dijit.form.TextBox'
|
||||
valid_extra_attrs = [
|
||||
'max_length',
|
||||
]
|
||||
|
||||
class PasswordInput(DojoWidgetMixin, widgets.PasswordInput):
|
||||
dojo_type = 'dijit.form.TextBox'
|
||||
valid_extra_attrs = [
|
||||
'max_length',
|
||||
]
|
||||
|
||||
class HiddenInput(DojoWidgetMixin, widgets.HiddenInput):
|
||||
dojo_type = 'dijit.form.TextBox' # otherwise dijit.form.Form can't get its values
|
||||
|
||||
class MultipleHiddenInput(DojoWidgetMixin, widgets.MultipleHiddenInput):
|
||||
dojo_type = 'dijit.form.TextBox' # otherwise dijit.form.Form can't get its values
|
||||
|
||||
class FileInput(DojoWidgetMixin, widgets.FileInput):
|
||||
dojo_type = 'dojox.form.FileInput'
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('%(base_url)s/dojox/form/resources/FileInput.css' % {
|
||||
'base_url':dojo_config.dojo_base_url
|
||||
},)
|
||||
}
|
||||
|
||||
class Textarea(DojoWidgetMixin, widgets.Textarea):
|
||||
"""Auto resizing textarea"""
|
||||
dojo_type = 'dijit.form.Textarea'
|
||||
valid_extra_attrs = [
|
||||
'max_length'
|
||||
]
|
||||
|
||||
if DateInput:
|
||||
class DateInput(DojoWidgetMixin, widgets.DateInput):
|
||||
dojo_type = 'dijit.form.DateTextBox'
|
||||
valid_extra_attrs = [
|
||||
'required',
|
||||
'help_text',
|
||||
'min_value',
|
||||
'max_value',
|
||||
]
|
||||
else: # fallback for older django versions
|
||||
class DateInput(TextInput):
|
||||
"""Copy of the implementation in Django 1.1. Before this widget did not exists."""
|
||||
dojo_type = 'dijit.form.DateTextBox'
|
||||
valid_extra_attrs = [
|
||||
'required',
|
||||
'help_text',
|
||||
'min_value',
|
||||
'max_value',
|
||||
]
|
||||
format = '%Y-%m-%d' # '2006-10-25'
|
||||
def __init__(self, attrs=None, format=None):
|
||||
super(DateInput, self).__init__(attrs)
|
||||
if format:
|
||||
self.format = format
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
if value is None:
|
||||
value = ''
|
||||
elif hasattr(value, 'strftime'):
|
||||
value = datetime_safe.new_date(value)
|
||||
value = value.strftime(self.format)
|
||||
return super(DateInput, self).render(name, value, attrs)
|
||||
|
||||
if TimeInput:
|
||||
class TimeInput(DojoWidgetMixin, widgets.TimeInput):
|
||||
dojo_type = 'dijit.form.TimeTextBox'
|
||||
valid_extra_attrs = [
|
||||
'required',
|
||||
'help_text',
|
||||
'min_value',
|
||||
'max_value',
|
||||
]
|
||||
format = "T%H:%M:%S" # special for dojo: 'T12:12:33'
|
||||
|
||||
def __init__(self, attrs=None, format=None):
|
||||
# always passing the dojo time format
|
||||
super(TimeInput, self).__init__(attrs, format=self.format)
|
||||
|
||||
def _has_changed(self, initial, data):
|
||||
try:
|
||||
input_format = self.format
|
||||
initial = datetime.time(*time.strptime(initial, input_format)[3:6])
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return super(TimeInput, self)._has_changed(self._format_value(initial), data)
|
||||
|
||||
else: # fallback for older django versions
|
||||
class TimeInput(TextInput):
|
||||
"""Copy of the implementation in Django 1.1. Before this widget did not exists."""
|
||||
dojo_type = 'dijit.form.TimeTextBox'
|
||||
valid_extra_attrs = [
|
||||
'required',
|
||||
'help_text',
|
||||
'min_value',
|
||||
'max_value',
|
||||
]
|
||||
format = "T%H:%M:%S" # special for dojo: 'T12:12:33'
|
||||
def __init__(self, attrs=None, format=None):
|
||||
super(TimeInput, self).__init__(attrs)
|
||||
if format:
|
||||
self.format = format
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
if value is None:
|
||||
value = ''
|
||||
elif hasattr(value, 'strftime'):
|
||||
value = value.strftime(self.format)
|
||||
return super(TimeInput, self).render(name, value, attrs)
|
||||
|
||||
class CheckboxInput(DojoWidgetMixin, widgets.CheckboxInput):
|
||||
dojo_type = 'dijit.form.CheckBox'
|
||||
|
||||
class Select(DojoWidgetMixin, widgets.Select):
|
||||
dojo_type = dojo_config.version < '1.4' and 'dijit.form.FilteringSelect' or 'dijit.form.Select'
|
||||
valid_extra_attrs = dojo_config.version < '1.4' and \
|
||||
['required', 'help_text',] or \
|
||||
['required',]
|
||||
|
||||
class NullBooleanSelect(DojoWidgetMixin, widgets.NullBooleanSelect):
|
||||
dojo_type = dojo_config.version < '1.4' and 'dijit.form.FilteringSelect' or 'dijit.form.Select'
|
||||
valid_extra_attrs = dojo_config.version < '1.4' and \
|
||||
['required', 'help_text',] or \
|
||||
['required',]
|
||||
|
||||
class SelectMultiple(DojoWidgetMixin, widgets.SelectMultiple):
|
||||
dojo_type = 'dijit.form.MultiSelect'
|
||||
|
||||
RadioInput = widgets.RadioInput
|
||||
RadioFieldRenderer = widgets.RadioFieldRenderer
|
||||
|
||||
class RadioSelect(DojoWidgetMixin, widgets.RadioSelect):
|
||||
dojo_type = 'dijit.form.RadioButton'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if dojo_config.version < '1.3':
|
||||
self.alt_require = 'dijit.form.CheckBox'
|
||||
super(RadioSelect, self).__init__(*args, **kwargs)
|
||||
|
||||
class CheckboxSelectMultiple(DojoWidgetMixin, widgets.CheckboxSelectMultiple):
|
||||
dojo_type = 'dijit.form.CheckBox'
|
||||
|
||||
class MultiWidget(DojoWidgetMixin, widgets.MultiWidget):
|
||||
dojo_type = None
|
||||
|
||||
class SplitDateTimeWidget(widgets.SplitDateTimeWidget):
|
||||
"DateTimeInput is using two input fields."
|
||||
date_format = DateInput.format
|
||||
time_format = TimeInput.format
|
||||
|
||||
def __init__(self, attrs=None, date_format=None, time_format=None):
|
||||
if date_format:
|
||||
self.date_format = date_format
|
||||
if time_format:
|
||||
self.time_format = time_format
|
||||
split_widgets = (DateInput(attrs=attrs, format=self.date_format),
|
||||
TimeInput(attrs=attrs, format=self.time_format))
|
||||
# Note that we're calling MultiWidget, not SplitDateTimeWidget, because
|
||||
# we want to define widgets.
|
||||
widgets.MultiWidget.__init__(self, split_widgets, attrs)
|
||||
|
||||
class SplitHiddenDateTimeWidget(DojoWidgetMixin, widgets.SplitHiddenDateTimeWidget):
|
||||
dojo_type = "dijit.form.TextBox"
|
||||
|
||||
DateTimeInput = SplitDateTimeWidget
|
||||
|
||||
#############################################
|
||||
# MORE ENHANCED DJANGO/DOJO WIDGETS
|
||||
#############################################
|
||||
|
||||
class SimpleTextarea(Textarea):
|
||||
"""No autoexpanding textarea"""
|
||||
dojo_type = "dijit.form.SimpleTextarea"
|
||||
|
||||
class EditorInput(Textarea):
|
||||
dojo_type = 'dijit.Editor'
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
if value is None: value = ''
|
||||
final_attrs = self.build_attrs(attrs, name=name)
|
||||
# dijit.Editor must be rendered in a div (see dijit/_editor/RichText.js)
|
||||
return mark_safe(u'<div%s>%s</div>' % (flatatt(final_attrs),
|
||||
force_unicode(value))) # we don't escape the value for the editor
|
||||
|
||||
class HorizontalSliderInput(TextInput):
|
||||
dojo_type = 'dijit.form.HorizontalSlider'
|
||||
valid_extra_attrs = [
|
||||
'max_value',
|
||||
'min_value',
|
||||
]
|
||||
field_attr_map = {
|
||||
'max_value': 'maximum',
|
||||
'min_value': 'minimum',
|
||||
}
|
||||
|
||||
def __init__(self, attrs=None):
|
||||
if dojo_config.version < '1.3':
|
||||
self.alt_require = 'dijit.form.Slider'
|
||||
super(HorizontalSliderInput, self).__init__(attrs)
|
||||
|
||||
class VerticalSliderInput(HorizontalSliderInput):
|
||||
dojo_type = 'dijit.form.VerticalSlider'
|
||||
|
||||
class ValidationTextInput(TextInput):
|
||||
dojo_type = 'dijit.form.ValidationTextBox'
|
||||
valid_extra_attrs = [
|
||||
'required',
|
||||
'help_text',
|
||||
'js_regex',
|
||||
'max_length',
|
||||
]
|
||||
js_regex_func = None
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
if self.js_regex_func:
|
||||
attrs = self.build_attrs(attrs, regExpGen=self.js_regex_func)
|
||||
return super(ValidationTextInput, self).render(name, value, attrs)
|
||||
|
||||
class ValidationPasswordInput(PasswordInput):
|
||||
dojo_type = 'dijit.form.ValidationTextBox'
|
||||
valid_extra_attrs = [
|
||||
'required',
|
||||
'help_text',
|
||||
'js_regex',
|
||||
'max_length',
|
||||
]
|
||||
|
||||
class EmailTextInput(ValidationTextInput):
|
||||
extra_dojo_require = [
|
||||
'dojox.validate.regexp'
|
||||
]
|
||||
js_regex_func = "dojox.validate.regexp.emailAddress"
|
||||
|
||||
def __init__(self, attrs=None):
|
||||
if dojo_config.version < '1.3':
|
||||
self.js_regex_func = 'dojox.regexp.emailAddress'
|
||||
super(EmailTextInput, self).__init__(attrs)
|
||||
|
||||
class IPAddressTextInput(ValidationTextInput):
|
||||
extra_dojo_require = [
|
||||
'dojox.validate.regexp'
|
||||
]
|
||||
js_regex_func = "dojox.validate.regexp.ipAddress"
|
||||
|
||||
def __init__(self, attrs=None):
|
||||
if dojo_config.version < '1.3':
|
||||
self.js_regex_func = 'dojox.regexp.ipAddress'
|
||||
super(IPAddressTextInput, self).__init__(attrs)
|
||||
|
||||
class URLTextInput(ValidationTextInput):
|
||||
extra_dojo_require = [
|
||||
'dojox.validate.regexp'
|
||||
]
|
||||
js_regex_func = "dojox.validate.regexp.url"
|
||||
|
||||
def __init__(self, attrs=None):
|
||||
if dojo_config.version < '1.3':
|
||||
self.js_regex_func = 'dojox.regexp.url'
|
||||
super(URLTextInput, self).__init__(attrs)
|
||||
|
||||
class NumberTextInput(TextInput):
|
||||
dojo_type = 'dijit.form.NumberTextBox'
|
||||
valid_extra_attrs = [
|
||||
'min_value',
|
||||
'max_value',
|
||||
'required',
|
||||
'help_text',
|
||||
'decimal_places',
|
||||
]
|
||||
|
||||
class RangeBoundTextInput(NumberTextInput):
|
||||
dojo_type = 'dijit.form.RangeBoundTextBox'
|
||||
|
||||
class NumberSpinnerInput(NumberTextInput):
|
||||
dojo_type = 'dijit.form.NumberSpinner'
|
||||
|
||||
class RatingInput(TextInput):
|
||||
dojo_type = 'dojox.form.Rating'
|
||||
valid_extra_attrs = [
|
||||
'max_value',
|
||||
]
|
||||
field_attr_map = {
|
||||
'max_value': 'numStars',
|
||||
}
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('%(base_url)s/dojox/form/resources/Rating.css' % {
|
||||
'base_url':dojo_config.dojo_base_url
|
||||
},)
|
||||
}
|
||||
|
||||
class DateInputAnim(DateInput):
|
||||
dojo_type = 'dojox.form.DateTextBox'
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('%(base_url)s/dojox/widget/Calendar/Calendar.css' % {
|
||||
'base_url':dojo_config.dojo_base_url
|
||||
},)
|
||||
}
|
||||
|
||||
class DropDownSelect(Select):
|
||||
dojo_type = 'dojox.form.DropDownSelect'
|
||||
valid_extra_attrs = []
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('%(base_url)s/dojox/form/resources/DropDownSelect.css' % {
|
||||
'base_url':dojo_config.dojo_base_url
|
||||
},)
|
||||
}
|
||||
|
||||
class CheckedMultiSelect(SelectMultiple):
|
||||
dojo_type = 'dojox.form.CheckedMultiSelect'
|
||||
valid_extra_attrs = []
|
||||
# TODO: fix attribute multiple=multiple
|
||||
# seems there is a dependency in dojox.form.CheckedMultiSelect for dijit.form.MultiSelect,
|
||||
# but CheckedMultiSelect is not extending that
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('%(base_url)s/dojox/form/resources/CheckedMultiSelect.css' % {
|
||||
'base_url':dojo_config.dojo_base_url
|
||||
},)
|
||||
}
|
||||
|
||||
class ComboBox(DojoWidgetMixin, widgets.Select):
|
||||
"""Nearly the same as FilteringSelect, but ignoring the option value."""
|
||||
dojo_type = 'dijit.form.ComboBox'
|
||||
valid_extra_attrs = [
|
||||
'required',
|
||||
'help_text',
|
||||
]
|
||||
|
||||
class FilteringSelect(ComboBox):
|
||||
dojo_type = 'dijit.form.FilteringSelect'
|
||||
|
||||
class ComboBoxStore(TextInput):
|
||||
"""A combobox that is receiving data from a given dojo data url.
|
||||
As default dojo.data.ItemFileReadStore is used. You can overwrite
|
||||
that behaviour by passing a different store name
|
||||
(e.g. dojox.data.QueryReadStore).
|
||||
Usage:
|
||||
ComboBoxStore("/dojo-data-store-url/")
|
||||
"""
|
||||
dojo_type = 'dijit.form.ComboBox'
|
||||
valid_extra_attrs = [
|
||||
'required',
|
||||
'help_text',
|
||||
]
|
||||
store = 'dojo.data.ItemFileReadStore'
|
||||
store_attrs = {}
|
||||
url = None
|
||||
|
||||
def __init__(self, url, attrs=None, store=None, store_attrs={}):
|
||||
self.url = url
|
||||
if store:
|
||||
self.store = store
|
||||
if store_attrs:
|
||||
self.store_attrs = store_attrs
|
||||
self.extra_dojo_require.append(self.store)
|
||||
super(ComboBoxStore, self).__init__(attrs)
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
if value is None: value = ''
|
||||
store_id = self.get_store_id(getattr(attrs, "id", None), name)
|
||||
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name, store=store_id)
|
||||
if value != '':
|
||||
# Only add the 'value' attribute if a value is non-empty.
|
||||
final_attrs['value'] = force_unicode(self._format_value(value))
|
||||
self.store_attrs.update({
|
||||
'dojoType': self.store,
|
||||
'url': self.url,
|
||||
'jsId':store_id
|
||||
})
|
||||
# TODO: convert store attributes to valid js-format (False => false, dict => {}, array = [])
|
||||
store_node = '<div%s></div>' % flatatt(self.store_attrs)
|
||||
return mark_safe(u'%s<input%s />' % (store_node, flatatt(final_attrs)))
|
||||
|
||||
def get_store_id(self, id, name):
|
||||
return "_store_" + (id and id or name)
|
||||
|
||||
class FilteringSelectStore(ComboBoxStore):
|
||||
dojo_type = 'dijit.form.FilteringSelect'
|
||||
|
||||
class ListInput(DojoWidgetMixin, widgets.TextInput):
|
||||
dojo_type = 'dojox.form.ListInput'
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('%(base_url)s/dojox/form/resources/ListInput.css' % {
|
||||
'base_url':dojo_config.dojo_base_url
|
||||
},)
|
||||
}
|
||||
|
||||
# THE RANGE SLIDER NEEDS A DIFFERENT REPRESENTATION WITHIN HTML
|
||||
# SOMETHING LIKE:
|
||||
# <div dojoType="dojox.form.RangeSlider"><input value="5"/><input value="10"/></div>
|
||||
'''class HorizontalRangeSlider(HorizontalSliderInput):
|
||||
"""This just can be used with a comma-separated-value like: 20,40"""
|
||||
dojo_type = 'dojox.form.HorizontalRangeSlider'
|
||||
alt_require = 'dojox.form.RangeSlider'
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('%(base_url)s/dojox/form/resources/RangeSlider.css' % {
|
||||
'base_url':dojo_config.dojo_base_url
|
||||
},)
|
||||
}
|
||||
'''
|
||||
# TODO: implement
|
||||
# dojox.form.RangeSlider
|
||||
# dojox.form.MultiComboBox
|
||||
# dojox.form.FileUploader
|
0
ntuh/dojango/management/__init__.py
Executable file
0
ntuh/dojango/management/commands/__init__.py
Executable file
319
ntuh/dojango/management/commands/dojobuild.py
Executable file
|
@ -0,0 +1,319 @@
|
|||
from optparse import make_option
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess # since python 2.4
|
||||
import sys
|
||||
from dojango.conf import settings
|
||||
|
||||
try:
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
except ImportError:
|
||||
# Fake BaseCommand out so imports on django 0.96 don't fail.
|
||||
BaseCommand = object
|
||||
class CommandError(Exception):
|
||||
pass
|
||||
|
||||
class Command(BaseCommand):
|
||||
'''This command is used to create your own dojo build. To start a build, you just
|
||||
have to type:
|
||||
|
||||
./manage.py dojobuild
|
||||
|
||||
in your django project path. With this call, the default build profile "dojango" is used
|
||||
and dojango.profile.js will act as its dojo build configuration. You can also add the
|
||||
option --build_version=dev1.1.1 (for example) to mark the build with it.
|
||||
If you want to call a specific build profile from DOJO_BUILD_PROFILES, you just have to
|
||||
append the profile name to this commandline call:
|
||||
|
||||
./manage.py dojobuild profilename
|
||||
|
||||
'''
|
||||
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--build_version', dest='build_version',
|
||||
help='Set the version of the build release (e.g. dojango_1.1.1).'),
|
||||
make_option('--minify', dest='minify', action="store_true", default=False,
|
||||
help='Does a dojo mini build (mainly removing unneeded files (tests/templates/...)'),
|
||||
make_option('--minify_extreme', dest='minify_extreme', action="store_true", default=False,
|
||||
help='Does a dojo extreme-mini build (keeps only what is defined in build profile and all media files)'),
|
||||
make_option('--prepare_zipserve', dest='prepare_zipserve', action="store_true", default=False,
|
||||
help='Zips everything you have built, so it can be deployed to Google AppEngine'),
|
||||
)
|
||||
help = "Builds a dojo release."
|
||||
args = '[dojo build profile name]'
|
||||
dojo_base_dir = None
|
||||
dojo_release_dir = None
|
||||
skip_files = None
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if len(args)==0:
|
||||
# with no param, we use the default profile, that is defined in the settings
|
||||
profile_name = settings.DOJO_BUILD_PROFILE
|
||||
else:
|
||||
profile_name = args[0]
|
||||
profile = self._get_profile(profile_name)
|
||||
used_src_version = profile['used_src_version'] % {'DOJO_BUILD_VERSION': settings.DOJO_BUILD_VERSION} # no dependencies to project's settings.py file!
|
||||
# used by minify_extreme!
|
||||
self.skip_files = profile.get("minify_extreme_skip_files", ())
|
||||
self.dojo_base_dir = "%(dojo_root)s/%(version)s" % \
|
||||
{'dojo_root':settings.BASE_DOJO_ROOT,
|
||||
'version':used_src_version}
|
||||
# does the defined dojo-directory exist?
|
||||
util_base_dir = "%(dojo_base_dir)s/util" % {'dojo_base_dir':self.dojo_base_dir}
|
||||
if not os.path.exists(util_base_dir):
|
||||
raise CommandError('Put the the dojo source files (version \'%(version)s\') in the folder \'%(folder)s/%(version)s\' or set a different version in settings.DOJANGO_DOJO_BUILD_VERSION' % \
|
||||
{'version':used_src_version,
|
||||
'folder':settings.BASE_DOJO_ROOT})
|
||||
# check, if java is installed
|
||||
try:
|
||||
# ignoring output of the java call
|
||||
subprocess.call(settings.DOJO_BUILD_JAVA_EXEC, stdout=subprocess.PIPE) # will work with python >= 2.4
|
||||
except:
|
||||
raise CommandError('Please install java. You need it for building dojo.')
|
||||
buildscript_dir = os.path.abspath('%s/buildscripts' % util_base_dir)
|
||||
if settings.DOJO_BUILD_USED_VERSION < '1.2.0':
|
||||
executable = '%(java_exec)s -jar ../shrinksafe/custom_rhino.jar build.js' % \
|
||||
{'java_exec':settings.DOJO_BUILD_JAVA_EXEC}
|
||||
else:
|
||||
# use the new build command line call!
|
||||
if(os.path.sep == "\\"):
|
||||
executable = 'build.bat'
|
||||
else:
|
||||
executable = './build.sh'
|
||||
# force executable rights!
|
||||
os.chmod(os.path.join(buildscript_dir, 'build.sh'), 0755)
|
||||
# use the passed version for building
|
||||
version = options.get('build_version', None)
|
||||
if not version:
|
||||
# if no option --build_version was passed, we use the default build version
|
||||
version = profile['build_version'] % {'DOJO_BUILD_VERSION': settings.DOJO_BUILD_VERSION} # no dependencies to project's settings.py file!
|
||||
# we add the version to our destination base path
|
||||
self.dojo_release_dir = '%(base_path)s/%(version)s' % {
|
||||
'base_path':profile['base_root'] % {'BASE_MEDIA_ROOT':settings.BASE_MEDIA_ROOT},
|
||||
'version':version} # we don't want to have a dependancy to the project's settings file!
|
||||
release_dir = os.path.abspath(os.path.join(self.dojo_release_dir, "../"))
|
||||
# the build command handling is so different between the versions!
|
||||
# sometimes we need to add /, sometimes not :-(
|
||||
if settings.DOJO_BUILD_USED_VERSION < '1.2.0':
|
||||
release_dir = release_dir + os.path.sep
|
||||
# setting up the build command
|
||||
build_addons = ""
|
||||
if settings.DOJO_BUILD_USED_VERSION >= '1.2.0':
|
||||
# since version 1.2.0 there is an additional commandline option that does the mini build (solved within js!)
|
||||
build_addons = "mini=true"
|
||||
exe_command = 'cd %(buildscript_dir)s && %(executable)s version=%(version)s releaseName="%(version)s" releaseDir="%(release_dir)s" %(options)s %(build_addons)s' % \
|
||||
{'buildscript_dir':buildscript_dir,
|
||||
'executable':executable,
|
||||
'version':version,
|
||||
'release_dir':release_dir,
|
||||
'options':profile['options'] % {'BASE_MEDIA_ROOT':settings.BASE_MEDIA_ROOT},
|
||||
'build_addons':build_addons}
|
||||
# print exe_command
|
||||
minify = options['minify']
|
||||
minify_extreme = options['minify_extreme']
|
||||
prepare_zipserve = options['prepare_zipserve']
|
||||
if settings.DOJO_BUILD_USED_VERSION < '1.2.0' and (minify or minify_extreme):
|
||||
self._dojo_mini_before_build()
|
||||
if sys.platform == 'win32': # fixing issue #39, if dojango is installed on a different drive
|
||||
exe_command = os.path.splitdrive(buildscript_dir)[0] + ' && ' + exe_command
|
||||
|
||||
# do the build
|
||||
os.system(exe_command)
|
||||
if settings.DOJO_BUILD_USED_VERSION < '1.2.0':
|
||||
if minify or minify_extreme:
|
||||
self._dojo_mini_after_build()
|
||||
if minify_extreme:
|
||||
self._dojo_mini_extreme()
|
||||
if prepare_zipserve:
|
||||
self._dojo_prepare_zipserve()
|
||||
|
||||
def _get_profile(self, name):
|
||||
default_profile_settings = settings.DOJO_BUILD_PROFILES_DEFAULT
|
||||
try:
|
||||
profile = settings.DOJO_BUILD_PROFILES[name]
|
||||
# mixing in the default settings for the build profiles!
|
||||
default_profile_settings.update(profile)
|
||||
return default_profile_settings
|
||||
except KeyError:
|
||||
raise CommandError('The profile \'%s\' does not exist in DOJO_BUILD_PROFILES' % name)
|
||||
|
||||
def _dojo_mini_before_build(self):
|
||||
# FIXME: refs #6616 - could be able to set a global copyright file and null out build_release.txt
|
||||
shutil.move("%s/util/buildscripts/copyright.txt" % self.dojo_base_dir, "%s/util/buildscripts/_copyright.txt" % self.dojo_base_dir)
|
||||
if not os.path.exists("%s/util/buildscripts/copyright_mini.txt" % self.dojo_base_dir):
|
||||
f = open("%s/util/buildscripts/copyright.txt" % self.dojo_base_dir, 'w')
|
||||
f.write('''/*
|
||||
Copyright (c) 2004-2008, The Dojo Foundation All Rights Reserved.
|
||||
Available via Academic Free License >= 2.1 OR the modified BSD license.
|
||||
see: http://dojotoolkit.org/license for details
|
||||
*/''')
|
||||
f.close()
|
||||
else:
|
||||
shutil.copyfile("%s/util/buildscripts/copyright_mini.txt" % self.dojo_base_dir, "%s/util/buildscripts/copyright.txt" % self.dojo_base_dir)
|
||||
shutil.move("%s/util/buildscripts/build_notice.txt" % self.dojo_base_dir, "%s/util/buildscripts/_build_notice.txt" % self.dojo_base_dir)
|
||||
# create an empty build-notice-file
|
||||
f = open("%s/util/buildscripts/build_notice.txt" % self.dojo_base_dir, 'w')
|
||||
f.close()
|
||||
|
||||
def _dojo_mini_after_build(self):
|
||||
try:
|
||||
'''Copied from the build_mini.sh shell script (thank you Pete Higgins :-))'''
|
||||
if not os.path.exists(self.dojo_release_dir):
|
||||
raise CommandError('The dojo build failed! Check messages above!')
|
||||
else:
|
||||
# remove dojox tests and demos - they all follow this convetion
|
||||
self._remove_files('%s/dojox' % self.dojo_release_dir, ('^tests$', '^demos$'))
|
||||
# removed dijit tests
|
||||
dijit_tests = ("dijit/tests", "dijit/demos", "dijit/bench",
|
||||
"dojo/tests", "util",
|
||||
"dijit/themes/themeTesterImages")
|
||||
self._remove_folders(dijit_tests)
|
||||
# noir isn't worth including yet
|
||||
noir_theme_path = ("%s/dijit/themes/noir" % self.dojo_release_dir,)
|
||||
self._remove_folders(noir_theme_path)
|
||||
# so the themes are there, lets assume that, piggyback on noir: FIXME later
|
||||
self._remove_files('%s/dijit/themes' % self.dojo_release_dir, ('^.*\.html$',))
|
||||
self._remove_files(self.dojo_release_dir, ('^.*\.uncompressed\.js$',))
|
||||
# WARNING: templates have been inlined into the .js -- if you are using dynamic templates,
|
||||
# or other build trickery, these lines might not work!
|
||||
self._remove_files("dijit/templates", ("^\.html$",))
|
||||
self._remove_files("dijit/form/templates", ("^\.html$",))
|
||||
self._remove_files("dijit/layout/templates", ("^\.html$",))
|
||||
# .. assume you didn't, and clean up all the README's (leaving LICENSE, mind you)
|
||||
self._remove_files('%s/dojo/dojox' % self.dojo_release_dir, ('^README$',))
|
||||
dojo_folders = ("dojo/_base",)
|
||||
self._remove_folders(dojo_folders)
|
||||
os.remove("%s/dojo/_base.js" % self.dojo_release_dir)
|
||||
os.remove("%s/dojo/build.txt" % self.dojo_release_dir)
|
||||
os.remove("%s/dojo/tests.js" % self.dojo_release_dir)
|
||||
except Exception, e:
|
||||
print e
|
||||
# cleanup from above, refs #6616
|
||||
shutil.move("%s/util/buildscripts/_copyright.txt" % self.dojo_base_dir, "%s/util/buildscripts/copyright.txt" % self.dojo_base_dir)
|
||||
shutil.move("%s/util/buildscripts/_build_notice.txt" % self.dojo_base_dir, "%s/util/buildscripts/build_notice.txt" % self.dojo_base_dir)
|
||||
|
||||
def _remove_folders(self, folders):
|
||||
for folder in folders:
|
||||
if os.path.exists("%s/%s" % (self.dojo_release_dir, folder)):
|
||||
shutil.rmtree("%s/%s" % (self.dojo_release_dir, folder))
|
||||
|
||||
def _remove_files(self, base_folder, regexp_list):
|
||||
for root, dirs, files in os.walk(base_folder):
|
||||
for file in files:
|
||||
# remove all html-files
|
||||
for regexp in regexp_list:
|
||||
my_re = re.compile(regexp)
|
||||
if my_re.match(file):
|
||||
os.remove(os.path.join(root, file))
|
||||
for dir in dirs:
|
||||
for regexp in regexp_list:
|
||||
my_re = re.compile(regexp)
|
||||
if my_re.match(dir):
|
||||
shutil.rmtree(os.path.join(root, dir))
|
||||
|
||||
SKIP_FILES = (
|
||||
'(.*\.png)',
|
||||
'(.*\.gif)',
|
||||
'(.*\.jpg)',
|
||||
'(.*\.svg)',
|
||||
'(.*\.swf)',
|
||||
'(.*\.fla)',
|
||||
'(.*\.mov)',
|
||||
'(.*\.smd)',
|
||||
'(dojo/_firebug/firebug\..*)',
|
||||
'(dojo/dojo\.(xd\.)?js)',
|
||||
'(dojo/nls/.*)',
|
||||
'(dojo/resources/dojo\.css)',
|
||||
'(dojo/resources/blank\.html)',
|
||||
'(dojo/resources/iframe_history\.html)',
|
||||
'(dijit/themes/tundra/tundra\.css)',
|
||||
'(dijit/themes/soria/soria\.css)',
|
||||
'(dijit/themes/nihilo/nihilo\.css)',
|
||||
'(dojox/dtl/contrib/.*)',
|
||||
'(dojox/dtl/ext-dojo/.*)',
|
||||
'(dojox/dtl/filter/.*)',
|
||||
'(dojox/dtl/render/.*)',
|
||||
'(dojox/dtl/tag/.*)',
|
||||
'(dojox/dtl/utils/.*)',
|
||||
'(dojox/io/proxy/xip_.*\.html)',
|
||||
)
|
||||
def _dojo_mini_extreme(self):
|
||||
"""
|
||||
This method removes all js files and just leaves all layer dojo files and static files (like "png", "gif", "svg", "swf", ...)
|
||||
"""
|
||||
# prepare the regexp of files not to be removed!
|
||||
# mixin the profile specific skip files
|
||||
skip_files = self.SKIP_FILES + self.skip_files
|
||||
my_re = re.compile('^(.*/)?(%s)$' % "|".join(skip_files))
|
||||
try:
|
||||
'''Copied from the build_mini.sh shell script'''
|
||||
if not os.path.exists(self.dojo_release_dir):
|
||||
raise CommandError('The dojo build failed! Check messages above!')
|
||||
else:
|
||||
for root, dirs, files in os.walk(self.dojo_release_dir):
|
||||
for file in files:
|
||||
# remove all html-files
|
||||
my_file = os.path.abspath(os.path.join(root, file))
|
||||
if not my_re.match(my_file):
|
||||
os.remove(my_file)
|
||||
# now remove all empty directories
|
||||
for root, dirs, files in os.walk(self.dojo_release_dir):
|
||||
for dir in dirs:
|
||||
try:
|
||||
# just empty directories will be removed!
|
||||
os.removedirs(os.path.join(root, dir))
|
||||
except OSError:
|
||||
pass
|
||||
except Exception, e:
|
||||
print e
|
||||
|
||||
DOJO_ZIP_SPECIAL = {'dojox': ['form', 'widget', 'grid']} # these modules will be zipped separately
|
||||
def _dojo_prepare_zipserve(self):
|
||||
"""
|
||||
Creates zip packages for each dojo module within the current release folder.
|
||||
It splits the module dojox into several modules, so it fits the 1000 files limit of
|
||||
Google AppEngine.
|
||||
"""
|
||||
for folder in os.listdir(self.dojo_release_dir):
|
||||
module_dir = '%s/%s' % (self.dojo_release_dir, folder)
|
||||
if os.path.isdir(module_dir):
|
||||
if folder in self.DOJO_ZIP_SPECIAL.keys():
|
||||
for special_module in self.DOJO_ZIP_SPECIAL[folder]:
|
||||
special_module_dir = os.path.join(module_dir, special_module)
|
||||
create_zip(special_module_dir,
|
||||
'%(base_module)s/%(special_module)s' % {
|
||||
'base_module': folder,
|
||||
'special_module': special_module
|
||||
},
|
||||
'%(module_dir)s.%(special_module)s.zip' % {
|
||||
'module_dir': module_dir,
|
||||
'special_module': special_module
|
||||
}
|
||||
)
|
||||
# remove the whole special module
|
||||
shutil.rmtree(special_module_dir)
|
||||
# now add the
|
||||
create_zip(module_dir, folder, module_dir + ".zip")
|
||||
shutil.rmtree(module_dir)
|
||||
|
||||
|
||||
def zipfolder(path, relname, archive):
|
||||
paths = os.listdir(path)
|
||||
for p in paths:
|
||||
p1 = os.path.join(path, p)
|
||||
p2 = os.path.join(relname, p)
|
||||
if os.path.isdir(p1):
|
||||
zipfolder(p1, p2, archive)
|
||||
else:
|
||||
archive.write(p1, p2)
|
||||
|
||||
def create_zip(path, relname, archname):
|
||||
import zipfile
|
||||
archive = zipfile.ZipFile(archname, "w", zipfile.ZIP_DEFLATED)
|
||||
if os.path.isdir(path):
|
||||
zipfolder(path, relname, archive)
|
||||
else:
|
||||
archive.write(path, relname)
|
||||
archive.close()
|
105
ntuh/dojango/management/commands/dojoload.py
Executable file
|
@ -0,0 +1,105 @@
|
|||
import os
|
||||
import sys
|
||||
import urllib
|
||||
import zipfile
|
||||
|
||||
from optparse import make_option
|
||||
from dojango.conf import settings
|
||||
|
||||
try:
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
except ImportError:
|
||||
# Fake BaseCommand out so imports on django 0.96 don't fail.
|
||||
BaseCommand = object
|
||||
class CommandError(Exception):
|
||||
pass
|
||||
|
||||
class Command(BaseCommand):
|
||||
'''This command helps with downloading a dojo source release. To download
|
||||
the currently defined 'settings.DOJANGO_DOJO_VERSION' just type:
|
||||
|
||||
./manage.py dojoload
|
||||
|
||||
in your django project path. For downloading a specific version a version
|
||||
string can be appended.
|
||||
|
||||
./manage.py dojoload --version 1.2.3
|
||||
'''
|
||||
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--dojo_version', dest='dojo_version',
|
||||
help='Download a defined version (e.g. 1.2.3) instead of the default (%s).' % settings.DOJO_VERSION),
|
||||
)
|
||||
help = "Downloads a dojo source release."
|
||||
dl_url = "http://download.dojotoolkit.org/release-%(version)s/dojo-release-%(version)s-src.zip"
|
||||
dl_to_path = settings.BASE_DOJO_ROOT + "/dojo-release-%(version)s-src.zip"
|
||||
move_from_dir = settings.BASE_DOJO_ROOT + "/dojo-release-%(version)s-src"
|
||||
move_to_dir = settings.BASE_DOJO_ROOT + "/%(version)s"
|
||||
extract_to_dir = settings.BASE_DOJO_ROOT
|
||||
total_kb = 0
|
||||
downloaded_kb = 0
|
||||
|
||||
|
||||
def handle(self, *args, **options):
|
||||
version = settings.DOJO_VERSION
|
||||
passed_version = options.get('dojo_version', None)
|
||||
if passed_version:
|
||||
version = passed_version
|
||||
dl_url = self.dl_url % {'version': version}
|
||||
dl_to_path = self.dl_to_path % {'version': version}
|
||||
move_from_dir = self.move_from_dir % {'version': version}
|
||||
move_to_dir = self.move_to_dir % {'version': version}
|
||||
if os.path.exists(move_to_dir):
|
||||
raise CommandError("You've already downloaded version %(version)s to %(move_to_dir)s" % {
|
||||
'version':version,
|
||||
'move_to_dir':move_to_dir,
|
||||
})
|
||||
else:
|
||||
print "Downloading %s to %s" % (dl_url, dl_to_path)
|
||||
self.download(dl_url, dl_to_path)
|
||||
if self.total_kb == -1: # stupid bug in urllib (there is no IOError thrown, when a 404 occurs
|
||||
os.remove(dl_to_path)
|
||||
print ""
|
||||
raise CommandError("There is no source release at %(url)s" % {
|
||||
'url':dl_url,
|
||||
'dir':dl_to_path,
|
||||
})
|
||||
print "\nExtracting file %s to %s" % (dl_to_path, move_to_dir)
|
||||
self.unzip_file_into_dir(dl_to_path, self.extract_to_dir)
|
||||
os.rename(move_from_dir, move_to_dir)
|
||||
print "Removing previous downloaded file %s" % dl_to_path
|
||||
os.remove(dl_to_path)
|
||||
|
||||
def download(self, dl_url, to_dir):
|
||||
try:
|
||||
urllib.urlretrieve(dl_url, to_dir, self.dl_reporthook)
|
||||
except IOError:
|
||||
raise CommandError("Downloading from %(url)s to directory %(dir)s failed." % {
|
||||
'url':dl_url,
|
||||
'dir':to_dir,
|
||||
})
|
||||
|
||||
def dl_reporthook(self, block_count, block_size, total_size):
|
||||
self.total_kb = total_size / 1024
|
||||
self.downloaded_kb = (block_count * block_size) / 1024
|
||||
sys.stdout.write('%s%d KB of %d KB downloaded' % (
|
||||
40*"\b", # replacing the current line
|
||||
self.downloaded_kb,
|
||||
self.total_kb
|
||||
)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
def unzip_file_into_dir(self, file, dir):
|
||||
try:
|
||||
os.mkdir(dir)
|
||||
except:
|
||||
pass
|
||||
zfobj = zipfile.ZipFile(file)
|
||||
for name in zfobj.namelist():
|
||||
if name.endswith('/'):
|
||||
os.mkdir(os.path.join(dir, name))
|
||||
else:
|
||||
outfile = open(os.path.join(dir, name), 'wb')
|
||||
outfile.write(zfobj.read(name))
|
||||
outfile.close()
|
72
ntuh/dojango/middleware.py
Executable file
|
@ -0,0 +1,72 @@
|
|||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponseServerError
|
||||
|
||||
from dojango.util import dojo_collector
|
||||
|
||||
class AJAXSimpleExceptionResponse:
|
||||
"""Thanks to newmaniese of http://www.djangosnippets.org/snippets/650/ .
|
||||
|
||||
Full doc (copied from link above).
|
||||
When debugging AJAX with Firebug, if a response is 500, it is a
|
||||
pain to have to view the entire source of the pretty exception page.
|
||||
This is a simple middleware that just returns the exception without
|
||||
any markup. You can add this anywhere in your python path and then
|
||||
put it in you settings file. It will only unprettify your exceptions
|
||||
when there is a XMLHttpRequest header. Tested in FF2 with the YUI XHR.
|
||||
Comments welcome.
|
||||
"""
|
||||
def process_exception(self, request, exception):
|
||||
#if settings.DEBUG:
|
||||
# we should use that setting in future version of dojango
|
||||
#if request.is_ajax(): # new in django version 1.0
|
||||
if request.META.get('HTTP_X_REQUESTED_WITH', None) == 'XMLHttpRequest':
|
||||
import sys, traceback
|
||||
(exc_type, exc_info, tb) = sys.exc_info()
|
||||
response = "%s\n" % exc_type.__name__
|
||||
response += "%s\n\n" % exc_info
|
||||
response += "TRACEBACK:\n"
|
||||
for tb in traceback.format_tb(tb):
|
||||
response += "%s\n" % tb
|
||||
return HttpResponseServerError(response)
|
||||
|
||||
class DojoCollector:
|
||||
"""This middleware enables/disables the global collector object for each
|
||||
request. It is needed, when the dojango.forms integration is used.
|
||||
"""
|
||||
def process_request(self, request):
|
||||
dojo_collector.activate()
|
||||
|
||||
def process_response(self, request, response):
|
||||
dojo_collector.deactivate()
|
||||
return response
|
||||
|
||||
class DojoAutoRequire:
|
||||
"""
|
||||
USE THE MIDDLEWARE ABOVE (IT IS USING A GLOBAL COLLECTOR OBEJCT)!
|
||||
|
||||
This middleware detects all dojoType="*" definitions in the returned
|
||||
response and uses that information to generate all needed dojo.require
|
||||
statements and places a <script> block in front of the </body> tag.
|
||||
|
||||
It is just processed for text/html documents!
|
||||
"""
|
||||
def process_response(self, request, response):
|
||||
# just process html-pages that were returned by a view
|
||||
if response and\
|
||||
response.get("Content-Type", "") == "text/html; charset=%s" % settings.DEFAULT_CHARSET and\
|
||||
len(response.content) > 0: # just for html pages!
|
||||
dojo_type_re = re.compile('\sdojoType\s*\=\s*[\'\"]([\w\d\.\-\_]*)[\'\"]\s*')
|
||||
unique_dojo_modules = set(dojo_type_re.findall(response.content)) # we just need each module once
|
||||
tail, sep, head = response.content.rpartition("</body>")
|
||||
response.content = "%(tail)s%(script)s%(sep)s%(head)s" % {
|
||||
'tail':tail,
|
||||
'script':'<script type="text/javascript">\n%s\n</script>\n' % self._get_dojo_requires(unique_dojo_modules),
|
||||
'sep':sep,
|
||||
'head':head,
|
||||
}
|
||||
return response
|
||||
|
||||
def _get_dojo_requires(self, dojo_modules):
|
||||
return "\n".join([u"dojo.require(\"%s\");" % require for require in dojo_modules])
|
3
ntuh/dojango/models.py
Executable file
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
80
ntuh/dojango/templates/dojango/base.html
Executable file
|
@ -0,0 +1,80 @@
|
|||
{% spaceless %}
|
||||
{% load dojango_base %}
|
||||
{% load dojango_filters %}
|
||||
{% if not DOJANGO %}
|
||||
{# if the context processor did not pass the DOJANGO context, we set it here #}
|
||||
{% set_dojango_context %}
|
||||
{% endif %}
|
||||
|
||||
{% if not LANGUAGE_CODE %}
|
||||
{% load i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_current_language_bidi as LANGUAGE_BIDI %}
|
||||
{% endif %}
|
||||
{% block _dojango_doctype %}
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
{% endblock %}
|
||||
{% endspaceless %}{% block _dojango_html_start_tag %}
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"{% block _dojango_html_lang %}{% endblock %}>
|
||||
{% endblock %}
|
||||
{% spaceless %}
|
||||
<head>
|
||||
{% block _dojango_meta_content_type %}<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />{% endblock %}{# This must be before any non-english text, i.e. title. http://www.dojotoolkit.org/book/dojo-book-0-9/part-3-programmatic-dijit-and-dojo/i18n/globalization-guidelines/encoding-guideli#textfiles #}
|
||||
{% block _dojango_meta_content_lang %}{% endblock %}
|
||||
<title>{% block dojango_page_title %}{% endblock %}</title>
|
||||
{% block _dojango_dojo_styles %}
|
||||
<style type="text/css">
|
||||
@import "{{ DOJANGO.DOJO_URL }}/resources/dojo.css";
|
||||
@import "{{ DOJANGO.THEME_CSS_URL }}";
|
||||
</style>
|
||||
{% endblock %}
|
||||
<script type="text/javascript">
|
||||
{# baseUrl setting is needed, if you want to combine a local with a remote build! #}
|
||||
{% block _dojango_dj_config %}
|
||||
var djConfig = {
|
||||
'isDebug':{{ DOJANGO.DEBUG|json }},
|
||||
'parseOnLoad':true,
|
||||
'baseUrl':"{{ DOJANGO.DOJO_BASE_PATH }}"
|
||||
{% if not DOJANGO.IS_LOCAL %}
|
||||
,'dojoBlankHtmlUrl':"{{ DOJANGO.DOJANGO_URL }}/resources/blank.html"
|
||||
,'dojoIframeHistoryUrl':"{{ DOJANGO.DOJANGO_URL }}/resources/blank.html"
|
||||
{% endif %}
|
||||
{% if DOJANGO.CDN_USE_SSL and not DOJANGO.IS_LOCAL %}
|
||||
,'modulePaths':{'dojo': '{{ DOJANGO.DOJO_URL }}', 'dijit': '{{ DOJANGO.DIJIT_URL }}', 'dojox': '{{ DOJANGO.DOJOX_URL }}'}
|
||||
{% endif %}
|
||||
};
|
||||
{% endblock %}
|
||||
</script>
|
||||
|
||||
{% block _dojango_post_dj_config %}{# don't use this block, it is used by base_i18n.html #}{% endblock %}
|
||||
|
||||
{% block dojango_post_dj_config %}{# if you want to manipulate djConfig stuff #}{% endblock %}
|
||||
|
||||
<script type="text/javascript" src="{{ DOJANGO.DOJO_SRC_FILE }}"></script>
|
||||
|
||||
{# for a locally built version dojango should already be included #}
|
||||
{% if not DOJANGO.IS_LOCAL_BUILD %}
|
||||
<script type="text/javascript" src="{{ DOJANGO.DOJANGO_SRC_FILE }}"></script>
|
||||
{% endif %}
|
||||
|
||||
{% block dojango_header_extra %}{% endblock %}
|
||||
</head>
|
||||
{% endspaceless %}
|
||||
|
||||
{% block dojango_body %}
|
||||
<body class="{{ DOJANGO.THEME }}">
|
||||
{% block dojango_content %}
|
||||
{% endblock %}
|
||||
{% block dojango_require %}
|
||||
<script type="text/javascript">
|
||||
{% for require in DOJANGO.COLLECTOR %}
|
||||
{% spaceless %}
|
||||
dojo.require("{{ require }}");
|
||||
{% endspaceless %}
|
||||
{% endfor %}
|
||||
</script>
|
||||
{% endblock %}
|
||||
</body>
|
||||
{% endblock %}
|
||||
|
||||
</html>
|
15
ntuh/dojango/templates/dojango/base_i18n.html
Executable file
|
@ -0,0 +1,15 @@
|
|||
{% extends "dojango/base.html" %}
|
||||
|
||||
{% if not LANGUAGE_CODE %}
|
||||
{% load i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_current_language_bidi as LANGUAGE_BIDI %}
|
||||
{% endif %}
|
||||
|
||||
{% block _dojango_html_lang %} xml:lang="{{ LANGUAGE_CODE }}" lang="{{ LANGUAGE_CODE }}"{% if LANGUAGE_BIDI %} dir="rtl"{% endif %}{% endblock %}
|
||||
{% block _dojango_meta_content_lang %}<meta http-equiv="content-language" content="{{ LANGUAGE_CODE }}" />{% endblock %}
|
||||
{% block _dojango_post_dj_config %}
|
||||
<script type="text/javascript">
|
||||
djConfig['locale'] = "{{ LANGUAGE_CODE }}";
|
||||
</script>
|
||||
{% endblock %}
|
34
ntuh/dojango/templates/dojango/include.html
Executable file
|
@ -0,0 +1,34 @@
|
|||
{% load dojango_base %}
|
||||
{% load dojango_filters %}
|
||||
{# where should we set the context? #}
|
||||
{% if not DOJANGO %}
|
||||
{# if the context processor did not pass the DOJANGO context, we set it here #}
|
||||
{% set_dojango_context %}
|
||||
{% endif %}
|
||||
<style type="text/css">
|
||||
@import "{{ DOJANGO.DOJO_URL }}/resources/dojo.css";
|
||||
@Import "{{ DOJANGO.THEME_CSS_URL }}";
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
{# baseUrl setting is needed, if you want to combine a local with a remote build! #}
|
||||
var djConfig = {
|
||||
'isDebug':{{ DOJANGO.DEBUG|json }},
|
||||
'parseOnLoad':true,
|
||||
'baseUrl':"{{ DOJANGO.DOJO_BASE_PATH }}"
|
||||
{% if not DOJANGO.IS_LOCAL %}
|
||||
,'dojoBlankHtmlUrl':"{{ DOJANGO.DOJANGO_URL }}/resources/blank.html"
|
||||
,'dojoIframeHistoryUrl':"{{ DOJANGO.DOJANGO_URL }}/resources/blank.html"
|
||||
{% endif %}
|
||||
{% if DOJANGO.CDN_USE_SSL and not DOJANGO.IS_LOCAL %}
|
||||
,'modulePaths':{'dojo': '{{ DOJANGO.DOJO_URL }}', 'dijit': '{{ DOJANGO.DIJIT_URL }}', 'dojox': '{{ DOJANGO.DOJOX_URL }}'}
|
||||
{% endif %}
|
||||
};
|
||||
</script>
|
||||
{% block _dojango_post_dj_config %}{# don't use this block, it is used by include_i18n.html #}{% endblock %}
|
||||
{% block dojango_post_dj_config %}{# if you want to manipulate djConfig stuff #}{% endblock %}
|
||||
<script type="text/javascript" src="{{ DOJANGO.DOJO_SRC_FILE }}">
|
||||
</script>
|
||||
{# for a local builded version dojango should already be included #}
|
||||
{% if not DOJANGO.IS_LOCAL_BUILD %}
|
||||
<script type="text/javascript" src="{{ DOJANGO.DOJANGO_SRC_FILE }}"></script>
|
||||
{% endif %}
|
14
ntuh/dojango/templates/dojango/include_i18n.html
Executable file
|
@ -0,0 +1,14 @@
|
|||
{% extends "dojango/include.html" %}
|
||||
{% load dojango_filters %}
|
||||
|
||||
{% load dojango_base %}
|
||||
{% if not LANGUAGE_CODE %}
|
||||
{% load i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% endif %}
|
||||
|
||||
{% block _dojango_post_dj_config %}
|
||||
<script type="text/javascript">
|
||||
djConfig['locale'] = "{{ LANGUAGE_CODE }}";
|
||||
</script>
|
||||
{% endblock %}
|
7
ntuh/dojango/templates/dojango/json_iframe.html
Executable file
|
@ -0,0 +1,7 @@
|
|||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<textarea>{{json_data}}</textarea>
|
||||
</body>
|
||||
</html>
|
57
ntuh/dojango/templates/dojango/templatetag/datagrid_disp.html
Executable file
|
@ -0,0 +1,57 @@
|
|||
{% load dojango_base %}
|
||||
{% load dojango_filters %}
|
||||
{% set_dojango_context %}
|
||||
|
||||
{# TODO: Add a css loader to the dojo collector #}
|
||||
<link rel="stylesheet"
|
||||
href="{{ DOJANGO.DOJOX_URL }}/grid/resources/{{ DOJANGO.THEME }}Grid.css"
|
||||
type="text/css" />
|
||||
|
||||
<script type="text/javascript">
|
||||
if(typeof(dojango) == 'undefined') dojango= {};
|
||||
if(typeof(dojango._datagrid) == 'undefined') dojango._datagrid= {};
|
||||
if(typeof(dojango._datagrid._stores) == 'undefined') dojango._datagrid._stores= {};
|
||||
|
||||
function {{id}}_noSort(row){ return !(false {{ nosort }}); }
|
||||
dojo.addOnLoad(function(){
|
||||
dojango._datagrid._stores.{{id}}=new dojox.data.QueryReadStore({ url: {{ json_store_url|json|safe }} });
|
||||
var {{id}}_layout=[
|
||||
{% for x in headers %}
|
||||
{
|
||||
field:"{{x.attname}}",
|
||||
name:"{{x.label|safe|capfirst}}",
|
||||
width: "{{x.column_width}}"
|
||||
{% if x.formatter %}, formatter: {{x.formatter}} {% endif %}
|
||||
}{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
dojango._datagrid.{{id}}= new dojox.grid.DataGrid({
|
||||
jsid:'{{id}}',
|
||||
id:'{{id}}',
|
||||
store:dojango._datagrid._stores.{{id}},
|
||||
structure:{{id}}_layout, {% if selection_mode %} selectionMode:'{{ selection_mode }}', {% endif %}
|
||||
canSort:{{id}}_noSort
|
||||
{% if query %},query:{{query|safe}}{% endif %}
|
||||
{% if rowsPerPage %},rowsPerPage:{{rowsPerPage|json}}{% endif %}
|
||||
{% if delayScroll %},delayScroll:{{delayScroll|json}}{% endif %}
|
||||
}, dojo.place(dojo.create("div"), dojo.byId("{{id}}_container")));
|
||||
dojango._datagrid.{{id}}.startup();
|
||||
});
|
||||
|
||||
{% if search_fields %}
|
||||
dojango._datagrid.{{id}}_last = "";
|
||||
function do_{{id}}_search(el) {
|
||||
if (el.value != dojango._datagrid.{{id}}_last) {
|
||||
dojango._datagrid.{{id}}_last = el.value;
|
||||
|
||||
dojango._datagrid.{{id}}.filter({
|
||||
search:el.value,
|
||||
search_fields:'{{search_fields}}'
|
||||
{% for k, v in query.items %},'{{k}}':'{{v}}'{% endfor %}
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
</script>
|
||||
{% if show_search %}<div style="width:{{width}};height:20px" > Search:<input style="border:solid 1px #ccc;margin-left:10px" type=text onkeyup="do_{{id}}_search(this)"/></div>{% endif %}
|
||||
<div id="{{id}}_container" style="width:{{width}}; height:{{height}}"></div>
|
757
ntuh/dojango/templates/dojango/test.html
Executable file
|
@ -0,0 +1,757 @@
|
|||
{% extends "dojango/base.html" %}
|
||||
|
||||
{% block dojango_post_dj_config %}
|
||||
<script>
|
||||
djConfig.parseOnLoad = false;
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block dojango_header_extra %}
|
||||
<style type="text/css">
|
||||
html, body { height: 100%; width: 100%; padding: 0; border: 0; }
|
||||
#main { height: 100%; width: 100%; padding: 0; border: 0; }
|
||||
/* #header, #mainSplit { margin: 10px; } */
|
||||
#leftAccordion { width: 25%; }
|
||||
#bottomTabs { height: 40%; }
|
||||
#main .dijitSplitterH { height: 7px }
|
||||
#main .dijitSplitterV { width: 7px }
|
||||
|
||||
/* pre-loader specific stuff to prevent unsightly flash of unstyled content */
|
||||
#loader {
|
||||
padding:0;
|
||||
margin:0;
|
||||
position:absolute;
|
||||
top:0; left:0;
|
||||
width:100%; height:100%;
|
||||
background:#ededed;
|
||||
z-index:999;
|
||||
vertical-align:center;
|
||||
}
|
||||
#loaderInner {
|
||||
padding:5px;
|
||||
position:relative;
|
||||
left:0;
|
||||
top:0;
|
||||
width:275px;
|
||||
background:#3c3;
|
||||
color:#fff;
|
||||
|
||||
}
|
||||
|
||||
hr.spacer { border:0; background-color:#ededed; width:80%; height:1px; }
|
||||
|
||||
/* for custom menu buttons, do not appear to have any effect */
|
||||
.myCustomTheme .dijitButtonNode {
|
||||
border:1px solid #000;
|
||||
vertical-align: middle;
|
||||
padding: 0.2em 0.2em;
|
||||
background: url("themeTesterImages/blackButtonEnabled.gif") repeat-x bottom left #474747;
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.myCustomTheme .dijitButtonHover .dijitButtonNode,
|
||||
.myCustomTheme .dijitToggleButtonHover .dijitButtonNode {
|
||||
background: url("themeTesterImages/blackButtonHover.gif") repeat-x bottom left #3b3b3b !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.__globalList = {dojo: true, dijit: true, dojox: true, djConfig: true};
|
||||
for(var i in window){
|
||||
window.__globalList[i] = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block dojango_body %}
|
||||
<body class="{{ DOJANGO.THEME }}">
|
||||
<!-- basic preloader: -->
|
||||
<div id="loader"><div id="loaderInner">Loading themeTester: </div></div>
|
||||
|
||||
<!-- data for tree and combobox -->
|
||||
<div dojoType="dojo.data.ItemFileReadStore" jsId="continentStore"
|
||||
url="countries/"></div>
|
||||
<div dojoType="dojox.data.QueryReadStore" jsId="stateStore" requestMethod="post" clientPaging="false"
|
||||
url="states/"></div>
|
||||
<!-- contentMenu popup -->
|
||||
<div dojoType="dijit.Menu" id="submenu1" contextMenuForWindow="true" style="display: none;">
|
||||
<div dojoType="dijit.MenuItem" onClick="alert('Hello world');">Enabled Item</div>
|
||||
<div dojoType="dijit.MenuItem" disabled="true">Disabled Item</div>
|
||||
<div dojoType="dijit.MenuSeparator"></div>
|
||||
<div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon dijitEditorIconCut"
|
||||
onClick="alert('not actually cutting anything, just a test!')">Cut</div>
|
||||
<div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon dijitEditorIconCopy"
|
||||
onClick="alert('not actually copying anything, just a test!')">Copy</div>
|
||||
<div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon dijitEditorIconPaste"
|
||||
onClick="alert('not actually pasting anything, just a test!')">Paste</div>
|
||||
<div dojoType="dijit.MenuSeparator"></div>
|
||||
<div dojoType="dijit.PopupMenuItem">
|
||||
<span>Enabled Submenu</span>
|
||||
<div dojoType="dijit.Menu" id="submenu2">
|
||||
<div dojoType="dijit.MenuItem" onClick="alert('Submenu 1!')">Submenu Item One</div>
|
||||
<div dojoType="dijit.MenuItem" onClick="alert('Submenu 2!')">Submenu Item Two</div>
|
||||
<div dojoType="dijit.PopupMenuItem">
|
||||
<span>Deeper Submenu</span>
|
||||
<div dojoType="dijit.Menu" id="submenu4">
|
||||
<div dojoType="dijit.MenuItem" onClick="alert('Sub-submenu 1!')">Sub-sub-menu Item One</div>
|
||||
<div dojoType="dijit.MenuItem" onClick="alert('Sub-submenu 2!')">Sub-sub-menu Item Two</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div dojoType="dijit.PopupMenuItem" disabled="true">
|
||||
<span>Disabled Submenu</span>
|
||||
<div dojoType="dijit.Menu" id="submenu3" style="display: none;">
|
||||
<div dojoType="dijit.MenuItem" onClick="alert('Submenu 1!')">Submenu Item One</div>
|
||||
<div dojoType="dijit.MenuItem" onClick="alert('Submenu 2!')">Submenu Item Two</div>
|
||||
</div>
|
||||
</div>
|
||||
<div dojoType="dijit.PopupMenuItem">
|
||||
<span>Different popup</span>
|
||||
<div dojoType="dijit.ColorPalette"></div>
|
||||
</div>
|
||||
<div dojoType="dijit.PopupMenuItem">
|
||||
<span>Different popup</span>
|
||||
<div dojoType="dijit._Calendar"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end contextMenu -->
|
||||
|
||||
<!-- "main" BorderContainer just contains page title and another BorderContainer -->
|
||||
<div id="main" dojoType="dijit.layout.BorderContainer">
|
||||
|
||||
<h1 id="header" dojoType="dijit.layout.ContentPane" region="top">Dijit Theme Test Page</h1>
|
||||
|
||||
<!-- "mainSplit" BorderContainer has all the real content -->
|
||||
<div dojoType="dijit.layout.BorderContainer" liveSplitters="false" design="sidebar"
|
||||
region="center" id="mainSplit">
|
||||
|
||||
<div dojoType="dijit.layout.AccordionContainer" duration="200"
|
||||
minSize="20" style="width: 300px;" id="leftAccordion" region="leading" splitter="true">
|
||||
|
||||
<div dojoType="dijit.layout.AccordionPane" title="Popups and Alerts"><div style="padding:8px">
|
||||
<h2>Tooltips:</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<span id="ttRich">rich text tooltip</span>
|
||||
<span dojoType="dijit.Tooltip" connectId="ttRich" style="display:none;">
|
||||
Embedded <b>bold</b> <i>RICH</i> text <span style="color:#309; font-size:x-large;">weirdness!</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li><a id="ttOne" href="#bogus">anchor tooltip</a>
|
||||
<span dojoType="dijit.Tooltip" connectId="ttOne" style="display:none;">tooltip on anchor</span>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<hr class="spacer">
|
||||
|
||||
<h2>Dialogs:</h2>
|
||||
<ul>
|
||||
<li><a href="#" onclick="dijit.byId('dialog1').show()">show Modal Dialog</a></li>
|
||||
</ul>
|
||||
|
||||
<div dojoType="dijit.form.DropDownButton">
|
||||
<span>Show Tooltip Dialog</span>
|
||||
<div dojoType="dijit.TooltipDialog" id="tooltipDlg" title="Enter Login information"
|
||||
execute="alert('submitted w/args:\n' + dojo.toJson(arguments[0], true));">
|
||||
<table>
|
||||
<tr>
|
||||
<td><label for="user">User:</label></td>
|
||||
<td><input dojoType=dijit.form.TextBox type="text" id="user" name="user" ></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="pwd">Password:</label></td>
|
||||
<td><input dojoType=dijit.form.TextBox type="password" id="pwd" name="pwd"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" align="center">
|
||||
<button dojoType=dijit.form.Button type="submit" name="submit">Login</button>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div dojoType="dijit.layout.AccordionPane" title="Dojo Tree from Store">
|
||||
<!-- tree widget -->
|
||||
<div dojoType="dijit.Tree" store="continentStore" query="{type:'continent'}"
|
||||
label="Continents">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div dojoType="dijit.layout.AccordionPane" title="Calendar" selected="true">
|
||||
<!-- calendar widget pane -->
|
||||
<input id="calendar1" dojoType="dijit._Calendar" onChange="myHandler(this.id,arguments[0])">
|
||||
</div>
|
||||
|
||||
<div dojoType="dijit.layout.AccordionPane" title="Color Picker">
|
||||
<!-- color palette picker -->
|
||||
<h2 class="testHeader">Dijit Color Palette(7x10)</h2>
|
||||
<div dojoType="dijit.ColorPalette" onChange="setColor(this.value);"></div>
|
||||
<br>
|
||||
Test color is: <span id="outputSpan"></span>
|
||||
|
||||
<br><br>
|
||||
<div dojoType="dijit.ColorPalette" palette="3x4"></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div><!-- end AccordionContainer -->
|
||||
|
||||
<!-- top tabs (marked as "center" to take up the main part of the BorderContainer) -->
|
||||
<div dojoType="dijit.layout.TabContainer" region="center" id="topTabs">
|
||||
<!-- first tab? -->
|
||||
<div id="tab1" dojoType="dijit.layout.ContentPane" title="Form Feel" style="padding:10px;display:none;">
|
||||
<h2>Various Form Elements:</h2>
|
||||
|
||||
<form name="dijitFormTest">
|
||||
|
||||
<p><input type="checkBox" dojoType="dijit.form.CheckBox" checked="checked"> Standard Dijit CheckBox
|
||||
<br><input type="checkBox" dojoType="dijit.form.CheckBox" disabled="disabled"> Disabled Dijit
|
||||
<br><input type="checkBox" dojoType="dijit.form.CheckBox" disabled="disabled" checked="checked"> Checked and Disabled Dijit
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span>Radio group #1:</span>
|
||||
<input type="radio" name="g1" id="g1rb1" value="news" dojoType="dijit.form.RadioButton">
|
||||
<label for="g1rb1">news</label>
|
||||
<input type="radio" name="g1" id="g1rb2" value="talk" dojoType="dijit.form.RadioButton" checked="checked"/>
|
||||
<label for="g1rb2">talk</label>
|
||||
<input type="radio" name="g1" id="g1rb3" value="weather" dojoType="dijit.form.RadioButton" disabled="disabled"/>
|
||||
<label for="g1rb3">weather (disabled)</label>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span>Radio group #2: (no default value, and has breaks)</span><br>
|
||||
<input type="radio" name="g2" id="g2rb1" value="top40" dojoType="dijit.form.RadioButton">
|
||||
<label for="g2rb1">top 40</label><br>
|
||||
<input type="radio" name="g2" id="g2rb2" value="oldies" dojoType="dijit.form.RadioButton">
|
||||
<label for="g2rb2">oldies</label><br>
|
||||
|
||||
<input type="radio" name="g2" id="g2rb3" value="country" dojoType="dijit.form.RadioButton">
|
||||
<label for="g2rb3">country</label><br>
|
||||
|
||||
<br>
|
||||
(Note if using keyboard: tab to navigate, and use arrow or space to select)
|
||||
</p>
|
||||
|
||||
<hr class="spacer">
|
||||
|
||||
<h2>dijit.form.NumberSpinner max=100</h2>
|
||||
<input dojoType="dijit.form.NumberSpinner" constraints="{max:100,places:0}" id="integertextbox3" value="10">
|
||||
|
||||
<hr class="spacer">
|
||||
|
||||
<h2>dijit.form.Textarea: (sans <i>any</i> styling...)</h2>
|
||||
<textarea dojoType="dijit.form.Textarea" name="areText">Lorem ipsum dolor sit amet,
|
||||
consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet
|
||||
dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci
|
||||
tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis
|
||||
autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat,
|
||||
vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
|
||||
dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait
|
||||
nulla facilisi.
|
||||
</textarea>
|
||||
|
||||
<hr class="spacer">
|
||||
|
||||
<h2>dijit.form.ComboBox</h2>
|
||||
<label for="datatestComboBox">US State test 2: </label>
|
||||
<input dojoType="dijit.form.ComboBox"
|
||||
value="California"
|
||||
class="medium"
|
||||
store="stateStore"
|
||||
searchAttr="name"
|
||||
style="width: 300px;"
|
||||
name="state2"
|
||||
id="datatestComboBox"
|
||||
pageSize="10"
|
||||
>
|
||||
|
||||
</form>
|
||||
|
||||
</div><!-- end first tab -->
|
||||
|
||||
<!-- second upper tab -->
|
||||
<div id="tab2" dojoType="dijit.layout.ContentPane" title="Various Dijits"
|
||||
style="padding:10px; display:none;">
|
||||
|
||||
<!-- Sliders: -->
|
||||
<div style="float:right;">
|
||||
<div dojoType="dijit.form.VerticalSlider" name="vertical1"
|
||||
onChange="dojo.byId('slider2input').value=arguments[0];"
|
||||
value="10"
|
||||
maximum="100"
|
||||
minimum="0"
|
||||
discreteValues="11"
|
||||
style="height:175px; clear:both"
|
||||
id="slider2">
|
||||
<ol dojoType="dijit.form.VerticalRuleLabels" container="leftDecoration"style="width:2em;color:gray;" labelStyle="right:0px;">
|
||||
<li>0
|
||||
<li>100
|
||||
</ol>
|
||||
|
||||
<div dojoType="dijit.form.VerticalRule" container="leftDecoration" count=11 style="width:5px;" ruleStyle="border-color:gray;"></div>
|
||||
<div dojoType="dijit.form.VerticalRule" container="rightDecoration" count=11 style="width:5px;" ruleStyle="border-color:gray;"></div>
|
||||
<ol dojoType="dijit.form.VerticalRuleLabels" container="rightDecoration"style="width:2em;color:gray;" maximum="100" count="6" numericMargin="1" constraints="{pattern:'#'}"></ol>
|
||||
</div>
|
||||
<br> Slider2 Value:<input readonly id="slider2input" size="3" value="10">
|
||||
</div>
|
||||
<h2>Sliders</h2>
|
||||
<div dojoType="dijit.form.HorizontalSlider" name="horizontal1"
|
||||
onChange="dojo.byId('slider1input').value=dojo.number.format(arguments[0]/100,{places:1,pattern:'#%'});"
|
||||
value="10"
|
||||
maximum="100"
|
||||
minimum="0"
|
||||
showButtons="false"
|
||||
intermediateChanges="true"
|
||||
style="width:50%; height: 20px;"
|
||||
id="horizontal1">
|
||||
<ol dojoType="dijit.form.HorizontalRuleLabels" container="topDecoration" style="height:1.2em;font-size:75%;color:gray;" numericMargin="1" count="6"></ol>
|
||||
<div dojoType="dijit.form.HorizontalRule" container="topDecoration" count=11 style="height:5px;"></div>
|
||||
<div dojoType="dijit.form.HorizontalRule" container="bottomDecoration" count=5 style="height:5px;"></div>
|
||||
<ol dojoType="dijit.form.HorizontalRuleLabels" container="bottomDecoration" style="height:1em;font-size:75%;color:gray;">
|
||||
<li>lowest
|
||||
<li>normal
|
||||
<li>highest
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<br>Value: <input readonly id="slider1input" size="5" value="10.0%">
|
||||
|
||||
<div dojoType="dijit.form.HorizontalSlider" name="horizontal2"
|
||||
minimum="1"
|
||||
value="2"
|
||||
maximum="3"
|
||||
discreteValues="3"
|
||||
showButtons="false"
|
||||
intermediateChanges="true"
|
||||
style="width:300px; height: 40px;"
|
||||
id="horizontal2">
|
||||
<div dojoType="dijit.form.HorizontalRule" container="bottomDecoration" count=3 style="height:5px;"></div>
|
||||
<ol dojoType="dijit.form.HorizontalRuleLabels" container="bottomDecoration"style="height:1em;font-size:75%;color:gray;">
|
||||
<li><img width=10 height=10 src="{{ DOJANGO.DOJANGO_URL }}/resources/note.gif"><br><span style="font-size: small">small</span>
|
||||
<li><img width=15 height=15 src="{{ DOJANGO.DOJANGO_URL }}/resources/note.gif"><br><span style="font-size: medium">medium</span>
|
||||
|
||||
<li><img width=20 height=20 src="{{ DOJANGO.DOJANGO_URL }}/resources/note.gif"><br><span style="font-size: large">large</span>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<br style="clear:both;">
|
||||
<hr class="spacer">
|
||||
|
||||
<h2>ProgressBar</h2>
|
||||
<div style="width:400px; height:20px;" annotate="true" maximum="200" id="setTestBar"
|
||||
progress="20" dojoType="dijit.ProgressBar"></div>
|
||||
|
||||
Indeterminate:
|
||||
<div style="width:400px; height:20px" indeterminate="true" dojoType="dijit.ProgressBar"></div>
|
||||
|
||||
<hr class="spacer">
|
||||
|
||||
<h2>TitlePane (nested)</h2>
|
||||
<div dojoType="dijit.TitlePane" title="Outer pane" width="275">
|
||||
<p>This is a title pane, containing another title pane ...</p>
|
||||
<div dojoType="dijit.TitlePane" title="Inner pane" width="125">
|
||||
|
||||
<p>And this is the inner title pane...</p>
|
||||
|
||||
<p>Sed sollicitudin suscipit risus. Nam
|
||||
ullamcorper. Sed nisl lectus, pellentesque nec,
|
||||
malesuada eget, ornare a, libero. Lorem ipsum dolor
|
||||
sit amet, consectetuer adipiscing elit.</p>
|
||||
|
||||
</div><!-- end inner titlepane -->
|
||||
<p>And this is the closing line for the outer title pane.</p>
|
||||
</div><!-- end outer title pane -->
|
||||
<h2>HTML After, check indent</h2>
|
||||
</div><!-- end:second upper tab -->
|
||||
|
||||
<!-- start third upper tab -->
|
||||
<div id="tab3" dojoType="dijit.layout.ContentPane" title="Buttons"
|
||||
style="padding:10px; display:none; ">
|
||||
|
||||
<h2>Simple, drop down & combo buttons</h2>
|
||||
<p>Buttons can do an action, display a menu, or both:</p>
|
||||
|
||||
<div class="box">
|
||||
<button dojoType="dijit.form.Button" iconClass="plusIcon" onClick='console.debug("clicked simple")'>
|
||||
Create
|
||||
</button>
|
||||
|
||||
<button dojoType="dijit.form.DropDownButton" iconClass="noteIcon">
|
||||
<span>Edit</span>
|
||||
<div dojoType="dijit.Menu" id="editMenu" style="display: none;">
|
||||
<div dojoType="dijit.MenuItem"
|
||||
iconClass="dijitEditorIcon dijitEditorIconCut"
|
||||
onClick="console.debug('not actually cutting anything, just a test!')">
|
||||
Cut
|
||||
</div>
|
||||
|
||||
<div dojoType="dijit.MenuItem"
|
||||
iconClass="dijitEditorIcon dijitEditorIconCopy"
|
||||
onClick="console.debug('not actually copying anything, just a test!')">
|
||||
Copy
|
||||
</div>
|
||||
|
||||
<div dojoType="dijit.MenuItem"
|
||||
iconClass="dijitEditorIcon dijitEditorIconPaste"
|
||||
onClick="console.debug('not actually pasting anything, just a test!')">
|
||||
Paste
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button dojoType="dijit.form.ComboButton" iconClass="noteIcon"
|
||||
optionsTitle='save options'
|
||||
onClick='console.debug("clicked combo save")'>
|
||||
<span>Save</span>
|
||||
<div dojoType="dijit.Menu" id="saveMenu" style="display: none;">
|
||||
<div dojoType="dijit.MenuItem"
|
||||
iconClass="dijitEditorIcon dijitEditorIconSave"
|
||||
onClick="console.debug('not actually saving anything, just a test!')">
|
||||
Save
|
||||
</div>
|
||||
<div dojoType="dijit.MenuItem"
|
||||
onClick="console.debug('not actually saving anything, just a test!')">
|
||||
Save As
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button dojoType="dijit.form.Button" iconClass="plusIcon" onClick='console.debug("clicked simple")'
|
||||
disabled='true'>
|
||||
Disabled
|
||||
</button>
|
||||
</div><!-- end:box -->
|
||||
|
||||
<hr class="spacer">
|
||||
|
||||
<h2>Sizing</h2>
|
||||
<p>Short button, tall buttons, big buttons, small buttons... These buttons
|
||||
size to their content (just like <button>).</p>
|
||||
|
||||
<div class="box">
|
||||
<button dojoType="dijit.form.Button" iconClass="flatScreenIcon" onclick='console.debug("big");'>
|
||||
<span style="font-size:xx-large">big</span>
|
||||
</button>
|
||||
|
||||
<button id="smallButton1" dojoType="dijit.form.Button" onclick='console.debug("small");'>
|
||||
<img src="{{ DOJANGO.DOJANGO_URL }}/resources/arrowSmall.gif" width="15" height="5">
|
||||
<span style="font-size:x-small">small</span>
|
||||
</button>
|
||||
|
||||
<button dojoType="dijit.form.Button" onclick='console.debug("long");'>
|
||||
<img src="{{ DOJANGO.DOJANGO_URL }}/resources/tube.gif" width="150" height="16"> long
|
||||
</button>
|
||||
|
||||
<button dojoType="dijit.form.Button" onclick='console.debug("tall");' width2height="0.1">
|
||||
<img src="{{ DOJANGO.DOJANGO_URL }}/resources/tubeTall.gif" height="75" width="35"><br>
|
||||
<span style="font-size:medium">tall</span>
|
||||
</button>
|
||||
<div style="clear: both;"></div>
|
||||
</div><!-- end box -->
|
||||
|
||||
<hr class="spacer">
|
||||
|
||||
<h2>Customized buttons</h2>
|
||||
<p>Dojo users can mix in their styles. Here's an example:</p>
|
||||
|
||||
<div><!-- custom styled button tests -->
|
||||
<button dojoType="dijit.form.Button" class="myCustomTheme"
|
||||
onclick='console.debug("short");'>
|
||||
<div class="dc">short</div>
|
||||
</button>
|
||||
|
||||
<button dojoType="dijit.form.Button" class="myCustomTheme"
|
||||
onclick='console.debug("longer");'>
|
||||
<div class="dc">bit longer</div>
|
||||
</button>
|
||||
|
||||
<button dojoType="dijit.form.Button" class="myCustomTheme"
|
||||
onclick='console.debug("longer yet");'>
|
||||
<div class="dc">ridiculously long</div>
|
||||
</button>
|
||||
|
||||
<div style="clear: both;"></div>
|
||||
</div><!-- end styled buttons -->
|
||||
|
||||
</div><!-- end third upper tab-->
|
||||
|
||||
<!-- fourth upper tab -->
|
||||
<div id="tab" dojoType="dijit.layout.ContentPane" title="Editable Text" style="padding:10px;" selected="selected">
|
||||
|
||||
<h2>dijit.Editor:</h2>
|
||||
<!-- FIXME:
|
||||
set height on this node to size the whole editor, but causes the tab to not scroll
|
||||
until you select another tab and come back. alternative is no height: here, but that
|
||||
causes editor to become VERY tall, and size to a normal height when selected (like the
|
||||
dijit.form.TextArea in "Form Feel" Tab), but in reverse. refs #3980 and is maybe new bug?
|
||||
-->
|
||||
<div style="border:1px solid #ededed;">
|
||||
<textarea height="175" dojoType="dijit.Editor" styleSheets="{{ DOJANGO.DOJO_URL }}/resources/dojo.css" sytle="width:400px; height:175px; overflow:auto; ">
|
||||
<ul>
|
||||
<li>Lorem <a href="http://dojotoolkit.org">and a link</a>, what do you think?</li>
|
||||
<li>This is the Editor with a Toolbar attached.</li>
|
||||
</ul>
|
||||
</textarea>
|
||||
</div>
|
||||
<hr class="spacer">
|
||||
|
||||
|
||||
<h2 class="testTitle">dijit.InlineEditBox + dijit.form.TextBox on <h3></h2>
|
||||
|
||||
(HTML before)
|
||||
<h3 id="editable" style="font-size:larger;" dojoType="dijit.InlineEditBox" onChange="myHandler(this.id,arguments[0])">
|
||||
Edit me - I trigger the onChange callback
|
||||
</h3>
|
||||
(HTML after)
|
||||
|
||||
<hr class="spacer">
|
||||
|
||||
<h2>dijit.InlineEditBox + dijit.form.Textarea</h2>
|
||||
|
||||
(HTML before)
|
||||
<p id="areaEditable" dojoType="dijit.InlineEditBox" editor="dijit.form.Textarea" autoSave="false">
|
||||
I'm one big paragraph. Go ahead and edit me. I dare you.
|
||||
The quick brown fox jumped over the lazy dog. Blah blah blah blah blah blah blah ...
|
||||
</p>
|
||||
(HTML after)
|
||||
|
||||
<p>
|
||||
These links will
|
||||
<a href="#" onClick="dijit.byId('areaEditable').setDisabled(true)">disable</a> /
|
||||
<a href="#" onClick="dijit.byId('areaEditable').setDisabled(false)">enable</a>
|
||||
the text area above.
|
||||
</p>
|
||||
|
||||
<hr class="spacer">
|
||||
|
||||
<h2>dijit.form.DateTextBox:</h2>
|
||||
|
||||
(HTML inline before)
|
||||
<span id="backgroundArea" dojoType="dijit.InlineEditBox" editor="dijit.form.DateTextBox" width="170px">12/30/2005</span>
|
||||
(HTML after)
|
||||
|
||||
<hr class="spacer">
|
||||
|
||||
<h2>dijit.form.TimeTextBox:</h2>
|
||||
|
||||
(HTML inline before)
|
||||
<span id="timePicker" dojoType="dijit.InlineEditBox" editor="dijit.form.TimeTextBox" width="150px">9:00 AM</span>
|
||||
(HTML after)
|
||||
|
||||
<hr class="spacer">
|
||||
|
||||
|
||||
<h2>dijit.form.FilteringSelect + Inline + remote data store:</h2>
|
||||
(HTML inline before)
|
||||
<span id="backgroundArea2" dojoType="dijit.InlineEditBox" editor="dijit.form.FilteringSelect"
|
||||
editorParams="{store: stateStore, autoComplete: true, promptMessage: 'Please enter a state'}"
|
||||
width="300px">
|
||||
Indiana
|
||||
</span>
|
||||
(HTML after)
|
||||
|
||||
</div><!-- end fourth upper tab -->
|
||||
|
||||
<!-- fifth upper tab -->
|
||||
<div id="tab5" dojoType="dijit.layout.ContentPane" title="DnD"
|
||||
style="padding:10px; display:none; ">
|
||||
<div style="float:left; margin:5px; ">
|
||||
<h3>Source 1</h3>
|
||||
<div dojoType="dojo.dnd.Source" style="border:3px solid #ccc; padding: 1em 3em; ">
|
||||
<div class="dojoDndItem">Item <strong>X</strong></div>
|
||||
<div class="dojoDndItem">Item <strong>Y</strong></div>
|
||||
<div class="dojoDndItem">Item <strong>Z</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="float:left; margin:5px; ">
|
||||
<h3>Source 2</h3>
|
||||
<div dojoType="dojo.dnd.Source" style="border:3px solid #ccc; padding: 1em 3em; ">
|
||||
<div class="dojoDndItem">Item <strong>1</strong></div>
|
||||
<div class="dojoDndItem">Item <strong>2</strong></div>
|
||||
<div class="dojoDndItem">Item <strong>3</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- sixth upper tab -->
|
||||
<div id="tab6" dojoType="dijit.layout.ContentPane" title="Closable"
|
||||
style="display:none; padding:10px; " closable="true">
|
||||
This pane is closable, just for the icon ...
|
||||
</div>
|
||||
</div><!-- end of region="center" TabContainer -->
|
||||
|
||||
<!-- bottom right tabs -->
|
||||
<div dojoType="dijit.layout.TabContainer" id="bottomTabs" tabPosition="bottom" selectedChild="btab1" region="bottom" splitter="true">
|
||||
|
||||
<!-- btab 1 -->
|
||||
<div id="btab1" dojoType="dijit.layout.ContentPane" title="Info" style=" padding:10px; ">
|
||||
<p>You can explore this single page after applying a Theme
|
||||
for use in creation of your own theme.</p>
|
||||
|
||||
<p>I am whole slew of Widgets on a page. Jump to <a href="../tests/">dijit tests</a> to
|
||||
test individual components.</p>
|
||||
|
||||
<p>There is a right-click [context] pop-up menu here, as well.</p>
|
||||
</div><!-- end:info btab1 -->
|
||||
|
||||
<div id="btab2" dojoType="dijit.layout.ContentPane" title="Alternate Themes" style="padding:20px;">
|
||||
<span id="themeData"></span>
|
||||
</div><!-- btab2 -->
|
||||
|
||||
<div id="btab3" dojoType="dijit.layout.ContentPane" title="Bottom 3" closable="true">
|
||||
<p>I am the last Tab</p>
|
||||
<div id="dialog2" dojoType="dijit.Dialog" title="Encased Dialog" style="display:none;">
|
||||
I am the second dialog. I am
|
||||
parented by the Low Tab Pane #3
|
||||
</div>
|
||||
</div><!-- btab3 -->
|
||||
|
||||
</div><!-- end Bottom TabContainer -->
|
||||
|
||||
</div> <!-- end of "mainSplit" BorderContainer -->
|
||||
</div><!-- end of "main" BorderContainer -->
|
||||
|
||||
<!-- dialog in body -->
|
||||
<div id="dialog1" dojoType="dijit.Dialog" title="Floating Modal Dialog" style="display:none;" href="{{ DOJANGO.DIJIT_URL }}/tests/layout/doc0.html"></div>
|
||||
<div id="dojangoDialog" dojoType="dijit.Dialog" title="Welcome to Dojango" style="text-align:center;">
|
||||
<h1 style="font-size:2em; font-weight:bold;">Congratulations, dojango is running!</h1>
|
||||
<img src="{{ DOJANGO.DOJANGO_URL }}/resources/dojango_logo.jpg" height="150" style="margin:4em;" />
|
||||
<br />
|
||||
Press ESC to close dialog, or<br />
|
||||
<button dojoType="dijit.form.Button" onclick="dijit.byId('dojangoDialog').hide()">Close dialog</button>
|
||||
</div>
|
||||
<script type="text/javascript"> // dojo.requires()
|
||||
|
||||
var loadingEl = dojo.byId("loaderInner");
|
||||
|
||||
loadingEl.innerHTML += "<br />* Menu widgets - dijit.Menu";
|
||||
dojo.require("dijit.Menu");
|
||||
dojo.require("dijit._Calendar");
|
||||
dojo.require("dijit.ColorPalette");
|
||||
dojo.require("dijit.ProgressBar");
|
||||
dojo.require("dijit.TitlePane");
|
||||
dojo.require("dijit.Tooltip");
|
||||
dojo.require("dijit.Tree");
|
||||
loadingEl.innerHTML += "<br />* Tree widget - dijit.Tree";
|
||||
|
||||
// editor:
|
||||
loadingEl.innerHTML += "<br />* Rich text editor - dijit.Editor";
|
||||
dojo.require("dijit.Editor");
|
||||
|
||||
// dnd:
|
||||
loadingEl.innerHTML += "<br />* Drag and drop library - dojo.dnd";
|
||||
dojo.require("dojo.dnd.Source");
|
||||
|
||||
// various Form elemetns
|
||||
loadingEl.innerHTML += "<br />* Form widgets - dijit.form";
|
||||
dojo.require("dijit.form.CheckBox");
|
||||
dojo.require("dijit.form.Textarea");
|
||||
dojo.require("dijit.form.FilteringSelect");
|
||||
dojo.require("dijit.form.TextBox");
|
||||
dojo.require("dijit.form.DateTextBox");
|
||||
dojo.require("dijit.form.TimeTextBox");
|
||||
dojo.require("dijit.form.Button");
|
||||
dojo.require("dijit.InlineEditBox");
|
||||
dojo.require("dijit.form.NumberSpinner");
|
||||
dojo.require("dijit.form.Slider");
|
||||
|
||||
// layouts used in page
|
||||
loadingEl.innerHTML += "<br />* Layout engine widgets - dijit.layout";
|
||||
dojo.require("dijit.layout.AccordionContainer");
|
||||
dojo.require("dijit.layout.ContentPane");
|
||||
dojo.require("dijit.layout.TabContainer");
|
||||
dojo.require("dijit.layout.BorderContainer");
|
||||
loadingEl.innerHTML += "<br />* Dialog widgets - dijit.Dialog";
|
||||
dojo.require("dijit.Dialog");
|
||||
|
||||
// scan page for widgets and instantiate them
|
||||
dojo.require("dojo.parser");
|
||||
|
||||
// humm?
|
||||
dojo.require("dojo.date.locale");
|
||||
|
||||
// for the Tree
|
||||
loadingEl.innerHTML += "<br />* Dojo server data interfaces - dojo.data stores";
|
||||
dojo.require("dojo.data.ItemFileReadStore");
|
||||
dojo.require("dojox.data.QueryReadStore");
|
||||
|
||||
// for the colorpalette
|
||||
function setColor(color){
|
||||
var theSpan = dojo.byId("outputSpan");
|
||||
dojo.style(theSpan,"color",color);
|
||||
theSpan.innerHTML = color;
|
||||
}
|
||||
|
||||
// for the calendar
|
||||
function myHandler(id,newValue){
|
||||
console.debug("onChange for id = " + id + ", value: " + newValue);
|
||||
}
|
||||
|
||||
dojo.addOnLoad(function() {
|
||||
|
||||
// this is just a list of 'official' dijit themes, you can use ?theme=String
|
||||
// for 'un-supported' themes, too. (eg: yours)
|
||||
var availableThemes = [
|
||||
{ theme:"tundra", author:"Dojo", baseUri:"../themes/" },
|
||||
{ theme:"soria", author:"nikolai", baseUri:"../themes/" },
|
||||
//{ theme:"noir", author:"owen", baseUri:"../themes/"},
|
||||
{ theme:"nihilo", author:"nikolai", baseUri:"../themes/" }
|
||||
];
|
||||
|
||||
var holder = dojo.byId('themeData');
|
||||
var tmpString='';
|
||||
dojo.forEach(availableThemes,function(theme){
|
||||
tmpString += '<a href="?theme='+theme.theme+'">'+theme.theme+'</'+'a> - by: '+theme.author+' <br>';
|
||||
});
|
||||
holder.innerHTML = tmpString;
|
||||
|
||||
var start = new Date().getTime();
|
||||
dojo.parser.parse(dojo.byId('container'));
|
||||
console.info("Total parse time: " + (new Date().getTime() - start) + "ms");
|
||||
dijit.byId("dojangoDialog").show();
|
||||
|
||||
dojo.byId('loaderInner').innerHTML += " done.";
|
||||
setTimeout(function hideLoader(){
|
||||
var loader = dojo.byId('loader');
|
||||
dojo.fadeOut({ node: loader, duration:500,
|
||||
onEnd: function(){
|
||||
loader.style.display = "none";
|
||||
}
|
||||
}).play();
|
||||
}, 250);
|
||||
|
||||
var strayGlobals = [];
|
||||
for(var i in window){
|
||||
if(!window.__globalList[i]){ strayGlobals.push(i); }
|
||||
}
|
||||
if(strayGlobals.length){
|
||||
console.warn("Stray globals: "+strayGlobals.join(", "));
|
||||
}
|
||||
});
|
||||
|
||||
/***
|
||||
dojo.addOnLoad(function(){
|
||||
// use "before advice" to print log message each time resize is called on a layout widget
|
||||
var origResize = dijit.layout._LayoutWidget.prototype.resize;
|
||||
dijit.layout._LayoutWidget.prototype.resize = function(mb){
|
||||
console.log(this + ": resize({w:"+ mb.w + ", h:" + mb.h + "})");
|
||||
origResize.apply(this, arguments);
|
||||
};
|
||||
|
||||
// content pane has no children so just use dojo's builtin after advice
|
||||
dojo.connect(dijit.layout.ContentPane.prototype, "resize", function(mb){
|
||||
console.log(this + ": resize({w:"+ mb.w + ", h:" + mb.h + "})");
|
||||
});
|
||||
});
|
||||
***/
|
||||
</script>
|
||||
</body>
|
||||
{% endblock %}
|
0
ntuh/dojango/templatetags/__init__.py
Executable file
39
ntuh/dojango/templatetags/dojango_base.py
Executable file
|
@ -0,0 +1,39 @@
|
|||
from django import template
|
||||
|
||||
from dojango.conf import settings # using the app-specific settings
|
||||
from dojango.util import json_encode as util_json_encode
|
||||
from dojango.util.config import Config
|
||||
|
||||
register = template.Library()
|
||||
|
||||
class DojangoParamsNode(template.Node):
|
||||
'''We set the DOJANGO context with this node!'''
|
||||
def __init__(self, profile=settings.DOJO_PROFILE, version=settings.DOJO_VERSION):
|
||||
self.profile = profile
|
||||
self.version = version
|
||||
def render(self, context):
|
||||
config = Config(self.profile, self.version)
|
||||
if not config.config:
|
||||
raise template.TemplateSyntaxError, "Could not find the profile '%s' in the DOJANGO_DOJO_PROFILES settings" % (self.profile)
|
||||
if not config.dojo_base_url:
|
||||
raise template.TemplateSyntaxError, "The version %s is not supported by the dojango profile '%s'" % (self.version, self.profile)
|
||||
context['DOJANGO'] = config.get_context_dict()
|
||||
return ''
|
||||
|
||||
@register.tag
|
||||
def set_dojango_context(parser, token):
|
||||
'''Sets the DOJANGO context constant in the context.
|
||||
It is also possible to set the used profile/version with it, e.g.:
|
||||
{% set_dojango_context "google" "1.1.1" %}'''
|
||||
tlist = token.split_contents()
|
||||
# the profile was passed
|
||||
if len(tlist) == 2:
|
||||
return DojangoParamsNode(tlist[1][1:-1])
|
||||
if len(tlist) == 3:
|
||||
return DojangoParamsNode(tlist[1][1:-1], tlist[2][1:-1])
|
||||
return DojangoParamsNode()
|
||||
|
||||
# TODO: Implement template-tag for layout components to register e.g. dojoType="dijit.layout.TabContainer"
|
||||
# {% dojo_type "dijit.layout.TabContainer" %}
|
||||
# This template tag informs the collector about new modules
|
||||
|
9
ntuh/dojango/templatetags/dojango_filters.py
Executable file
|
@ -0,0 +1,9 @@
|
|||
from django.template import Library
|
||||
|
||||
from dojango.util import json_encode
|
||||
|
||||
register = Library()
|
||||
|
||||
@register.filter
|
||||
def json(input):
|
||||
return json_encode(input)
|
161
ntuh/dojango/templatetags/dojango_grid.py
Executable file
|
@ -0,0 +1,161 @@
|
|||
from django import template
|
||||
from django.db.models import get_model
|
||||
from django.db import models
|
||||
from django.template import TemplateSyntaxError
|
||||
from django.template.loader import get_template
|
||||
|
||||
from dojango.util import extract_nodelist_options
|
||||
from dojango.util.dojo_collector import add_module
|
||||
from dojango.util.perms import access_model
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
|
||||
import random
|
||||
|
||||
register = template.Library()
|
||||
disp_list_guid = 0
|
||||
|
||||
FIELD_ATTRIBUTES = ('column_width', 'label', 'formatter')
|
||||
|
||||
@register.tag
|
||||
def simple_datagrid(parser, token):
|
||||
"""
|
||||
Generates a dojo datagrid for a given app's model.
|
||||
i.e. {% simple_datagrid myapp mymodel %}
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
if len(bits) < 3:
|
||||
raise TemplateSyntaxError, "You have to pass app- and model-name to {% simple_datagrid app model %}"
|
||||
return DatagridNode(bits[1],bits[2],None)
|
||||
|
||||
@register.tag
|
||||
def datagrid(parser, token):
|
||||
"""
|
||||
Generates a dojo datagrid for a given app's model. renders
|
||||
the contents until {% enddatagrid %} and takes options in
|
||||
the form of option=value per line.
|
||||
"""
|
||||
bits = token.split_contents()
|
||||
nodelist = parser.parse(('enddatagrid',))
|
||||
parser.delete_first_token()
|
||||
app, model = None, None
|
||||
if len(bits) == 3:
|
||||
app = bits[1]
|
||||
model = bits[2]
|
||||
return DatagridNode(app, model,nodelist)
|
||||
|
||||
class DatagridNode(template.Node):
|
||||
"""
|
||||
If nodelist is not None this will render the contents under the templates
|
||||
context then render the dojango/templatetag/datagrid_disp.html template
|
||||
under a context created by the options parsed out of the block.
|
||||
|
||||
Available options:
|
||||
|
||||
list_display: list or tuple of model attributes (model fields or model functions). defaults to all of the sql fields of the model
|
||||
column_width: dict with model attribute:width
|
||||
default_width: default width if not specified by column_width. defaults to "auto"
|
||||
width: width of the datagrid, defaults to "100%"
|
||||
height: height of datagrid, defaults to "100%"
|
||||
id: id of datagird, optional but useful to if planning on using dojo.connect to the grid.
|
||||
label: dict of attribute:label for header. (other ways exist of setting these)
|
||||
query: way to specify conditions for the table. i.e. to only display elements whose id>10: query={ 'id__gt':10 }
|
||||
search: list or tuple of fields to query against when searching
|
||||
show_search: Display search field (default: True). If False, you'll create your custom search field and call do_{{id}}_search
|
||||
nosort: fields not to sort on
|
||||
formatter: dict of attribute:js formatter function
|
||||
json_store_url: URL for the ReadQueryStore
|
||||
selection_mode: dojo datagrid selectionMode
|
||||
"""
|
||||
model = None
|
||||
app_name = None
|
||||
model_name = None
|
||||
|
||||
def __init__(self, app, model, options):
|
||||
if app and model:
|
||||
self.model = get_model(app,model)
|
||||
self.app_name = app
|
||||
self.model_name = model
|
||||
self.options = options
|
||||
|
||||
def render(self, context):
|
||||
opts = {}
|
||||
global disp_list_guid
|
||||
disp_list_guid = disp_list_guid + 1
|
||||
# add dojo modules
|
||||
add_module("dojox.data.QueryReadStore")
|
||||
add_module("dojox.grid.DataGrid")
|
||||
|
||||
# Setable options, not listed: label, query, search, nosort
|
||||
if self.model:
|
||||
opts['list_display'] = [x.attname for x in self.model._meta.fields]
|
||||
opts['width'] = {}
|
||||
opts['label'] = {}
|
||||
opts['default_width'] = "auto"
|
||||
opts['width'] = "100%"
|
||||
opts['height'] = "100%"
|
||||
opts['query']={}
|
||||
opts['query']['inclusions']=[]
|
||||
|
||||
opts['id'] = "disp_list_%s_%s" % (disp_list_guid,random.randint(10000,99999))
|
||||
try:
|
||||
# reverse lookup of the datagrid-list url (see dojango/urls.py)
|
||||
if self.model:
|
||||
opts['json_store_url'] = reverse("dojango-datagrid-list", args=(self.app_name, self.model_name))
|
||||
except NoReverseMatch:
|
||||
pass
|
||||
|
||||
# User overrides
|
||||
if self.options:
|
||||
opts.update(extract_nodelist_options(self.options,context))
|
||||
if not opts['query'].has_key('inclusions'): opts['query']['inclusions'] = []
|
||||
|
||||
# we must ensure that the json_store_url is defined
|
||||
if not opts.get('json_store_url', False):
|
||||
raise TemplateSyntaxError, "Please enable the url 'dojango-datagrid-list' in your urls.py or pass a 'json_store_url' to the datagrid templatetag."
|
||||
|
||||
# Incase list_display was passed as tuple, turn to list for mutability
|
||||
if not self.model and not opts.get('list_display', False):
|
||||
raise TemplateSyntaxError, "'list_display' not defined. If you use your own 'json_store_url' you have to define which fields are visible."
|
||||
opts['list_display'] = list(opts['list_display'])
|
||||
|
||||
# Config for template
|
||||
opts['headers'] = []
|
||||
|
||||
# Get field labels using verbose name (take into account i18n), will be used
|
||||
# for column labels
|
||||
verbose_field_names = {}
|
||||
if self.model:
|
||||
verbose_field_names = dict([(f.name, f.verbose_name) for f in self.model._meta.fields])
|
||||
|
||||
for field in opts['list_display']:
|
||||
ret = {'attname':field}
|
||||
for q in FIELD_ATTRIBUTES:
|
||||
if opts.has_key(q) and opts[q].has_key(field):
|
||||
ret[q] = opts[q][field]
|
||||
# custom default logic
|
||||
if not ret.has_key('label'):
|
||||
ret['label'] = verbose_field_names.get(field, field.replace('_', ' '))
|
||||
if not ret.has_key('column_width'):
|
||||
ret['column_width']= opts['default_width']
|
||||
# add as inclusion if not a attribute of model
|
||||
if self.model and not field in map(lambda x: x.attname, self.model._meta.fields):
|
||||
opts['query']['inclusions'].append(field)
|
||||
# add to header
|
||||
opts['headers'].append(ret)
|
||||
|
||||
# no sort fields
|
||||
if opts.has_key("nosort"):
|
||||
opts['nosort'] = "".join(["||row==%s"%(opts['list_display'].index(r)+1) for r in opts['nosort']])
|
||||
|
||||
# additional context info/modifications
|
||||
opts["model_name"] = self.model_name
|
||||
opts["app_name"] = self.app_name
|
||||
opts['query']['inclusions'] = ",".join(opts['query']['inclusions'])
|
||||
if opts.has_key('search'):
|
||||
opts['search_fields'] = ",".join(opts['search'])
|
||||
opts['show_search'] = opts.get('show_search', True)
|
||||
else:
|
||||
opts['show_search'] = False
|
||||
|
||||
# return rendered template
|
||||
return get_template("dojango/templatetag/datagrid_disp.html").render(template.Context(opts))
|
18
ntuh/dojango/urls.py
Executable file
|
@ -0,0 +1,18 @@
|
|||
import os
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
from django.conf import settings
|
||||
|
||||
from dojango.util import media
|
||||
|
||||
urlpatterns = patterns('dojango',
|
||||
url(r'^test/$', 'views.test', name='dojango-test'),
|
||||
url(r'^test/countries/$', 'views.test_countries'),
|
||||
url(r'^test/states/$', 'views.test_states'),
|
||||
# Note: define accessible objects in DOJANGO_DATAGRID_ACCESS setting
|
||||
url(r'^datagrid-list/(?P<app_name>.+)/(?P<model_name>.+)/$', 'views.datagrid_list', name="dojango-datagrid-list"),
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
# serving the media files for dojango / dojo (js/css/...)
|
||||
urlpatterns += media.url_patterns
|
246
ntuh/dojango/util/__init__.py
Executable file
|
@ -0,0 +1,246 @@
|
|||
import os
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from dojango.conf import settings # using the app-specific settings
|
||||
from django.core.serializers.json import DateTimeAwareJSONEncoder
|
||||
from django.db.models import Model
|
||||
from django.db.models import ImageField, FileField
|
||||
from django.db.models.query import QuerySet
|
||||
from django.http import HttpResponse
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import simplejson as json
|
||||
from django.utils.functional import Promise
|
||||
|
||||
try:
|
||||
# we need it, if we want to serialize query- and model-objects
|
||||
# of google appengine within json_encode
|
||||
from google import appengine
|
||||
except:
|
||||
appengine = None
|
||||
|
||||
try:
|
||||
# this is just available since django version 1.0
|
||||
# google appengine does not provide this function yet!
|
||||
from django.utils.encoding import force_unicode
|
||||
except Exception:
|
||||
import types
|
||||
# code copied from django version 1.0
|
||||
class DjangoUnicodeDecodeError(UnicodeDecodeError):
|
||||
def __init__(self, obj, *args):
|
||||
self.obj = obj
|
||||
UnicodeDecodeError.__init__(self, *args)
|
||||
|
||||
def __str__(self):
|
||||
original = UnicodeDecodeError.__str__(self)
|
||||
return '%s. You passed in %r (%s)' % (original, self.obj,
|
||||
type(self.obj))
|
||||
|
||||
def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):
|
||||
"""
|
||||
Similar to smart_unicode, except that lazy instances are resolved to
|
||||
strings, rather than kept as lazy objects.
|
||||
|
||||
If strings_only is True, don't convert (some) non-string-like objects.
|
||||
"""
|
||||
if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.date, datetime.time, float)):
|
||||
return s
|
||||
try:
|
||||
if not isinstance(s, basestring,):
|
||||
if hasattr(s, '__unicode__'):
|
||||
s = unicode(s)
|
||||
else:
|
||||
try:
|
||||
s = unicode(str(s), encoding, errors)
|
||||
except UnicodeEncodeError:
|
||||
if not isinstance(s, Exception):
|
||||
raise
|
||||
# If we get to here, the caller has passed in an Exception
|
||||
# subclass populated with non-ASCII data without special
|
||||
# handling to display as a string. We need to handle this
|
||||
# without raising a further exception. We do an
|
||||
# approximation to what the Exception's standard str()
|
||||
# output should be.
|
||||
s = ' '.join([force_unicode(arg, encoding, strings_only,
|
||||
errors) for arg in s])
|
||||
elif not isinstance(s, unicode):
|
||||
# Note: We use .decode() here, instead of unicode(s, encoding,
|
||||
# errors), so that if s is a SafeString, it ends up being a
|
||||
# SafeUnicode at the end.
|
||||
s = s.decode(encoding, errors)
|
||||
except UnicodeDecodeError, e:
|
||||
raise DjangoUnicodeDecodeError(s, *e.args)
|
||||
return s
|
||||
|
||||
def extract_nodelist_options(nodelist, context=None):
|
||||
"""
|
||||
Returns a dict containing the key=value options listed within the nodelist.
|
||||
The value can be a python dict, list, tuple, or number/string literal.
|
||||
"""
|
||||
ret={}
|
||||
if context:
|
||||
rendered = nodelist.render(context)
|
||||
else:
|
||||
rendered = nodelist.render({})
|
||||
if rendered.find('=')>0:
|
||||
for key,val in [ opt.strip().split("=") for opt in rendered.split("\n") if opt.find('=')>-1 ]:
|
||||
ret[key.strip()]=eval(val.strip())
|
||||
return ret
|
||||
|
||||
def debug(request):
|
||||
"Returns context variables helpful for debugging."
|
||||
context_extras = {}
|
||||
if settings.DEBUG:
|
||||
context_extras['DEBUG'] = True
|
||||
return context_extras
|
||||
|
||||
|
||||
def json_encode(data):
|
||||
"""
|
||||
The main issues with django's default json serializer is that properties that
|
||||
had been added to an object dynamically are being ignored (and it also has
|
||||
problems with some models).
|
||||
"""
|
||||
|
||||
def _any(data):
|
||||
ret = None
|
||||
# Opps, we used to check if it is of type list, but that fails
|
||||
# i.e. in the case of django.newforms.utils.ErrorList, which extends
|
||||
# the type "list". Oh man, that was a dumb mistake!
|
||||
if isinstance(data, list):
|
||||
ret = _list(data)
|
||||
# Same as for lists above.
|
||||
elif appengine and isinstance(data, appengine.ext.db.Query):
|
||||
ret = _list(data)
|
||||
elif isinstance(data, dict):
|
||||
ret = _dict(data)
|
||||
elif isinstance(data, Decimal):
|
||||
# json.dumps() cant handle Decimal
|
||||
ret = str(data)
|
||||
elif isinstance(data, QuerySet):
|
||||
# Actually its the same as a list ...
|
||||
ret = _list(data)
|
||||
elif isinstance(data, Model):
|
||||
ret = _model(data)
|
||||
elif appengine and isinstance(data, appengine.ext.db.Model):
|
||||
ret = _googleModel(data)
|
||||
# here we need to encode the string as unicode (otherwise we get utf-16 in the json-response)
|
||||
elif isinstance(data, basestring):
|
||||
ret = unicode(data)
|
||||
# see http://code.djangoproject.com/ticket/5868
|
||||
elif isinstance(data, Promise):
|
||||
ret = force_unicode(data)
|
||||
elif isinstance(data, datetime.datetime):
|
||||
# For dojo.date.stamp we convert the dates to use 'T' as separator instead of space
|
||||
# i.e. 2008-01-01T10:10:10 instead of 2008-01-01 10:10:10
|
||||
ret = str(data).replace(' ', 'T')
|
||||
elif isinstance(data, datetime.date):
|
||||
ret = str(data)
|
||||
elif isinstance(data, datetime.time):
|
||||
ret = "T" + str(data)
|
||||
else:
|
||||
# always fallback to a string!
|
||||
ret = data
|
||||
return ret
|
||||
|
||||
def _model(data):
|
||||
ret = {}
|
||||
# If we only have a model, we only want to encode the fields.
|
||||
for f in data._meta.fields:
|
||||
# special FileField handling (they can't be json serialized)
|
||||
if isinstance(f, ImageField) or isinstance(f, FileField):
|
||||
ret[f.attname] = unicode(getattr(data, f.attname))
|
||||
else:
|
||||
ret[f.attname] = _any(getattr(data, f.attname))
|
||||
# And additionally encode arbitrary properties that had been added.
|
||||
fields = dir(data.__class__) + ret.keys()
|
||||
# ignoring _state and delete properties
|
||||
add_ons = [k for k in dir(data) if k not in fields and k not in ('delete', '_state',)]
|
||||
for k in add_ons:
|
||||
ret[k] = _any(getattr(data, k))
|
||||
return ret
|
||||
|
||||
def _googleModel(data):
|
||||
ret = {}
|
||||
ret['id'] = data.key().id()
|
||||
for f in data.fields():
|
||||
ret[f] = _any(getattr(data, f))
|
||||
return ret
|
||||
|
||||
def _list(data):
|
||||
ret = []
|
||||
for v in data:
|
||||
ret.append(_any(v))
|
||||
return ret
|
||||
|
||||
def _dict(data):
|
||||
ret = {}
|
||||
for k,v in data.items():
|
||||
ret[k] = _any(v)
|
||||
return ret
|
||||
|
||||
ret = _any(data)
|
||||
return json.dumps(ret, cls=DateTimeAwareJSONEncoder)
|
||||
|
||||
def json_decode(json_string):
|
||||
"""
|
||||
This function is just for convenience/completeness (because we have json_encode).
|
||||
Sometimes you want to convert a json-string to a python object.
|
||||
It throws a ValueError, if the JSON String is invalid.
|
||||
"""
|
||||
return json.loads(json_string)
|
||||
|
||||
def to_json_response(data, func_name=None, use_iframe=False):
|
||||
"""
|
||||
This functions creates a http response object. It mainly set the right
|
||||
headers for you.
|
||||
If you pass a func_name to it, it'll surround the json data with a function name.
|
||||
"""
|
||||
data = json_encode(data)
|
||||
# as of dojo version 1.2.0, prepending {}&&\n is the most secure way!!!
|
||||
# for dojo version < 1.2.0 you have to set DOJANGO_DOJO_SECURE_JSON = False
|
||||
# in your settings file!
|
||||
if settings.DOJO_SECURE_JSON:
|
||||
data = "{}&&\n" + data
|
||||
# don't wrap it into a function, if we use an iframe
|
||||
if func_name and not use_iframe:
|
||||
data = "%s(%s)" % (func_name, data)
|
||||
mimetype = "application/json"
|
||||
if use_iframe:
|
||||
mimetype = "text/html"
|
||||
data = render_to_string("dojango/json_iframe.html", {'json_data': data})
|
||||
ret = HttpResponse(data, mimetype=mimetype+"; charset=%s" % settings.DEFAULT_CHARSET)
|
||||
# The following are for IE especially
|
||||
ret['Pragma'] = "no-cache"
|
||||
ret['Cache-Control'] = "must-revalidate"
|
||||
ret['If-Modified-Since'] = str(datetime.datetime.now())
|
||||
return ret
|
||||
|
||||
def to_dojo_data(items, identifier='id', num_rows=None):
|
||||
"""Return the data as the dojo.data API defines.
|
||||
The dojo.data API expects the data like so:
|
||||
{identifier:"whatever",
|
||||
items: [
|
||||
{name:"Lenin", id:3, ....},
|
||||
]
|
||||
}
|
||||
The identifier is optional.
|
||||
"""
|
||||
ret = {'items':items}
|
||||
if identifier:
|
||||
ret['identifier'] = identifier
|
||||
if num_rows:
|
||||
ret['numRows'] = num_rows
|
||||
return ret
|
||||
|
||||
def is_number(s):
|
||||
"""Is this the right way for checking a number.
|
||||
Maybe there is a better pythonic way :-)"""
|
||||
try:
|
||||
int(s)
|
||||
return True
|
||||
except TypeError:
|
||||
pass
|
||||
except ValueError:
|
||||
pass
|
||||
return False
|
141
ntuh/dojango/util/config.py
Executable file
|
@ -0,0 +1,141 @@
|
|||
from dojango.conf import settings # using the app-specific settings
|
||||
from dojango.util import dojo_collector
|
||||
from dojango.util import media
|
||||
|
||||
class Config:
|
||||
|
||||
profile = None
|
||||
version = None
|
||||
config = None
|
||||
dojo_base_url = None
|
||||
|
||||
def __init__(self, profile=settings.DOJO_PROFILE, version=settings.DOJO_VERSION):
|
||||
self.profile = profile
|
||||
self.version = version
|
||||
self.config = self._get_config()
|
||||
self.dojo_base_url = self._get_dojo_url()
|
||||
|
||||
def _get_config(self):
|
||||
'''Getting a config dictionary using the giving profile. See the profile list in conf/settings.py'''
|
||||
try:
|
||||
config = settings.DOJO_PROFILES[self.profile]
|
||||
return config
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def _get_dojo_url(self):
|
||||
'''Getting the dojo-base-path dependend on the profile and the version'''
|
||||
# the configuration of this profile (e.g. use crossdomain, uncompressed version, ....)
|
||||
# if no version is set you are free to use your own
|
||||
if self.config == None or not self.version in self.config.get('versions', (self.version)):
|
||||
return None
|
||||
# so we simply append our own profile without importing the dojango settings.py to the project's settings.py
|
||||
config_base_url = self.config.get('base_url', '') % \
|
||||
{'BASE_MEDIA_URL':settings.BASE_MEDIA_URL,
|
||||
'BUILD_MEDIA_URL':settings.BUILD_MEDIA_URL}
|
||||
# and putting the complete url together
|
||||
return "%(base)s/%(version)s" % {"base":config_base_url,
|
||||
"version":self.version}
|
||||
|
||||
def get_context_dict(self):
|
||||
ret = {}
|
||||
# all constants must be uppercase
|
||||
for key in self.config:
|
||||
ret[key.upper()] = self.config[key]
|
||||
ret['IS_LOCAL_BUILD'] = self.config.get("is_local_build", False)
|
||||
ret['IS_LOCAL'] = self.config.get("is_local", False)
|
||||
ret['UNCOMPRESSED'] = self.config.get("uncompressed", False)
|
||||
ret['USE_GFX'] = self.config.get("use_gfx", False)
|
||||
ret['VERSION'] = self.version
|
||||
# preparing all dojo related urls here
|
||||
ret['THEME_CSS_URL'] = self.theme_css_url()
|
||||
ret['THEME'] = settings.DOJO_THEME
|
||||
ret['BASE_MEDIA_URL'] = settings.BASE_MEDIA_URL
|
||||
ret['DOJO_BASE_PATH'] = '%s/dojo/' % self.dojo_base_path()
|
||||
ret['DOJO_URL'] = self.dojo_url()
|
||||
ret['DIJIT_URL'] = self.dijit_url()
|
||||
ret['DOJOX_URL'] = self.dojox_url()
|
||||
ret['DOJO_SRC_FILE'] = self.dojo_src_file()
|
||||
ret['DOJANGO_SRC_FILE'] = self.dojango_src_file()
|
||||
ret['DEBUG'] = settings.DOJO_DEBUG
|
||||
ret['COLLECTOR'] = dojo_collector.get_modules()
|
||||
ret['CDN_USE_SSL'] = settings.CDN_USE_SSL
|
||||
# adding all installed dojo-media namespaces
|
||||
ret.update(self.dojo_media_urls())
|
||||
return ret
|
||||
|
||||
def dojo_src_file(self):
|
||||
'''Get the main dojo javascript file
|
||||
Look in conf/settings.py for available profiles.'''
|
||||
# set some special cases concerning the configuration
|
||||
uncompressed_str = ""
|
||||
gfx_str = ""
|
||||
xd_str = ""
|
||||
if self.config.get('uncompressed', False):
|
||||
uncompressed_str = ".uncompressed.js"
|
||||
if self.config.get('use_gfx', False):
|
||||
gfx_str = "gfx-"
|
||||
if self.config.get('use_xd', False):
|
||||
xd_str = ".xd"
|
||||
return "%(path)s/dojo/%(gfx)sdojo%(xd)s.js%(uncompressed)s" % {"path":self.dojo_base_url,
|
||||
"xd": xd_str,
|
||||
"gfx": gfx_str,
|
||||
"uncompressed": uncompressed_str}
|
||||
|
||||
def dojango_src_file(self):
|
||||
'''Getting the main javascript profile file url of this awesome app :-)
|
||||
You need to include this within your html to achieve the advantages
|
||||
of this app.
|
||||
TODO: Listing some advantages!
|
||||
'''
|
||||
return "%s/dojango.js" % self.dojango_url()
|
||||
|
||||
def dojo_media_urls(self):
|
||||
'''Getting dict of 'DOJONAMESPACE_URL's for each installed dojo ns in app/dojo-media'''
|
||||
ret = {}
|
||||
for app in media.dojo_media_library:
|
||||
if media.dojo_media_library[app]:
|
||||
for dojo_media in media.dojo_media_library[app]:
|
||||
ret["%s_URL" % dojo_media[1].upper()] = '%s/%s' % (self.dojo_base_path(), dojo_media[1])
|
||||
return ret
|
||||
|
||||
def dojango_url(self):
|
||||
return '%s/%s/dojango' % (settings.BASE_MEDIA_URL, self.version)
|
||||
|
||||
def dojo_url(self):
|
||||
'''Like the "dojango_dojo_src_file" templatetag, but just returning the base path
|
||||
of dojo.'''
|
||||
return "%s/dojo" % self.dojo_base_url
|
||||
|
||||
def dijit_url(self):
|
||||
'''Like the "dojango_dojo_src_file" templatetag, but just returning the base path
|
||||
of dijit.'''
|
||||
return "%s/dijit" % self.dojo_base_url
|
||||
|
||||
def dojox_url(self):
|
||||
'''Like the "dojango_dojo_src_file" templatetag, but just returning the base path
|
||||
of dojox.'''
|
||||
return "%s/dojox" % self.dojo_base_url
|
||||
|
||||
def dojo_base_path(self):
|
||||
'''djConfig.baseUrl can be used to mix an external xd-build with some local dojo modules.
|
||||
If we use an external build it must be '/' and for a local version, we just have to set the
|
||||
path to the dojo path.
|
||||
Use it within djConfig.baseUrl by appending "dojo/".
|
||||
'''
|
||||
base_path = '%s/%s' % (settings.BASE_MEDIA_URL, self.version)
|
||||
if self.config.get('is_local', False):
|
||||
base_path = self.dojo_base_url
|
||||
return base_path
|
||||
|
||||
def theme_css_url(self):
|
||||
'''Like the "dojango_dojo_src_file" templatetag, but returning the theme css path. It uses the
|
||||
DOJO_THEME and DOJO_THEME_PATH settings to determine the right path.'''
|
||||
if settings.DOJO_THEME_URL:
|
||||
# if an own them is used, the theme must be located in themename/themename.css
|
||||
return settings.DOJO_THEME_URL + "/%s/%s.css" % (settings.DOJO_THEME, settings.DOJO_THEME)
|
||||
return "%s/dijit/themes/%s/%s.css" % (self.dojo_base_url, settings.DOJO_THEME, settings.DOJO_THEME)
|
||||
|
||||
def theme(self):
|
||||
return settings.DOJO_THEME
|
51
ntuh/dojango/util/dojo_collector.py
Executable file
|
@ -0,0 +1,51 @@
|
|||
from threading import local
|
||||
|
||||
__all__ = ['activate', 'deactivate', 'get_collector', 'add_module']
|
||||
|
||||
_active = local()
|
||||
|
||||
def activate():
|
||||
"""
|
||||
Activates a global accessible object, where we can save information about
|
||||
required dojo modules.
|
||||
"""
|
||||
class Collector:
|
||||
used_dojo_modules = []
|
||||
|
||||
def add(self, module):
|
||||
# just add a module once!
|
||||
if not module in self.used_dojo_modules:
|
||||
self.used_dojo_modules.append(module)
|
||||
|
||||
_active.value = Collector()
|
||||
|
||||
def deactivate():
|
||||
"""
|
||||
Resets the currently active global object
|
||||
"""
|
||||
if hasattr(_active, "value"):
|
||||
del _active.value
|
||||
|
||||
def get_collector():
|
||||
"""Returns the currently active collector object."""
|
||||
t = getattr(_active, "value", None)
|
||||
if t is not None:
|
||||
try:
|
||||
return t
|
||||
except AttributeError:
|
||||
return None
|
||||
return None
|
||||
|
||||
def get_modules():
|
||||
collector = get_collector()
|
||||
if collector is not None:
|
||||
return collector.used_dojo_modules
|
||||
return []
|
||||
|
||||
def add_module(module):
|
||||
collector = get_collector()
|
||||
if collector is not None:
|
||||
collector.add(module)
|
||||
# otherwise do nothing
|
||||
pass
|
||||
|