Compare commits

...

2 commits

Author SHA1 Message Date
c1acd3de87 init commit 2024-12-12 10:19:16 +08:00
c83cadbb5d init commit 2024-12-12 10:19:03 +08:00
970 changed files with 186048 additions and 0 deletions

10
.gitignore vendored
View file

@ -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
View 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
View 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

File diff suppressed because one or more lines are too long

758
forteo/analysis.ipynb Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
forteo/cAR3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
forteo/drug.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

1
forteo/ntuhgov Symbolic link
View file

@ -0,0 +1 @@
../ntuh/submodule/ntuhgov/

50
forteo/scrape.py Executable file
View 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)

View 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
View 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
View 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>

View 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
View file

1
ntuh/README.txt Executable file
View file

@ -0,0 +1 @@
1234

0
ntuh/__init__.py Executable file
View file

14
ntuh/apache/django.wsgi Executable file
View 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
View 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
View 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
View 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
View file

25
ntuh/ck/admin.py Executable file
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

View file

View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

62
ntuh/ck/vs.csv Executable file
View 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,鄭乃禎
1 CS 徐紹勛
2 CS 李元麒
3 CS 李章銘
4 CS 陳晉興
5 CS 黃培銘
6 CVS 吳毅暉
7 CVS 周迺寬
8 CVS 張重義
9 CVS 王植賢
10 CVS 王水深
11 CVS 紀乃新
12 CVS 虞希禹
13 CVS 許榮彬
14 CVS 邱英世
15 CVS 陳益祥
16 CVS 黃書健
17 GS 何承懋
18 GS 何明志
19 GS 吳耀銘
20 GS 張金堅
21 GS 李伯皇
22 GS 林明燦
23 GS 林本仁
24 GS 梁金銅
25 GS 楊卿堯
26 GS 王明暘
27 GS 田郁文
28 GS 胡瑞恆
29 GS 蔡孟昆
30 GS 袁瑞晃
31 GS 賴逸儒
32 GS 郭文宏
33 GS 陳坤源
34 GS 陳炯年
35 GS 黃俊升
36 GS 黃凱文
37 GS 黃約翰
38 ICU 柯文哲
39 NS 曾勝弘
40 NS 曾漢民
41 NS 杜永光
42 NS 楊士弘
43 NS 王國川
44 NS 蔡瑞章
45 NS 蕭輔仁
46 NS 賴達明
47 NS 郭夢菲
48 NS 陳敞牧
49 NS 黃勝堅
50 PED 林文熙
51 PED 蔡明憲
52 PED 許文明
53 PED 賴鴻緒
54 PS 戴浩志
55 PS 楊永健
56 PS 洪學義
57 PS 湯月碧
58 PS 簡雄飛
59 PS 謝孟祥
60 PS 謝榮賢
61 PS 郭源松
62 PS 鄭乃禎

62
ntuh/ck/vs0.csv Executable file
View 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><>
1 GS ���
2 PED 苦翬狐
3 GS 癒风�
4 GS 独玊ど
5 GS 狶�篱
6 PS 傣る貉
7 ICU 琠ゅ�
8 CS �じ腝
9 GS 璊风�
10 NS �ッ�
11 PED 狶ゅ撼
12 CS 畗残吃
13 NS 朝疮�
14 PS 尝方猀
15 GS 朝�方
16 GS ��в
17 CVS ㏄癷糴
18 PS 虏动�
19 GS バ�ゅ
20 NS 苦笷�
21 CVS �璣�
22 GS 辩�簧
23 CVS 朝痲不
24 PS 瑇厩竡
25 GS 朝��
26 PS 拦疎в
27 GS 尝ゅЩ
28 CVS 砛篴眑
29 CS �彻皇
30 PED 讲�舅
31 NS �瓣�
32 PS 綠�赫
33 NS 独秤绊
34 CS 朝�砍
35 GS �模皇
36 PS 法ッ胺
37 NS 纯簙チ
38 NS 纯秤グ
39 CVS ��瞏
40 CVS 眎�竡
41 PS 谅﹕不
42 GS 苦秇晶
43 GS 讲﹕�
44 CVS 阜��
45 PED 砛ゅ�
46 CS 独蚌皇
47 CVS 独�胺
48 GS 法�丑
49 NS 讲风彻
50 NS 尝冠滇
51 GS 狶セく
52 NS 法�グ
53 PS 谅篴藉
54 NS 拷徊く
55 GS �┯婪
56 CVS �从藉
57 CVS �驾穟
58 GS 眎�绊
59 GS 独�揩
60 GS 独惩ゅ
61 GS ��穤
62 CVS ��穝

14
ntuh/context_processors.py Executable file
View 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
View 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
View 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
View 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
View 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
View 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.

View file

View 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()

View 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
View 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
View file

97
ntuh/dojango/conf/settings.py Executable file
View 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)

View 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
View 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

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

View 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
"""

View 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()

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

View 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

View 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

View 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()

View 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

View file

View 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
View 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

View 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
]
}

View file

@ -0,0 +1,5 @@
dojo.provide("dojango._base");
dojo.mixin(dojango, {
// This where we can mixin functions that are directly accessbile via dojango.*
});

View 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

View 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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 B

View file

@ -0,0 +1 @@
<html><head><script>isLoaded = true;</script></head><body></body></html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

View 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
}
);

View 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"});
}
});

View file

@ -0,0 +1,5 @@
.imgSelected {
opacity:0.5;
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
filter: alpha(opacity=50);
}

View 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
]
}

View 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
View 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
View 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
View 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
View 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
View 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

View file

View file

View 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()

View 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
View 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
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View 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>

View 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 %}

View 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 %}

View 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 %}

View file

@ -0,0 +1,7 @@
<html>
<head>
</head>
<body>
<textarea>{{json_data}}</textarea>
</body>
</html>

View 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" >&nbsp;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>

View 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 &amp; 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 &lt;button&gt;).</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 &lt;h3&gt;</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 %}

View file

View 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

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

View 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
View 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
View 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
View 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

View 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

Some files were not shown because too many files have changed in this diff Show more