diff --git a/.gitignore b/.gitignore index ab3e8ce..481a697 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README b/README new file mode 100644 index 0000000..83b0c47 --- /dev/null +++ b/README @@ -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 + diff --git a/forteo/Untitled.ipynb b/forteo/Untitled.ipynb new file mode 100644 index 0000000..41dab2e --- /dev/null +++ b/forteo/Untitled.ipynb @@ -0,0 +1,435 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 17, + "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": 2, + "metadata": {}, + "outputs": [], + "source": [ + "SHEETS = (\n", + " (\"神外-總院_1070920.xlsx\", \"總院\"), \n", + "# (\"(骨穩)總院_分科1070928.xlsx\", \"工作表1\")\n", + " (\"(骨穩)總院_分科1071002.xlsx\", \"工作表1\")\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "client = MongoClient(\"mongodb.xiao.tw\", 27017)\n", + "db = client.forteo\n", + "posts = db.posts" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib.font_manager import FontManager\n", + "\n", + "import subprocess\n", + "\n", + "def get_matplot_zh_font():\n", + " fm = FontManager()\n", + " mat_fonts = set(f.name for f in fm.ttflist)\n", + "\n", + " output = subprocess.check_output('fc-list :lang=zh -f \"%{family}\\n\"', shell=True)\n", + " output=str(output)\n", + " zh_fonts = set(f.split(',', 1)[0] for f in output.split('\\n'))\n", + "\n", + " print(mat_fonts)\n", + " print(zh_fonts)\n", + " available = list(mat_fonts & zh_fonts)\n", + "\n", + " print ('*' * 10, '可用的字體', '*' * 10)\n", + " for f in available:\n", + " print(f)\n", + " return available\n", + "\n", + "def set_matplot_zh_font():\n", + " available = get_matplot_zh_font()\n", + " if len(available) > 0:\n", + " mpl.rcParams['font.sans-serif'] = [available[0]] # 指定默認字體\n", + " mpl.rcParams['axes.unicode_minus'] = False # 解決保存圖像是負號'-'顯示為方塊的問題\n", + " \n", + "# set_matplot_zh_font()\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "frames = []" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "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": 7, + "metadata": {}, + "outputs": [], + "source": [ + "df=pd.concat(frames, ignore_index=True, sort=False)\n", + "df.to_excel('concat.xls')" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "8.680568609022558\n", + "6.696428571428571 0.14285714285714285 24.0\n" + ] + } + ], + "source": [ + "print(df['用藥時間'].mean())\n", + "print(df['用藥時間'].median(), df['用藥時間'].min(), df['用藥時間'].max())" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [], + "source": [ + "antiresorptives = {\n", + " 'Prolia': 'RANKL inhibitor',\n", + " 'Evista': 'SERM',\n", + " 'Fosamax': 'Bisphosphonate',\n", + " 'Aclasta': 'Bisphosphonate',\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "countDrug = Counter()\n", + "\n", + "column_ar = []\n", + "\n", + "for index, row in df.iterrows():\n", + " tmp_ar = None\n", + " post = posts.find_one({\"_id\": row['病歷號']})\n", + " if post is None:\n", + " continue\n", + " print(index, row['病歷號'], post)\n", + "\n", + "# print(index, row['病歷號'])\n", + " drug_set = set()\n", + " if post['drug']:\n", + " \n", + " for drugs in post['drug']:\n", + "# print(drugs)\n", + " for tr in drugs['Drug']:\n", + " pqtr = pq(tr)\n", + " drugname = pqtr('td')[0].text\n", + " drugname = drugname.replace('(管4) ', '')\n", + " drug_set.add(drugname)\n", + "# countDrug[pqtr('td')[0].text] += 1\n", + "# print(pqtr('td')[0].text)\n", + "# break\n", + "# print(drug_set)\n", + " if tmp_ar is None:\n", + " for ar in antiresorptives:\n", + " if drugname.startswith(ar):\n", + " tmp_ar = ar\n", + "\n", + " for d in drug_set:\n", + " countDrug[d] += 1\n", + " \n", + " column_ar.append(tmp_ar)\n", + "# break\n", + "# print(countDrug)\n", + "# row['AR'] = column_ar\n", + " df.loc[index, 'AR'] = tmp_ar\n", + " if tmp_ar:\n", + " df.loc[index, 'AR2'] = antiresorptives[tmp_ar]\n", + " df.loc[index, 'antiresorptives'] = tmp_ar if tmp_ar == 'Prolia' else ' Others'\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "cDrug = countDrug\n", + "del cDrug['Forteo Prefilled Inj 600 mcg/2.4 mL /pen']\n", + "del cDrug['(4mm) Micro-Fine Insulin Pen Needle 32G /set']\n", + "del cDrug['(8mm) Micro-Fine Insulin Pen Needle 31G /set']\n", + "labels, values = zip(*countDrug.most_common(10))\n", + "# plt.rcParams['font.sans-serif']=['Arial Unicode MS'] #用来正常显示中文标签\n", + "plt.pie(values, labels=labels)\n", + "plt.savefig('drug.png', dpi=400)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "antiresorptives = {\n", + " 'Prolia': 'RANKL inhibitor',\n", + " 'Evista': 'SERM',\n", + " 'Fosamax': 'Bisphosphonate',\n", + " 'Aclasta': 'Bisphosphonate',\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Counter({'Prolia': 30, 'Evista': 10, 'Fosamax': 6, 'Aclasta': 5})\n", + "Counter({'RANKL inhibitor': 30, 'Bisphosphonate': 11, 'SERM': 10})\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "cAR = Counter()\n", + "ccAR = Counter()\n", + "\n", + "for c in countDrug:\n", + " for ar in antiresorptives:\n", + " if c.startswith(ar):\n", + " cAR[ar]+=countDrug[c]\n", + " ccAR[antiresorptives[ar]]+=countDrug[c]\n", + " \n", + "print(cAR)\n", + "print(ccAR)\n", + "labels, values = zip(*cAR.most_common())\n", + "plt.pie(values, labels=labels, autopct='%.2f')\n", + "plt.savefig('cAR.png', dpi=400)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [], + "source": [ + "PATTERNS = (\n", + " 'T-\\s*score\\s*=\\s*([-+]?\\d*\\.\\d+)',\n", + " 'T-\\s*score\\s*=\\s*(- \\d*\\.\\d+)',\n", + " 'T-score is\\s*([-+]?\\d*\\.\\d+)',\n", + " 'T score =\\s*([-+]?\\d*\\.\\d+)',\n", + ")\n", + "def T_score(html):\n", + " min = None\n", + " for pattern in PATTERNS:\n", + " for m in re.findall(pattern, html):\n", + "# print(m)\n", + " t = float(m.replace(' ', ''))\n", + "# print (t)\n", + " if min and t > min:\n", + " continue\n", + " min = t \n", + " return min \n" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "PRE = []\n", + "POST =[]\n", + "\n", + "for index, row in df.iterrows():\n", + " post = posts.find_one({\"_id\": row['病歷號']})\n", + " if post is None:\n", + " continue\n", + " print(index, row['病歷號'], post)\n", + "\n", + "# print(index, row['病歷號'])\n", + "# print(post)\n", + " if post['report']:\n", + " BMD = {}\n", + " for r in post['report']:\n", + "# print(r)\n", + "\n", + " r_class = r['報告類別'].upper()\n", + " r_date = r['檢查日期']\n", + " if 'BMD' in r_class:\n", + "# print(r_date, r_class)\n", + "# print(r)\n", + "# print(row['病歷號'], r_date, r_class)\n", + " t_score = T_score(r['html'])\n", + " if t_score is None:\n", + "# # No T score in report\n", + "# print(row['病歷號'], r_date, t_score, r_class)\n", + " continue\n", + " \n", + " if r_date not in BMD or t_score < BMD[r_date]:\n", + " BMD[r_date] = t_score\n", + " \n", + "# print(row['簽署日'], row['流失/停藥日期'])\n", + "# print(type(row['簽署日']), type(row['流失/停藥日期']))\n", + " \n", + " if type(row['簽署日']) is not str and math.isnan(row['簽署日']):\n", + " dStart = row['流失/停藥日期']\n", + " else:\n", + " dStart = row['簽署日']\n", + " if BMD: \n", + "# print(row['病歷號'], dStart, BMD)\n", + " \n", + " pre = None\n", + " post = None\n", + " \n", + " for d, b in BMD.items():\n", + " if d > dStart:\n", + " post = b\n", + " else:\n", + " pre = b\n", + " \n", + " if pre and post:\n", + " PRE.append(pre)\n", + " POST.append(post)\n", + "# print(row['病歷號'], dStart, pre, post)\n", + " df.loc[index, 'BMD Change'] = post-pre\n", + "\n", + "df.to_excel('concat3.xls')\n", + " \n", + "D=df.boxplot(column='BMD Change',by='antiresorptives') \n", + "D.get_figure().savefig('cAR3.png', dpi=400)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "d = {\n", + " 'bmd_before': PRE, \n", + " 'bmd_after': POST,\n", + "}\n", + "\n", + "df2 = pd.DataFrame(data=d)\n", + "df2.describe() \n", + "df2.plot(kind='box') \n", + "# plt.savefig('boxplot_outliers.png')\n", + " \n", + "# print(stats.ttest_rel(df2['bmd_before'], df2['bmd_after']))\n" + ] + }, + { + "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 +} diff --git a/forteo/analysis.ipynb b/forteo/analysis.ipynb new file mode 100644 index 0000000..bf440b8 --- /dev/null +++ b/forteo/analysis.ipynb @@ -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": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
醫院醫師系統編號病患姓名簽署日key-in日患者出生年月日患者狀況流失/停藥日期用藥時間病患是否參加P1NP
是否自費
健保給付808080808080808043800
自費2712712712712712712712711872711
\n", + "
" + ], + "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": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
用藥時間系統編號
countmeanstdmin25%50%75%maxcountmeanstdmin25%50%75%max
是否自費
健保給付80.012.3348217.3265340.1428576.45535712.39285718.65178624.080.040738.8750003719.02218135494.037584.040289.544118.7547796.0
自費271.07.4500536.2004200.2142862.7321435.64285710.16071424.0271.041558.9372693793.11138735258.038007.041723.044780.0047792.0
\n", + "
" + ], + "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": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
醫院醫師系統編號病患姓名簽署日key-in日患者出生年月日流失/停藥日期是否自費用藥時間病患是否參加P1NP
患者狀況
10-成功問卷111111111111111111111181111110
11-成功問卷-次回拒訪11111110110
12-成功問卷-已停藥2102102102102102102102102102101
20-電話錯誤/無此人11111110110
24-拒訪88888880880
26-往生66666660660
2B-五次聯絡不到11111110110
2D-暫時停藥-觀察中33333331330
2E-暫時停藥-住院中11111110110
2G-連續三個月連絡不到66666660660
31-無人接聽11111110110
33-語音信箱/答錄機22222221220
\n", + "
" + ], + "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": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
用藥時間系統編號
countmeanstdmin25%50%75%maxcountmeanstdmin25%50%75%max
是否自費
健保給付36.013.1121036.8406380.7500007.67857116.19642918.65178620.85714336.038853.5555563038.40964535494.037015.7537777.040363.2547017.0
自費174.07.1826775.4941010.6785712.7589295.6785719.70535723.714286174.040316.9540233181.14521635335.037448.5040391.542677.7546896.0
\n", + "
" + ], + "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 +} diff --git a/forteo/cAR.png b/forteo/cAR.png new file mode 100644 index 0000000..3f6b0fe Binary files /dev/null and b/forteo/cAR.png differ diff --git a/forteo/cAR3.png b/forteo/cAR3.png new file mode 100644 index 0000000..5ed42b7 Binary files /dev/null and b/forteo/cAR3.png differ diff --git a/forteo/drug.png b/forteo/drug.png new file mode 100644 index 0000000..daf6f08 Binary files /dev/null and b/forteo/drug.png differ diff --git a/forteo/ntuhgov b/forteo/ntuhgov new file mode 120000 index 0000000..7c8e505 --- /dev/null +++ b/forteo/ntuhgov @@ -0,0 +1 @@ +../ntuh/submodule/ntuhgov/ \ No newline at end of file diff --git a/forteo/scrape.py b/forteo/scrape.py new file mode 100755 index 0000000..6062cda --- /dev/null +++ b/forteo/scrape.py @@ -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) + diff --git a/forteo/split_ttc_font_to_ttf.py b/forteo/split_ttc_font_to_ttf.py new file mode 100644 index 0000000..e3e6488 --- /dev/null +++ b/forteo/split_ttc_font_to_ttf.py @@ -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() \ No newline at end of file diff --git a/ntuh/.project b/ntuh/.project new file mode 100755 index 0000000..6f0c31d --- /dev/null +++ b/ntuh/.project @@ -0,0 +1,18 @@ + + + ntuh + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + org.python.pydev.django.djangoNature + + diff --git a/ntuh/.pydevproject b/ntuh/.pydevproject new file mode 100755 index 0000000..d4fefba --- /dev/null +++ b/ntuh/.pydevproject @@ -0,0 +1,7 @@ + + + + +/home/xfr/myenv/bin/python +python 2.7 + diff --git a/ntuh/.settings/org.eclipse.core.resources.prefs b/ntuh/.settings/org.eclipse.core.resources.prefs new file mode 100755 index 0000000..26c6096 --- /dev/null +++ b/ntuh/.settings/org.eclipse.core.resources.prefs @@ -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 diff --git a/ntuh/FILE b/ntuh/FILE new file mode 100644 index 0000000..e69de29 diff --git a/ntuh/README.txt b/ntuh/README.txt new file mode 100755 index 0000000..81c545e --- /dev/null +++ b/ntuh/README.txt @@ -0,0 +1 @@ +1234 diff --git a/ntuh/__init__.py b/ntuh/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/ntuh/apache/django.wsgi b/ntuh/apache/django.wsgi new file mode 100755 index 0000000..184fb3f --- /dev/null +++ b/ntuh/apache/django.wsgi @@ -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) diff --git a/ntuh/assistant.py b/ntuh/assistant.py new file mode 100755 index 0000000..068e3b0 --- /dev/null +++ b/ntuh/assistant.py @@ -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() diff --git a/ntuh/categories.txt b/ntuh/categories.txt new file mode 100755 index 0000000..dc2bc8b --- /dev/null +++ b/ntuh/categories.txt @@ -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 diff --git a/ntuh/ck/0.py b/ntuh/ck/0.py new file mode 100755 index 0000000..1f251bc --- /dev/null +++ b/ntuh/ck/0.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +from intra import * + +print HeightWeight('D120264406') +#print ReportResult('D120264406') diff --git a/ntuh/ck/__init__.py b/ntuh/ck/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/ntuh/ck/admin.py b/ntuh/ck/admin.py new file mode 100755 index 0000000..948987f --- /dev/null +++ b/ntuh/ck/admin.py @@ -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) + diff --git a/ntuh/ck/forms.py b/ntuh/ck/forms.py new file mode 100755 index 0000000..372a913 --- /dev/null +++ b/ntuh/ck/forms.py @@ -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 diff --git a/ntuh/ck/models.py b/ntuh/ck/models.py new file mode 100755 index 0000000..fc0bda2 --- /dev/null +++ b/ntuh/ck/models.py @@ -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='行為') diff --git a/ntuh/ck/selenium.py b/ntuh/ck/selenium.py new file mode 100755 index 0000000..66fc455 --- /dev/null +++ b/ntuh/ck/selenium.py @@ -0,0 +1,2071 @@ + +""" +Copyright 2006 ThoughtWorks, 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. +""" +__docformat__ = "restructuredtext en" + +# This file has been automatically generated via XSL + +import httplib +import urllib +import re + +class selenium: + """ + Defines an object that runs Selenium commands. + + Element Locators + ~~~~~~~~~~~~~~~~ + + Element Locators tell Selenium which HTML element a command refers to. + The format of a locator is: + + \ *locatorType*\ **=**\ \ *argument* + + + We support the following strategies for locating elements: + + + * \ **identifier**\ =\ *id*: + Select the element with the specified @id attribute. If no match is + found, select the first element whose @name attribute is \ *id*. + (This is normally the default; see below.) + * \ **id**\ =\ *id*: + Select the element with the specified @id attribute. + * \ **name**\ =\ *name*: + Select the first element with the specified @name attribute. + + * username + * name=username + + + The name may optionally be followed by one or more \ *element-filters*, separated from the name by whitespace. If the \ *filterType* is not specified, \ **value**\ is assumed. + + * name=flavour value=chocolate + + + * \ **dom**\ =\ *javascriptExpression*: + + Find an element by evaluating the specified string. This allows you to traverse the HTML Document Object + Model using JavaScript. Note that you must not return a value in this string; simply make it the last expression in the block. + + * dom=document.forms['myForm'].myDropdown + * dom=document.images[56] + * dom=function foo() { return document.links[1]; }; foo(); + + + * \ **xpath**\ =\ *xpathExpression*: + Locate an element using an XPath expression. + + * xpath=//img[@alt='The image alt text'] + * xpath=//table[@id='table1']//tr[4]/td[2] + * xpath=//a[contains(@href,'#id1')] + * xpath=//a[contains(@href,'#id1')]/@class + * xpath=(//table[@class='stylee'])//th[text()='theHeaderText']/../td + * xpath=//input[@name='name2' and @value='yes'] + * xpath=//\*[text()="right"] + + + * \ **link**\ =\ *textPattern*: + Select the link (anchor) element which contains text matching the + specified \ *pattern*. + + * link=The link text + + + * \ **css**\ =\ *cssSelectorSyntax*: + Select the element using css selectors. Please refer to CSS2 selectors, CSS3 selectors for more information. You can also check the TestCssLocators test in the selenium test suite for an example of usage, which is included in the downloaded selenium core package. + + * css=a[href="#id3"] + * css=span#firstChild + span + + + Currently the css selector locator supports all css1, css2 and css3 selectors except namespace in css3, some pseudo classes(:nth-of-type, :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type, :visited, :hover, :active, :focus, :indeterminate) and pseudo elements(::first-line, ::first-letter, ::selection, ::before, ::after). + + * \ **ui**\ =\ *uiSpecifierString*: + Locate an element by resolving the UI specifier string to another locator, and evaluating it. See the Selenium UI-Element Reference for more details. + + * ui=loginPages::loginButton() + * ui=settingsPages::toggle(label=Hide Email) + * ui=forumPages::postBody(index=2)//a[2] + + + + + + Without an explicit locator prefix, Selenium uses the following default + strategies: + + + * \ **dom**\ , for locators starting with "document." + * \ **xpath**\ , for locators starting with "//" + * \ **identifier**\ , otherwise + + Element Filters + ~~~~~~~~~~~~~~~ + + Element filters can be used with a locator to refine a list of candidate elements. They are currently used only in the 'name' element-locator. + + Filters look much like locators, ie. + + \ *filterType*\ **=**\ \ *argument* + + Supported element-filters are: + + \ **value=**\ \ *valuePattern* + + + Matches elements based on their values. This is particularly useful for refining a list of similarly-named toggle-buttons. + + \ **index=**\ \ *index* + + + Selects a single element based on its position in the list (offset from zero). + + String-match Patterns + ~~~~~~~~~~~~~~~~~~~~~ + + Various Pattern syntaxes are available for matching string values: + + + * \ **glob:**\ \ *pattern*: + Match a string against a "glob" (aka "wildmat") pattern. "Glob" is a + kind of limited regular-expression syntax typically used in command-line + shells. In a glob pattern, "\*" represents any sequence of characters, and "?" + represents any single character. Glob patterns match against the entire + string. + * \ **regexp:**\ \ *regexp*: + Match a string using a regular-expression. The full power of JavaScript + regular-expressions is available. + * \ **regexpi:**\ \ *regexpi*: + Match a string using a case-insensitive regular-expression. + * \ **exact:**\ \ *string*: + + Match a string exactly, verbatim, without any of that fancy wildcard + stuff. + + + + If no pattern prefix is specified, Selenium assumes that it's a "glob" + pattern. + + + + For commands that return multiple values (such as verifySelectOptions), + the string being matched is a comma-separated list of the return values, + where both commas and backslashes in the values are backslash-escaped. + When providing a pattern, the optional matching syntax (i.e. glob, + regexp, etc.) is specified once, as usual, at the beginning of the + pattern. + + + """ + +### This part is hard-coded in the XSL + def __init__(self, host, port, browserStartCommand, browserURL): + self.host = host + self.port = port + self.browserStartCommand = browserStartCommand + self.browserURL = browserURL + self.sessionId = None + self.extensionJs = "" + + def setExtensionJs(self, extensionJs): + self.extensionJs = extensionJs + + def start(self): + result = self.get_string("getNewBrowserSession", [self.browserStartCommand, self.browserURL, self.extensionJs]) + try: + self.sessionId = result + except ValueError: + raise Exception, result + + def stop(self): + self.do_command("testComplete", []) + self.sessionId = None + + def do_command(self, verb, args): + conn = httplib.HTTPConnection(self.host, self.port) + body = u'cmd=' + urllib.quote_plus(unicode(verb).encode('utf-8')) + for i in range(len(args)): + body += '&' + unicode(i+1) + '=' + urllib.quote_plus(unicode(args[i]).encode('utf-8')) + if (None != self.sessionId): + body += "&sessionId=" + unicode(self.sessionId) + headers = {"Content-Type": "application/x-www-form-urlencoded; charset=utf-8"} + conn.request("POST", "/selenium-server/driver/", body, headers) + + response = conn.getresponse() + #print response.status, response.reason + data = unicode(response.read(), "UTF-8") + result = response.reason + #print "Selenium Result: " + repr(data) + "\n\n" + if (not data.startswith('OK')): + raise Exception, data + return data + + def get_string(self, verb, args): + result = self.do_command(verb, args) + return result[3:] + + def get_string_array(self, verb, args): + csv = self.get_string(verb, args) + token = "" + tokens = [] + escape = False + for i in range(len(csv)): + letter = csv[i] + if (escape): + token = token + letter + escape = False + continue + if (letter == '\\'): + escape = True + elif (letter == ','): + tokens.append(token) + token = "" + else: + token = token + letter + tokens.append(token) + return tokens + + def get_number(self, verb, args): + # Is there something I need to do here? + return self.get_string(verb, args) + + def get_number_array(self, verb, args): + # Is there something I need to do here? + return self.get_string_array(verb, args) + + def get_boolean(self, verb, args): + boolstr = self.get_string(verb, args) + if ("true" == boolstr): + return True + if ("false" == boolstr): + return False + raise ValueError, "result is neither 'true' nor 'false': " + boolstr + + def get_boolean_array(self, verb, args): + boolarr = self.get_string_array(verb, args) + for i in range(len(boolarr)): + if ("true" == boolstr): + boolarr[i] = True + continue + if ("false" == boolstr): + boolarr[i] = False + continue + raise ValueError, "result is neither 'true' nor 'false': " + boolarr[i] + return boolarr + + + +### From here on, everything's auto-generated from XML + + + def click(self,locator): + """ + Clicks on a link, button, checkbox or radio button. If the click action + causes a new page to load (like a link usually does), call + waitForPageToLoad. + + 'locator' is an element locator + """ + self.do_command("click", [locator,]) + + + def double_click(self,locator): + """ + Double clicks on a link, button, checkbox or radio button. If the double click action + causes a new page to load (like a link usually does), call + waitForPageToLoad. + + 'locator' is an element locator + """ + self.do_command("doubleClick", [locator,]) + + + def context_menu(self,locator): + """ + Simulates opening the context menu for the specified element (as might happen if the user "right-clicked" on the element). + + 'locator' is an element locator + """ + self.do_command("contextMenu", [locator,]) + + + def click_at(self,locator,coordString): + """ + Clicks on a link, button, checkbox or radio button. If the click action + causes a new page to load (like a link usually does), call + waitForPageToLoad. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("clickAt", [locator,coordString,]) + + + def double_click_at(self,locator,coordString): + """ + Doubleclicks on a link, button, checkbox or radio button. If the action + causes a new page to load (like a link usually does), call + waitForPageToLoad. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("doubleClickAt", [locator,coordString,]) + + + def context_menu_at(self,locator,coordString): + """ + Simulates opening the context menu for the specified element (as might happen if the user "right-clicked" on the element). + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("contextMenuAt", [locator,coordString,]) + + + def fire_event(self,locator,eventName): + """ + Explicitly simulate an event, to trigger the corresponding "on\ *event*" + handler. + + 'locator' is an element locator + 'eventName' is the event name, e.g. "focus" or "blur" + """ + self.do_command("fireEvent", [locator,eventName,]) + + + def focus(self,locator): + """ + Move the focus to the specified element; for example, if the element is an input field, move the cursor to that field. + + 'locator' is an element locator + """ + self.do_command("focus", [locator,]) + + + def key_press(self,locator,keySequence): + """ + Simulates a user pressing and releasing a key. + + 'locator' is an element locator + 'keySequence' is Either be a string("\" followed by the numeric keycode of the key to be pressed, normally the ASCII value of that key), or a single character. For example: "w", "\119". + """ + self.do_command("keyPress", [locator,keySequence,]) + + + def shift_key_down(self): + """ + Press the shift key and hold it down until doShiftUp() is called or a new page is loaded. + + """ + self.do_command("shiftKeyDown", []) + + + def shift_key_up(self): + """ + Release the shift key. + + """ + self.do_command("shiftKeyUp", []) + + + def meta_key_down(self): + """ + Press the meta key and hold it down until doMetaUp() is called or a new page is loaded. + + """ + self.do_command("metaKeyDown", []) + + + def meta_key_up(self): + """ + Release the meta key. + + """ + self.do_command("metaKeyUp", []) + + + def alt_key_down(self): + """ + Press the alt key and hold it down until doAltUp() is called or a new page is loaded. + + """ + self.do_command("altKeyDown", []) + + + def alt_key_up(self): + """ + Release the alt key. + + """ + self.do_command("altKeyUp", []) + + + def control_key_down(self): + """ + Press the control key and hold it down until doControlUp() is called or a new page is loaded. + + """ + self.do_command("controlKeyDown", []) + + + def control_key_up(self): + """ + Release the control key. + + """ + self.do_command("controlKeyUp", []) + + + def key_down(self,locator,keySequence): + """ + Simulates a user pressing a key (without releasing it yet). + + 'locator' is an element locator + 'keySequence' is Either be a string("\" followed by the numeric keycode of the key to be pressed, normally the ASCII value of that key), or a single character. For example: "w", "\119". + """ + self.do_command("keyDown", [locator,keySequence,]) + + + def key_up(self,locator,keySequence): + """ + Simulates a user releasing a key. + + 'locator' is an element locator + 'keySequence' is Either be a string("\" followed by the numeric keycode of the key to be pressed, normally the ASCII value of that key), or a single character. For example: "w", "\119". + """ + self.do_command("keyUp", [locator,keySequence,]) + + + def mouse_over(self,locator): + """ + Simulates a user hovering a mouse over the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseOver", [locator,]) + + + def mouse_out(self,locator): + """ + Simulates a user moving the mouse pointer away from the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseOut", [locator,]) + + + def mouse_down(self,locator): + """ + Simulates a user pressing the left mouse button (without releasing it yet) on + the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseDown", [locator,]) + + + def mouse_down_right(self,locator): + """ + Simulates a user pressing the right mouse button (without releasing it yet) on + the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseDownRight", [locator,]) + + + def mouse_down_at(self,locator,coordString): + """ + Simulates a user pressing the left mouse button (without releasing it yet) at + the specified location. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseDownAt", [locator,coordString,]) + + + def mouse_down_right_at(self,locator,coordString): + """ + Simulates a user pressing the right mouse button (without releasing it yet) at + the specified location. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseDownRightAt", [locator,coordString,]) + + + def mouse_up(self,locator): + """ + Simulates the event that occurs when the user releases the mouse button (i.e., stops + holding the button down) on the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseUp", [locator,]) + + + def mouse_up_right(self,locator): + """ + Simulates the event that occurs when the user releases the right mouse button (i.e., stops + holding the button down) on the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseUpRight", [locator,]) + + + def mouse_up_at(self,locator,coordString): + """ + Simulates the event that occurs when the user releases the mouse button (i.e., stops + holding the button down) at the specified location. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseUpAt", [locator,coordString,]) + + + def mouse_up_right_at(self,locator,coordString): + """ + Simulates the event that occurs when the user releases the right mouse button (i.e., stops + holding the button down) at the specified location. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseUpRightAt", [locator,coordString,]) + + + def mouse_move(self,locator): + """ + Simulates a user pressing the mouse button (without releasing it yet) on + the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseMove", [locator,]) + + + def mouse_move_at(self,locator,coordString): + """ + Simulates a user pressing the mouse button (without releasing it yet) on + the specified element. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseMoveAt", [locator,coordString,]) + + + def type(self,locator,value): + """ + Sets the value of an input field, as though you typed it in. + + + Can also be used to set the value of combo boxes, check boxes, etc. In these cases, + value should be the value of the option selected, not the visible text. + + + 'locator' is an element locator + 'value' is the value to type + """ + self.do_command("type", [locator,value,]) + + + def type_keys(self,locator,value): + """ + Simulates keystroke events on the specified element, as though you typed the value key-by-key. + + + This is a convenience method for calling keyDown, keyUp, keyPress for every character in the specified string; + this is useful for dynamic UI widgets (like auto-completing combo boxes) that require explicit key events. + + Unlike the simple "type" command, which forces the specified value into the page directly, this command + may or may not have any visible effect, even in cases where typing keys would normally have a visible effect. + For example, if you use "typeKeys" on a form element, you may or may not see the results of what you typed in + the field. + + In some cases, you may need to use the simple "type" command to set the value of the field and then the "typeKeys" command to + send the keystroke events corresponding to what you just typed. + + + 'locator' is an element locator + 'value' is the value to type + """ + self.do_command("typeKeys", [locator,value,]) + + + def set_speed(self,value): + """ + Set execution speed (i.e., set the millisecond length of a delay which will follow each selenium operation). By default, there is no such delay, i.e., + the delay is 0 milliseconds. + + 'value' is the number of milliseconds to pause after operation + """ + self.do_command("setSpeed", [value,]) + + + def get_speed(self): + """ + Get execution speed (i.e., get the millisecond length of the delay following each selenium operation). By default, there is no such delay, i.e., + the delay is 0 milliseconds. + + See also setSpeed. + + """ + return self.get_string("getSpeed", []) + + + def check(self,locator): + """ + Check a toggle-button (checkbox/radio) + + 'locator' is an element locator + """ + self.do_command("check", [locator,]) + + + def uncheck(self,locator): + """ + Uncheck a toggle-button (checkbox/radio) + + 'locator' is an element locator + """ + self.do_command("uncheck", [locator,]) + + + def select(self,selectLocator,optionLocator): + """ + Select an option from a drop-down using an option locator. + + + + Option locators provide different ways of specifying options of an HTML + Select element (e.g. for selecting a specific option, or for asserting + that the selected option satisfies a specification). There are several + forms of Select Option Locator. + + + * \ **label**\ =\ *labelPattern*: + matches options based on their labels, i.e. the visible text. (This + is the default.) + + * label=regexp:^[Oo]ther + + + * \ **value**\ =\ *valuePattern*: + matches options based on their values. + + * value=other + + + * \ **id**\ =\ *id*: + + matches options based on their ids. + + * id=option1 + + + * \ **index**\ =\ *index*: + matches an option based on its index (offset from zero). + + * index=2 + + + + + + If no option locator prefix is provided, the default behaviour is to match on \ **label**\ . + + + + 'selectLocator' is an element locator identifying a drop-down menu + 'optionLocator' is an option locator (a label by default) + """ + self.do_command("select", [selectLocator,optionLocator,]) + + + def add_selection(self,locator,optionLocator): + """ + Add a selection to the set of selected options in a multi-select element using an option locator. + + @see #doSelect for details of option locators + + 'locator' is an element locator identifying a multi-select box + 'optionLocator' is an option locator (a label by default) + """ + self.do_command("addSelection", [locator,optionLocator,]) + + + def remove_selection(self,locator,optionLocator): + """ + Remove a selection from the set of selected options in a multi-select element using an option locator. + + @see #doSelect for details of option locators + + 'locator' is an element locator identifying a multi-select box + 'optionLocator' is an option locator (a label by default) + """ + self.do_command("removeSelection", [locator,optionLocator,]) + + + def remove_all_selections(self,locator): + """ + Unselects all of the selected options in a multi-select element. + + 'locator' is an element locator identifying a multi-select box + """ + self.do_command("removeAllSelections", [locator,]) + + + def submit(self,formLocator): + """ + Submit the specified form. This is particularly useful for forms without + submit buttons, e.g. single-input "Search" forms. + + 'formLocator' is an element locator for the form you want to submit + """ + self.do_command("submit", [formLocator,]) + + + def open(self,url): + """ + Opens an URL in the test frame. This accepts both relative and absolute + URLs. + + The "open" command waits for the page to load before proceeding, + ie. the "AndWait" suffix is implicit. + + \ *Note*: The URL must be on the same domain as the runner HTML + due to security restrictions in the browser (Same Origin Policy). If you + need to open an URL on another domain, use the Selenium Server to start a + new browser session on that domain. + + 'url' is the URL to open; may be relative or absolute + """ + self.do_command("open", [url,]) + + + def open_window(self,url,windowID): + """ + Opens a popup window (if a window with that ID isn't already open). + After opening the window, you'll need to select it using the selectWindow + command. + + + This command can also be a useful workaround for bug SEL-339. In some cases, Selenium will be unable to intercept a call to window.open (if the call occurs during or before the "onLoad" event, for example). + In those cases, you can force Selenium to notice the open window's name by using the Selenium openWindow command, using + an empty (blank) url, like this: openWindow("", "myFunnyWindow"). + + + 'url' is the URL to open, which can be blank + 'windowID' is the JavaScript window ID of the window to select + """ + self.do_command("openWindow", [url,windowID,]) + + + def select_window(self,windowID): + """ + Selects a popup window using a window locator; once a popup window has been selected, all + commands go to that window. To select the main window again, use null + as the target. + + + + + Window locators provide different ways of specifying the window object: + by title, by internal JavaScript "name," or by JavaScript variable. + + + * \ **title**\ =\ *My Special Window*: + Finds the window using the text that appears in the title bar. Be careful; + two windows can share the same title. If that happens, this locator will + just pick one. + + * \ **name**\ =\ *myWindow*: + Finds the window using its internal JavaScript "name" property. This is the second + parameter "windowName" passed to the JavaScript method window.open(url, windowName, windowFeatures, replaceFlag) + (which Selenium intercepts). + + * \ **var**\ =\ *variableName*: + Some pop-up windows are unnamed (anonymous), but are associated with a JavaScript variable name in the current + application window, e.g. "window.foo = window.open(url);". In those cases, you can open the window using + "var=foo". + + + + + If no window locator prefix is provided, we'll try to guess what you mean like this: + + 1.) if windowID is null, (or the string "null") then it is assumed the user is referring to the original window instantiated by the browser). + + 2.) if the value of the "windowID" parameter is a JavaScript variable name in the current application window, then it is assumed + that this variable contains the return value from a call to the JavaScript window.open() method. + + 3.) Otherwise, selenium looks in a hash it maintains that maps string names to window "names". + + 4.) If \ *that* fails, we'll try looping over all of the known windows to try to find the appropriate "title". + Since "title" is not necessarily unique, this may have unexpected behavior. + + If you're having trouble figuring out the name of a window that you want to manipulate, look at the Selenium log messages + which identify the names of windows created via window.open (and therefore intercepted by Selenium). You will see messages + like the following for each window as it is opened: + + ``debug: window.open call intercepted; window ID (which you can use with selectWindow()) is "myNewWindow"`` + + In some cases, Selenium will be unable to intercept a call to window.open (if the call occurs during or before the "onLoad" event, for example). + (This is bug SEL-339.) In those cases, you can force Selenium to notice the open window's name by using the Selenium openWindow command, using + an empty (blank) url, like this: openWindow("", "myFunnyWindow"). + + + 'windowID' is the JavaScript window ID of the window to select + """ + self.do_command("selectWindow", [windowID,]) + + + def select_pop_up(self,windowID): + """ + Simplifies the process of selecting a popup window (and does not offer + functionality beyond what ``selectWindow()`` already provides). + + * If ``windowID`` is either not specified, or specified as + "null", the first non-top window is selected. The top window is the one + that would be selected by ``selectWindow()`` without providing a + ``windowID`` . This should not be used when more than one popup + window is in play. + * Otherwise, the window will be looked up considering + ``windowID`` as the following in order: 1) the "name" of the + window, as specified to ``window.open()``; 2) a javascript + variable which is a reference to a window; and 3) the title of the + window. This is the same ordered lookup performed by + ``selectWindow`` . + + + + 'windowID' is an identifier for the popup window, which can take on a number of different meanings + """ + self.do_command("selectPopUp", [windowID,]) + + + def deselect_pop_up(self): + """ + Selects the main window. Functionally equivalent to using + ``selectWindow()`` and specifying no value for + ``windowID``. + + """ + self.do_command("deselectPopUp", []) + + + def select_frame(self,locator): + """ + Selects a frame within the current window. (You may invoke this command + multiple times to select nested frames.) To select the parent frame, use + "relative=parent" as a locator; to select the top frame, use "relative=top". + You can also select a frame by its 0-based index number; select the first frame with + "index=0", or the third frame with "index=2". + + + You may also use a DOM expression to identify the frame you want directly, + like this: ``dom=frames["main"].frames["subframe"]`` + + + 'locator' is an element locator identifying a frame or iframe + """ + self.do_command("selectFrame", [locator,]) + + + def get_whether_this_frame_match_frame_expression(self,currentFrameString,target): + """ + Determine whether current/locator identify the frame containing this running code. + + + This is useful in proxy injection mode, where this code runs in every + browser frame and window, and sometimes the selenium server needs to identify + the "current" frame. In this case, when the test calls selectFrame, this + routine is called for each frame to figure out which one has been selected. + The selected frame will return true, while all others will return false. + + + 'currentFrameString' is starting frame + 'target' is new frame (which might be relative to the current one) + """ + return self.get_boolean("getWhetherThisFrameMatchFrameExpression", [currentFrameString,target,]) + + + def get_whether_this_window_match_window_expression(self,currentWindowString,target): + """ + Determine whether currentWindowString plus target identify the window containing this running code. + + + This is useful in proxy injection mode, where this code runs in every + browser frame and window, and sometimes the selenium server needs to identify + the "current" window. In this case, when the test calls selectWindow, this + routine is called for each window to figure out which one has been selected. + The selected window will return true, while all others will return false. + + + 'currentWindowString' is starting window + 'target' is new window (which might be relative to the current one, e.g., "_parent") + """ + return self.get_boolean("getWhetherThisWindowMatchWindowExpression", [currentWindowString,target,]) + + + def wait_for_pop_up(self,windowID,timeout): + """ + Waits for a popup window to appear and load up. + + 'windowID' is the JavaScript window "name" of the window that will appear (not the text of the title bar) If unspecified, or specified as "null", this command will wait for the first non-top window to appear (don't rely on this if you are working with multiple popups simultaneously). + 'timeout' is a timeout in milliseconds, after which the action will return with an error. If this value is not specified, the default Selenium timeout will be used. See the setTimeout() command. + """ + self.do_command("waitForPopUp", [windowID,timeout,]) + + + def choose_cancel_on_next_confirmation(self): + """ + + + By default, Selenium's overridden window.confirm() function will + return true, as if the user had manually clicked OK; after running + this command, the next call to confirm() will return false, as if + the user had clicked Cancel. Selenium will then resume using the + default behavior for future confirmations, automatically returning + true (OK) unless/until you explicitly call this command for each + confirmation. + + + + Take note - every time a confirmation comes up, you must + consume it with a corresponding getConfirmation, or else + the next selenium operation will fail. + + + + """ + self.do_command("chooseCancelOnNextConfirmation", []) + + + def choose_ok_on_next_confirmation(self): + """ + + + Undo the effect of calling chooseCancelOnNextConfirmation. Note + that Selenium's overridden window.confirm() function will normally automatically + return true, as if the user had manually clicked OK, so you shouldn't + need to use this command unless for some reason you need to change + your mind prior to the next confirmation. After any confirmation, Selenium will resume using the + default behavior for future confirmations, automatically returning + true (OK) unless/until you explicitly call chooseCancelOnNextConfirmation for each + confirmation. + + + + Take note - every time a confirmation comes up, you must + consume it with a corresponding getConfirmation, or else + the next selenium operation will fail. + + + + """ + self.do_command("chooseOkOnNextConfirmation", []) + + + def answer_on_next_prompt(self,answer): + """ + Instructs Selenium to return the specified answer string in response to + the next JavaScript prompt [window.prompt()]. + + 'answer' is the answer to give in response to the prompt pop-up + """ + self.do_command("answerOnNextPrompt", [answer,]) + + + def go_back(self): + """ + Simulates the user clicking the "back" button on their browser. + + """ + self.do_command("goBack", []) + + + def refresh(self): + """ + Simulates the user clicking the "Refresh" button on their browser. + + """ + self.do_command("refresh", []) + + + def close(self): + """ + Simulates the user clicking the "close" button in the titlebar of a popup + window or tab. + + """ + self.do_command("close", []) + + + def is_alert_present(self): + """ + Has an alert occurred? + + + + This function never throws an exception + + + + """ + return self.get_boolean("isAlertPresent", []) + + + def is_prompt_present(self): + """ + Has a prompt occurred? + + + + This function never throws an exception + + + + """ + return self.get_boolean("isPromptPresent", []) + + + def is_confirmation_present(self): + """ + Has confirm() been called? + + + + This function never throws an exception + + + + """ + return self.get_boolean("isConfirmationPresent", []) + + + def get_alert(self): + """ + Retrieves the message of a JavaScript alert generated during the previous action, or fail if there were no alerts. + + + Getting an alert has the same effect as manually clicking OK. If an + alert is generated but you do not consume it with getAlert, the next Selenium action + will fail. + + Under Selenium, JavaScript alerts will NOT pop up a visible alert + dialog. + + Selenium does NOT support JavaScript alerts that are generated in a + page's onload() event handler. In this case a visible dialog WILL be + generated and Selenium will hang until someone manually clicks OK. + + + """ + return self.get_string("getAlert", []) + + + def get_confirmation(self): + """ + Retrieves the message of a JavaScript confirmation dialog generated during + the previous action. + + + + By default, the confirm function will return true, having the same effect + as manually clicking OK. This can be changed by prior execution of the + chooseCancelOnNextConfirmation command. + + + + If an confirmation is generated but you do not consume it with getConfirmation, + the next Selenium action will fail. + + + + NOTE: under Selenium, JavaScript confirmations will NOT pop up a visible + dialog. + + + + NOTE: Selenium does NOT support JavaScript confirmations that are + generated in a page's onload() event handler. In this case a visible + dialog WILL be generated and Selenium will hang until you manually click + OK. + + + + """ + return self.get_string("getConfirmation", []) + + + def get_prompt(self): + """ + Retrieves the message of a JavaScript question prompt dialog generated during + the previous action. + + + Successful handling of the prompt requires prior execution of the + answerOnNextPrompt command. If a prompt is generated but you + do not get/verify it, the next Selenium action will fail. + + NOTE: under Selenium, JavaScript prompts will NOT pop up a visible + dialog. + + NOTE: Selenium does NOT support JavaScript prompts that are generated in a + page's onload() event handler. In this case a visible dialog WILL be + generated and Selenium will hang until someone manually clicks OK. + + + """ + return self.get_string("getPrompt", []) + + + def get_location(self): + """ + Gets the absolute URL of the current page. + + """ + return self.get_string("getLocation", []) + + + def get_title(self): + """ + Gets the title of the current page. + + """ + return self.get_string("getTitle", []) + + + def get_body_text(self): + """ + Gets the entire text of the page. + + """ + return self.get_string("getBodyText", []) + + + def get_value(self,locator): + """ + Gets the (whitespace-trimmed) value of an input field (or anything else with a value parameter). + For checkbox/radio elements, the value will be "on" or "off" depending on + whether the element is checked or not. + + 'locator' is an element locator + """ + return self.get_string("getValue", [locator,]) + + + def get_text(self,locator): + """ + Gets the text of an element. This works for any element that contains + text. This command uses either the textContent (Mozilla-like browsers) or + the innerText (IE-like browsers) of the element, which is the rendered + text shown to the user. + + 'locator' is an element locator + """ + return self.get_string("getText", [locator,]) + + + def highlight(self,locator): + """ + Briefly changes the backgroundColor of the specified element yellow. Useful for debugging. + + 'locator' is an element locator + """ + self.do_command("highlight", [locator,]) + + + def get_eval(self,script): + """ + Gets the result of evaluating the specified JavaScript snippet. The snippet may + have multiple lines, but only the result of the last line will be returned. + + + Note that, by default, the snippet will run in the context of the "selenium" + object itself, so ``this`` will refer to the Selenium object. Use ``window`` to + refer to the window of your application, e.g. ``window.document.getElementById('foo')`` + + If you need to use + a locator to refer to a single element in your application page, you can + use ``this.browserbot.findElement("id=foo")`` where "id=foo" is your locator. + + + 'script' is the JavaScript snippet to run + """ + return self.get_string("getEval", [script,]) + + + def is_checked(self,locator): + """ + Gets whether a toggle-button (checkbox/radio) is checked. Fails if the specified element doesn't exist or isn't a toggle-button. + + 'locator' is an element locator pointing to a checkbox or radio button + """ + return self.get_boolean("isChecked", [locator,]) + + + def get_table(self,tableCellAddress): + """ + Gets the text from a cell of a table. The cellAddress syntax + tableLocator.row.column, where row and column start at 0. + + 'tableCellAddress' is a cell address, e.g. "foo.1.4" + """ + return self.get_string("getTable", [tableCellAddress,]) + + + def get_selected_labels(self,selectLocator): + """ + Gets all option labels (visible text) for selected options in the specified select or multi-select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectedLabels", [selectLocator,]) + + + def get_selected_label(self,selectLocator): + """ + Gets option label (visible text) for selected option in the specified select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string("getSelectedLabel", [selectLocator,]) + + + def get_selected_values(self,selectLocator): + """ + Gets all option values (value attributes) for selected options in the specified select or multi-select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectedValues", [selectLocator,]) + + + def get_selected_value(self,selectLocator): + """ + Gets option value (value attribute) for selected option in the specified select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string("getSelectedValue", [selectLocator,]) + + + def get_selected_indexes(self,selectLocator): + """ + Gets all option indexes (option number, starting at 0) for selected options in the specified select or multi-select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectedIndexes", [selectLocator,]) + + + def get_selected_index(self,selectLocator): + """ + Gets option index (option number, starting at 0) for selected option in the specified select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string("getSelectedIndex", [selectLocator,]) + + + def get_selected_ids(self,selectLocator): + """ + Gets all option element IDs for selected options in the specified select or multi-select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectedIds", [selectLocator,]) + + + def get_selected_id(self,selectLocator): + """ + Gets option element ID for selected option in the specified select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string("getSelectedId", [selectLocator,]) + + + def is_something_selected(self,selectLocator): + """ + Determines whether some option in a drop-down menu is selected. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_boolean("isSomethingSelected", [selectLocator,]) + + + def get_select_options(self,selectLocator): + """ + Gets all option labels in the specified select drop-down. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectOptions", [selectLocator,]) + + + def get_attribute(self,attributeLocator): + """ + Gets the value of an element attribute. The value of the attribute may + differ across browsers (this is the case for the "style" attribute, for + example). + + 'attributeLocator' is an element locator followed by an @ sign and then the name of the attribute, e.g. "foo@bar" + """ + return self.get_string("getAttribute", [attributeLocator,]) + + + def is_text_present(self,pattern): + """ + Verifies that the specified text pattern appears somewhere on the rendered page shown to the user. + + 'pattern' is a pattern to match with the text of the page + """ + return self.get_boolean("isTextPresent", [pattern,]) + + + def is_element_present(self,locator): + """ + Verifies that the specified element is somewhere on the page. + + 'locator' is an element locator + """ + return self.get_boolean("isElementPresent", [locator,]) + + + def is_visible(self,locator): + """ + Determines if the specified element is visible. An + element can be rendered invisible by setting the CSS "visibility" + property to "hidden", or the "display" property to "none", either for the + element itself or one if its ancestors. This method will fail if + the element is not present. + + 'locator' is an element locator + """ + return self.get_boolean("isVisible", [locator,]) + + + def is_editable(self,locator): + """ + Determines whether the specified input element is editable, ie hasn't been disabled. + This method will fail if the specified element isn't an input element. + + 'locator' is an element locator + """ + return self.get_boolean("isEditable", [locator,]) + + + def get_all_buttons(self): + """ + Returns the IDs of all buttons on the page. + + + If a given button has no ID, it will appear as "" in this array. + + + """ + return self.get_string_array("getAllButtons", []) + + + def get_all_links(self): + """ + Returns the IDs of all links on the page. + + + If a given link has no ID, it will appear as "" in this array. + + + """ + return self.get_string_array("getAllLinks", []) + + + def get_all_fields(self): + """ + Returns the IDs of all input fields on the page. + + + If a given field has no ID, it will appear as "" in this array. + + + """ + return self.get_string_array("getAllFields", []) + + + def get_attribute_from_all_windows(self,attributeName): + """ + Returns every instance of some attribute from all known windows. + + 'attributeName' is name of an attribute on the windows + """ + return self.get_string_array("getAttributeFromAllWindows", [attributeName,]) + + + def dragdrop(self,locator,movementsString): + """ + deprecated - use dragAndDrop instead + + 'locator' is an element locator + 'movementsString' is offset in pixels from the current location to which the element should be moved, e.g., "+70,-300" + """ + self.do_command("dragdrop", [locator,movementsString,]) + + + def set_mouse_speed(self,pixels): + """ + Configure the number of pixels between "mousemove" events during dragAndDrop commands (default=10). + + Setting this value to 0 means that we'll send a "mousemove" event to every single pixel + in between the start location and the end location; that can be very slow, and may + cause some browsers to force the JavaScript to timeout. + + If the mouse speed is greater than the distance between the two dragged objects, we'll + just send one "mousemove" at the start location and then one final one at the end location. + + + 'pixels' is the number of pixels between "mousemove" events + """ + self.do_command("setMouseSpeed", [pixels,]) + + + def get_mouse_speed(self): + """ + Returns the number of pixels between "mousemove" events during dragAndDrop commands (default=10). + + """ + return self.get_number("getMouseSpeed", []) + + + def drag_and_drop(self,locator,movementsString): + """ + Drags an element a certain distance and then drops it + + 'locator' is an element locator + 'movementsString' is offset in pixels from the current location to which the element should be moved, e.g., "+70,-300" + """ + self.do_command("dragAndDrop", [locator,movementsString,]) + + + def drag_and_drop_to_object(self,locatorOfObjectToBeDragged,locatorOfDragDestinationObject): + """ + Drags an element and drops it on another element + + 'locatorOfObjectToBeDragged' is an element to be dragged + 'locatorOfDragDestinationObject' is an element whose location (i.e., whose center-most pixel) will be the point where locatorOfObjectToBeDragged is dropped + """ + self.do_command("dragAndDropToObject", [locatorOfObjectToBeDragged,locatorOfDragDestinationObject,]) + + + def window_focus(self): + """ + Gives focus to the currently selected window + + """ + self.do_command("windowFocus", []) + + + def window_maximize(self): + """ + Resize currently selected window to take up the entire screen + + """ + self.do_command("windowMaximize", []) + + + def get_all_window_ids(self): + """ + Returns the IDs of all windows that the browser knows about. + + """ + return self.get_string_array("getAllWindowIds", []) + + + def get_all_window_names(self): + """ + Returns the names of all windows that the browser knows about. + + """ + return self.get_string_array("getAllWindowNames", []) + + + def get_all_window_titles(self): + """ + Returns the titles of all windows that the browser knows about. + + """ + return self.get_string_array("getAllWindowTitles", []) + + + def get_html_source(self): + """ + Returns the entire HTML source between the opening and + closing "html" tags. + + """ + return self.get_string("getHtmlSource", []) + + + def set_cursor_position(self,locator,position): + """ + Moves the text cursor to the specified position in the given input element or textarea. + This method will fail if the specified element isn't an input element or textarea. + + 'locator' is an element locator pointing to an input element or textarea + 'position' is the numerical position of the cursor in the field; position should be 0 to move the position to the beginning of the field. You can also set the cursor to -1 to move it to the end of the field. + """ + self.do_command("setCursorPosition", [locator,position,]) + + + def get_element_index(self,locator): + """ + Get the relative index of an element to its parent (starting from 0). The comment node and empty text node + will be ignored. + + 'locator' is an element locator pointing to an element + """ + return self.get_number("getElementIndex", [locator,]) + + + def is_ordered(self,locator1,locator2): + """ + Check if these two elements have same parent and are ordered siblings in the DOM. Two same elements will + not be considered ordered. + + 'locator1' is an element locator pointing to the first element + 'locator2' is an element locator pointing to the second element + """ + return self.get_boolean("isOrdered", [locator1,locator2,]) + + + def get_element_position_left(self,locator): + """ + Retrieves the horizontal position of an element + + 'locator' is an element locator pointing to an element OR an element itself + """ + return self.get_number("getElementPositionLeft", [locator,]) + + + def get_element_position_top(self,locator): + """ + Retrieves the vertical position of an element + + 'locator' is an element locator pointing to an element OR an element itself + """ + return self.get_number("getElementPositionTop", [locator,]) + + + def get_element_width(self,locator): + """ + Retrieves the width of an element + + 'locator' is an element locator pointing to an element + """ + return self.get_number("getElementWidth", [locator,]) + + + def get_element_height(self,locator): + """ + Retrieves the height of an element + + 'locator' is an element locator pointing to an element + """ + return self.get_number("getElementHeight", [locator,]) + + + def get_cursor_position(self,locator): + """ + Retrieves the text cursor position in the given input element or textarea; beware, this may not work perfectly on all browsers. + + + Specifically, if the cursor/selection has been cleared by JavaScript, this command will tend to + return the position of the last location of the cursor, even though the cursor is now gone from the page. This is filed as SEL-243. + + This method will fail if the specified element isn't an input element or textarea, or there is no cursor in the element. + + 'locator' is an element locator pointing to an input element or textarea + """ + return self.get_number("getCursorPosition", [locator,]) + + + def get_expression(self,expression): + """ + Returns the specified expression. + + + This is useful because of JavaScript preprocessing. + It is used to generate commands like assertExpression and waitForExpression. + + + 'expression' is the value to return + """ + return self.get_string("getExpression", [expression,]) + + + def get_xpath_count(self,xpath): + """ + Returns the number of nodes that match the specified xpath, eg. "//table" would give + the number of tables. + + 'xpath' is the xpath expression to evaluate. do NOT wrap this expression in a 'count()' function; we will do that for you. + """ + return self.get_number("getXpathCount", [xpath,]) + + + def assign_id(self,locator,identifier): + """ + Temporarily sets the "id" attribute of the specified element, so you can locate it in the future + using its ID rather than a slow/complicated XPath. This ID will disappear once the page is + reloaded. + + 'locator' is an element locator pointing to an element + 'identifier' is a string to be used as the ID of the specified element + """ + self.do_command("assignId", [locator,identifier,]) + + + def allow_native_xpath(self,allow): + """ + Specifies whether Selenium should use the native in-browser implementation + of XPath (if any native version is available); if you pass "false" to + this function, we will always use our pure-JavaScript xpath library. + Using the pure-JS xpath library can improve the consistency of xpath + element locators between different browser vendors, but the pure-JS + version is much slower than the native implementations. + + 'allow' is boolean, true means we'll prefer to use native XPath; false means we'll only use JS XPath + """ + self.do_command("allowNativeXpath", [allow,]) + + + def ignore_attributes_without_value(self,ignore): + """ + Specifies whether Selenium will ignore xpath attributes that have no + value, i.e. are the empty string, when using the non-native xpath + evaluation engine. You'd want to do this for performance reasons in IE. + However, this could break certain xpaths, for example an xpath that looks + for an attribute whose value is NOT the empty string. + + The hope is that such xpaths are relatively rare, but the user should + have the option of using them. Note that this only influences xpath + evaluation when using the ajaxslt engine (i.e. not "javascript-xpath"). + + 'ignore' is boolean, true means we'll ignore attributes without value at the expense of xpath "correctness"; false means we'll sacrifice speed for correctness. + """ + self.do_command("ignoreAttributesWithoutValue", [ignore,]) + + + def wait_for_condition(self,script,timeout): + """ + Runs the specified JavaScript snippet repeatedly until it evaluates to "true". + The snippet may have multiple lines, but only the result of the last line + will be considered. + + + Note that, by default, the snippet will be run in the runner's test window, not in the window + of your application. To get the window of your application, you can use + the JavaScript snippet ``selenium.browserbot.getCurrentWindow()``, and then + run your JavaScript in there + + + 'script' is the JavaScript snippet to run + 'timeout' is a timeout in milliseconds, after which this command will return with an error + """ + self.do_command("waitForCondition", [script,timeout,]) + + + def set_timeout(self,timeout): + """ + Specifies the amount of time that Selenium will wait for actions to complete. + + + Actions that require waiting include "open" and the "waitFor\*" actions. + + The default timeout is 30 seconds. + + 'timeout' is a timeout in milliseconds, after which the action will return with an error + """ + self.do_command("setTimeout", [timeout,]) + + + def wait_for_page_to_load(self,timeout): + """ + Waits for a new page to load. + + + You can use this command instead of the "AndWait" suffixes, "clickAndWait", "selectAndWait", "typeAndWait" etc. + (which are only available in the JS API). + + Selenium constantly keeps track of new pages loading, and sets a "newPageLoaded" + flag when it first notices a page load. Running any other Selenium command after + turns the flag to false. Hence, if you want to wait for a page to load, you must + wait immediately after a Selenium command that caused a page-load. + + + 'timeout' is a timeout in milliseconds, after which this command will return with an error + """ + self.do_command("waitForPageToLoad", [timeout,]) + + + def wait_for_frame_to_load(self,frameAddress,timeout): + """ + Waits for a new frame to load. + + + Selenium constantly keeps track of new pages and frames loading, + and sets a "newPageLoaded" flag when it first notices a page load. + + + See waitForPageToLoad for more information. + + 'frameAddress' is FrameAddress from the server side + 'timeout' is a timeout in milliseconds, after which this command will return with an error + """ + self.do_command("waitForFrameToLoad", [frameAddress,timeout,]) + + + def get_cookie(self): + """ + Return all cookies of the current page under test. + + """ + return self.get_string("getCookie", []) + + + def get_cookie_by_name(self,name): + """ + Returns the value of the cookie with the specified name, or throws an error if the cookie is not present. + + 'name' is the name of the cookie + """ + return self.get_string("getCookieByName", [name,]) + + + def is_cookie_present(self,name): + """ + Returns true if a cookie with the specified name is present, or false otherwise. + + 'name' is the name of the cookie + """ + return self.get_boolean("isCookiePresent", [name,]) + + + def create_cookie(self,nameValuePair,optionsString): + """ + Create a new cookie whose path and domain are same with those of current page + under test, unless you specified a path for this cookie explicitly. + + 'nameValuePair' is name and value of the cookie in a format "name=value" + 'optionsString' is options for the cookie. Currently supported options include 'path', 'max_age' and 'domain'. the optionsString's format is "path=/path/, max_age=60, domain=.foo.com". The order of options are irrelevant, the unit of the value of 'max_age' is second. Note that specifying a domain that isn't a subset of the current domain will usually fail. + """ + self.do_command("createCookie", [nameValuePair,optionsString,]) + + + def delete_cookie(self,name,optionsString): + """ + Delete a named cookie with specified path and domain. Be careful; to delete a cookie, you + need to delete it using the exact same path and domain that were used to create the cookie. + If the path is wrong, or the domain is wrong, the cookie simply won't be deleted. Also + note that specifying a domain that isn't a subset of the current domain will usually fail. + + Since there's no way to discover at runtime the original path and domain of a given cookie, + we've added an option called 'recurse' to try all sub-domains of the current domain with + all paths that are a subset of the current path. Beware; this option can be slow. In + big-O notation, it operates in O(n\*m) time, where n is the number of dots in the domain + name and m is the number of slashes in the path. + + 'name' is the name of the cookie to be deleted + 'optionsString' is options for the cookie. Currently supported options include 'path', 'domain' and 'recurse.' The optionsString's format is "path=/path/, domain=.foo.com, recurse=true". The order of options are irrelevant. Note that specifying a domain that isn't a subset of the current domain will usually fail. + """ + self.do_command("deleteCookie", [name,optionsString,]) + + + def delete_all_visible_cookies(self): + """ + Calls deleteCookie with recurse=true on all cookies visible to the current page. + As noted on the documentation for deleteCookie, recurse=true can be much slower + than simply deleting the cookies using a known domain/path. + + """ + self.do_command("deleteAllVisibleCookies", []) + + + def set_browser_log_level(self,logLevel): + """ + Sets the threshold for browser-side logging messages; log messages beneath this threshold will be discarded. + Valid logLevel strings are: "debug", "info", "warn", "error" or "off". + To see the browser logs, you need to + either show the log window in GUI mode, or enable browser-side logging in Selenium RC. + + 'logLevel' is one of the following: "debug", "info", "warn", "error" or "off" + """ + self.do_command("setBrowserLogLevel", [logLevel,]) + + + def run_script(self,script): + """ + Creates a new "script" tag in the body of the current test window, and + adds the specified text into the body of the command. Scripts run in + this way can often be debugged more easily than scripts executed using + Selenium's "getEval" command. Beware that JS exceptions thrown in these script + tags aren't managed by Selenium, so you should probably wrap your script + in try/catch blocks if there is any chance that the script will throw + an exception. + + 'script' is the JavaScript snippet to run + """ + self.do_command("runScript", [script,]) + + + def add_location_strategy(self,strategyName,functionDefinition): + """ + Defines a new function for Selenium to locate elements on the page. + For example, + if you define the strategy "foo", and someone runs click("foo=blah"), we'll + run your function, passing you the string "blah", and click on the element + that your function + returns, or throw an "Element not found" error if your function returns null. + + We'll pass three arguments to your function: + + * locator: the string the user passed in + * inWindow: the currently selected window + * inDocument: the currently selected document + + + The function must return null if the element can't be found. + + 'strategyName' is the name of the strategy to define; this should use only letters [a-zA-Z] with no spaces or other punctuation. + 'functionDefinition' is a string defining the body of a function in JavaScript. For example: ``return inDocument.getElementById(locator);`` + """ + self.do_command("addLocationStrategy", [strategyName,functionDefinition,]) + + + def capture_entire_page_screenshot(self,filename,kwargs): + """ + Saves the entire contents of the current window canvas to a PNG file. + Contrast this with the captureScreenshot command, which captures the + contents of the OS viewport (i.e. whatever is currently being displayed + on the monitor), and is implemented in the RC only. Currently this only + works in Firefox when running in chrome mode, and in IE non-HTA using + the EXPERIMENTAL "Snapsie" utility. The Firefox implementation is mostly + borrowed from the Screengrab! Firefox extension. Please see + http://www.screengrab.org and http://snapsie.sourceforge.net/ for + details. + + 'filename' is the path to the file to persist the screenshot as. No filename extension will be appended by default. Directories will not be created if they do not exist, and an exception will be thrown, possibly by native code. + 'kwargs' is a kwargs string that modifies the way the screenshot is captured. Example: "background=#CCFFDD" . Currently valid options: + * background + the background CSS for the HTML document. This may be useful to set for capturing screenshots of less-than-ideal layouts, for example where absolute positioning causes the calculation of the canvas dimension to fail and a black background is exposed (possibly obscuring black text). + + + """ + self.do_command("captureEntirePageScreenshot", [filename,kwargs,]) + + + def rollup(self,rollupName,kwargs): + """ + Executes a command rollup, which is a series of commands with a unique + name, and optionally arguments that control the generation of the set of + commands. If any one of the rolled-up commands fails, the rollup is + considered to have failed. Rollups may also contain nested rollups. + + 'rollupName' is the name of the rollup command + 'kwargs' is keyword arguments string that influences how the rollup expands into commands + """ + self.do_command("rollup", [rollupName,kwargs,]) + + + def add_script(self,scriptContent,scriptTagId): + """ + Loads script content into a new script tag in the Selenium document. This + differs from the runScript command in that runScript adds the script tag + to the document of the AUT, not the Selenium document. The following + entities in the script content are replaced by the characters they + represent: + + < + > + & + + The corresponding remove command is removeScript. + + 'scriptContent' is the Javascript content of the script to add + 'scriptTagId' is (optional) the id of the new script tag. If specified, and an element with this id already exists, this operation will fail. + """ + self.do_command("addScript", [scriptContent,scriptTagId,]) + + + def remove_script(self,scriptTagId): + """ + Removes a script tag from the Selenium document identified by the given + id. Does nothing if the referenced tag doesn't exist. + + 'scriptTagId' is the id of the script element to remove. + """ + self.do_command("removeScript", [scriptTagId,]) + + + def use_xpath_library(self,libraryName): + """ + Allows choice of one of the available libraries. + + 'libraryName' is name of the desired library Only the following three can be chosen: + * "ajaxslt" - Google's library + * "javascript-xpath" - Cybozu Labs' faster library + * "default" - The default library. Currently the default library is "ajaxslt" . + + If libraryName isn't one of these three, then no change will be made. + """ + self.do_command("useXpathLibrary", [libraryName,]) + + + def set_context(self,context): + """ + Writes a message to the status bar and adds a note to the browser-side + log. + + 'context' is the message to be sent to the browser + """ + self.do_command("setContext", [context,]) + + + def attach_file(self,fieldLocator,fileLocator): + """ + Sets a file input (upload) field to the file listed in fileLocator + + 'fieldLocator' is an element locator + 'fileLocator' is a URL pointing to the specified file. Before the file can be set in the input field (fieldLocator), Selenium RC may need to transfer the file to the local machine before attaching the file in a web page form. This is common in selenium grid configurations where the RC server driving the browser is not the same machine that started the test. Supported Browsers: Firefox ("\*chrome") only. + """ + self.do_command("attachFile", [fieldLocator,fileLocator,]) + + + def capture_screenshot(self,filename): + """ + Captures a PNG screenshot to the specified file. + + 'filename' is the absolute path to the file to be written, e.g. "c:\blah\screenshot.png" + """ + self.do_command("captureScreenshot", [filename,]) + + + def capture_screenshot_to_string(self): + """ + Capture a PNG screenshot. It then returns the file as a base 64 encoded string. + + """ + return self.get_string("captureScreenshotToString", []) + + + def captureNetworkTraffic(self, type): + """ + Returns the network traffic seen by the browser, including headers, AJAX requests, status codes, and timings. When this function is called, the traffic log is cleared, so the returned content is only the traffic seen since the last call. + + 'type' is The type of data to return the network traffic as. Valid values are: json, xml, or plain. + """ + return self.get_string("captureNetworkTraffic", [type,]) + + def addCustomRequestHeader(self, key, value): + """ + Tells the Selenium server to add the specificed key and value as a custom outgoing request header. This only works if the browser is configured to use the built in Selenium proxy. + + 'key' the header name. + 'value' the header value. + """ + return self.do_command("addCustomRequestHeader", [key,value,]) + + def capture_entire_page_screenshot_to_string(self,kwargs): + """ + Downloads a screenshot of the browser current window canvas to a + based 64 encoded PNG file. The \ *entire* windows canvas is captured, + including parts rendered outside of the current view port. + + Currently this only works in Mozilla and when running in chrome mode. + + 'kwargs' is A kwargs string that modifies the way the screenshot is captured. Example: "background=#CCFFDD". This may be useful to set for capturing screenshots of less-than-ideal layouts, for example where absolute positioning causes the calculation of the canvas dimension to fail and a black background is exposed (possibly obscuring black text). + """ + return self.get_string("captureEntirePageScreenshotToString", [kwargs,]) + + + def shut_down_selenium_server(self): + """ + Kills the running Selenium Server and all browser sessions. After you run this command, you will no longer be able to send + commands to the server; you can't remotely start the server once it has been stopped. Normally + you should prefer to run the "stop" command, which terminates the current browser session, rather than + shutting down the entire server. + + """ + self.do_command("shutDownSeleniumServer", []) + + + def retrieve_last_remote_control_logs(self): + """ + Retrieve the last messages logged on a specific remote control. Useful for error reports, especially + when running multiple remote controls in a distributed environment. The maximum number of log messages + that can be retrieve is configured on remote control startup. + + """ + return self.get_string("retrieveLastRemoteControlLogs", []) + + + def key_down_native(self,keycode): + """ + Simulates a user pressing a key (without releasing it yet) by sending a native operating system keystroke. + This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing + a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and + metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular + element, focus on the element first before running this command. + + 'keycode' is an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes! + """ + self.do_command("keyDownNative", [keycode,]) + + + def key_up_native(self,keycode): + """ + Simulates a user releasing a key by sending a native operating system keystroke. + This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing + a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and + metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular + element, focus on the element first before running this command. + + 'keycode' is an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes! + """ + self.do_command("keyUpNative", [keycode,]) + + + def key_press_native(self,keycode): + """ + Simulates a user pressing and releasing a key by sending a native operating system keystroke. + This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing + a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and + metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular + element, focus on the element first before running this command. + + 'keycode' is an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes! + """ + self.do_command("keyPressNative", [keycode,]) + diff --git a/ntuh/ck/templatetags/__init__.py b/ntuh/ck/templatetags/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/ntuh/ck/templatetags/calculate_age.py b/ntuh/ck/templatetags/calculate_age.py new file mode 100755 index 0000000..13a0197 --- /dev/null +++ b/ntuh/ck/templatetags/calculate_age.py @@ -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) + diff --git a/ntuh/ck/templatetags/stack.py b/ntuh/ck/templatetags/stack.py new file mode 100755 index 0000000..bcfc019 --- /dev/null +++ b/ntuh/ck/templatetags/stack.py @@ -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 + diff --git a/ntuh/ck/tests.py b/ntuh/ck/tests.py new file mode 100755 index 0000000..2247054 --- /dev/null +++ b/ntuh/ck/tests.py @@ -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 +"""} + diff --git a/ntuh/ck/unf.py b/ntuh/ck/unf.py new file mode 100755 index 0000000..97c0513 --- /dev/null +++ b/ntuh/ck/unf.py @@ -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, """ + + + +外科部病歷未完成 + + +""" + + print >> output, "" + print >> output, "" % time.asctime() + print >> output, "" % ('7至30日','超過30日') + for r in result: + print >> output ,"" + for c in r: + print >> output, "" % c + print >> output ,"" + + print >> output, "" + print >> output, "" + print >> output, "" % d1 + print >> output, "" % r1 + print >> output, "" % d2 + print >> output, "" % r2 + print >> output, "" + + print >> output, "
%s
%s%s
%s
---
總計%i總計%i總計%i總計%i
" + print >> output, "
" + print >> output, "" + + + 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, "" + for div in division_sort: + print >> output, "" % div + print >> output, "" + + print >> output, "" + for div in division_sort: + print >> output, "" % div1[div] + print >> output, "" + + print >> output, "" + for div in division_sort: + print >> output, "" % div2[div] + print >> output, "" + + print >> output, "" + for div in division_sort: + print >> output, "" % div3[div] + print >> output, "" + + + print >> output, "
%s
7至30日%s
超過30日%s
合計%s
" + + print >> output, "" + + + + 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, """ + + + +上月外科部病歷未完成 + + +""" + + print >> output, "" + print >> output, "" % time.asctime() + print >> output, "" % EndDate + for r in result: + print >> output ,"" + for c in r: + print >> output, "" % c + print >> output ,"" + + print >> output, "" + print >> output, "" + print >> output, "" % d1 + print >> output, "" % r1 + print >> output, "" + print >> output, "
%s
%s前
%s
---
總計%i總計%i
" + + print >> output, "
" + print >> output, "" + + + div1 = dict(division1) + +# print div1 +# print div2 +# print div3 + + division_sort = sorted(list(div1), key=lambda x: -div1[x]) + # print division_sort + + print >> output, "" + for div in division_sort: + print >> output, "" % div + print >> output, "" + + print >> output, "" + for div in division_sort: + print >> output, "" % div1[div] + print >> output, "" + + + print >> output, "
%s
合計%s
" + + print >> output, "" + + output.close() + + return result + + +unf() +unf_month() diff --git a/ntuh/ck/views.py b/ntuh/ck/views.py new file mode 100755 index 0000000..1e0dc9d --- /dev/null +++ b/ntuh/ck/views.py @@ -0,0 +1,1380 @@ +# coding=utf-8 +# Create your views here. + +import datetime, re +import mx.DateTime + +from django.contrib import auth +from django.contrib.auth.decorators import login_required +from django.core import serializers +from django.core.mail import send_mail +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.db.models import * +from django.http import Http404, HttpResponse, HttpResponseRedirect +#from django.newforms import form_for_instance +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.utils import simplejson +from django.views.generic import * + +from forms import * +from models import * + +import intra + + +@login_required +def patient_add(request): + if request.method == 'POST': + force = True + result = intra.Default_Dr(request.POST) + else: + chartno = request.GET.get('ChartNo', '') + name = request.GET.get('Name', '') + idcode = request.GET.get('idcode', '') + force = request.GET.get('force', '') + result = intra.Default_Dr({'ChartNo': chartno, + 'Name': name, + 'idcode': idcode, + }) + + if force and len(result)==1: + r=result[0] + if r['gender'] == 'M': + r['gender'] = 1 + else: + r['gender'] = 2 + hw = intra.HeightWeight(r['id_cards']) + p = Patient(name = r['name'], + medical_records = r['medical_records'], + gender = r['gender'], + birthday = r['birthday'], + address = r['address'], + phone = r['phone'], + id_cards = r['id_cards'], + height = hw['Height'], + weight = hw['Weight'], + ) + p.save() + return HttpResponseRedirect('/patient/detail/%d/' % p.id) + + form = PatientForm() + return render_to_response('ck/patient_add.html', + { + 'form': form, + 'result': result, + }, context_instance=RequestContext(request)) + +@login_required +def patient_detail(request, object_id): + if object_id=='': + object_id = request.session.get('patient') + patient = Patient.objects.get(id=object_id) + if object_id: + qset = ( + Q(patient=object_id) + ) + treatments = Treatment.objects.filter(qset).distinct() + else: + treatments = [] + +# update patient timestamp + +# ts = patient.timestamp.date() +# for treatment in treatments: +# if treatment.date_completed > ts: +# ts = treatment.date_completed +# if patient.timestamp.date() != ts: +# patient.timestamp = datetime.datetime.combine(ts, patient.timestamp.time()) +# patient.save() + + pacsimages = PACSImage.objects.filter(Q(patient=object_id),Q(Modality__startswith='CT')|Q(Modality__startswith='MR')).order_by('-ExamDate') + if len(pacsimages) > 9: + pacsimages = pacsimages[0:9] + + response = render_to_response("ck/patient_detail.html", + { + "patient": patient, + "treatments": treatments, + "pacsimages": pacsimages, + }, context_instance=RequestContext(request)) + response.set_cookie('patient', object_id) + request.session['patient'] = object_id + return response + +@login_required +def patient_follow(request): + pass + + +@login_required +def patient_print(request, object_id): + if object_id=='': + object_id = request.session.get('patient') + patient = Patient.objects.get(id=object_id) + if object_id: + qset = ( + Q(patient=object_id) + ) + treatments = Treatment.objects.filter(qset).distinct() + else: + treatments = [] + +# update patient timestamp + +# ts = patient.timestamp.date() +# for treatment in treatments: +# if treatment.date_completed > ts: +# ts = treatment.date_completed +# if patient.timestamp.date() != ts: +# patient.timestamp = datetime.datetime.combine(ts, patient.timestamp.time()) +# patient.save() + + response = render_to_response("ck/patient_print.html", + { + "patient": patient, + "treatments": treatments, + }, context_instance=RequestContext(request)) + response.set_cookie('patient', object_id) + request.session['patient'] = object_id + return response + + +@login_required +def patient_search(request): + query = request.GET.get('q', '') + if query: + qset = ( + Q(medical_records__icontains=query) | + Q(name__icontains=query) | + Q(id_cards__icontains=query) + ) + results = Patient.objects.filter(qset).distinct() + if len(results) == 0: + if re.match('^\d+', query): + return HttpResponseRedirect('/patient/add/?ChartNo='+query) + elif re.match('^\w\d+', query): + return HttpResponseRedirect('/patient/add/?idcode='+query) + else: + return HttpResponseRedirect('/patient/add/?Name='+query) + else: + results = [] +# print "query=", query +# print "results=", results + return render_to_response("ck/patient_search.html", + { + "results": results, + "query": query, + }, context_instance=RequestContext(request)) + + +@login_required +def prior_oncrt_query(request, object_id): + if object_id=='': + object_id = request.session.get('patient') + patient = Patient.objects.get(id=object_id) + priors = patient.priortreatment_set; + + answers = intra.oncrt_query(patient.medical_records) + +# return HttpResponse(str(answers)) + + for answer in answers: + if answer['way'] != 'Radiosurgery' and not priors.filter(date=answer['start_date']): + p = PriorTreatment( + patient = patient, + date = answer['start_date'], + treatment = 3, + period = 1, + dose = answer['total_dose'], + memo = answer['site'] + ' ' + answer['remarks'], + ) + p.save() + return HttpResponseRedirect('/patient/detail/'+object_id) + + +@login_required +def prior_op_note(request, object_id): + if object_id=='': + object_id = request.session.get('patient') + patient = Patient.objects.get(id=object_id) + priors = patient.priortreatment_set; + + answers = intra.op_note_case(patient.medical_records) + +# return HttpResponse(str(answers)) + + for answer in answers: + if not priors.filter(date=answer['surgery_date_time']): + p = PriorTreatment( + patient = patient, + date = answer['surgery_date_time'], + treatment = 1, + memo = ' '.join([answer['division'], answer['name_surgery'], answer['surgeon']]) + ) + p.save() + return HttpResponseRedirect('/patient/detail/'+object_id) + +@login_required +def prior_path_exam(request, object_id): + if object_id=='': + object_id = request.session.get('patient') + patient = Patient.objects.get(id=object_id) + pathexams = patient.pathexam_set; + + answers = intra.path_exam(patient.medical_records) + +# return HttpResponse(str(answers)) + + for answer in answers: + if not pathexams.filter(path_code=answer['path_code']): + p = PathExam( + patient = patient, + path_code = answer['path_code'], + specimen_code = answer['specimen_code'], + specimen_get_date = answer['specimen_get_date'], + report_date = answer['report_date'], + division = answer['division'], + bed = answer['bed'], + report = answer['report'], + ) + + p.save() + return HttpResponseRedirect('/patient/detail/'+object_id) + +@login_required +def query_height_weight(request, object_id): + if object_id=='': + object_id = request.session.get('patient') + patient = Patient.objects.get(id=object_id) + hw = intra.HeightWeight(patient.id_cards) + patient.height = hw['Height'] + patient.weight = hw['Weight'] + patient.save() + + return HttpResponseRedirect('/patient/detail/'+object_id) + + +@login_required +def record_weekly(request, date): + if not date: + date = mx.DateTime.today() + + Monday = mx.DateTime.DateTimeFrom(date) + mx.DateTime.RelativeDateTime(days=-6,weekday=(mx.DateTime.Monday,0)) + NextMonday = Monday + mx.DateTime.RelativeDateTime(days=+7) + prevweek = mx.DateTime.DateTimeFrom(date) + mx.DateTime.RelativeDateTime(days=-7) + nextweek = mx.DateTime.DateTimeFrom(date) + mx.DateTime.RelativeDateTime(days=+7) + + weekday = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] + + weekdate = {} + d = Monday + for day in weekday: + weekdate[day] = d.strftime('%m/%d') + d = d + mx.DateTime.RelativeDateTime(days=+1) + +# print NextMonday, NNMonday + qset1 = Q(DTSTART__range=(Monday, NextMonday)) + + qset2 = Q(mode__exact=310) #310: 治療 + + events = VEVENT.objects.filter(qset1, qset2).order_by('DTSTART') + + tids = [] + + for event in events: + tid = event.treatment.id + if not tid in tids: + tids.append(tid) + + treatments = [] + for tid in tids: + t = Treatment.objects.get(id=tid); + treatment = { + 'id': t.id, + 'icd9': t.icd9, + 'other_diagnosis': t.other_diagnosis, + 'accounting': t.get_accounting_display(), + 'dates': [], + 'surgeon': t.surgeon, + 'oncologist': t.oncologist, + 'referral': t.referral, + } + + for event in events: + if tid == event.treatment.id: + treatment['dates'].append(event.DTSTART.date()) + +# for lesion in Treatment.objects.get(id=tid).lesion_set: +# treatment. + + + treatments.append(treatment) + + + +# treatments = Treatment.objects.filter(id__in=tids) + + response = render_to_response("ck/record_weekly.html", + { + "prevweek": prevweek.date, + "nextweek": nextweek.date, + "weeknumber": Monday.strftime('%G年 第%V週'), + "weekdate": weekdate, + "events": events, + "treatments": treatments, + }, context_instance=RequestContext(request)) + return response + + +@login_required +def timetable_scheduling(request, date): + if not date: + date = mx.DateTime.today() + + Monday = mx.DateTime.DateTimeFrom(date) + mx.DateTime.RelativeDateTime(days=+6,weekday=(mx.DateTime.Monday,0)) + NextMonday = Monday + mx.DateTime.RelativeDateTime(days=+7) + prevweek = mx.DateTime.DateTimeFrom(date) + mx.DateTime.RelativeDateTime(days=-7) + nextweek = mx.DateTime.DateTimeFrom(date) + mx.DateTime.RelativeDateTime(days=+7) +# print NextMonday, NNMonday + qset = ( + Q(DTSTART__range=(Monday, NextMonday)) + ) + + treatments = Treatment.objects.filter(output__id__lt=900) + + weekday = ['mon', 'tue', 'wed', 'thu', 'fri'] + + weekdate = {} + d = Monday + for day in weekday: + weekdate[day] = d.strftime('%m/%d') + d = d + mx.DateTime.RelativeDateTime(days=+1) + + timetable = [] + for treatment in treatments: + if treatment.vevent_set.filter(qset): + r = {} + patient = treatment.patient + r['name'] = patient.name + r['medical_records'] = patient.medical_records + r['indications'] = str(treatment.surgeon) + '\n' + str(treatment.oncologist) + r['diagnosis_site'] = treatment.other_diagnosis + r['number'] = '' + r['node'] = '' + r['mode'] = '' + r['time'] = '' + r['actual_treatment'] = '' + for day in weekday: + r[day] = '' + for event in treatment.vevent_set.order_by('DTSTART'): + wd = (event.DTSTART.date() - datetime.datetime.fromtimestamp(Monday).date()).days + if wd in range(0, 6): + r[weekday[wd]] += event.DTSTART.strftime('%H:%M') + ' ' + event.get_mode_display() + '\n' + timetable.append(r) + + +# print timetable + + response = render_to_response("ck/timetable_scheduling.html", + { + "prevweek": prevweek.date, + "nextweek": nextweek.date, + "timetable": timetable, + "weekdate": weekdate, + }, context_instance=RequestContext(request)) + return response + + +@login_required +def treatment_detail(request, object_id): + if object_id=='': + object_id = request.session.get('treatment') + treatment = Treatment.objects.get(id=object_id) + patient = Patient.objects.get(id=treatment.patient_id) +# response.set_cookie('patient', patient.id) + request.session['patient'] = patient.id + if object_id: + qset = ( + Q(treatment=object_id) + ) + vevents = VEVENT.objects.filter(qset).distinct().order_by('DTSTART') + lesions = Lesion.objects.filter(qset).distinct() + else: + vevents = [] + lesions = [] + +# update treatment date_completed + + for vevent in vevents: + vevent.SUMMARY = vevent.treatment.patient.name + vevent.get_mode_display() + vevent.save() + + ds = False + dc = False + + for lesion in lesions: + if lesion.start_date != None: + if not ds or lesion.start_date < ds: + ds = lesion.start_date + if lesion.end_date != None: + if not dc or lesion.end_date > dc: + dc = lesion.end_date + + if not ds: + for vevent in vevents: + if vevent.mode != 310: + continue + if not ds or vevent.DTSTART.date() < ds: + ds = vevent.DTSTART.date() + if not dc or vevent.DTSTART.date() > dc: + dc = vevent.DTSTART.date() + + if not ds: + for vevent in vevents: + if not ds or vevent.DTSTART.date() < ds: + ds = vevent.DTSTART.date() + if not dc or vevent.DTSTART.date() > dc: + dc = vevent.DTSTART.date() + + if ds and treatment.date_started != ds: + treatment.date_started = ds + treatment.save() + if dc and treatment.date_completed != dc: + treatment.date_completed = dc + treatment.save() + + + response = render_to_response("ck/treatment_detail.html", + { + "patient": patient, + "treatment": treatment, + "vevents": vevents, + "lesions": lesions, + }, context_instance=RequestContext(request)) + response.set_cookie('patient', patient.id) + request.session['patient'] = patient.id + response.set_cookie('treatment', object_id) + request.session['treatment'] = object_id + return response + + +@login_required +def treatment_print(request, object_id): + if object_id=='': + object_id = request.session.get('treatment') + treatment = Treatment.objects.get(id=object_id) + patient = Patient.objects.get(id=treatment.patient_id) + if object_id: + qset = ( + Q(treatment=object_id) + ) + vevents = VEVENT.objects.filter(qset).distinct().order_by('DTSTART') + lesions = Lesion.objects.filter(qset).distinct() + else: + vevents = [] + lesions = [] + +# update treatment date_completed + + dc = treatment.date_completed + + for vevent in vevents: + vevent.SUMMARY = vevent.treatment.patient.name + vevent.get_mode_display() + vevent.save() + if vevent.DTSTART.date() > dc: + dc = vevent.DTSTART.date() + + if treatment.date_completed != dc: + treatment.date_completed = dc + treatment.save() + + paginator = Paginator(lesions, 3) # Show 3 lesions per page + + # Make sure page request is an int. If not, deliver first page. + try: + page = int(request.GET.get('page', '1')) + except ValueError: + page = 1 + + # If page request (9999) is out of range, deliver last page of results. + try: + contacts = paginator.page(page) + except (EmptyPage, InvalidPage): + contacts = paginator.page(paginator.num_pages) + + response = render_to_response("ck/treatment_print.html", + { + "patient": patient, + "treatment": treatment, + "vevents": vevents, + "contacts": contacts, + }, context_instance=RequestContext(request)) + response.set_cookie('patient', object_id) + request.session['patient'] = object_id + response.set_cookie('treatment', object_id) + request.session['treatment'] = object_id + return response + + +@login_required +def treatment_record(request, object_id): + if object_id=='': + object_id = request.session.get('treatment') + treatment = Treatment.objects.get(id=object_id) + patient = Patient.objects.get(id=treatment.patient_id) + if object_id: + qset = ( + Q(treatment=object_id) + ) + vevents = VEVENT.objects.filter(qset).distinct().order_by('DTSTART') + lesions = Lesion.objects.filter(qset).distinct() + else: + vevents = [] + lesions = [] + +# update treatment date_completed + + dc = treatment.date_completed + + for vevent in vevents: + vevent.SUMMARY = vevent.treatment.patient.name + vevent.get_mode_display() + vevent.save() + if vevent.DTSTART.date() > dc: + dc = vevent.DTSTART.date() + + if treatment.date_completed != dc: + treatment.date_completed = dc + treatment.save() + +# response = render_to_response("ck/treatment_record.html", + response = render_to_response("ck/brief_history.html", + + { + "patient": patient, + "treatment": treatment, + "vevents": vevents, + "lesions": lesions, + }, context_instance=RequestContext(request)) + response.set_cookie('patient', object_id) + request.session['patient'] = object_id + response.set_cookie('treatment', object_id) + request.session['treatment'] = object_id + return response + + +@login_required +def treatment_report(request, object_id): + if object_id=='': + object_id = request.session.get('treatment') + treatment = Treatment.objects.get(id=object_id) + patient = Patient.objects.get(id=treatment.patient_id) + if object_id: + qset = ( + Q(treatment=object_id) + ) + vevents = VEVENT.objects.filter(qset).distinct().order_by('DTSTART') + lesions = Lesion.objects.filter(qset).distinct() + else: + vevents = [] + lesions = [] + +# update treatment date_completed + + dc = treatment.date_completed + + for vevent in vevents: + vevent.SUMMARY = vevent.treatment.patient.name + vevent.get_mode_display() + vevent.save() + if vevent.DTSTART.date() > dc: + dc = vevent.DTSTART.date() + + if treatment.date_completed != dc: + treatment.date_completed = dc + treatment.save() + + paginator = Paginator(lesions, 5) # Show 25 contacts per page + + # Make sure page request is an int. If not, deliver first page. + try: + page = int(request.GET.get('page', '1')) + except ValueError: + page = 1 + + # If page request (9999) is out of range, deliver last page of results. + try: + contacts = paginator.page(page) + except (EmptyPage, InvalidPage): + contacts = paginator.page(paginator.num_pages) + + response = render_to_response("ck/treatment_report.html", + { + "patient": patient, + "treatment": treatment, + "vevents": vevents, + "contacts": contacts, + }, context_instance=RequestContext(request)) + response.set_cookie('patient', object_id) + request.session['patient'] = object_id + response.set_cookie('treatment', object_id) + request.session['treatment'] = object_id + return response + + +@login_required +def treatment_update(request, object_id): + if request.method == 'POST': +# TreatmentForm = form_for_model(Treatment) + form = TreatmentForm(request.POST) + form.save() + return HttpResponseRedirect('/treatment/list/') + + treatment = Treatment.objects.get(id=object_id) + patient = Patient.objects.get(id=treatment.patient.id) +# TreatmentForm = form_for_instance(treatment) + form = TreatmentForm(instance=treatment) + response = render_to_response("ck/treatment_form2.html", + { + "form": form, + "patient": patient, + }, context_instance=RequestContext(request)) + response.set_cookie('treatment', object_id) + request.session['treatment'] = object_id + return response + + +@login_required +def followup_newimages(request): +# date_start = Treatment.objects.values('patient_id').annotate(date=Min('date_started')) +# print date_start.query +# print date_start + + date_query = Treatment.objects.values('patient_id').annotate(started=Min('date_started'),completed=Max('date_completed')) +# print date_query.query +# print date_query + + images = PACSImage.objects.filter(Q(Saved = 0),Q(Modality__startswith='CT')|Q(Modality__startswith='MR')).order_by('-ExamDate') +# images = PACSImage.objects.get(Q(modality='CT')|Q(modality__startswith='MR')) +# print images + + results = [] + + blank = 0 + + for image in images: + if blank > 50: + break + try: + treat_date = date_query.get(patient=image.patient) + except: + continue +# print image.ExamDate, image.patient.id, treat_date + if image.ExamDate > treat_date['completed']: + try: + LF = LesionFollow.objects.get(Q(Lesion__treatment__patient = image.patient),Q(Date = image.ExamDate)) + image.Saved = 10 #有輸入 + except: + pass + +# print image.patient.name, image.ExamDate + results.append(image) + if image.Saved == 0: + blank += 1 + + + return render_to_response("ck/followup_newimages.html", + { + "images": results, + }, context_instance=RequestContext(request)) + +@login_required +def followup_markimages(request, RequestSheetNo, Saved): + image = PACSImage.objects.get(Q(RequestSheetNo = RequestSheetNo)) + image.Saved = Saved + image.save() + return HttpResponseRedirect('/followup/newimages/') + +@login_required +def followup_nosee1(request): + treatment_date = Treatment.objects.values('patient_id').annotate(first=Min('date_started'),last=Max('date_completed')) + followup_date = Followup.objects.values('patient_id').annotate(first=Min('date'),last=Max('date')) + record_date = MedicalRecord.objects.values('patient_id').annotate(first=Min('InDate'),last=Max('InDate')) + image_date = PACSImage.objects.values('patient_id').annotate(first=Min('ExamDate'),last=Max('ExamDate')) + + patients = Patient.objects.all() + results = [] +# n = datetime.datetime.now() + for patient in patients: + if patient.dead: + continue + + try: + d = treatment_date.get(patient=patient) + date = d['last'] + except: +# print "%s no treatment" % patient.id + continue + + try: + d = followup_date.get(patient=patient) + if d['last'] > date: + date = d['last'] + except: +# print "%s no follow" % patient.id + pass + + try: + d = record_date.get(patient=patient) + if d['last'] > date: + date = d['last'] + except: +# print "%s no record" % patient.id + pass + + try: + d = image_date.get(patient=patient) + if d['last'] > date: + date = d['last'] + except: +# print "%s no image" % patient.id + pass + +# results['patient.id']=date + results.append((patient, date)) + +# print results + results = sorted(results, key=lambda last: last[1])[:25] +# print datetime.datetime.now() - n +# print results + + return render_to_response("ck/followup_nosee.html", + { + "results": results, + }, context_instance=RequestContext(request)) + +@login_required +def followup_nosee(request): + ###Load all dates into hash first + treatment_date = Treatment.objects.values('patient_id').annotate(first=Min('date_started'),last=Max('date_completed')) + t = {} + for item in treatment_date: + t[item['patient_id']] = item['last'] + + followup_date = Followup.objects.values('patient_id').annotate(first=Min('date'),last=Max('date')) + f = {} + for item in followup_date: + f[item['patient_id']] = item['last'] + + record_date = MedicalRecord.objects.values('patient_id').annotate(first=Min('InDate'),last=Max('InDate')) + r = {} + for item in record_date: + r[item['patient_id']] = item['last'] + + image_date = PACSImage.objects.values('patient_id').annotate(first=Min('ExamDate'),last=Max('ExamDate')) + i = {} + for item in image_date: + i[item['patient_id']] = item['last'] + + patients = Patient.objects.all() + results = [] +# n = datetime.datetime.now() + for patient in patients: + if patient.dead: + continue + + try: + d = t[patient.id] + date = d + except: +# print "%s no treatment" % patient.id + continue + + try: + d = f[patient.id] + if d > date: + date = d + except: +# print "%s no follow" % patient.id + pass + + try: + d = r[patient.id] + if d > date: + date = d + except: +# print "%s no record" % patient.id + pass + + try: + d = i[patient.id] + if d > date: + date = d + except: +# print "%s no image" % patient.id + pass + +# results['patient.id']=date + results.append((patient, date)) + +# print results + results = sorted(results, key=lambda last: last[1])[:25] +# print datetime.datetime.now() - n +# print results + + model = Patient + form_class = None + model, form_class = create_update.get_model_and_form_class(model, form_class) + form = form_class() + + return render_to_response("ck/followup_nosee.html", + { + "results": results, + "form": form, + }, context_instance=RequestContext(request)) + +@login_required +def followup_image_long(request): + ###Load all dates into hash first + treatment_date = Treatment.objects.values('patient_id').annotate(first=Min('date_started'),last=Max('date_completed')) + t = {} + for item in treatment_date: + t[item['patient_id']] = item['first'] + + image_date = PACSImage.objects.filter(Saved__in=[0,10]).values('patient_id').annotate(first=Min('ExamDate'),last=Max('ExamDate')) + i = {} + for item in image_date: + i[item['patient_id']] = item['last'] + + v = {} + follows = LesionFollow.objects.select_related().all() +# print follows + for follow in follows: + if not v.has_key(follow.Lesion.treatment.patient.id): + v[follow.Lesion.treatment.patient.id] = follow.Date + else: + v[follow.Lesion.treatment.patient.id] = max(follow.Date, v[follow.Lesion.treatment.patient.id]) + +# print v + + + +# patients = Patient.objects.select_related().all() + patients = Patient.objects.all() + + results = [] +# n = datetime.datetime.now() +# print t +# print i + for patient in patients: + if patient.dead: + continue + + try: + d = t[patient.id] + tdate = d + except: +# print "%s no treatment" % patient.id + continue + + try: + d = i[patient.id] + idate = d + except: +# print "%s no image" % patient.id + continue + + daydiff = (idate-tdate).days + if daydiff < 365: + continue + + try: + d = v[patient.id] + vdate = d + except: +# print "%s no image" % patient.id + vdate = '' + + + +# results['patient.id']=date + results.append((patient, daydiff, tdate, idate, vdate)) + +# print type(idate-tdate) +# print results + results = sorted(results, key=lambda last: -last[1]) +# print datetime.datetime.now() - n +# print results + +# model = Patient +# form_class = None +# model, form_class = create_update.get_model_and_form_class(model, form_class) +# form = form_class() + + return render_to_response("ck/followup_image_long.html", + { + "results": results, +# "form": form, + }, context_instance=RequestContext(request)) + + +@login_required +def followup_new_volume(request,object_id): +# print request +# print 'object_id=', object_id + + idate = '' + + p = request.POST +# print p + if p.has_key('Lesion'): + lesion = Lesion.objects.get(id=p['Lesion']) + + f = lambda x:None if x == '' else x + + l = LesionFollow( + Lesion = lesion, + Date = p['Date'], + Volume = f(p['Volume']), + A = f(p['A']), + B = f(p['B']), + C = f(p['C']), + Memo = p['Memo'], + ) + l.save() + idate = p['Date'] + + try: + idate = request.GET['idate'] + except: + idate = request.COOKIES['date'] + + try: + patient = Patient.objects.get(id=object_id) + except: + patient = Patient.objects.get(id=request.COOKIES['patient']) + + model = LesionFollow + form_class = None + model, form_class = create_update.get_model_and_form_class(model, form_class) + form = form_class() + + response = render_to_response("ck/followup_new_volume.html", + { + "patient": patient, + "idate": idate, + "form": form, + }, context_instance=RequestContext(request)) + response.set_cookie('patient', patient.id) + response.set_cookie('date', idate) + return response + + + + +########## AJAX + +@login_required +def below(request): + return render_to_response('ck/below.html') + +@login_required +def get_target_location(request): +# return HttpResponse(serializers.serialize("xml", TargetLocation.objects.all())) + return HttpResponse(serializers.serialize("json", TargetLocation.objects.all())) + +@login_required +def get_sublocation(request): + i = request.GET.get('id', '') + if i: + return HttpResponse(serializers.serialize("json", SubLocation.objects.filter(id=i))) + target = request.GET.get('target', '') + return HttpResponse(serializers.serialize("json", SubLocation.objects.filter(target_location=target))) + +@login_required +def get_pathology(request): + sl = request.GET.get('sublocation', '') + return HttpResponse(serializers.serialize("json", Pathology.objects.filter(sublocation=sl))) + +def get_icd9cm(request): + id = request.GET.get('id', False) + name = request.GET.get('q', '') + limit = request.GET.get('s', 99) + + if (name.count('.')): + name = name.replace('.', '')[0:5].rstrip() + if id: + codes = ICD9Diag.objects.filter(code=id) + elif len(name): + qset = ( + Q(code__istartswith=name) | +# Q(code__istartswith='0'+name) | +# Q(code__istartswith='00'+name) | + Q(desc__icontains=name) + ) + codes = ICD9Diag.objects.filter(qset)[:100] + else: + codes =ICD9Diag.objects.all()[:100] + + ret = """ +[ +""" + +# ret = '{"results":[' + + for code in codes: +# ret += '["%s","%s"],' % (code.code, code) + ret += '["%s","%s"],' % (code, code.code) +# ret += '\n{"id":"%s","name":"%s"},' % (code.code, code) + + + ret = ret[:-1]+""" +] +""" + + + return HttpResponse(ret) + + +def get_icd9cm_old(request): + id = request.GET.get('id', False) + name = request.GET.get('name', '')[:-1] + if (name.count('.')): + name = name.replace('.', '')[0:5].rstrip() + if id: + codes = ICD9Diag.objects.filter(code=id) + elif len(name): + qset = ( + Q(code__istartswith=name) | +# Q(code__istartswith='0'+name) | +# Q(code__istartswith='00'+name) | + Q(desc__icontains=name) + ) + codes = ICD9Diag.objects.filter(qset)[:100] + else: + codes =ICD9Diag.objects.all()[:100] + + ret = """ +{ 'identifier': 'code', + 'label': 'name', + 'items': [ +""" + +# ret = '{"results":[' + + for code in codes: + ret += '\n{"code":"%s","name":"%s"},' % (code.code, code) +# ret += '\n{"id":"%s","name":"%s"},' % (code.code, code) + + + ret = ret[:-1]+"]}" + + + return HttpResponse(ret) + + +def get_followup(request): + date = request.GET.get('date', '') + if date: + d = mx.DateTime.DateTimeFrom(date) + fr = d + mx.DateTime.RelativeDateTime(weeks=-6) + to = d + mx.DateTime.RelativeDateTime(weeks=+6) + events = Patient.objects.filter(next_followup__range=(fr, to)) + else: + events = Patient.objects.all() + + return HttpResponse(serializers.serialize("json", events, ensure_ascii=False)) + + +def get_vevent_old(request): + date = request.GET.get('date', '') + if date: + d = mx.DateTime.DateTimeFrom(date) + fr = d + mx.DateTime.RelativeDateTime(weeks=-6) + to = d + mx.DateTime.RelativeDateTime(weeks=+6) + events = VEVENT.objects.filter(DTSTART__range=(fr, to)) + else: + events = VEVENT.objects.all() + + return HttpResponse(serializers.serialize("json", events, ensure_ascii=False)) + +''' +DataDumper +''' + +import types +from django.db import models +import pyxslt.serialize +from django.utils import simplejson as json +from django.core.serializers.json import DateTimeAwareJSONEncoder +from decimal import * + +class DataDumper: + fields = {} + def selectObjectFields(self,objectType,fields = []): + self.fields[objectType] = fields + + def dump(self,data,format='xml'): + """ + The main issues with django's default json serializer is that properties that + had been added to a object dynamically are being ignored (and it also has + problems with some models). + """ + + def _any(data): + ret = None + if type(data) is types.ListType: + ret = _list(data) + elif type(data) is types.DictType: + ret = _dict(data) + elif isinstance(data, Decimal): + # json.dumps() cant handle Decimal + ret = str(data) + elif isinstance(data, models.query.QuerySet): + # Actually its the same as a list ... + ret = _list(data) + elif isinstance(data, models.Model): + ret = _model(data) + else: + ret = data + return ret + + def _model(data): + ret = {} + # If we only have a model, we only want to encode the fields. + objType = data.__class__.__name__ + for f in data._meta.fields: + if (self.fields[objType]) and (f.attname in self.fields[objType]): + ret[f.attname] = _any(getattr(data, f.attname)) + # And additionally encode arbitrary properties that had been added. + fields = dir(data.__class__) + ret.keys() + add_ons = [k for k in dir(data) if k not in fields] + for k in add_ons: + if (self.fields[objType]) and (k in self.fields[objType]): + ret[k] = _any(getattr(data, k)) + 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) + if(format == 'xml'): + return pyxslt.serialize.toString(prettyPrintXml=False,data=ret,) + else: + return json.dumps(ret, cls=DateTimeAwareJSONEncoder) + +''' +DataDumper +''' + +@login_required +def get_vevent(request): + events = VEVENT.objects.extra(select={ + 'title' : "SUMMARY", + 'allDay': "false", +# 'start' : "UNIX_TIMESTAMP(DATE_SUB(DTSTART, INTERVAL 8 HOUR))", +# 'end' : "UNIX_TIMESTAMP(ADDTIME(DATE_SUB(DTSTART, INTERVAL 8 HOUR),DURATION))", + 'start' : "UNIX_TIMESTAMP(DTSTART)", + 'end' : "UNIX_TIMESTAMP(ADDTIME(DTSTART,DURATION))", +# 'end' : "UNIX_TIMESTAMP(ADDTIME(DTSTART,DURATION))", +# 'end' : "UNIX_TIMESTAMP(ADDTIME(DTSTART,MAX(DURATION,'00:30:00')))", + 'url' : "CONCAT('/event/update/',id,'/')", + }) + + start = request.GET.get('start', '') + end = request.GET.get('end' , '') + + if start: + fr = datetime.datetime.fromtimestamp(float(start)) + to = datetime.datetime.fromtimestamp(float(end)) + events = events.filter(DTSTART__range=(fr, to)) + + dumper = DataDumper() + dumper.selectObjectFields('VEVENT', ['id', 'title', 'allDay', 'start', 'end', 'url']) + dumper.dump(events,'json') + + return HttpResponse(dumper.dump(events,'json')) + +# return HttpResponse(serializers.serialize("json", events, ensure_ascii=False)) + +# entries = [] +# for event in events: +# starttime = mx.DateTime.DateTimeFrom(event.DTSTART.isoformat()) +# duration = mx.DateTime.DateTimeDeltaFrom(hours=event.DURATION.hour, minutes=event.DURATION.minute) +# endtime = starttime + duration +# entry = { +# 'id': event.id, +# 'starttime': starttime.strftime('%Y-%m-%dT%H:%M:%S'), +# 'endtime': starttime.strftime('%Y-%m-%dT%H:%M:%S'), +# 'allday': False, +# 'repeated': False, +# 'title': event.treatment.patient.name + event.get_mode_display(), +# 'url': "/treatment/detail/%i/" % event.treatment.id, +# 'body': event.SUMMARY, +# 'attributes': { +# 'Oncologist': event.treatment.oncologist.name, +# 'Surgeon': event.treatment.surgeon.name, +# }, +# 'type': [event.get_mode_display(), event.get_mode_display()] +# } +# entries.append(entry) +# return HttpResponse(serializers.serialize("xml", entries, ensure_ascii=False)) + +@login_required +def event_drop(request): + + id = request.GET.get('id' , '') + dayDelta = int(request.GET.get('dayDelta' , '')) + minuteDelta = int(request.GET.get('minuteDelta', '')) + + vevent = VEVENT.objects.get(id=id) + t = vevent.DTSTART + start = mx.DateTime.DateTime(t.year,t.month,t.day,t.hour,t.minute,t.second+1e-6*t.microsecond) + start = start + mx.DateTime.RelativeDateTime(days=dayDelta) + mx.DateTime.RelativeDateTime(minutes=minuteDelta) + vevent.DTSTART = datetime.datetime.fromtimestamp(start) + vevent.save() + + return HttpResponse(serializers.serialize("json", VEVENT.objects.filter(id=id), ensure_ascii=False)) + +@login_required +def event_resize(request): + + id = request.GET.get('id' , '') + dayDelta = int(request.GET.get('dayDelta' , '')) + minuteDelta = int(request.GET.get('minuteDelta', '')) + + vevent = VEVENT.objects.get(id=id) + t = vevent.DURATION + d = datetime.timedelta(days=dayDelta,minutes=minuteDelta) + dt = datetime.datetime.combine(datetime.date.today(), t) + d + vevent.DURATION = dt.time() + vevent.save() + + return HttpResponse(serializers.serialize("json", VEVENT.objects.filter(id=id), ensure_ascii=False)) + +@login_required +def update_vevent(request): + +# id = request.GET.get('id', '') +# starttime = request.GET.get('starttime', '') +# endtime = request.GET.get('endtime', '') + + id = request.POST.get('id', '') + starttime = mx.DateTime.DateTimeFrom(request.POST.get('starttime', '')[:-6]) + endtime = mx.DateTime.DateTimeFrom(request.POST.get('endtime', '')[:-6]) + duration = mx.DateTime.Age(endtime, starttime) + duration = str(duration.hours)+':'+str(duration.minutes) + + print id, starttime, endtime, duration + + vevent = VEVENT.objects.get(id=id) + vevent.DTSTART = starttime +# vevent.DURATION = duration + vevent.save() + + return HttpResponse(serializers.serialize("json", VEVENT.objects.filter(id=id), ensure_ascii=False)) +# return HttpResponse("") + + +def listCalendarByRange(sd, ed, cnt): +# print ret +# $title = array('team meeting', 'remote meeting', 'project plan review', 'annual report', 'go to dinner'); +# $location = array('Lodan', 'Newswer', 'Belion', 'Moore', 'Bytelin'); + + + events = VEVENT.objects.extra(select={ + 'title' : "SUMMARY", + 'allDay': "false", +# 'start' : "UNIX_TIMESTAMP(DATE_SUB(DTSTART, INTERVAL 8 HOUR))", +# 'end' : "UNIX_TIMESTAMP(ADDTIME(DATE_SUB(DTSTART, INTERVAL 8 HOUR),DURATION))", + 'start' : "UNIX_TIMESTAMP(DTSTART)", + 'end' : "UNIX_TIMESTAMP(ADDTIME(DTSTART,DURATION))", +# 'end' : "UNIX_TIMESTAMP(ADDTIME(DTSTART,DURATION))", +# 'end' : "UNIX_TIMESTAMP(ADDTIME(DTSTART,MAX(DURATION,'00:30:00')))", + 'url' : "CONCAT('/event/update/',id,'/')", + }) + + events = events.filter(DTSTART__range=(sd, ed)) +# print events + + revents = [] + + for event in events: + d = event.DURATION + revent = (event.id, + event.title, + event.DTSTART.isoformat(), + (event.DTSTART+datetime.timedelta(minutes=d.minute, hours=d.hour)).isoformat(), + 0, #IsAllDayEvent + 0, #more than one day event + 0, #Recurring event + event.id % 14, #Color + 1, #editable + dict(event.MODE_CHOICES).get(event.mode,''), #Location + '', #attends + ) + print revent + revents.append(revent) + + ret = {} + ret['events'] = revents + ret["issort"] = True + ret["start"] = sd.isoformat() + ret["end"] = ed.isoformat() + ret['error'] = None + + return ret + + +def listCalendar(day, type): + date = datetime.datetime.strptime(day, '%m/%d/%Y') + print date + if type == 'month': + st = date - datetime.timedelta(31) + et = date + datetime.timedelta(31) + cnt = 50 + elif type == 'week': + st = date - datetime.timedelta(7) + et = date + datetime.timedelta(7) + cnt = 20 + elif type == 'day': + st = date - datetime.timedelta(1) + et = date + datetime.timedelta(1) + cnt = 5 + + return listCalendarByRange(st, et, cnt) + +#@login_required +def datafeed(request): + + try: + + # print request + method = request.GET['method'] + # print method + + if method == 'add': + pass + elif method == 'list': + ret = listCalendar(request.POST['showdate'], request.POST['viewtype']) + elif method == 'update': + pass + elif method == 'remove': + pass + elif method == 'adddetails': + pass + + except: + ret = listCalendar('8/3/2011', 'month') + + dumper = DataDumper() + return HttpResponse(dumper.dump(ret,'json')) + + diff --git a/ntuh/ck/vs.csv b/ntuh/ck/vs.csv new file mode 100755 index 0000000..83d1565 --- /dev/null +++ b/ntuh/ck/vs.csv @@ -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,鄭乃禎 diff --git a/ntuh/ck/vs0.csv b/ntuh/ck/vs0.csv new file mode 100755 index 0000000..6bb2caa --- /dev/null +++ b/ntuh/ck/vs0.csv @@ -0,0 +1,62 @@ +GS,B +PED,E +GS,K +GS,T +GS,L +PS, +ICU,_ +CS,Q +GS,J +NS,å +PED,L庳 +CS,}г +NS, +PS,Q +GS,[ +GS, +CVS,Pie +PS,² +GS,Э +NS,F +CVS,^@ +GS, +CVS,q +PS,xǸq +GS,~ +PS,E +GS,姻 +CVS,\al +CS, +PED, +NS,t +PS,GD +NS,Ӱ +CS,ʿ +GS,dģ +PS,ð +NS,~ +NS,ӥ +CVS,` +CVS,iq +PS,©s +GS,h +GS,s +CVS,Ƭ +PED,\ +CS, +CVS,Ѱ +GS, +NS,糹 +NS,ڵ +GS,L +NS,h +PS,ºa +NS, +GS, +CVS,ӽ +CVS,dݷu +GS,i +GS, +GS,ͤ +GS,z +CVS,Ds diff --git a/ntuh/context_processors.py b/ntuh/context_processors.py new file mode 100755 index 0000000..98513dd --- /dev/null +++ b/ntuh/context_processors.py @@ -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, + } \ No newline at end of file diff --git a/ntuh/cron.sh b/ntuh/cron.sh new file mode 100755 index 0000000..1abfe30 --- /dev/null +++ b/ntuh/cron.sh @@ -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 + diff --git a/ntuh/cron_hourly.sh b/ntuh/cron_hourly.sh new file mode 100755 index 0000000..4f2d3c0 --- /dev/null +++ b/ntuh/cron_hourly.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +. /home/ntuh/d18env/bin/activate + +locale + +cd $(dirname $0) +python getop.py diff --git a/ntuh/doc/plastic_category.txt b/ntuh/doc/plastic_category.txt new file mode 100755 index 0000000..70c4e64 --- /dev/null +++ b/ntuh/doc/plastic_category.txt @@ -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 diff --git a/ntuh/dojango/__init__.py b/ntuh/dojango/__init__.py new file mode 100755 index 0000000..dc84c29 --- /dev/null +++ b/ntuh/dojango/__init__.py @@ -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 diff --git a/ntuh/dojango/appengine/README b/ntuh/dojango/appengine/README new file mode 100755 index 0000000..8507ba0 --- /dev/null +++ b/ntuh/dojango/appengine/README @@ -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. diff --git a/ntuh/dojango/appengine/__init__.py b/ntuh/dojango/appengine/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/ntuh/dojango/appengine/dojo_serve.py b/ntuh/dojango/appengine/dojo_serve.py new file mode 100755 index 0000000..9dfa175 --- /dev/null +++ b/ntuh/dojango/appengine/dojo_serve.py @@ -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!
") + if stats: + self.response.out.write("

Memcache stats:

") + for key in stats.keys(): + self.response.out.write("%s: %s
" % (key, stats[key])) + self.response.out.write("

") + +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() diff --git a/ntuh/dojango/appengine/memcache_zipserve.py b/ntuh/dojango/appengine/memcache_zipserve.py new file mode 100755 index 0000000..613ce80 --- /dev/null +++ b/ntuh/dojango/appengine/memcache_zipserve.py @@ -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() diff --git a/ntuh/dojango/bin/dojobuild.py b/ntuh/dojango/bin/dojobuild.py new file mode 100755 index 0000000..ef16e46 --- /dev/null +++ b/ntuh/dojango/bin/dojobuild.py @@ -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__) \ No newline at end of file diff --git a/ntuh/dojango/conf/__init__.py b/ntuh/dojango/conf/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/ntuh/dojango/conf/settings.py b/ntuh/dojango/conf/settings.py new file mode 100755 index 0000000..7d77ffe --- /dev/null +++ b/ntuh/dojango/conf/settings.py @@ -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) diff --git a/ntuh/dojango/context_processors.py b/ntuh/dojango/context_processors.py new file mode 100755 index 0000000..c465925 --- /dev/null +++ b/ntuh/dojango/context_processors.py @@ -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 \ No newline at end of file diff --git a/ntuh/dojango/data/__init__.py b/ntuh/dojango/data/__init__.py new file mode 100755 index 0000000..1c3a9cd --- /dev/null +++ b/ntuh/dojango/data/__init__.py @@ -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 \ No newline at end of file diff --git a/ntuh/dojango/data/modelstore/__init__.py b/ntuh/dojango/data/modelstore/__init__.py new file mode 100755 index 0000000..c4138eb --- /dev/null +++ b/ntuh/dojango/data/modelstore/__init__.py @@ -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' +) diff --git a/ntuh/dojango/data/modelstore/exceptions.py b/ntuh/dojango/data/modelstore/exceptions.py new file mode 100755 index 0000000..9e2ecf2 --- /dev/null +++ b/ntuh/dojango/data/modelstore/exceptions.py @@ -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 + """ diff --git a/ntuh/dojango/data/modelstore/fields.py b/ntuh/dojango/data/modelstore/fields.py new file mode 100755 index 0000000..0f32bcd --- /dev/null +++ b/ntuh/dojango/data/modelstore/fields.py @@ -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': ''} + """ + + # 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() diff --git a/ntuh/dojango/data/modelstore/methods.py b/ntuh/dojango/data/modelstore/methods.py new file mode 100755 index 0000000..7bcd65f --- /dev/null +++ b/ntuh/dojango/data/modelstore/methods.py @@ -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') diff --git a/ntuh/dojango/data/modelstore/services.py b/ntuh/dojango/data/modelstore/services.py new file mode 100755 index 0000000..22d0513 --- /dev/null +++ b/ntuh/dojango/data/modelstore/services.py @@ -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 diff --git a/ntuh/dojango/data/modelstore/stores.py b/ntuh/dojango/data/modelstore/stores.py new file mode 100755 index 0000000..1a90a0b --- /dev/null +++ b/ntuh/dojango/data/modelstore/stores.py @@ -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: .__ + """ + 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 diff --git a/ntuh/dojango/data/modelstore/treestore.py b/ntuh/dojango/data/modelstore/treestore.py new file mode 100755 index 0000000..5054280 --- /dev/null +++ b/ntuh/dojango/data/modelstore/treestore.py @@ -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() \ No newline at end of file diff --git a/ntuh/dojango/data/modelstore/utils.py b/ntuh/dojango/data/modelstore/utils.py new file mode 100755 index 0000000..6a012cf --- /dev/null +++ b/ntuh/dojango/data/modelstore/utils.py @@ -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 .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 diff --git a/ntuh/dojango/data/piston/__init__.py b/ntuh/dojango/data/piston/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/ntuh/dojango/data/piston/emitters.py b/ntuh/dojango/data/piston/emitters.py new file mode 100755 index 0000000..72a68d8 --- /dev/null +++ b/ntuh/dojango/data/piston/emitters.py @@ -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') diff --git a/ntuh/dojango/decorators.py b/ntuh/dojango/decorators.py new file mode 100755 index 0000000..85648db --- /dev/null +++ b/ntuh/dojango/decorators.py @@ -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 diff --git a/ntuh/dojango/dojo-media/dojango.profile.js b/ntuh/dojango/dojo-media/dojango.profile.js new file mode 100755 index 0000000..daa5fd0 --- /dev/null +++ b/ntuh/dojango/dojo-media/dojango.profile.js @@ -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 + ] +} + diff --git a/ntuh/dojango/dojo-media/dojango/_base.js b/ntuh/dojango/dojo-media/dojango/_base.js new file mode 100755 index 0000000..5d0a2bd --- /dev/null +++ b/ntuh/dojango/dojo-media/dojango/_base.js @@ -0,0 +1,5 @@ +dojo.provide("dojango._base"); + +dojo.mixin(dojango, { + // This where we can mixin functions that are directly accessbile via dojango.* +}); \ No newline at end of file diff --git a/ntuh/dojango/dojo-media/dojango/dojango.js b/ntuh/dojango/dojo-media/dojango/dojango.js new file mode 100755 index 0000000..5def028 --- /dev/null +++ b/ntuh/dojango/dojo-media/dojango/dojango.js @@ -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 diff --git a/ntuh/dojango/dojo-media/dojango/form/Form.js b/ntuh/dojango/dojo-media/dojango/form/Form.js new file mode 100755 index 0000000..ec7faaf --- /dev/null +++ b/ntuh/dojango/dojo-media/dojango/form/Form.js @@ -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("
", ""); // 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 diff --git a/ntuh/dojango/dojo-media/dojango/resources/dojango_logo.jpg b/ntuh/dojango/dojo-media/dojango/resources/dojango_logo.jpg new file mode 100755 index 0000000..16ecba6 Binary files /dev/null and b/ntuh/dojango/dojo-media/dojango/resources/dojango_logo.jpg differ diff --git a/ntuh/dojango/dojo-media/dojango/resources/note.gif b/ntuh/dojango/dojo-media/dojango/resources/note.gif new file mode 100755 index 0000000..0f921be Binary files /dev/null and b/ntuh/dojango/dojo-media/dojango/resources/note.gif differ diff --git a/ntuh/dojango/dojo-media/dojango/resources/tube.gif b/ntuh/dojango/dojo-media/dojango/resources/tube.gif new file mode 100755 index 0000000..b506513 Binary files /dev/null and b/ntuh/dojango/dojo-media/dojango/resources/tube.gif differ diff --git a/ntuh/dojango/dojo-media/dojango/resources/tubeTall.gif b/ntuh/dojango/dojo-media/dojango/resources/tubeTall.gif new file mode 100755 index 0000000..e4fdb8b Binary files /dev/null and b/ntuh/dojango/dojo-media/dojango/resources/tubeTall.gif differ diff --git a/ntuh/dojango/dojo-media/dojango/widget/ThumbnailPicker.js b/ntuh/dojango/dojo-media/dojango/widget/ThumbnailPicker.js new file mode 100755 index 0000000..743ae5b --- /dev/null +++ b/ntuh/dojango/dojo-media/dojango/widget/ThumbnailPicker.js @@ -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 + } +); \ No newline at end of file diff --git a/ntuh/dojango/dojo-media/dojango/widget/plugins/InsertImage.js b/ntuh/dojango/dojo-media/dojango/widget/plugins/InsertImage.js new file mode 100755 index 0000000..bccdd31 --- /dev/null +++ b/ntuh/dojango/dojo-media/dojango/widget/plugins/InsertImage.js @@ -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: + // | // + // | // + // | 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: [ + '
', + '', + '
' + ].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"}); + } +}); \ No newline at end of file diff --git a/ntuh/dojango/dojo-media/dojango/widget/resources/ThumbnailPicker.css b/ntuh/dojango/dojo-media/dojango/widget/resources/ThumbnailPicker.css new file mode 100755 index 0000000..38e03db --- /dev/null +++ b/ntuh/dojango/dojo-media/dojango/widget/resources/ThumbnailPicker.css @@ -0,0 +1,5 @@ +.imgSelected { + opacity:0.5; + -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; + filter: alpha(opacity=50); +} \ No newline at end of file diff --git a/ntuh/dojango/dojo-media/dojango_optimized.profile.js b/ntuh/dojango/dojo-media/dojango_optimized.profile.js new file mode 100755 index 0000000..49ba182 --- /dev/null +++ b/ntuh/dojango/dojo-media/dojango_optimized.profile.js @@ -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 + ] +} \ No newline at end of file diff --git a/ntuh/dojango/dojo-media/src/README b/ntuh/dojango/dojo-media/src/README new file mode 100755 index 0000000..19e29b1 --- /dev/null +++ b/ntuh/dojango/dojo-media/src/README @@ -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! diff --git a/ntuh/dojango/forms/__init__.py b/ntuh/dojango/forms/__init__.py new file mode 100755 index 0000000..bff1ed3 --- /dev/null +++ b/ntuh/dojango/forms/__init__.py @@ -0,0 +1,4 @@ +from django.forms import * +from widgets import * +from fields import * +from models import * diff --git a/ntuh/dojango/forms/fields.py b/ntuh/dojango/forms/fields.py new file mode 100755 index 0000000..f2e5ce7 --- /dev/null +++ b/ntuh/dojango/forms/fields.py @@ -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 diff --git a/ntuh/dojango/forms/formsets.py b/ntuh/dojango/forms/formsets.py new file mode 100755 index 0000000..039f151 --- /dev/null +++ b/ntuh/dojango/forms/formsets.py @@ -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) diff --git a/ntuh/dojango/forms/models.py b/ntuh/dojango/forms/models.py new file mode 100755 index 0000000..a6ba2ed --- /dev/null +++ b/ntuh/dojango/forms/models.py @@ -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) diff --git a/ntuh/dojango/forms/widgets.py b/ntuh/dojango/forms/widgets.py new file mode 100755 index 0000000..ac667a0 --- /dev/null +++ b/ntuh/dojango/forms/widgets.py @@ -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'%s' % (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 = '' % flatatt(self.store_attrs) + return mark_safe(u'%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: +#
+'''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 diff --git a/ntuh/dojango/management/__init__.py b/ntuh/dojango/management/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/ntuh/dojango/management/commands/__init__.py b/ntuh/dojango/management/commands/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/ntuh/dojango/management/commands/dojobuild.py b/ntuh/dojango/management/commands/dojobuild.py new file mode 100755 index 0000000..43be4ca --- /dev/null +++ b/ntuh/dojango/management/commands/dojobuild.py @@ -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() diff --git a/ntuh/dojango/management/commands/dojoload.py b/ntuh/dojango/management/commands/dojoload.py new file mode 100755 index 0000000..6188a86 --- /dev/null +++ b/ntuh/dojango/management/commands/dojoload.py @@ -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() diff --git a/ntuh/dojango/middleware.py b/ntuh/dojango/middleware.py new file mode 100755 index 0000000..55b4ee6 --- /dev/null +++ b/ntuh/dojango/middleware.py @@ -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 \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]) \ No newline at end of file diff --git a/ntuh/dojango/models.py b/ntuh/dojango/models.py new file mode 100755 index 0000000..71a8362 --- /dev/null +++ b/ntuh/dojango/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/ntuh/dojango/templates/dojango/base.html b/ntuh/dojango/templates/dojango/base.html new file mode 100755 index 0000000..e9d70e5 --- /dev/null +++ b/ntuh/dojango/templates/dojango/base.html @@ -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 %} + +{% endblock %} +{% endspaceless %}{% block _dojango_html_start_tag %} + +{% endblock %} +{% spaceless %} + + {% block _dojango_meta_content_type %}{% 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 %} + {% block dojango_page_title %}{% endblock %} + {% block _dojango_dojo_styles %} + + {% endblock %} + + + {% 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 %} + + + + {# for a locally built version dojango should already be included #} + {% if not DOJANGO.IS_LOCAL_BUILD %} + + {% endif %} + + {% block dojango_header_extra %}{% endblock %} + +{% endspaceless %} + +{% block dojango_body %} + + {% block dojango_content %} + {% endblock %} + {% block dojango_require %} + + {% endblock %} + +{% endblock %} + + diff --git a/ntuh/dojango/templates/dojango/base_i18n.html b/ntuh/dojango/templates/dojango/base_i18n.html new file mode 100755 index 0000000..c89102f --- /dev/null +++ b/ntuh/dojango/templates/dojango/base_i18n.html @@ -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 %}{% endblock %} +{% block _dojango_post_dj_config %} + +{% endblock %} \ No newline at end of file diff --git a/ntuh/dojango/templates/dojango/include.html b/ntuh/dojango/templates/dojango/include.html new file mode 100755 index 0000000..6a8826b --- /dev/null +++ b/ntuh/dojango/templates/dojango/include.html @@ -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 %} + + +{% 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 %} + +{# for a local builded version dojango should already be included #} +{% if not DOJANGO.IS_LOCAL_BUILD %} + +{% endif %} \ No newline at end of file diff --git a/ntuh/dojango/templates/dojango/include_i18n.html b/ntuh/dojango/templates/dojango/include_i18n.html new file mode 100755 index 0000000..57d5373 --- /dev/null +++ b/ntuh/dojango/templates/dojango/include_i18n.html @@ -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 %} + +{% endblock %} \ No newline at end of file diff --git a/ntuh/dojango/templates/dojango/json_iframe.html b/ntuh/dojango/templates/dojango/json_iframe.html new file mode 100755 index 0000000..b4e53b2 --- /dev/null +++ b/ntuh/dojango/templates/dojango/json_iframe.html @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ntuh/dojango/templates/dojango/templatetag/datagrid_disp.html b/ntuh/dojango/templates/dojango/templatetag/datagrid_disp.html new file mode 100755 index 0000000..f2486d2 --- /dev/null +++ b/ntuh/dojango/templates/dojango/templatetag/datagrid_disp.html @@ -0,0 +1,57 @@ +{% load dojango_base %} +{% load dojango_filters %} +{% set_dojango_context %} + +{# TODO: Add a css loader to the dojo collector #} + + + +{% if show_search %}
 Search:
{% endif %} +
diff --git a/ntuh/dojango/templates/dojango/test.html b/ntuh/dojango/templates/dojango/test.html new file mode 100755 index 0000000..bbc86ee --- /dev/null +++ b/ntuh/dojango/templates/dojango/test.html @@ -0,0 +1,757 @@ +{% extends "dojango/base.html" %} + +{% block dojango_post_dj_config %} + +{% endblock %} + +{% block dojango_header_extra %} + + + + +{% endblock %} + +{% block dojango_body %} + + +
Loading themeTester:
+ + +
+
+ + + + + +
+ +

Dijit Theme Test Page

+ + +
+ +
+ +
+

Tooltips:

+
    +
  • + rich text tooltip + + Embedded bold RICH text weirdness! + +
  • + +
  • anchor tooltip + tooltip on anchor +
  • + +
+ +
+ +

Dialogs:

+ + +
+ Show Tooltip Dialog +
+ + + + + + + + + + + +
+ +
+
+
+
+
+ +
+ +
+
+
+ +
+ + +
+ +
+ +

Dijit Color Palette(7x10)

+
+
+ Test color is: + +

+
+
+ + + +
+ + +
+ + + + + + + + + + +
+ +

dijit.Editor:

+ +
+ +
+
+ + +

dijit.InlineEditBox + dijit.form.TextBox on <h3>

+ + (HTML before) +

+ Edit me - I trigger the onChange callback +

+ (HTML after) + +
+ +

dijit.InlineEditBox + dijit.form.Textarea

+ + (HTML before) +

+ 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 ... +

+ (HTML after) + +

+ These links will + disable / + enable + the text area above. +

+ +
+ +

dijit.form.DateTextBox:

+ + (HTML inline before) + 12/30/2005 + (HTML after) + +
+ +

dijit.form.TimeTextBox:

+ + (HTML inline before) + 9:00 AM + (HTML after) + +
+ + +

dijit.form.FilteringSelect + Inline + remote data store:

+ (HTML inline before) + + Indiana + + (HTML after) + +
+ + + + + + +
+ + +
+ + +
+

You can explore this single page after applying a Theme + for use in creation of your own theme.

+ +

I am whole slew of Widgets on a page. Jump to dijit tests to + test individual components.

+ +

There is a right-click [context] pop-up menu here, as well.

+
+ +
+ +
+ +
+

I am the last Tab

+ +
+ +
+ +
+
+ + + +
+

Congratulations, dojango is running!

+ +
+ Press ESC to close dialog, or
+ +
+ + +{% endblock %} diff --git a/ntuh/dojango/templatetags/__init__.py b/ntuh/dojango/templatetags/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/ntuh/dojango/templatetags/dojango_base.py b/ntuh/dojango/templatetags/dojango_base.py new file mode 100755 index 0000000..f669348 --- /dev/null +++ b/ntuh/dojango/templatetags/dojango_base.py @@ -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 + diff --git a/ntuh/dojango/templatetags/dojango_filters.py b/ntuh/dojango/templatetags/dojango_filters.py new file mode 100755 index 0000000..f9bc98f --- /dev/null +++ b/ntuh/dojango/templatetags/dojango_filters.py @@ -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) diff --git a/ntuh/dojango/templatetags/dojango_grid.py b/ntuh/dojango/templatetags/dojango_grid.py new file mode 100755 index 0000000..ae06071 --- /dev/null +++ b/ntuh/dojango/templatetags/dojango_grid.py @@ -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)) diff --git a/ntuh/dojango/urls.py b/ntuh/dojango/urls.py new file mode 100755 index 0000000..e71c0c3 --- /dev/null +++ b/ntuh/dojango/urls.py @@ -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.+)/(?P.+)/$', 'views.datagrid_list', name="dojango-datagrid-list"), +) + +if settings.DEBUG: + # serving the media files for dojango / dojo (js/css/...) + urlpatterns += media.url_patterns diff --git a/ntuh/dojango/util/__init__.py b/ntuh/dojango/util/__init__.py new file mode 100755 index 0000000..de39f3a --- /dev/null +++ b/ntuh/dojango/util/__init__.py @@ -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 diff --git a/ntuh/dojango/util/config.py b/ntuh/dojango/util/config.py new file mode 100755 index 0000000..37cf593 --- /dev/null +++ b/ntuh/dojango/util/config.py @@ -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 diff --git a/ntuh/dojango/util/dojo_collector.py b/ntuh/dojango/util/dojo_collector.py new file mode 100755 index 0000000..1486812 --- /dev/null +++ b/ntuh/dojango/util/dojo_collector.py @@ -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 + diff --git a/ntuh/dojango/util/form.py b/ntuh/dojango/util/form.py new file mode 100755 index 0000000..4e2e8fa --- /dev/null +++ b/ntuh/dojango/util/form.py @@ -0,0 +1,39 @@ +from dojango.util import is_number + +def get_combobox_data(request): + """Return the standard live search data that are posted from a ComboBox widget. + summary: + A ComboBox using the QueryReadStore sends the following data: + name - is the search string (it always ends with a '*', that is how the dojo.data API defined it) + start - the paging start + count - the number of entries to return + The ComboBox and QueryReadStore usage should be like this: +
+ + The most important things here are the attributes requestMethod and doClientPaging! + The 'doClientPaging' makes the combobox send 'start' and 'count' parameters and the server + shall do the paging. + + returns: + a tuple containing + search_string - the string typed into the combobox + start - at which data set to start + end - at which data set to stop + 'start' and 'end' are already prepared to be directly used in the + limit part of the queryset, i.e. Idea.objects.all()[start:end] + + throws: + Exception - if the request method is not POST. + ValueError - if start or count parameter is not an int. + """ + if not request.method=='POST': + raise Exception('POST request expected.') + string = request.POST.get('name', '') + if string.endswith('*'): # Actually always the case. + string = string[:-1] + start = int(request.POST.get('start', 0)) + end = request.POST.get('count', 10) + if not is_number(end): # The dojo combobox may return "Infinity" tsss + end = 10 + end = start+int(end) + return string, start, end \ No newline at end of file diff --git a/ntuh/dojango/util/media.py b/ntuh/dojango/util/media.py new file mode 100755 index 0000000..c06bfa6 --- /dev/null +++ b/ntuh/dojango/util/media.py @@ -0,0 +1,96 @@ +from django.conf import settings +from dojango.conf import settings as dojango_settings +from django.core.exceptions import ImproperlyConfigured +from django.utils._os import safe_join +from django.conf.urls.defaults import patterns +from os import path, listdir + +def find_app_dir(app_name): + """Given an app name (from settings.INSTALLED_APPS) return the abspath + to that app directory""" + i = app_name.rfind('.') + if i == -1: + m, a = app_name, None + else: + m, a = app_name[:i], app_name[i+1:] + try: + if a is None: + mod = __import__(m, {}, {}, []) + else: + mod = getattr(__import__(m, {}, {}, [a]), a) + return path.dirname(path.abspath(mod.__file__)) + except ImportError, e: + raise ImproperlyConfigured, 'ImportError %s: %s' % (app_name, e.args[0]) + +def find_app_dojo_dir(app_name): + """Checks, if a dojo-media directory exists within a given app and returns the absolute path to it.""" + base = find_app_dir(app_name) + if base: + media_dir = safe_join(base, 'dojo-media') + if path.isdir(media_dir): return media_dir + return None # no dojo-media directory was found within that app + +def find_app_dojo_dir_and_url(app_name): + """Returns a list of tuples of dojo modules within an apps 'dojo-media' directory. + Each tuple contains the abspath to the module directory and the module name. + """ + ret = [] + media_dir = find_app_dojo_dir(app_name) + if not media_dir: return None + for d in listdir(media_dir): + if path.isdir(safe_join(media_dir, d)): + if d not in ("src", "release") and not d.startswith("."): + ret.append(tuple([safe_join(media_dir, d), "%(module)s" % { + 'module': d + }])) + return tuple(ret) + +dojo_media_library = dict((app, find_app_dojo_dir_and_url(app)) + for app in settings.INSTALLED_APPS) +dojo_media_apps = tuple(app for app in settings.INSTALLED_APPS + if dojo_media_library[app]) + +def _check_app_dojo_dirs(): + """Checks, that each dojo module is just present once. Otherwise it would throw an error.""" + check = {} + for app in dojo_media_apps: + root_and_urls = dojo_media_library[app] + for elem in root_and_urls: + root, url = elem + if url in check and root != dojo_media_library[check[url]][0]: + raise ImproperlyConfigured, ( + "Two apps (%s, %s) contain the same dojo module (%s) in the dojo-media-directory pointing to two different directories (%s, %s)" % + (repr(app), repr(check[url]), repr(root.split("/")[-1]), repr(root), repr(dojo_media_library[check[url]][0][0]))) + check[url] = app + +def _build_urlmap(): + """Creating a url map for all dojo modules (dojo-media directory), that are available within activated apps.""" + seen = {} + valid_urls = [] # keep the order! + for app in dojo_media_apps: + root_and_urls = dojo_media_library[app] + for elem in root_and_urls: + root, url = elem + if url.startswith('/'): url = url[1:] + if url in seen: continue + valid_urls.append((url, root)) + seen[url] = root + base_url = dojango_settings.DOJO_MEDIA_URL # dojango_settings.BASE_MEDIA_URL + if base_url.startswith('/'): base_url = base_url[1:] + # all new modules need to be available next to dojo, so we need to allow a version-string in between + # e.g. /dojo-media/1.3.1/mydojonamespace == /dojo-media/1.2.0/mydojonamespace + valid_urls = [("%(base_url)s/([\w\d\.\-]*/)?%(url)s" % { + 'base_url': base_url, + 'url': m[0] + }, m[1]) for m in valid_urls] + + valid_urls.append(("%(base_url)s/release/" % {'base_url': base_url}, path.join(dojango_settings.BASE_MEDIA_ROOT, "release"))) + valid_urls.append(("%(base_url)s/" % {'base_url': base_url}, path.join(dojango_settings.BASE_MEDIA_ROOT, "src"))) + return valid_urls + +_check_app_dojo_dirs() # is each dojo module just created once? + +dojo_media_urls = _build_urlmap() +urls = [ ('^%s(?P.*)$' % url, 'serve', {'document_root': root, 'show_indexes': True} ) + for url, root in dojo_media_urls ] +url_patterns = patterns('django.views.static', *urls) # url_patterns that can be used directly within urls.py \ No newline at end of file diff --git a/ntuh/dojango/util/perms.py b/ntuh/dojango/util/perms.py new file mode 100755 index 0000000..71d4a31 --- /dev/null +++ b/ntuh/dojango/util/perms.py @@ -0,0 +1,26 @@ +from django.conf import settings + +def access_model(app_name, model_name, request=None, instance=None): + """ + Return true to allow access to a given instance of app_name.model_name + """ + acl = getattr(settings, "DOJANGO_DATAGRID_ACCESS", []) + for x in acl: + try: + if x.find(".")>0: + app,model = x.split('.') + if app_name == app and model_name==model: return True + else: + if app_name == x or model_name==x: return True + except: + pass + return False + +def access_model_field(app_name, model_name, field_name, request=None, instance=None): + """ + Return true to allow access of a given field_name to model app_name.model_name given + a specific object of said model. + """ + # in django version 1.2 a new attribute is on all models: _state of type ModelState + # that field shouldn't be accessible + return not field_name in ('delete', '_state',) \ No newline at end of file diff --git a/ntuh/dojango/views.py b/ntuh/dojango/views.py new file mode 100755 index 0000000..06d47dd --- /dev/null +++ b/ntuh/dojango/views.py @@ -0,0 +1,240 @@ +# Create your views here. +from django.db.models import get_model +from django.db import models +from django.shortcuts import render_to_response +from django.conf import settings + +from dojango.util import to_dojo_data, json_encode +from dojango.decorators import json_response +from dojango.util import to_dojo_data +from dojango.util.form import get_combobox_data +from dojango.util.perms import access_model, access_model_field + +import operator + +# prof included for people using http://www.djangosnippets.org/snippets/186/ +AVAILABLE_OPTS = ('search_fields','prof','inclusions','sort','search','count','order','start') + +@json_response +def datagrid_list(request, app_name, model_name, access_model_callback=access_model, access_field_callback=access_model_field): + """ + Renders a json representation of a model within an app. Set to handle GET params passed + by dojos ReadQueryStore for the dojango datagrid. The following GET params are handled with + specially: + 'search_fields','inclusions','sort','search','count','order','start' + + search_fields: list of fields for model to equal the search, each OR'd together. + search: see search_fields + sort: sets order_by + count: sets limit + start: sets offset + inclusions: list of functions in the model that will be called and result added to JSON + + any other GET param will be added to the filter on the model to determine what gets returned. ie + a GET param of id__gt=5 will result in the equivalent of model.objects.all().filter( id__gt=5 ) + + The access_model_callback is a function that gets passed the request, app_name, model_name, and + an instance of the model which will only be added to the json response if returned True + + The access_field_callback gets passed the request, app_name, model_name, field_name, + and the instance. Return true to allow access of a given field_name to model + app_name.model_name given instance model. + + The default callbacks will allow access to any model in added to the DOJANGO_DATAGRID_ACCESS + in settings.py and any function/field that is not "delete" + """ + + # get the model + model = get_model(app_name,model_name) + + # start with a very broad query set + target = model.objects.all() + + # modify query set based on the GET params, dont do the start/count splice + # custom options passed from "query" param in datagrid + for key in [ d for d in request.GET.keys() if not d in AVAILABLE_OPTS]: + target = target.filter(**{str(key):request.GET[key]}) + num = target.count() + + # until after all clauses added + if request.GET.has_key('search') and request.GET.has_key('search_fields'): + ored = [models.Q(**{str(k).strip(): unicode(request.GET['search'])} ) for k in request.GET['search_fields'].split(",")] + target = target.filter(reduce(operator.or_, ored)) + + if request.GET.has_key('sort') and request.GET["sort"] not in request.GET["inclusions"] and request.GET["sort"][1:] not in request.GET["inclusions"]: + # if the sort field is in inclusions, it must be a function call.. + target = target.order_by(request.GET['sort']) + else: + if request.GET.has_key('sort') and request.GET["sort"].startswith('-'): + target = sorted(target, lambda x,y: cmp(getattr(x,request.GET["sort"][1:])(),getattr(y,request.GET["sort"][1:])())); + target.reverse(); + elif request.GET.has_key('sort'): + target = sorted(target, lambda x,y: cmp(getattr(x,request.GET["sort"])(),getattr(y,request.GET["sort"])())); + + + # get only the limit number of models with a given offset + target=target[int(request.GET['start']):int(request.GET['start'])+int(request.GET['count'])] + # create a list of dict objects out of models for json conversion + complete = [] + for data in target: + # TODO: complete rewrite to use dojangos already existing serializer (or the dojango ModelStore) + if access_model_callback(app_name, model_name, request, data): + ret = {} + for f in data._meta.fields: + if access_field_callback(app_name, model_name, f.attname, request, data): + if isinstance(f, models.ImageField) or isinstance(f, models.FileField): # filefields can't be json serialized + ret[f.attname] = unicode(getattr(data, f.attname)) + else: + ret[f.attname] = getattr(data, f.attname) #json_encode() this? + fields = dir(data.__class__) + ret.keys() + add_ons = [k for k in dir(data) if k not in fields and access_field_callback(app_name, model_name, k, request, data)] + for k in add_ons: + ret[k] = getattr(data, k) + if request.GET.has_key('inclusions'): + for k in request.GET['inclusions'].split(','): + if k == "": continue + if access_field_callback(app_name, model_name, k, request, data): + try: + ret[k] = getattr(data,k)() + except: + try: + ret[k] = eval("data.%s"%".".join(k.split("__"))) + except: + ret[k] = getattr(data,k) + complete.append(ret) + else: + raise Exception, "You're not allowed to query the model '%s.%s' (add it to the array of the DOJANGO_DATAGRID_ACCESS setting)" % (model_name, app_name) + return to_dojo_data(complete, identifier=model._meta.pk.name, num_rows=num) + +########### +# Tests # +########### + +def test(request): + return render_to_response('dojango/test.html') + +@json_response +def test_countries(request): + countries = { 'identifier': 'name', + 'label': 'name', + 'items': [ + { 'name':'Africa', 'type':'continent', 'population':'900 million', 'area': '30,221,532 sq km', + 'timezone': '-1 UTC to +4 UTC', + 'children':[{'_reference':'Egypt'}, {'_reference':'Kenya'}, {'_reference':'Sudan'}] }, + { 'name':'Egypt', 'type':'country' }, + { 'name':'Kenya', 'type':'country', + 'children':[{'_reference':'Nairobi'}, {'_reference':'Mombasa'}] }, + { 'name':'Nairobi', 'type':'city' }, + { 'name':'Mombasa', 'type':'city' }, + { 'name':'Sudan', 'type':'country', + 'children':{'_reference':'Khartoum'} }, + { 'name':'Khartoum', 'type':'city' }, + { 'name':'Asia', 'type':'continent', + 'children':[{'_reference':'China'}, {'_reference':'India'}, {'_reference':'Russia'}, {'_reference':'Mongolia'}] }, + { 'name':'China', 'type':'country' }, + { 'name':'India', 'type':'country' }, + { 'name':'Russia', 'type':'country' }, + { 'name':'Mongolia', 'type':'country' }, + { 'name':'Australia', 'type':'continent', 'population':'21 million', + 'children':{'_reference':'Commonwealth of Australia'}}, + { 'name':'Commonwealth of Australia', 'type':'country', 'population':'21 million'}, + { 'name':'Europe', 'type':'continent', + 'children':[{'_reference':'Germany'}, {'_reference':'France'}, {'_reference':'Spain'}, {'_reference':'Italy'}] }, + { 'name':'Germany', 'type':'country' }, + { 'name':'Spain', 'type':'country' }, + { 'name':'Italy', 'type':'country' }, + { 'name':'North America', 'type':'continent', + 'children':[{'_reference':'Mexico'}, {'_reference':'Canada'}, {'_reference':'United States of America'}] }, + { 'name':'Mexico', 'type':'country', 'population':'108 million', 'area':'1,972,550 sq km', + 'children':[{'_reference':'Mexico City'}, {'_reference':'Guadalajara'}] }, + { 'name':'Mexico City', 'type':'city', 'population':'19 million', 'timezone':'-6 UTC'}, + { 'name':'Guadalajara', 'type':'city', 'population':'4 million', 'timezone':'-6 UTC' }, + { 'name':'Canada', 'type':'country', 'population':'33 million', 'area':'9,984,670 sq km', + 'children':[{'_reference':'Ottawa'}, {'_reference':'Toronto'}] }, + { 'name':'Ottawa', 'type':'city', 'population':'0.9 million', 'timezone':'-5 UTC'}, + { 'name':'Toronto', 'type':'city', 'population':'2.5 million', 'timezone':'-5 UTC' }, + { 'name':'United States of America', 'type':'country' }, + { 'name':'South America', 'type':'continent', + 'children':[{'_reference':'Brazil'}, {'_reference':'Argentina'}] }, + { 'name':'Brazil', 'type':'country', 'population':'186 million' }, + { 'name':'Argentina', 'type':'country', 'population':'40 million' }, + ] + } + + return countries + +@json_response +def test_states(request): + states = [ + {'name':"Alabama", 'label':"Alabama",'abbreviation':"AL"}, + {'name':"Alaska", 'label':"Alaska",'abbreviation':"AK"}, + {'name':"American Samoa", 'label':"American Samoa",'abbreviation':"AS"}, + {'name':"Arizona", 'label':"Arizona",'abbreviation':"AZ"}, + {'name':"Arkansas", 'label':"Arkansas",'abbreviation':"AR"}, + {'name':"Armed Forces Europe", 'label':"Armed Forces Europe",'abbreviation':"AE"}, + {'name':"Armed Forces Pacific", 'label':"Armed Forces Pacific",'abbreviation':"AP"}, + {'name':"Armed Forces the Americas", 'label':"Armed Forces the Americas",'abbreviation':"AA"}, + {'name':"California", 'label':"California",'abbreviation':"CA"}, + {'name':"Colorado", 'label':"Colorado",'abbreviation':"CO"}, + {'name':"Connecticut", 'label':"Connecticut",'abbreviation':"CT"}, + {'name':"Delaware", 'label':"Delaware",'abbreviation':"DE"}, + {'name':"District of Columbia", 'label':"District of Columbia",'abbreviation':"DC"}, + {'name':"Federated States of Micronesia", 'label':"Federated States of Micronesia",'abbreviation':"FM"}, + {'name':"Florida", 'label':"Florida",'abbreviation':"FL"}, + {'name':"Georgia", 'label':"Georgia",'abbreviation':"GA"}, + {'name':"Guam", 'label':"Guam",'abbreviation':"GU"}, + {'name':"Hawaii", 'label':"Hawaii",'abbreviation':"HI"}, + {'name':"Idaho", 'label':"Idaho",'abbreviation':"ID"}, + {'name':"Illinois", 'label':"Illinois",'abbreviation':"IL"}, + {'name':"Indiana", 'label':"Indiana",'abbreviation':"IN"}, + {'name':"Iowa", 'label':"Iowa",'abbreviation':"IA"}, + {'name':"Kansas", 'label':"Kansas",'abbreviation':"KS"}, + {'name':"Kentucky", 'label':"Kentucky",'abbreviation':"KY"}, + {'name':"Louisiana", 'label':"Louisiana",'abbreviation':"LA"}, + {'name':"Maine", 'label':"Maine",'abbreviation':"ME"}, + {'name':"Marshall Islands", 'label':"Marshall Islands",'abbreviation':"MH"}, + {'name':"Maryland", 'label':"Maryland",'abbreviation':"MD"}, + {'name':"Massachusetts", 'label':"Massachusetts",'abbreviation':"MA"}, + {'name':"Michigan", 'label':"Michigan",'abbreviation':"MI"}, + {'name':"Minnesota", 'label':"Minnesota",'abbreviation':"MN"}, + {'name':"Mississippi", 'label':"Mississippi",'abbreviation':"MS"}, + {'name':"Missouri", 'label':"Missouri",'abbreviation':"MO"}, + {'name':"Montana", 'label':"Montana",'abbreviation':"MT"}, + {'name':"Nebraska", 'label':"Nebraska",'abbreviation':"NE"}, + {'name':"Nevada", 'label':"Nevada",'abbreviation':"NV"}, + {'name':"New Hampshire", 'label':"New Hampshire",'abbreviation':"NH"}, + {'name':"New Jersey", 'label':"New Jersey",'abbreviation':"NJ"}, + {'name':"New Mexico", 'label':"New Mexico",'abbreviation':"NM"}, + {'name':"New York", 'label':"New York",'abbreviation':"NY"}, + {'name':"North Carolina", 'label':"North Carolina",'abbreviation':"NC"}, + {'name':"North Dakota", 'label':"North Dakota",'abbreviation':"ND"}, + {'name':"Northern Mariana Islands", 'label':"Northern Mariana Islands",'abbreviation':"MP"}, + {'name':"Ohio", 'label':"Ohio",'abbreviation':"OH"}, + {'name':"Oklahoma", 'label':"Oklahoma",'abbreviation':"OK"}, + {'name':"Oregon", 'label':"Oregon",'abbreviation':"OR"}, + {'name':"Pennsylvania", 'label':"Pennsylvania",'abbreviation':"PA"}, + {'name':"Puerto Rico", 'label':"Puerto Rico",'abbreviation':"PR"}, + {'name':"Rhode Island", 'label':"Rhode Island",'abbreviation':"RI"}, + {'name':"South Carolina", 'label':"South Carolina",'abbreviation':"SC"}, + {'name':"South Dakota", 'label':"South Dakota",'abbreviation':"SD"}, + {'name':"Tennessee", 'label':"Tennessee",'abbreviation':"TN"}, + {'name':"Texas", 'label':"Texas",'abbreviation':"TX"}, + {'name':"Utah", 'label':"Utah",'abbreviation':"UT"}, + {'name':"Vermont", 'label':"Vermont",'abbreviation':"VT"}, + {'name': "Virgin Islands, U.S.",'label':"Virgin Islands, U.S.",'abbreviation':"VI"}, + {'name':"Virginia", 'label':"Virginia",'abbreviation':"VA"}, + {'name':"Washington", 'label':"Washington",'abbreviation':"WA"}, + {'name':"West Virginia", 'label':"West Virginia",'abbreviation':"WV"}, + {'name':"Wisconsin", 'label':"Wisconsin",'abbreviation':"WI"}, + {'name':"Wyoming", 'label':"Wyoming",'abbreviation':"WY"} + ] + # Implement a very simple search! + search_string, start, end = get_combobox_data(request) + ret = [] + for state in states: + if state['name'].lower().startswith(search_string.lower()): + ret.append(state) + ret = ret[start:end] + + # Convert the data into dojo.date-store compatible format. + return to_dojo_data(ret, identifier='abbreviation') diff --git a/ntuh/fileupload b/ntuh/fileupload new file mode 120000 index 0000000..3f085f9 --- /dev/null +++ b/ntuh/fileupload @@ -0,0 +1 @@ +submodule/django-jquery-file-upload/fileupload/ \ No newline at end of file diff --git a/ntuh/get_inpatient.py b/ntuh/get_inpatient.py new file mode 100755 index 0000000..bbf621f --- /dev/null +++ b/ntuh/get_inpatient.py @@ -0,0 +1,206 @@ +#!/usr/bin/xvfb-run python +# -*- coding: utf-8 -*- + +import six + +if six.PY2: + import sys + reload(sys) # Reload does the trick! + sys.setdefaultencoding('utf8') + +import os +os.environ['DJANGO_SETTINGS_MODULE'] = 'ntuh.settings' + +import django +django.setup() + + +from dateutil.relativedelta import * + +import datetime +import json +import glob + +from django.db.models import * + +import timeout_decorator + +# from ntuhgov import xportal +# from ntuhgov import portal_spynner +from ntuhgov.portal_selenium import Login, QueryInPatientByMonth, PatientMedicalRecordListQuery +from registry.models import * + + +import logging +FORMAT = '%(asctime)s %(levelname)s %(message)s' +logging.basicConfig(format=FORMAT, level=logging.INFO) + +# SESSION=portal_spynner.Login() + +def ScanInpatient(ID, year, month): + logging.info('Scan %s %d-%d' % (ID, year, month)) +# list = xportal.GetPatientList(ID, year, month) + list = QueryInPatientByMonth(ID, year, month) + for Pat in list: +# print json.dumps(Pat, indent=1) +# print json.dumps(Pat, ensure_ascii=False, indent=1) + # logging.info(Pat) + p, created = Inpatient.objects.get_or_create(ChartNo = Pat['PatChartNo']) + p.Ward = Pat['WardLabel'] + p.Room = Pat['RoomLabel'] + p.Bed = Pat['BedLabel'] + p.Name = Pat['LinkPatientName'] +# p.ChartNo = Pat['ChartNo'] + p.Sex = Pat['PatientSex'] + if 'PatientAgeTitle' in Pat: # 'PatientAge': '待確認' + p.Birthday = Pat['PatientAgeTitle'][-10:].replace('_', '-') + p.Age = Pat['PatientAge'] + p.HospitalDays = Pat[u'住院總天數'][:-1] + p.Enter = Pat[u'入'].replace('/', '-') + p.save() + + +def ScanAllInpatient(): + today = datetime.datetime.today().date() + first = Inpatient.objects.latest('Enter').Enter + datetime.timedelta(days = -365) # should be 30 if can run daily +# last = today + datetime.timedelta(days = -28) + for ph in Physician.objects.all(): + vs = ph.EmployeeID +# ScanInpatient(vs, last.year, last.month) + + last = first + while True: + ScanInpatient(vs, last.year, last.month) + last += datetime.timedelta(days = 30) + +# print last, today +# print last.month, today.month +# print type(last), type(today) + + if last > today and last.month != today.month: + break + +# ForceFetch: also fetch patient discharged more than 7 days +@timeout_decorator.timeout(3600) +def ScanDischargeNote(MAX_COUNT=1000, ForceFetch = False): # should be 100 if can run daily + count = 0 + for i in Inpatient.objects.extra(select={'discharge': "Enter + HospitalDays"}).order_by('-discharge', 'ChartNo'): + + #skip VIP + if 'OOO' in i.Name: + continue + + DischargeDays = (datetime.date.today() - i.Enter).days - i.HospitalDays + if DischargeDays <= 0: + logging.info('Skip %s...Still admitted' % i.ChartNo) + continue + + if DischargeDays >= 365: + continue + + if DischargeDays >= 7: + Notes = DischargeNote.objects.filter(ChartNo=i.ChartNo) + # logging.info(Notes) + # LastNoteDate = LastNote.latest('OutDate').OutDate + LastNote = Notes.order_by('OutDate').last() + if LastNote: # is not None + LastNoteDate = LastNote.OutDate + logging.info((i.Enter, i.HospitalDays, LastNoteDate)) + if (LastNoteDate - i.Enter).days + 1 >= i.HospitalDays: + logging.info( 'Skip %s...%s' % (i.ChartNo, LastNoteDate)) + continue + # else: + # continue + + +# if not ForceFetch and DischargeDays >= 7: +# continue +# LastNoteDate = DischargeNote.objects.filter(ChartNo=i.ChartNo).aggregate(Max('OutDate'))['OutDate__max'] +# if LastNoteDate and (LastNoteDate - i.Enter).days >= i.HospitalDays: +# print 'Skip %s...%s' % (i.ChartNo, LastNoteDate) +# continue + + logging.info('%d Fetch %s... discharged %d days ago' % (count, i.ChartNo, DischargeDays)) + +# notes = xportal.ShowDischargeNote(i.ChartNo) +# print 1 + notes = PatientMedicalRecordListQuery(i.ChartNo, AfterDate = i.Enter+relativedelta(years=-1)) +# print json.dumps(notes, ensure_ascii=False, indent=1) + for note in notes['InPat']: + if 'html' not in note or u'暫存' in note['html']: + logging.info((' 暫存', note['InDate'], note['OutDate'])) + continue +# if u'版本' in note['HTML']: +# print '版本' + + dn, created = DischargeNote.objects.get_or_create(AccountIDSE = note['AccountIDSE']) + + dn.ChartNo = i.ChartNo + + dn.HospName = note['HospName'] + dn.DeptName = note['DeptName'] + dn.InDate = note['InDate'].replace('/','-') + dn.OutDate = note['OutDate'].replace('/','-') + dn.WardName = note['WardName'] + dn.RoomName = note['RoomName'] + dn.BedName = note['BedName'] + dn.MainDrName = note['MainDrName'] + dn.MainDiagnosisName = note['MainDiagnosisName'] + dn.StatusName = note['StatusName'] +# dn.ShowDischargeNote = note['ShowDischargeNote'] + +# dn.AccountIDSE = note['AccountIDSE'] + dn.Func = note['Func'] + dn.KeyCodeList = note['KeyCodeList'] + dn.KeyNameList = note['KeyNameList'] + dn.HTML = note['html'] + + dn.save() + + count += 1 + if count > MAX_COUNT: + break + return count + + +#xportal.ShowDischargeNote('3260946') + + +def RemovePDF(): + fileList = glob.glob('*.pdf') + + # Iterate over the list of filepaths & remove each file. + for filePath in fileList: + try: + os.remove(filePath) + except: + print("Error while deleting file : ", filePath) + +ScanAllInpatient() +try: + ScanDischargeNote() +except Exception as e: + logging.exception(e) + +RemovePDF() + + + +#print ScanDischargeNote(MAX_COUNT=10000, ForceFetch = True) + + +#for year in range(2008, 2013): +# for month in range(1, 13): +# for ph in Physician.objects.all(): +# vs = ph.EmployeeID +# ScanInpatient(vs, year, month) + + +#for month in range(1, 13): +# for ph in Physician.objects.all(): +# vs = ph.EmployeeID +# ScanInpatient(vs, 2007, month) + +# ScanInpatient(vs, 2012, 5) + + diff --git a/ntuh/getas18.py b/ntuh/getas18.py new file mode 100755 index 0000000..6990059 --- /dev/null +++ b/ntuh/getas18.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +# 舊方法,應該用 getres + +from datetime import datetime + +# import collections +import os +import re + +from openpyxl import Workbook + +OP_DIR = "/shares/Public/0/OP/" + +today = datetime.today() +yymm = today.strftime('%Y-%m') + +OUT_XLSX = '/shares/Public/0/%s.xlsx' % yymm + + +# print(yymm) + + +pattern = 'Assistants(.+?)
' + +R_DICT = {} + +def DumpR(): + wb = Workbook() + ws = wb.active + for r in sorted(R_DICT.keys()): + print('%s,%s,%d'% (r, R_DICT[r]['title'], R_DICT[r]['count'])) + ws.append([r, R_DICT[r]['title'], R_DICT[r]['count']]) + + wb.save(OUT_XLSX) + + +def AddR(r): + r = r.replace(' ', ' ') + + if '陳榮宏' in r: + print (r) + + if ' ' in r: + title, name = r.split(' ') + else: + title, name = (None, r) + + if name in R_DICT: + R_DICT[name]['count'] += 1 + else: + R_DICT[name] = {'count': 1, 'title': ''} + + if title: + R_DICT[name]['title'] = title + +def GetAssistants(filepath): + f = open(filepath) + note = f.read() + f.close() + # print (note) + # print(filepath) + + match = re.search(pattern, note) + + if match is None: + return + + # print (match.group(1)) + + for r in match.group(1).split(', '): + AddR(r.strip()) + + # DumpR() + # exit() + +for f in os.listdir(OP_DIR): + if yymm not in f: + continue + filepath = os.path.join(OP_DIR, f) + GetAssistants(filepath) + +DumpR() + + + diff --git a/ntuh/getctmr.py b/ntuh/getctmr.py new file mode 100755 index 0000000..c681ad6 --- /dev/null +++ b/ntuh/getctmr.py @@ -0,0 +1,80 @@ +#!/usr/bin/xvfb-run python +# -*- coding: utf-8 -*- + +### PYTHON + +from datetime import * + +import random +import time + +import logging +FORMAT = '%(asctime)s %(levelname)s %(message)s' +logging.basicConfig(format=FORMAT, level=logging.INFO) + +import os +os.environ['DJANGO_SETTINGS_MODULE'] = 'ntuh.settings' + +import six +if six.PY2: + import sys + reload(sys) # Reload does the trick! + sys.setdefaultencoding('utf8') + +### PUBLIC + +from dateutil.relativedelta import * +from django.db.models import * + +import django +django.setup() + +### PRIVATE + +# from ntuhgov import xportal +from ntuhgov.portal_selenium import Login, ReportPathology, CTMR +from registry.models import * + + +def main(): + + req_no_of_random_items = 13 + + qs = PathologyReport.objects.all() + possible_ids = list(qs.values_list('PersonID', flat=True)) + possible_ids = random.choices(possible_ids, k=req_no_of_random_items) + + SECONDS_LEFT = 300 + + for PersonID in possible_ids: + seconds = random.randint(0, SECONDS_LEFT) + logging.info((PersonID, 'sleep', seconds, 'of', SECONDS_LEFT)) + SECONDS_LEFT -= seconds + time.sleep(seconds) + reports = CTMR(PersonID) + for r in reports: + p, created = XrayTextReport.objects.get_or_create(ReportKey = r['ReportKey']) + + p.ChartNo = r['ChartNo'] + p.ReportKey = r['ReportKey'] + p.ReportCode = r['ReportCode'] + p.PersonID = r['PersonID'] + + p.OrderDesc = r['OrderDesc'] + p.ExamDate = r['ExamDate'].replace('/', '-').split(' ')[0] + if '*' not in r['ReportDate']: + p.ReportDate = r['ReportDate'].replace('/', '-').split(' ')[0] + + p.Impression = r['Impression'] + logging.info(p.__dict__) + + p.Exam = r['Exam'] + + p.url = r['url'] + p.html = r['html'] + + p.save() + + +if __name__ == '__main__': + main() diff --git a/ntuh/getop.py b/ntuh/getop.py new file mode 100755 index 0000000..39344c6 --- /dev/null +++ b/ntuh/getop.py @@ -0,0 +1,124 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# from django.core.management import setup_environ +# import settings +# setup_environ(settings) + +#from django.conf import settings + +import six + +if six.PY2: + import sys + reload(sys) # Reload does the trick! + sys.setdefaultencoding('utf8') + +import os +os.environ['DJANGO_SETTINGS_MODULE'] = 'ntuh.settings' +# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ntuh.settings") + +import django +django.setup() + +# from django.db.models.loading import get_models +# loaded_models = get_models() + + +from pprint import pprint +from datetime import date, datetime, timedelta +from dateutil.relativedelta import * + +#from django.utils.html import strip_tags +from django.core.exceptions import ObjectDoesNotExist +# from django.template.defaultfilters import removetags + +from registry.models import * +from registry.utils import SaveOP +# from ntuhgov.portal_spynner import Login, SimpleQueryOpSchedule +from ntuhgov.portal_selenium import Login, SimpleQueryOpSchedule + + + +if six.PY2: + from BeautifulSoup import BeautifulSoup +else: + from bs4 import BeautifulSoup + +import calendar + +#def strip_tags(page): +# return ' '.join(BeautifulSoup(page).findAll(text=True)) + + +def strip_tags(source): #strip the + + +For debugging purposes, I have created a grid.loader.js which does the same +loading of the files as in previous versions. The location of the file is in +src directory of the package. In order to use this, the variable pathojsfiles +should be adjusted to point to the appropriate folder - see 3.4.x docs. + diff --git a/ntuh/sitestatic/jquery.jqGrid/js/jquery-1.7.2.min.js b/ntuh/sitestatic/jquery.jqGrid/js/jquery-1.7.2.min.js new file mode 100755 index 0000000..16ad06c --- /dev/null +++ b/ntuh/sitestatic/jquery.jqGrid/js/jquery-1.7.2.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.7.2 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="
"+""+"
",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="
t
",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( +a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
","
"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f +.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/ntuh/sitestatic/jquery.jqGrid/js/jquery.jqGrid.min.js b/ntuh/sitestatic/jquery.jqGrid/js/jquery.jqGrid.min.js new file mode 100755 index 0000000..45633a1 --- /dev/null +++ b/ntuh/sitestatic/jquery.jqGrid/js/jquery.jqGrid.min.js @@ -0,0 +1,520 @@ +/* +* jqGrid 4.3.3 - jQuery Grid +* Copyright (c) 2008, Tony Tomov, tony@trirand.com +* Dual licensed under the MIT and GPL licenses +* http://www.opensource.org/licenses/mit-license.php +* http://www.gnu.org/licenses/gpl-2.0.html +* Date:2012-05-31 +* Modules: grid.base.js; jquery.fmatter.js; grid.custom.js; grid.common.js; grid.formedit.js; grid.filter.js; grid.inlinedit.js; grid.celledit.js; jqModal.js; jqDnR.js; grid.subgrid.js; grid.grouping.js; grid.treegrid.js; grid.import.js; JsonXml.js; grid.tbltogrid.js; grid.jqueryui.js; +*/ +/* + jqGrid 4.3.3 - jQuery Grid + Copyright (c) 2008, Tony Tomov, tony@trirand.com + Dual licensed under the MIT and GPL licenses + http://www.opensource.org/licenses/mit-license.php + http://www.gnu.org/licenses/gpl-2.0.html + Date: 2012-05-31 +*/ +(function(b){b.jgrid=b.jgrid||{};b.extend(b.jgrid,{version:"4.3.3",htmlDecode:function(b){return b&&(" "==b||" "==b||1===b.length&&160===b.charCodeAt(0))?"":!b?b:(""+b).replace(/>/g,">").replace(/</g,"<").replace(/"/g,'"').replace(/&/g,"&")},htmlEncode:function(b){return!b?b:(""+b).replace(/&/g,"&").replace(/\"/g,""").replace(//g,">")},format:function(d){var f=b.makeArray(arguments).slice(1);void 0===d&&(d="");return d.replace(/\{(\d+)\}/g, +function(b,e){return f[e]})},getCellIndex:function(d){d=b(d);if(d.is("tr"))return-1;d=(!d.is("td")&&!d.is("th")?d.closest("td,th"):d)[0];return b.browser.msie?b.inArray(d,d.parentNode.cells):d.cellIndex},stripHtml:function(b){var b=b+"",f=/<("[^"]*"|'[^']*'|[^'">])*>/gi;return b?(b=b.replace(f,""))&&" "!==b&&" "!==b?b.replace(/\"/g,"'"):"":b},stripPref:function(d,f){var c=b.type(d);if("string"==c||"number"==c)d=""+d,f=""!==d?(""+f).replace(""+d,""):f;return f},stringToDoc:function(b){var f; +if("string"!==typeof b)return b;try{f=(new DOMParser).parseFromString(b,"text/xml")}catch(c){f=new ActiveXObject("Microsoft.XMLDOM"),f.async=!1,f.loadXML(b)}return f&&f.documentElement&&"parsererror"!=f.documentElement.tagName?f:null},parse:function(d){"while(1);"==d.substr(0,9)&&(d=d.substr(9));"/*"==d.substr(0,2)&&(d=d.substr(2,d.length-4));d||(d="{}");return!0===b.jgrid.useJSON&&"object"===typeof JSON&&"function"===typeof JSON.parse?JSON.parse(d):eval("("+d+")")},parseDate:function(d,f){var c= +{m:1,d:1,y:1970,h:0,i:0,s:0,u:0},e,a,i;e=/[\\\/:_;.,\t\T\s-]/;if(f&&null!==f&&void 0!==f){f=b.trim(f);f=f.split(e);void 0!==b.jgrid.formatter.date.masks[d]&&(d=b.jgrid.formatter.date.masks[d]);var d=d.split(e),g=b.jgrid.formatter.date.monthNames,h=b.jgrid.formatter.date.AmPm,j=function(a,b){0===a?12===b&&(b=0):12!==b&&(b+=12);return b};e=0;for(a=d.length;ei&&(f[e]=i+1,c.m=f[e])),"F"==d[e]&&(i=b.inArray(f[e],g),-1!==i&&11i&&f[e]==h[i]&&(f[e]=i,c.h=j(f[e],c.h))),"A"==d[e]&&(i=b.inArray(f[e],h),-1!==i&&1=e?c.y=1900+c.y:0<=e&&69>=e&&(c.y=2E3+c.y);void 0!==c.j&&(c.d=c.j);void 0!==c.n&&(c.m=parseInt(c.n,10)-1)}return new Date(c.y,c.m,c.d,c.h,c.i,c.s,c.u)},jqID:function(b){return(""+b).replace(/[!"#$%&'()*+,.\/:;<=>?@\[\\\]\^`{|}~]/g,"\\$&")}, +guid:1,uidPref:"jqg",randId:function(d){return(d?d:b.jgrid.uidPref)+b.jgrid.guid++},getAccessor:function(b,f){var c,e,a=[],i;if("function"===typeof f)return f(b);c=b[f];if(void 0===c)try{if("string"===typeof f&&(a=f.split(".")),i=a.length)for(c=b;c&&i--;)e=a.shift(),c=c[e]}catch(g){}return c},getXmlData:function(d,f,c){var e="string"===typeof f?f.match(/^(.*)\[(\w+)\]$/):null;if("function"===typeof f)return f(d);if(e&&e[2])return e[1]?b(e[1],d).attr(e[2]):b(d).attr(e[2]);d=b(f,d);return c?d:0
"),f=d.appendTo("body").find("td").width();d.remove();return 5!==f},ajaxOptions:{},from:function(d){return new function(f,c){"string"==typeof f&&(f=b.data(f));var e=this,a=f,i=!0,d=!1,h=c,j=/[\$,%]/g,m=null,k=null,n=0,p=!1,o="",u=[],w=!0;if("object"==typeof f&&f.push)0b?e:0;!i&&"number"!==typeof a&&"number"!==typeof b&&(a=(""+a).toLowerCase(),b=(""+b).toLowerCase());return ab?e:0};this._performSort=function(){0!==u.length&&(a=e._doSort(a,0))};this._doSort=function(a,b){var c=u[b].by,i=u[b].dir, +d=u[b].type,f=u[b].datefmt;if(b==u.length-1)return e._getOrder(a,c,i,d,f);b++;c=e._getGroup(a,c,i,d,f);i=[];for(d=0;d",d)};this.less=function(a,b,d){return e._compareValues(e.less,a,b,"<",d)};this.greaterOrEquals=function(a,b,d){return e._compareValues(e.greaterOrEquals,a,b,">=",d)};this.lessOrEquals=function(a,b,d){return e._compareValues(e.lessOrEquals,a,b,"<=",d)};this.startsWith=function(a,c){var i=void 0===c||null===c?a:c,i=d?b.trim(i.toString()).length: +i.toString().length;w?e._append(e._getStr("jQuery.jgrid.getAccessor(this,'"+a+"')")+".substr(0,"+i+") == "+e._getStr('"'+e._toStr(c)+'"')):(i=d?b.trim(c.toString()).length:c.toString().length,e._append(e._getStr("this")+".substr(0,"+i+") == "+e._getStr('"'+e._toStr(a)+'"')));e._setCommand(e.startsWith,a);e._resetNegate();return e};this.endsWith=function(a,c){var i=void 0===c||null===c?a:c,i=d?b.trim(i.toString()).length:i.toString().length;w?e._append(e._getStr("jQuery.jgrid.getAccessor(this,'"+a+ +"')")+".substr("+e._getStr("jQuery.jgrid.getAccessor(this,'"+a+"')")+".length-"+i+","+i+') == "'+e._toStr(c)+'"'):e._append(e._getStr("this")+".substr("+e._getStr("this")+'.length-"'+e._toStr(a)+'".length,"'+e._toStr(a)+'".length) == "'+e._toStr(a)+'"');e._setCommand(e.endsWith,a);e._resetNegate();return e};this.contains=function(a,b){w?e._append(e._getStr("jQuery.jgrid.getAccessor(this,'"+a+"')")+'.indexOf("'+e._toStr(b)+'",0) > -1'):e._append(e._getStr("this")+'.indexOf("'+e._toStr(a)+'",0) > -1'); +e._setCommand(e.contains,a);e._resetNegate();return e};this.groupBy=function(b,d,c,i){return!e._hasData()?null:e._getGroup(a,b,d,c,i)};this.orderBy=function(a,d,c,i){d=void 0===d||null===d?"a":b.trim(d.toString().toLowerCase());if(null===c||void 0===c)c="text";if(null===i||void 0===i)i="Y-m-d";if("desc"==d||"descending"==d)d="d";if("asc"==d||"ascending"==d)d="a";u.push({by:a,dir:d,type:c,datefmt:i});return e};return e}(d,null)},extend:function(d){b.extend(b.fn.jqGrid,d);this.no_legacy_api||b.fn.extend(d)}}); +b.fn.jqGrid=function(d){if("string"==typeof d){var f=b.jgrid.getAccessor(b.fn.jqGrid,d);if(!f)throw"jqGrid - No such method: "+d;var c=b.makeArray(arguments).slice(1);return f.apply(this,c)}return this.each(function(){if(!this.grid){var e=b.extend(!0,{url:"",height:150,page:1,rowNum:20,rowTotal:null,records:0,pager:"",pgbuttons:!0,pginput:!0,colModel:[],rowList:[],colNames:[],sortorder:"asc",sortname:"",datatype:"xml",mtype:"GET",altRows:!1,selarrrow:[],savedRow:[],shrinkToFit:!0,xmlReader:{},jsonReader:{}, +subGrid:!1,subGridModel:[],reccount:0,lastpage:0,lastsort:0,selrow:null,beforeSelectRow:null,onSelectRow:null,onSortCol:null,ondblClickRow:null,onRightClickRow:null,onPaging:null,onSelectAll:null,loadComplete:null,gridComplete:null,loadError:null,loadBeforeSend:null,afterInsertRow:null,beforeRequest:null,beforeProcessing:null,onHeaderClick:null,viewrecords:!1,loadonce:!1,multiselect:!1,multikey:!1,editurl:null,search:!1,caption:"",hidegrid:!0,hiddengrid:!1,postData:{},userData:{},treeGrid:!1,treeGridModel:"nested", +treeReader:{},treeANode:-1,ExpandColumn:null,tree_root_level:0,prmNames:{page:"page",rows:"rows",sort:"sidx",order:"sord",search:"_search",nd:"nd",id:"id",oper:"oper",editoper:"edit",addoper:"add",deloper:"del",subgridid:"id",npage:null,totalrows:"totalrows"},forceFit:!1,gridstate:"visible",cellEdit:!1,cellsubmit:"remote",nv:0,loadui:"enable",toolbar:[!1,""],scroll:!1,multiboxonly:!1,deselectAfterSort:!0,scrollrows:!1,autowidth:!1,scrollOffset:18,cellLayout:5,subGridWidth:20,multiselectWidth:20,gridview:!1, +rownumWidth:25,rownumbers:!1,pagerpos:"center",recordpos:"right",footerrow:!1,userDataOnFooter:!1,hoverrows:!0,altclass:"ui-priority-secondary",viewsortcols:[!1,"vertical",!0],resizeclass:"",autoencode:!1,remapColumns:[],ajaxGridOptions:{},direction:"ltr",toppager:!1,headertitles:!1,scrollTimeout:40,data:[],_index:{},grouping:!1,groupingView:{groupField:[],groupOrder:[],groupText:[],groupColumnShow:[],groupSummary:[],showSummaryOnHide:!1,sortitems:[],sortnames:[],groupDataSorted:!1,summary:[],summaryval:[], +plusicon:"ui-icon-circlesmall-plus",minusicon:"ui-icon-circlesmall-minus"},ignoreCase:!1,cmTemplate:{},idPrefix:""},b.jgrid.defaults,d||{}),a=this,c={headers:[],cols:[],footers:[],dragStart:function(c,d,f){this.resizing={idx:c,startX:d.clientX,sOL:f[0]};this.hDiv.style.cursor="col-resize";this.curGbox=b("#rs_m"+b.jgrid.jqID(e.id),"#gbox_"+b.jgrid.jqID(e.id));this.curGbox.css({display:"block",left:f[0],top:f[1],height:f[2]});b(a).triggerHandler("jqGridResizeStart",[d,c]);b.isFunction(e.resizeStart)&& +e.resizeStart.call(this,d,c);document.onselectstart=function(){return!1}},dragMove:function(a){if(this.resizing){var b=a.clientX-this.resizing.startX,a=this.headers[this.resizing.idx],c="ltr"===e.direction?a.width+b:a.width-b,d;33=j&&(void 0=== +e.lastpage||parseInt((k+f+F-1)/F,10)<=e.lastpage))t=parseInt((a-k+F-1)/F,10),0<=k||2>t||!0===e.scroll?(y=Math.round((k+f)/F)+1,j=-1):j=1;0e.lastpage||1==e.lastpage||y===e.page&&y===e.lastpage))c.hDiv.loading?c.timer=setTimeout(c.populateVisible,e.scrollTimeout):(e.page=y,K&&(c.selectionPreserver(d[0]),c.emptyRows(c.bDiv,!1,!1)),c.populate(t))}}},scrollGrid:function(a){if(e.scroll){var b=c.bDiv.scrollTop;void 0===c.scrollTop&& +(c.scrollTop=0);b!=c.scrollTop&&(c.scrollTop=b,c.timer&&clearTimeout(c.timer),c.timer=setTimeout(c.populateVisible,e.scrollTimeout))}c.hDiv.scrollLeft=c.bDiv.scrollLeft;e.footerrow&&(c.sDiv.scrollLeft=c.bDiv.scrollLeft);a&&a.stopPropagation()},selectionPreserver:function(a){var c=a.p,d=c.selrow,e=c.selarrrow?b.makeArray(c.selarrrow):null,f=a.grid.bDiv.scrollLeft,g=function(){var i;c.selrow=null;c.selarrrow=[];if(c.multiselect&&e&&0"),m,k=b.browser.msie?!0:!1;a.p.direction=b.trim(a.p.direction.toLowerCase());-1==b.inArray(a.p.direction,["ltr","rtl"])&&(a.p.direction="ltr");h=a.p.direction;b(j).insertBefore(this);b(this).appendTo(j).removeClass("scroll");var n=b("
");b(n).insertBefore(j).attr({id:"gbox_"+this.id,dir:h});b(j).appendTo(n).attr("id","gview_"+this.id);m=k&&6>=b.browser.version?'': +"";b("
").append(m).insertBefore(j);b("
"+this.p.loadtext+"
").insertBefore(j);b(this).attr({cellspacing:"0",cellpadding:"0",border:"0",role:"grid","aria-multiselectable":!!this.p.multiselect,"aria-labelledby":"gbox_"+this.id});var p=function(a,b){a=parseInt(a,10);return isNaN(a)?b?b:0:a},o=function(d,e,f,g,h,j){var J=a.p.colModel[d],k=J.align,y='style="', +t=J.classes,K=J.name,r=[];k&&(y=y+("text-align:"+k+";"));J.hidden===true&&(y=y+"display:none;");if(e===0)y=y+("width: "+c.headers[d].width+"px;");else if(J.cellattr&&b.isFunction(J.cellattr))if((d=J.cellattr.call(a,h,f,g,J,j))&&typeof d==="string"){d=d.replace(/style/i,"style").replace(/title/i,"title");if(d.indexOf("title")>-1)J.title=false;d.indexOf("class")>-1&&(t=void 0);r=d.split("style");if(r.length===2){r[1]=b.trim(r[1].replace("=",""));if(r[1].indexOf("'")===0||r[1].indexOf('"')===0)r[1]= +r[1].substring(1);y=y+r[1].replace(/'/gi,'"')}else y=y+'"'}if(!r.length){r[0]="";y=y+'"'}y=y+((t!==void 0?' class="'+t+'"':"")+(J.title&&f?' title="'+b.jgrid.stripHtml(f)+'"':""));y=y+(' aria-describedby="'+a.p.id+"_"+K+'"');return y+r[0]},u=function(c){return c===void 0||c===null||c===""?" ":a.p.autoencode?b.jgrid.htmlEncode(c):c+""},w=function(c,d,e,f,g){var i=a.p.colModel[e];if(typeof i.formatter!=="undefined"){c={rowId:c,colModel:i,gid:a.p.id,pos:e};d=b.isFunction(i.formatter)?i.formatter.call(a, +d,c,f,g):b.fmatter?b.fn.fmatter.call(a,i.formatter,d,c,f,g):u(d)}else d=u(d);return d},N=function(a,b,c,d,e){b=w(a,b,c,e,"add");return'"+b+""},D=function(b,c,d){var e='';return'"+e+""},O=function(a,b,c,d){c=(parseInt(c,10)-1)*parseInt(d,10)+1+b;return'"+c+""},V=function(b){var c,d=[],e=0,f;for(f=0;fdiv:first",c).css({height:"auto"}).children("div:first").css({height:0,display:"none"});c.scrollTop=0}if(e===true&&a.p.treeGrid===true){a.p.data=[];a.p._index={}}},T=function(){var c=a.p.data.length,d,e,f;d=a.p.rownumbers===true?1:0;e=a.p.multiselect===true?1:0;f=a.p.subGrid===true?1:0;d=a.p.keyIndex===false||a.p.loadonce===true?a.p.localReader.id:a.p.colModel[a.p.keyIndex+ +e+f+d].name;for(e=0;e"},$=function(c,d,e,f,g){var i=new Date,h=a.p.datatype!="local"&&a.p.loadonce||a.p.datatype=="xmlstring",j=a.p.xmlReader,k=a.p.datatype=="local"?"local":"xml";if(h){a.p.data=[];a.p._index={};a.p.localReader.id="_id_"}a.p.reccount=0;if(b.isXMLDoc(c)){if(a.p.treeANode===-1&&!a.p.scroll){P(d,false,true);e=1}else e=e>1?e:1;var t,K,r=0,m,A=a.p.multiselect===true?1:0,n=a.p.subGrid=== +true?1:0,p=a.p.rownumbers===true?1:0,L,o=[],w,l={},q,v,B=[],u=a.p.altRows===true?" "+a.p.altclass:"",z;j.repeatitems||(o=V(k));L=a.p.keyIndex===false?b.isFunction(j.id)?j.id.call(a,c):j.id:a.p.keyIndex;if(o.length>0&&!isNaN(L)){a.p.remapColumns&&a.p.remapColumns.length&&(L=b.inArray(L,a.p.remapColumns));L=o[L]}k=(L+"").indexOf("[")===-1?o.length?function(a,c){return b(L,a).text()||c}:function(a,c){return b(j.cell,a).eq(L).text()||c}:function(a,b){return a.getAttribute(L.replace(/[\[\]]/g,""))||b}; +a.p.userData={};a.p.page=b.jgrid.getXmlData(c,j.page)||0;a.p.lastpage=b.jgrid.getXmlData(c,j.total);if(a.p.lastpage===void 0)a.p.lastpage=1;a.p.records=b.jgrid.getXmlData(c,j.records)||0;b.isFunction(j.userdata)?a.p.userData=j.userdata.call(a,c)||{}:b.jgrid.getXmlData(c,j.userdata,true).each(function(){a.p.userData[this.getAttribute("name")]=b(this).text()});c=b.jgrid.getXmlData(c,j.root,true);(c=b.jgrid.getXmlData(c,j.row,true))||(c=[]);var s=c.length,G=0,R={},x=parseInt(a.p.rowNum,10);if(s>0&&a.p.page<= +0)a.p.page=1;if(c&&s){var C=a.p.scroll?b.jgrid.randId():1;g&&(x=x*(g+1));for(var g=b.isFunction(a.p.afterInsertRow),E=a.p.grouping&&a.p.groupingView.groupCollapse===true;G");if(a.p.grouping){R=b(a).jqGrid("groupingPrepare",B,R,l,G);B=[]}if(h||a.p.treeGrid===true){l._id_=v;a.p.data.push(l);a.p._index[v]=a.p.data.length-1}if(a.p.gridview===false){b("tbody:first",d).append(B.join(""));b(a).triggerHandler("jqGridAfterInsertRow",[v,l,q]); +g&&a.p.afterInsertRow.call(a,v,l,q);B=[]}l={};r++;G++;if(r==x)break}}if(a.p.gridview===true){K=a.p.treeANode>-1?a.p.treeANode:0;if(a.p.grouping){b(a).jqGrid("groupingRender",R,a.p.colModel.length);R=null}else a.p.treeGrid===true&&K>0?b(a.rows[K]).after(B.join("")):b("tbody:first",d).append(B.join(""))}if(a.p.subGrid===true)try{b(a).jqGrid("addSubGrid",A+p)}catch(S){}a.p.totaltime=new Date-i;if(r>0&&a.p.records===0)a.p.records=s;B=null;if(a.p.treeGrid===true)try{b(a).jqGrid("setTreeNode",K+1,r+K+1)}catch(T){}if(!a.p.treeGrid&& +!a.p.scroll)a.grid.bDiv.scrollTop=0;a.p.reccount=r;a.p.treeANode=-1;a.p.userDataOnFooter&&b(a).jqGrid("footerData","set",a.p.userData,true);if(h){a.p.records=s;a.p.lastpage=Math.ceil(s/x)}f||a.updatepager(false,true);if(h)for(;r1?e:1;var h,j=a.p.datatype!="local"&&a.p.loadonce||a.p.datatype=="jsonstring";if(j){a.p.data=[];a.p._index={};a.p.localReader.id="_id_"}a.p.reccount=0;if(a.p.datatype=="local"){d=a.p.localReader;h="local"}else{d=a.p.jsonReader;h="json"}var k=0,t,m,r=[],n,A=a.p.multiselect?1:0,p=a.p.subGrid? +1:0,o=a.p.rownumbers===true?1:0,l,w,s={},u,q,v=[],B=a.p.altRows===true?" "+a.p.altclass:"",z;a.p.page=b.jgrid.getAccessor(c,d.page)||0;l=b.jgrid.getAccessor(c,d.total);a.p.lastpage=l===void 0?1:l;a.p.records=b.jgrid.getAccessor(c,d.records)||0;a.p.userData=b.jgrid.getAccessor(c,d.userdata)||{};d.repeatitems||(n=r=V(h));h=a.p.keyIndex===false?b.isFunction(d.id)?d.id.call(a,c):d.id:a.p.keyIndex;if(r.length>0&&!isNaN(h)){a.p.remapColumns&&a.p.remapColumns.length&&(h=b.inArray(h,a.p.remapColumns));h= +r[h]}(w=b.jgrid.getAccessor(c,d.root))||(w=[]);l=w.length;c=0;if(l>0&&a.p.page<=0)a.p.page=1;var x=parseInt(a.p.rowNum,10),C=a.p.scroll?b.jgrid.randId():1;g&&(x=x*(g+1));for(var G=b.isFunction(a.p.afterInsertRow),R={},E=a.p.grouping&&a.p.groupingView.groupCollapse===true;c");if(a.p.grouping){R=b(a).jqGrid("groupingPrepare",v,R,s,c);v=[]}if(j||a.p.treeGrid===true){s._id_=q;a.p.data.push(s);a.p._index[q]=a.p.data.length-1}if(a.p.gridview===false){b("#"+b.jgrid.jqID(a.p.id)+ +" tbody:first").append(v.join(""));b(a).triggerHandler("jqGridAfterInsertRow",[q,s,g]);G&&a.p.afterInsertRow.call(a,q,s,g);v=[]}s={};k++;c++;if(k==x)break}if(a.p.gridview===true){u=a.p.treeANode>-1?a.p.treeANode:0;a.p.grouping?b(a).jqGrid("groupingRender",R,a.p.colModel.length):a.p.treeGrid===true&&u>0?b(a.rows[u]).after(v.join("")):b("#"+b.jgrid.jqID(a.p.id)+" tbody:first").append(v.join(""))}if(a.p.subGrid===true)try{b(a).jqGrid("addSubGrid",A+o)}catch(M){}a.p.totaltime=new Date-i;if(k>0&&a.p.records=== +0)a.p.records=l;if(a.p.treeGrid===true)try{b(a).jqGrid("setTreeNode",u+1,k+u+1)}catch(Q){}if(!a.p.treeGrid&&!a.p.scroll)a.grid.bDiv.scrollTop=0;a.p.reccount=k;a.p.treeANode=-1;a.p.userDataOnFooter&&b(a).jqGrid("footerData","set",a.p.userData,true);if(j){a.p.records=l;a.p.lastpage=Math.ceil(l/x)}f||a.updatepager(false,true);if(j)for(;k0&&h&&l.or();try{c(d.groups[g])}catch(k){alert(k)}e++}h&&l.orEnd()}if(d.rules!==void 0){if(e>0){h=l.select();l=b.jgrid.from(h); +a.p.ignoreCase&&(l=l.ignoreCase())}try{(i=d.rules.length&&d.groupOp.toString().toUpperCase()==="OR")&&l.orBegin();for(g=0;g0&&j&&j==="OR"&&(l=l.or());l=p[U.op](l,j)(U.field,U.data,f[U.field])}e++}i&&l.orEnd()}catch(na){alert(na)}}}var d,e=false,f={},g=[],h=[],i,j,k;if(b.isArray(a.p.data)){var m=a.p.grouping?a.p.groupingView:false,n,r;b.each(a.p.colModel,function(){j=this.sorttype||"text";if(j=="date"||j== +"datetime"){if(this.formatter&&typeof this.formatter==="string"&&this.formatter=="date"){i=this.formatoptions&&this.formatoptions.srcformat?this.formatoptions.srcformat:b.jgrid.formatter.date.srcformat;k=this.formatoptions&&this.formatoptions.newformat?this.formatoptions.newformat:b.jgrid.formatter.date.newformat}else i=k=this.datefmt||"Y-m-d";f[this.name]={stype:j,srcfmt:i,newfmt:k}}else f[this.name]={stype:j,srcfmt:"",newfmt:""};if(a.p.grouping){r=0;for(n=m.groupField.length;r1)if(g.npage!==null){e[g.npage]=c;j=c-1;c=1}else i=function(b){a.p.page++;a.grid.hDiv.loading=false;h&&a.p.loadComplete.call(a,b);H(c-1)};else g.npage!==null&&delete a.p.postData[g.npage];if(a.p.grouping){b(a).jqGrid("groupingSetup");a.p.groupingView.groupDataSorted=== +true&&(e[g.sort]=a.p.groupingView.groupField[0]+" "+a.p.groupingView.groupOrder[0]+", "+e[g.sort])}b.extend(a.p.postData,e);var k=!a.p.scroll?1:a.rows.length-1,e=b(a).triggerHandler("jqGridBeforeRequest");if(!(e===false||e==="stop"))if(b.isFunction(a.p.datatype))a.p.datatype.call(a,a.p.postData,"load_"+a.p.id);else{if(b.isFunction(a.p.beforeRequest)){e=a.p.beforeRequest.call(a);e===void 0&&(e=true);if(e===false)return}f=a.p.datatype.toLowerCase();switch(f){case "json":case "jsonp":case "xml":case "script":b.ajax(b.extend({url:a.p.url, +type:a.p.mtype,dataType:f,data:b.isFunction(a.p.serializeGridData)?a.p.serializeGridData.call(a,a.p.postData):a.p.postData,success:function(e,g,h){if(b.isFunction(a.p.beforeProcessing)&&a.p.beforeProcessing.call(a,e,g,h)===false)Q();else{f==="xml"?$(e,a.grid.bDiv,k,c>1,j):aa(e,a.grid.bDiv,k,c>1,j);b(a).triggerHandler("jqGridLoadComplete",[e]);i&&i.call(a,e);b(a).triggerHandler("jqGridAfterLoadComplete",[e]);d&&a.grid.populateVisible();if(a.p.loadonce||a.p.treeGrid)a.p.datatype="local";c===1&&Q()}}, +error:function(d,e,f){b.isFunction(a.p.loadError)&&a.p.loadError.call(a,d,e,f);c===1&&Q()},beforeSend:function(c,d){var e=true;b.isFunction(a.p.loadBeforeSend)&&(e=a.p.loadBeforeSend.call(a,c,d));e===void 0&&(e=true);if(e===false)return false;ca()}},b.jgrid.ajaxOptions,a.p.ajaxGridOptions));break;case "xmlstring":ca();e=b.jgrid.stringToDoc(a.p.datastr);$(e,a.grid.bDiv);b(a).triggerHandler("jqGridLoadComplete",[e]);h&&a.p.loadComplete.call(a,e);b(a).triggerHandler("jqGridAfterLoadComplete",[e]);a.p.datatype= +"local";a.p.datastr=null;Q();break;case "jsonstring":ca();e=typeof a.p.datastr=="string"?b.jgrid.parse(a.p.datastr):a.p.datastr;aa(e,a.grid.bDiv);b(a).triggerHandler("jqGridLoadComplete",[e]);h&&a.p.loadComplete.call(a,e);b(a).triggerHandler("jqGridAfterLoadComplete",[e]);a.p.datatype="local";a.p.datastr=null;Q();break;case "local":case "clientside":ca();a.p.datatype="local";e=ma();aa(e,a.grid.bDiv,k,c>1,j);b(a).triggerHandler("jqGridLoadComplete",[e]);i&&i.call(a,e);b(a).triggerHandler("jqGridAfterLoadComplete", +[e]);d&&a.grid.populateVisible();Q()}}}},da=function(c){b("#cb_"+b.jgrid.jqID(a.p.id),a.grid.hDiv)[a.p.useProp?"prop":"attr"]("checked",c);if(a.p.frozenColumns&&a.p.id+"_frozen")b("#cb_"+b.jgrid.jqID(a.p.id),a.grid.fhDiv)[a.p.useProp?"prop":"attr"]("checked",c)};m=function(c,e){var d="",f="",g="",i,j,k,m,l=function(c){var e;b.isFunction(a.p.onPaging)&&(e=a.p.onPaging.call(a,c));a.p.selrow=null; +if(a.p.multiselect){a.p.selarrrow=[];da(false)}a.p.savedRow=[];return e=="stop"?false:true},c=c.substr(1),e=e+("_"+c);i="pg_"+c;j=c+"_left";k=c+"_center";m=c+"_right";b("#"+b.jgrid.jqID(c)).append("
").attr("dir", +"ltr");if(a.p.rowList.length>0){g="";g=g+""}h=="rtl"&&(f=f+g);a.p.pginput===true&&(d=""+b.jgrid.format(a.p.pgtext||"","","")+"");if(a.p.pgbuttons===true){j=["first"+e,"prev"+e,"next"+e,"last"+e];h=="rtl"&&j.reverse();f=f+("");f=f+("");f=f+(d!==""?""+d+"": +"")+("");f=f+("")}else d!==""&&(f=f+d);h=="ltr"&&(f=f+g);f=f+"";a.p.viewrecords===true&&b("td#"+c+"_"+a.p.recordpos,"#"+i).append("
");b("td#"+c+"_"+a.p.pagerpos,"#"+i).append(f);g=b(".ui-jqgrid").css("font-size")|| +"11px";b(document.body).append("");f=b(f).clone().appendTo("#testpg").width();b("#testpg").remove();if(f>0){d!==""&&(f=f+50);b("td#"+c+"_"+a.p.pagerpos,"#"+i).width(f)}a.p._nvtd=[];a.p._nvtd[0]=f?Math.floor((a.p.width-f)/2):Math.floor(a.p.width/3);a.p._nvtd[1]=0;f=null;b(".ui-pg-selbox","#"+i).bind("change",function(){a.p.page=Math.round(a.p.rowNum*(a.p.page-1)/this.value-0.5)+1;a.p.rowNum= +this.value;a.p.pager&&b(".ui-pg-selbox",a.p.pager).val(this.value);a.p.toppager&&b(".ui-pg-selbox",a.p.toppager).val(this.value);if(!l("records"))return false;H();return false});if(a.p.pgbuttons===true){b(".ui-pg-button","#"+i).hover(function(){if(b(this).hasClass("ui-state-disabled"))this.style.cursor="default";else{b(this).addClass("ui-state-hover");this.style.cursor="pointer"}},function(){if(!b(this).hasClass("ui-state-disabled")){b(this).removeClass("ui-state-hover");this.style.cursor="default"}}); +b("#first"+b.jgrid.jqID(e)+", #prev"+b.jgrid.jqID(e)+", #next"+b.jgrid.jqID(e)+", #last"+b.jgrid.jqID(e)).click(function(){var b=p(a.p.page,1),c=p(a.p.lastpage,1),d=false,f=true,g=true,i=true,h=true;if(c===0||c===1)h=i=g=f=false;else if(c>1&&b>=1)if(b===1)g=f=false;else{if(b===c)h=i=false}else if(c>1&&b===0){h=i=false;b=c-1}if(this.id==="first"+e&&f){a.p.page=1;d=true}if(this.id==="prev"+e&&g){a.p.page=b-1;d=true}if(this.id==="next"+e&&i){a.p.page=b+1;d=true}if(this.id==="last"+e&&h){a.p.page=c;d= +true}if(d){if(!l(this.id))return false;H()}return false})}a.p.pginput===true&&b("input.ui-pg-input","#"+i).keypress(function(c){if((c.charCode?c.charCode:c.keyCode?c.keyCode:0)==13){a.p.page=b(this).val()>0?b(this).val():a.p.page;if(!l("user"))return false;H();return false}return this})};var ja=function(c,e,d,f){if(a.p.colModel[e].sortable&&!(a.p.savedRow.length>0)){if(!d){if(a.p.lastsort==e)if(a.p.sortorder=="asc")a.p.sortorder="desc";else{if(a.p.sortorder=="desc")a.p.sortorder="asc"}else a.p.sortorder= +a.p.colModel[e].firstsortorder||"asc";a.p.page=1}if(f){if(a.p.lastsort==e&&a.p.sortorder==f&&!d)return;a.p.sortorder=f}d=a.grid.headers[a.p.lastsort].el;f=a.grid.headers[e].el;b("span.ui-grid-ico-sort",d).addClass("ui-state-disabled");b(d).attr("aria-selected","false");b("span.ui-icon-"+a.p.sortorder,f).removeClass("ui-state-disabled");b(f).attr("aria-selected","true");if(!a.p.viewsortcols[0]&&a.p.lastsort!=e){b("span.s-ico",d).hide();b("span.s-ico",f).show()}c=c.substring(5+a.p.id.length+1);a.p.sortname= +a.p.colModel[e].index||c;d=a.p.sortorder;if(b(a).triggerHandler("jqGridSortCol",[c,e,d])==="stop")a.p.lastsort=e;else if(b.isFunction(a.p.onSortCol)&&a.p.onSortCol.call(a,c,e,d)=="stop")a.p.lastsort=e;else{if(a.p.datatype=="local")a.p.deselectAfterSort&&b(a).jqGrid("resetSelection");else{a.p.selrow=null;a.p.multiselect&&da(false);a.p.selarrrow=[];a.p.savedRow=[]}if(a.p.scroll){d=a.grid.bDiv.scrollLeft;P(a.grid.bDiv,true,false);a.grid.hDiv.scrollLeft=d}a.p.subGrid&&a.p.datatype=="local"&&b("td.sgexpanded", +"#"+b.jgrid.jqID(a.p.id)).each(function(){b(this).trigger("click")});H();a.p.lastsort=e;if(a.p.sortname!=c&&e)a.p.lastsort=e}}},oa=function(c){var e,d={},f=b.jgrid.cellWidth()?0:a.p.cellLayout;for(e=d[0]=d[1]=d[2]=0;e<=c;e++)a.p.colModel[e].hidden===false&&(d[0]=d[0]+(a.p.colModel[e].width+f));a.p.direction=="rtl"&&(d[0]=a.p.width-d[0]);d[0]=d[0]-a.grid.bDiv.scrollLeft;b(a.grid.cDiv).is(":visible")&&(d[1]=d[1]+(b(a.grid.cDiv).height()+parseInt(b(a.grid.cDiv).css("padding-top"),10)+parseInt(b(a.grid.cDiv).css("padding-bottom"), +10)));if(a.p.toolbar[0]===true&&(a.p.toolbar[1]=="top"||a.p.toolbar[1]=="both"))d[1]=d[1]+(b(a.grid.uDiv).height()+parseInt(b(a.grid.uDiv).css("border-top-width"),10)+parseInt(b(a.grid.uDiv).css("border-bottom-width"),10));a.p.toppager&&(d[1]=d[1]+(b(a.grid.topDiv).height()+parseInt(b(a.grid.topDiv).css("border-bottom-width"),10)));d[2]=d[2]+(b(a.grid.bDiv).height()+b(a.grid.hDiv).height());return d},ka=function(c){var d,e=a.grid.headers,f=b.jgrid.getCellIndex(c);for(d=0;d"),this.p.colModel.unshift({name:"cb",width:b.jgrid.cellWidth()?a.p.multiselectWidth+a.p.cellLayout:a.p.multiselectWidth,sortable:!1,resizable:!1,hidedlg:!0,search:!1,align:"center",fixed:!0}));this.p.rownumbers&&(this.p.colNames.unshift(""),this.p.colModel.unshift({name:"rn", +width:a.p.rownumWidth,sortable:!1,resizable:!1,hidedlg:!0,search:!1,align:"center",fixed:!0}));a.p.xmlReader=b.extend(!0,{root:"rows",row:"row",page:"rows>page",total:"rows>total",records:"rows>records",repeatitems:!0,cell:"cell",id:"[id]",userdata:"userdata",subgrid:{root:"rows",row:"row",repeatitems:!0,cell:"cell"}},a.p.xmlReader);a.p.jsonReader=b.extend(!0,{root:"rows",page:"page",total:"total",records:"records",repeatitems:!0,cell:"cell",id:"id",userdata:"userdata",subgrid:{root:"rows",repeatitems:!0, +cell:"cell"}},a.p.jsonReader);a.p.localReader=b.extend(!0,{root:"rows",page:"page",total:"total",records:"records",repeatitems:!1,cell:"cell",id:"id",userdata:"userdata",subgrid:{root:"rows",repeatitems:!0,cell:"cell"}},a.p.localReader);a.p.scroll&&(a.p.pgbuttons=!1,a.p.pginput=!1,a.p.rowList=[]);a.p.data.length&&T();var x="",la,M,ea,ba,fa,z,l,X;M=X="";if(!0===a.p.shrinkToFit&&!0===a.p.forceFit)for(f=a.p.colModel.length-1;0<=f;f--)if(!a.p.colModel[f].hidden){a.p.colModel[f].resizable= +!1;break}"horizontal"==a.p.viewsortcols[1]&&(X=" ui-i-asc",M=" ui-i-desc");la=k?"class='ui-th-div-ie'":"";X="");for(f=0;f",M=a.p.colModel[f].index||a.p.colModel[f].name,x+="
"+a.p.colNames[f],a.p.colModel[f].width=a.p.colModel[f].width?parseInt(a.p.colModel[f].width,10):150,"boolean"!==typeof a.p.colModel[f].title&&(a.p.colModel[f].title=!0),M==a.p.sortname&&(a.p.lastsort=f),x+=X+"
";X=null;b(this).append(x+""); +b("thead tr:first th",this).hover(function(){b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")});if(this.p.multiselect){var ga=[],Y;b("#cb_"+b.jgrid.jqID(a.p.id),this).bind("click",function(){a.p.selarrrow=[];var c=a.p.frozenColumns===true?a.p.id+"_frozen":"";if(this.checked){b(a.rows).each(function(d){if(d>0&&!b(this).hasClass("ui-subgrid")&&!b(this).hasClass("jqgroup")&&!b(this).hasClass("ui-state-disabled")){b("#jqg_"+b.jgrid.jqID(a.p.id)+"_"+b.jgrid.jqID(this.id))[a.p.useProp? +"prop":"attr"]("checked",true);b(this).addClass("ui-state-highlight").attr("aria-selected","true");a.p.selarrrow.push(this.id);a.p.selrow=this.id;if(c){b("#jqg_"+b.jgrid.jqID(a.p.id)+"_"+b.jgrid.jqID(this.id),a.grid.fbDiv)[a.p.useProp?"prop":"attr"]("checked",true);b("#"+b.jgrid.jqID(this.id),a.grid.fbDiv).addClass("ui-state-highlight")}}});Y=true;ga=[]}else{b(a.rows).each(function(d){if(d>0&&!b(this).hasClass("ui-subgrid")&&!b(this).hasClass("ui-state-disabled")){b("#jqg_"+b.jgrid.jqID(a.p.id)+"_"+ +b.jgrid.jqID(this.id))[a.p.useProp?"prop":"attr"]("checked",false);b(this).removeClass("ui-state-highlight").attr("aria-selected","false");ga.push(this.id);if(c){b("#jqg_"+b.jgrid.jqID(a.p.id)+"_"+b.jgrid.jqID(this.id),a.grid.fbDiv)[a.p.useProp?"prop":"attr"]("checked",false);b("#"+b.jgrid.jqID(this.id),a.grid.fbDiv).removeClass("ui-state-highlight")}}});a.p.selrow=null;Y=false}b(a).triggerHandler("jqGridSelectAll",[Y?a.p.selarrrow:ga,Y]);b.isFunction(a.p.onSelectAll)&&a.p.onSelectAll.call(a,Y?a.p.selarrrow: +ga,Y)})}!0===a.p.autowidth&&(x=b(n).innerWidth(),a.p.width=00){m=c.width-e*f-l;if(!isNaN(a.p.height)){m=m-h;k=true}d=0;b.each(a.p.colModel,function(b){if(this.hidden===false&&!this.fixed){this.width=j=Math.round(m*this.width/(a.p.tblwidth-e*f-l));d=d+j;g=b}});o=0;k?c.width-l-(d+e*f)!==h&&(o=c.width-l-(d+e*f)-h):!k&&Math.abs(c.width-l-(d+e*f))!==1&&(o=c.width-l-(d+e*f));a.p.colModel[g].width=a.p.colModel[g].width+o;a.p.tblwidth=d+o+e*f+l;if(a.p.tblwidth>a.p.width){a.p.colModel[g].width=a.p.colModel[g].width-(a.p.tblwidth- +parseInt(a.p.width,10));a.p.tblwidth=a.p.width}}})();b(n).css("width",c.width+"px").append("
 
");b(j).css("width",c.width+"px");var x=b("thead:first",a).get(0),S="";a.p.footerrow&&(S+="");var j=b("tr:first",x),Z=""; +a.p.disableClick=!1;b("th",j).each(function(d){ea=a.p.colModel[d].width;if(typeof a.p.colModel[d].resizable==="undefined")a.p.colModel[d].resizable=true;if(a.p.colModel[d].resizable){ba=document.createElement("span");b(ba).html(" ").addClass("ui-jqgrid-resize ui-jqgrid-resize-"+h);b.browser.opera||b(ba).css("cursor","col-resize");b(this).addClass(a.p.resizeclass)}else ba="";b(this).css("width",ea+"px").prepend(ba);var e="";if(a.p.colModel[d].hidden){b(this).css("display","none");e="display:none;"}Z= +Z+("");c.headers[d]={width:ea,el:this};fa=a.p.colModel[d].sortable;if(typeof fa!=="boolean")fa=a.p.colModel[d].sortable=true;e=a.p.colModel[d].name;e=="cb"||e=="subgrid"||e=="rn"||a.p.viewsortcols[2]&&b(">div",this).addClass("ui-jqgrid-sortable");if(fa)if(a.p.viewsortcols[0]){b("div span.s-ico",this).show();d==a.p.lastsort&&b("div span.ui-icon-"+a.p.sortorder,this).removeClass("ui-state-disabled")}else if(d==a.p.lastsort){b("div span.s-ico", +this).show();b("div span.ui-icon-"+a.p.sortorder,this).removeClass("ui-state-disabled")}a.p.footerrow&&(S=S+(""))}).mousedown(function(d){if(b(d.target).closest("th>span.ui-jqgrid-resize").length==1){var e=ka(this);if(a.p.forceFit===true){var f=a.p,g=e,h;for(h=e+1;h
 
").append(x),C=a.p.caption&&!0===a.p.hiddengrid?!0:!1;f=b("
");x=null;c.hDiv=document.createElement("div");b(c.hDiv).css({width:c.width+"px"}).addClass("ui-state-default ui-jqgrid-hdiv").append(f);b(f).append(j);j=null;C&&b(c.hDiv).hide();a.p.pager&&("string"==typeof a.p.pager?"#"!=a.p.pager.substr(0,1)&&(a.p.pager="#"+a.p.pager): +a.p.pager="#"+b(a.p.pager).attr("id"),b(a.p.pager).css({width:c.width+"px"}).appendTo(n).addClass("ui-state-default ui-jqgrid-pager ui-corner-bottom"),C&&b(a.p.pager).hide(),m(a.p.pager,""));!1===a.p.cellEdit&&!0===a.p.hoverrows&&b(a).bind("mouseover",function(a){l=b(a.target).closest("tr.jqgrow");b(l).attr("class")!=="ui-subgrid"&&b(l).addClass("ui-state-hover")}).bind("mouseout",function(a){l=b(a.target).closest("tr.jqgrow");b(l).removeClass("ui-state-hover")});var s,E,ha;b(a).before(c.hDiv).click(function(c){z= +c.target;l=b(z,a.rows).closest("tr.jqgrow");if(b(l).length===0||l[0].className.indexOf("ui-state-disabled")>-1||(b(z,a).closest("table.ui-jqgrid-btable").attr("id")||"").replace("_frozen","")!==a.id)return this;var d=b(z).hasClass("cbox"),e=b(a).triggerHandler("jqGridBeforeSelectRow",[l[0].id,c]);(e=e===false||e==="stop"?false:true)&&b.isFunction(a.p.beforeSelectRow)&&(e=a.p.beforeSelectRow.call(a,l[0].id,c));if(!(z.tagName=="A"||(z.tagName=="INPUT"||z.tagName=="TEXTAREA"||z.tagName=="OPTION"||z.tagName== +"SELECT")&&!d)&&e===true){s=l[0].id;E=b.jgrid.getCellIndex(z);ha=b(z).closest("td,th").html();b(a).triggerHandler("jqGridCellSelect",[s,E,ha,c]);b.isFunction(a.p.onCellSelect)&&a.p.onCellSelect.call(a,s,E,ha,c);if(a.p.cellEdit===true)if(a.p.multiselect&&d)b(a).jqGrid("setSelection",s,true,c);else{s=l[0].rowIndex;try{b(a).jqGrid("editCell",s,E,true)}catch(f){}}else if(a.p.multikey)if(c[a.p.multikey])b(a).jqGrid("setSelection",s,true,c);else{if(a.p.multiselect&&d){d=b("#jqg_"+b.jgrid.jqID(a.p.id)+"_"+ +s).is(":checked");b("#jqg_"+b.jgrid.jqID(a.p.id)+"_"+s)[a.p.useProp?"prop":"attr"]("checked",d)}}else{if(a.p.multiselect&&a.p.multiboxonly&&!d){var g=a.p.frozenColumns?a.p.id+"_frozen":"";b(a.p.selarrrow).each(function(c,d){var e=a.rows.namedItem(d);b(e).removeClass("ui-state-highlight");b("#jqg_"+b.jgrid.jqID(a.p.id)+"_"+b.jgrid.jqID(d))[a.p.useProp?"prop":"attr"]("checked",false);if(g){b("#"+b.jgrid.jqID(d),"#"+b.jgrid.jqID(g)).removeClass("ui-state-highlight");b("#jqg_"+b.jgrid.jqID(a.p.id)+"_"+ +b.jgrid.jqID(d),"#"+b.jgrid.jqID(g))[a.p.useProp?"prop":"attr"]("checked",false)}});a.p.selarrrow=[]}b(a).jqGrid("setSelection",s,true,c)}}}).bind("reloadGrid",function(c,d){if(a.p.treeGrid===true)a.p.datatype=a.p.treedatatype;d&&d.current&&a.grid.selectionPreserver(a);if(a.p.datatype=="local"){b(a).jqGrid("resetSelection");a.p.data.length&&T()}else if(!a.p.treeGrid){a.p.selrow=null;if(a.p.multiselect){a.p.selarrrow=[];da(false)}a.p.savedRow=[]}a.p.scroll&&P(a.grid.bDiv,true,false);if(d&&d.page){var e= +d.page;if(e>a.p.lastpage)e=a.p.lastpage;e<1&&(e=1);a.p.page=e;a.grid.bDiv.scrollTop=a.grid.prevRowHeight?(e-1)*a.grid.prevRowHeight*a.p.rowNum:0}if(a.grid.prevRowHeight&&a.p.scroll){delete a.p.lastpage;a.grid.populateVisible()}else a.grid.populate();return false}).dblclick(function(c){z=c.target;l=b(z,a.rows).closest("tr.jqgrow");if(b(l).length!==0){s=l[0].rowIndex;E=b.jgrid.getCellIndex(z);b(a).triggerHandler("jqGridDblClickRow",[b(l).attr("id"),s,E,c]);b.isFunction(this.p.ondblClickRow)&&a.p.ondblClickRow.call(a, +b(l).attr("id"),s,E,c)}}).bind("contextmenu",function(c){z=c.target;l=b(z,a.rows).closest("tr.jqgrow");if(b(l).length!==0){a.p.multiselect||b(a).jqGrid("setSelection",l[0].id,true,c);s=l[0].rowIndex;E=b.jgrid.getCellIndex(z);b(a).triggerHandler("jqGridRightClickRow",[b(l).attr("id"),s,E,c]);b.isFunction(this.p.onRightClickRow)&&a.p.onRightClickRow.call(a,b(l).attr("id"),s,E,c)}});c.bDiv=document.createElement("div");k&&"auto"===(""+a.p.height).toLowerCase()&&(a.p.height="100%");b(c.bDiv).append(b('
').append("
").append(this)).addClass("ui-jqgrid-bdiv").css({height:a.p.height+(isNaN(a.p.height)?"":"px"),width:c.width+"px"}).scroll(c.scrollGrid);b("table:first",c.bDiv).css({width:a.p.tblwidth+"px"});k?(2==b("tbody",this).size()&&b("tbody:gt(0)",this).remove(),a.p.multikey&&b(c.bDiv).bind("selectstart",function(){return false})):a.p.multikey&&b(c.bDiv).bind("mousedown",function(){return false});C&&b(c.bDiv).hide();c.cDiv=document.createElement("div"); +var ia=!0===a.p.hidegrid?b("").addClass("ui-jqgrid-titlebar-close HeaderButton").hover(function(){ia.addClass("ui-state-hover")},function(){ia.removeClass("ui-state-hover")}).append("").css("rtl"==h?"left":"right","0px"):"";b(c.cDiv).append(ia).append(""+a.p.caption+"").addClass("ui-jqgrid-titlebar ui-widget-header ui-corner-top ui-helper-clearfix"); +b(c.cDiv).insertBefore(c.hDiv);a.p.toolbar[0]&&(c.uDiv=document.createElement("div"),"top"==a.p.toolbar[1]?b(c.uDiv).insertBefore(c.hDiv):"bottom"==a.p.toolbar[1]&&b(c.uDiv).insertAfter(c.hDiv),"both"==a.p.toolbar[1]?(c.ubDiv=document.createElement("div"),b(c.uDiv).insertBefore(c.hDiv).addClass("ui-userdata ui-state-default").attr("id","t_"+this.id),b(c.ubDiv).insertAfter(c.hDiv).addClass("ui-userdata ui-state-default").attr("id","tb_"+this.id),C&&b(c.ubDiv).hide()):b(c.uDiv).width(c.width).addClass("ui-userdata ui-state-default").attr("id", +"t_"+this.id),C&&b(c.uDiv).hide());a.p.toppager&&(a.p.toppager=b.jgrid.jqID(a.p.id)+"_toppager",c.topDiv=b("
")[0],a.p.toppager="#"+a.p.toppager,b(c.topDiv).insertBefore(c.hDiv).addClass("ui-state-default ui-jqgrid-toppager").width(c.width),m(a.p.toppager,"_t"));a.p.footerrow&&(c.sDiv=b("
")[0],f=b("
"),b(c.sDiv).append(f).insertAfter(c.hDiv).width(c.width),b(f).append(S),c.footers= +b(".ui-jqgrid-ftable",c.sDiv)[0].rows[0].cells,a.p.rownumbers&&(c.footers[0].className="ui-state-default jqgrid-rownum"),C&&b(c.sDiv).hide());f=null;if(a.p.caption){var pa=a.p.datatype;!0===a.p.hidegrid&&(b(".ui-jqgrid-titlebar-close",c.cDiv).click(function(d){var e=b.isFunction(a.p.onHeaderClick),f=".ui-jqgrid-bdiv, .ui-jqgrid-hdiv, .ui-jqgrid-pager, .ui-jqgrid-sdiv",g,h=this;if(a.p.toolbar[0]===true){a.p.toolbar[1]=="both"&&(f=f+(", #"+b(c.ubDiv).attr("id")));f=f+(", #"+b(c.uDiv).attr("id"))}g= +b(f,"#gview_"+b.jgrid.jqID(a.p.id)).length;a.p.gridstate=="visible"?b(f,"#gbox_"+b.jgrid.jqID(a.p.id)).slideUp("fast",function(){g--;if(g===0){b("span",h).removeClass("ui-icon-circle-triangle-n").addClass("ui-icon-circle-triangle-s");a.p.gridstate="hidden";b("#gbox_"+b.jgrid.jqID(a.p.id)).hasClass("ui-resizable")&&b(".ui-resizable-handle","#gbox_"+b.jgrid.jqID(a.p.id)).hide();b(a).triggerHandler("jqGridHeaderClick",[a.p.gridstate,d]);e&&(C||a.p.onHeaderClick.call(a,a.p.gridstate,d))}}):a.p.gridstate== +"hidden"&&b(f,"#gbox_"+b.jgrid.jqID(a.p.id)).slideDown("fast",function(){g--;if(g===0){b("span",h).removeClass("ui-icon-circle-triangle-s").addClass("ui-icon-circle-triangle-n");if(C){a.p.datatype=pa;H();C=false}a.p.gridstate="visible";b("#gbox_"+b.jgrid.jqID(a.p.id)).hasClass("ui-resizable")&&b(".ui-resizable-handle","#gbox_"+b.jgrid.jqID(a.p.id)).show();b(a).triggerHandler("jqGridHeaderClick",[a.p.gridstate,d]);e&&(C||a.p.onHeaderClick.call(a,a.p.gridstate,d))}});return false}),C&&(a.p.datatype= +"local",b(".ui-jqgrid-titlebar-close",c.cDiv).trigger("click")))}else b(c.cDiv).hide();b(c.hDiv).after(c.bDiv).mousemove(function(a){if(c.resizing){c.dragMove(a);return false}});b(".ui-jqgrid-labels",c.hDiv).bind("selectstart",function(){return false});b(document).mouseup(function(){if(c.resizing){c.dragEnd();return false}return true});a.formatCol=o;a.sortData=ja;a.updatepager=function(c,d){var e,f,g,h,i,j,k,m="",l=a.p.pager?"_"+b.jgrid.jqID(a.p.pager.substr(1)):"",n=a.p.toppager?"_"+a.p.toppager.substr(1): +"";g=parseInt(a.p.page,10)-1;g<0&&(g=0);g=g*parseInt(a.p.rowNum,10);i=g+a.p.reccount;if(a.p.scroll){e=b("tbody:first > tr:gt(0)",a.grid.bDiv);g=i-e.length;a.p.reccount=e.length;if(f=e.outerHeight()||a.grid.prevRowHeight){e=g*f;f=parseInt(a.p.records,10)*f;b(">div:first",a.grid.bDiv).css({height:f}).children("div:first").css({height:e,display:e?"":"none"})}a.grid.bDiv.scrollLeft=a.grid.hDiv.scrollLeft}m=a.p.pager?a.p.pager:"";if(m=m+(a.p.toppager?m?","+a.p.toppager:a.p.toppager:"")){k=b.jgrid.formatter.integer|| +{};e=p(a.p.page);f=p(a.p.lastpage);b(".selbox",m)[this.p.useProp?"prop":"attr"]("disabled",false);if(a.p.pginput===true){b(".ui-pg-input",m).val(a.p.page);h=a.p.toppager?"#sp_1"+l+",#sp_1"+n:"#sp_1"+l;b(h).html(b.fmatter?b.fmatter.util.NumberFormat(a.p.lastpage,k):a.p.lastpage)}if(a.p.viewrecords)if(a.p.reccount===0)b(".ui-paging-info",m).html(a.p.emptyrecords);else{h=g+1;j=a.p.records;if(b.fmatter){h=b.fmatter.util.NumberFormat(h,k);i=b.fmatter.util.NumberFormat(i,k);j=b.fmatter.util.NumberFormat(j, +k)}b(".ui-paging-info",m).html(b.jgrid.format(a.p.recordtext,h,i,j))}if(a.p.pgbuttons===true){e<=0&&(e=f=0);if(e==1||e===0){b("#first"+l+", #prev"+l).addClass("ui-state-disabled").removeClass("ui-state-hover");a.p.toppager&&b("#first_t"+n+", #prev_t"+n).addClass("ui-state-disabled").removeClass("ui-state-hover")}else{b("#first"+l+", #prev"+l).removeClass("ui-state-disabled");a.p.toppager&&b("#first_t"+n+", #prev_t"+n).removeClass("ui-state-disabled")}if(e==f||e===0){b("#next"+l+", #last"+l).addClass("ui-state-disabled").removeClass("ui-state-hover"); +a.p.toppager&&b("#next_t"+n+", #last_t"+n).addClass("ui-state-disabled").removeClass("ui-state-hover")}else{b("#next"+l+", #last"+l).removeClass("ui-state-disabled");a.p.toppager&&b("#next_t"+n+", #last_t"+n).removeClass("ui-state-disabled")}}}c===true&&a.p.rownumbers===true&&b("td.jqgrid-rownum",a.rows).each(function(a){b(this).html(g+1+a)});d&&a.p.jqgdnd&&b(a).jqGrid("gridDnD","updateDnD");b(a).triggerHandler("jqGridGridComplete");b.isFunction(a.p.gridComplete)&&a.p.gridComplete.call(a);b(a).triggerHandler("jqGridAfterGridComplete")}; +a.refreshIndex=T;a.setHeadCheckBox=da;a.constructTr=I;a.formatter=function(a,b,c,d,e){return w(a,b,c,d,e)};b.extend(c,{populate:H,emptyRows:P});this.grid=c;a.addXmlData=function(b){$(b,a.grid.bDiv)};a.addJSONData=function(b){aa(b,a.grid.bDiv)};this.grid.cols=this.rows[0].cells;H();a.p.hiddengrid=!1;b(window).unload(function(){a=null})}}}})};b.jgrid.extend({getGridParam:function(b){var f=this[0];return!f||!f.grid?void 0:b?"undefined"!=typeof f.p[b]?f.p[b]:null:f.p},setGridParam:function(d){return this.each(function(){this.grid&& +"object"===typeof d&&b.extend(!0,this.p,d)})},getDataIDs:function(){var d=[],f=0,c,e=0;this.each(function(){if((c=this.rows.length)&&0=e+g?b(this.grid.bDiv)[0].scrollTop=h-(e+g)+i+g:h span:first",j).html(h).attr(i): +b("td:eq("+a+")",j).html(h).attr(i))}),"local"==g.p.datatype){var n=b.jgrid.stripPref(g.p.idPrefix,d),p=g.p._index[n];if(g.p.treeGrid)for(var o in g.p.treeReader)k.hasOwnProperty(g.p.treeReader[o])&&delete k[g.p.treeReader[o]];"undefined"!=typeof p&&(g.p.data[p]=b.extend(!0,g.p.data[p],k));k=null}}catch(u){a=!1}a&&("string"===m?b(j).addClass(c):"object"===m&&b(j).css(c),b(g).triggerHandler("jqGridAfterGridComplete"))});return a},addRowData:function(d,f,c,e){c||(c="last");var a=!1,i,g,h,j,m,k,n,p, +o="",u,w,N,D,O,V;f&&(b.isArray(f)?(u=!0,c="last",w=d):(f=[f],u=!1),this.each(function(){var W=f.length;m=this.p.rownumbers===true?1:0;h=this.p.multiselect===true?1:0;j=this.p.subGrid===true?1:0;if(!u)if(typeof d!="undefined")d=d+"";else{d=b.jgrid.randId();if(this.p.keyIndex!==false){w=this.p.colModel[this.p.keyIndex+h+j+m].name;typeof f[0][w]!="undefined"&&(d=f[0][w])}}N=this.p.altclass;for(var P=0,T="",I={},$=b.isFunction(this.p.afterInsertRow)?true:false;P0"}if(h){p='';o=this.formatCol(m,1,"",null,d,true);g[g.length]='"+p+""}j&&(g[g.length]=b(this).jqGrid("addSubGridCell",h+m,1));for(n=h+j+m;n"+p+""}g.unshift(this.constructTr(d,false,T,I,I));g[g.length]="";if(this.rows.length===0)b("table:first",this.grid.bDiv).append(g.join(""));else switch(c){case "last":b(this.rows[this.rows.length-1]).after(g.join(""));k=this.rows.length- +1;break;case "first":b(this.rows[0]).after(g.join(""));k=1;break;case "after":(k=this.rows.namedItem(e))&&(b(this.rows[k.rowIndex+1]).hasClass("ui-subgrid")?b(this.rows[k.rowIndex+1]).after(g):b(k).after(g.join("")));k++;break;case "before":if(k=this.rows.namedItem(e)){b(k).before(g.join(""));k=k.rowIndex}k--}this.p.subGrid===true&&b(this).jqGrid("addSubGrid",h+m,k);this.p.records++;this.p.reccount++;b(this).triggerHandler("jqGridAfterInsertRow",[d,D,D]);$&&this.p.afterInsertRow.call(this,d,D,D); +P++;if(this.p.datatype=="local"){I[this.p.localReader.id]=V;this.p._index[V]=this.p.data.length;this.p.data.push(I);I={}}}this.p.altRows===true&&!u&&(c=="last"?(this.rows.length-1)%2==1&&b(this.rows[this.rows.length-1]).addClass(N):b(this.rows).each(function(a){a%2==1?b(this).addClass(N):b(this).removeClass(N)}));this.updatepager(true,true);a=true}));return a},footerData:function(d,f,c){function e(a){for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}var a,i=!1,g={},h;"undefined"==typeof d&& +(d="get");"boolean"!=typeof c&&(c=!0);d=d.toLowerCase();this.each(function(){var j=this,m;if(!j.grid||!j.p.footerrow||"set"==d&&e(f))return!1;i=!0;b(this.p.colModel).each(function(e){a=this.name;"set"==d?void 0!==f[a]&&(m=c?j.formatter("",f[a],e,f,"edit"):f[a],h=this.title?{title:b.jgrid.stripHtml(m)}:{},b("tr.footrow td:eq("+e+")",j.grid.sDiv).html(m).attr(h),i=!0):"get"==d&&(g[a]=b("tr.footrow td:eq("+e+")",j.grid.sDiv).html())})});return"get"==d?g:i},showHideCol:function(d,f){return this.each(function(){var c= +this,e=!1,a=b.jgrid.cellWidth()?0:c.p.cellLayout,i;if(c.grid){"string"===typeof d&&(d=[d]);f="none"!=f?"":"none";var g=""===f?!0:!1,h=c.p.groupHeader&&("object"===typeof c.p.groupHeader||b.isFunction(c.p.groupHeader));h&&b(c).jqGrid("destroyGroupHeader",!1);b(this.p.colModel).each(function(h){if(-1!==b.inArray(this.name,d)&&this.hidden===g){if(!0===c.p.frozenColumns&&!0===this.frozen)return!0;b("tr",c.grid.hDiv).each(function(){b(this.cells[h]).css("display",f)});b(c.rows).each(function(){b(this).hasClass("jqgroup")|| +b(this.cells[h]).css("display",f)});c.p.footerrow&&b("tr.footrow td:eq("+h+")",c.grid.sDiv).css("display",f);i=this.widthOrg?this.widthOrg:parseInt(this.width,10);c.p.tblwidth="none"===f?c.p.tblwidth-(i+a):c.p.tblwidth+(i+a);this.hidden=!g;e=!0;b(c).triggerHandler("jqGridShowHideCol",[g,this.name,h])}});!0===e&&b(c).jqGrid("setGridWidth",!0===c.p.shrinkToFit?c.p.tblwidth:c.p.width);h&&b(c).jqGrid("setGroupHeaders",c.p.groupHeader)}})},hideCol:function(d){return this.each(function(){b(this).jqGrid("showHideCol", +d,"none")})},showCol:function(d){return this.each(function(){b(this).jqGrid("showHideCol",d,"")})},remapColumns:function(d,f,c){function e(a){var c;c=a.length?b.makeArray(a):b.extend({},a);b.each(d,function(b){a[b]=c[this]})}function a(a,c){b(">tr"+(c||""),a).each(function(){var a=this,c=b.makeArray(a.cells);b.each(d,function(){var b=c[this];b&&a.appendChild(b)})})}var i=this.get(0);e(i.p.colModel);e(i.p.colNames);e(i.grid.headers);a(b("thead:first",i.grid.hDiv),c&&":not(.ui-jqgrid-labels)");f&&a(b("#"+ +b.jgrid.jqID(i.p.id)+" tbody:first"),".jqgfirstrow, tr.jqgrow, tr.jqfoot");i.p.footerrow&&a(b("tbody:first",i.grid.sDiv));i.p.remapColumns&&(i.p.remapColumns.length?e(i.p.remapColumns):i.p.remapColumns=b.makeArray(d));i.p.lastsort=b.inArray(i.p.lastsort,d);i.p.treeGrid&&(i.p.expColInd=b.inArray(i.p.expColInd,d));b(i).triggerHandler("jqGridRemapColumns",[d,f,c])},setGridWidth:function(d,f){return this.each(function(){if(this.grid){var c=this,e,a=0,i=b.jgrid.cellWidth()?0:c.p.cellLayout,g,h=0,j=!1, +m=c.p.scrollOffset,k,n=0,p=0,o;"boolean"!=typeof f&&(f=c.p.shrinkToFit);if(!isNaN(d)){d=parseInt(d,10);c.grid.width=c.p.width=d;b("#gbox_"+b.jgrid.jqID(c.p.id)).css("width",d+"px");b("#gview_"+b.jgrid.jqID(c.p.id)).css("width",d+"px");b(c.grid.bDiv).css("width",d+"px");b(c.grid.hDiv).css("width",d+"px");c.p.pager&&b(c.p.pager).css("width",d+"px");c.p.toppager&&b(c.p.toppager).css("width",d+"px");!0===c.p.toolbar[0]&&(b(c.grid.uDiv).css("width",d+"px"),"both"==c.p.toolbar[1]&&b(c.grid.ubDiv).css("width", +d+"px"));c.p.footerrow&&b(c.grid.sDiv).css("width",d+"px");!1===f&&!0===c.p.forceFit&&(c.p.forceFit=!1);if(!0===f){b.each(c.p.colModel,function(){if(this.hidden===false){e=this.widthOrg?this.widthOrg:parseInt(this.width,10);a=a+(e+i);this.fixed?n=n+(e+i):h++;p++}});if(0===h)return;c.p.tblwidth=a;k=d-i*h-n;if(!isNaN(c.p.height)&&(b(c.grid.bDiv)[0].clientHeightd?(j=c.p.tblwidth-parseInt(d,10), +c.p.tblwidth=d,e=c.p.colModel[g].width-=j):e=c.p.colModel[g].width;c.grid.headers[g].width=e;c.grid.headers[g].el.style.width=e+"px";u&&(c.grid.cols[g].style.width=e+"px");c.p.footerrow&&(c.grid.footers[g].style.width=e+"px")}c.p.tblwidth&&(b("table:first",c.grid.bDiv).css("width",c.p.tblwidth+"px"),b("table:first",c.grid.hDiv).css("width",c.p.tblwidth+"px"),c.grid.hDiv.scrollLeft=c.grid.bDiv.scrollLeft,c.p.footerrow&&b("table:first",c.grid.sDiv).css("width",c.p.tblwidth+"px"))}}})},setGridHeight:function(d){return this.each(function(){if(this.grid){var f= +b(this.grid.bDiv);f.css({height:d+(isNaN(d)?"":"px")});!0===this.p.frozenColumns&&b("#"+b.jgrid.jqID(this.p.id)+"_frozen").parent().height(f.height()-16);this.p.height=d;this.p.scroll&&this.grid.populateVisible()}})},setCaption:function(d){return this.each(function(){this.p.caption=d;b("span.ui-jqgrid-title, span.ui-jqgrid-title-rtl",this.grid.cDiv).html(d);b(this.grid.cDiv).show()})},setLabel:function(d,f,c,e){return this.each(function(){var a=-1;if(this.grid&&"undefined"!=typeof d&&(b(this.p.colModel).each(function(b){if(this.name== +d)return a=b,!1}),0<=a)){var i=b("tr.ui-jqgrid-labels th:eq("+a+")",this.grid.hDiv);if(f){var g=b(".s-ico",i);b("[id^=jqgh_]",i).empty().html(f).append(g);this.p.colNames[a]=f}c&&("string"===typeof c?b(i).addClass(c):b(i).css(c));"object"===typeof e&&b(i).attr(e)}})},setCell:function(d,f,c,e,a,i){return this.each(function(){var g=-1,h,j;if(this.grid&&(isNaN(f)?b(this.p.colModel).each(function(a){if(this.name==f)return g=a,!1}):g=parseInt(f,10),0<=g&&(h=this.rows.namedItem(d)))){var m=b("td:eq("+g+ +")",h);if(""!==c||!0===i)h=this.formatter(d,c,g,h,"edit"),j=this.p.colModel[g].title?{title:b.jgrid.stripHtml(h)}:{},this.p.treeGrid&&0a,d=a+"",e=c.decimalSeparator?c.decimalSeparator:".",g;if(b.fmatter.isNumber(c.decimalPlaces)){var h=c.decimalPlaces,d=Math.pow(10, +h),d=Math.round(a*d)/d+"";g=d.lastIndexOf(".");if(0g?(d+=e,g=d.length-1):"."!==e&&(d=d.replace(".",e));for(;d.length-1-gi&&(c[g]=i+1)),"F"==a[g]&&(i=b.inArray(c[g],k.i18n.monthNames),-1!==i&&11=i?h.y=1900+h.y:0<=i&&69>=i&&(h.y=2E3+h.y);i=new Date(h.y,h.m,h.d,h.h,h.i,h.s,h.u)}f in d.masks?f=d.masks[f]: +f||(f="Y-m-d");a=i.getHours();c=i.getMinutes();h=i.getDate();g=i.getMonth()+1;j=i.getTimezoneOffset();var l=i.getSeconds(),r=i.getMilliseconds(),n=i.getDay(),m=i.getFullYear(),o=(n+6)%7+1,p=(new Date(m,g-1,h)-new Date(m,0,1))/864E5,q={d:e(h),D:k.i18n.dayNames[n],j:h,l:k.i18n.dayNames[n+7],N:o,S:d.S(h),w:n,z:p,W:5>o?Math.floor((p+o-1)/7)+1:Math.floor((p+o-1)/7)||(4>((new Date(m-1,0,1)).getDay()+6)%7?53:52),F:k.i18n.monthNames[g-1+12],m:e(g),M:k.i18n.monthNames[g-1],n:g,t:"?",L:"?",o:"?",Y:m,y:(""+ +m).substring(2),a:12>a?d.AmPm[0]:d.AmPm[1],A:12>a?d.AmPm[2]:d.AmPm[3],B:"?",g:a%12||12,G:a,h:e(a%12||12),H:e(a),i:e(c),s:e(l),u:r,e:"?",I:"?",O:(0'+a+"
"};b.fn.fmatter.checkbox=function(a,c){var f=b.extend({},c.checkbox),d;void 0!==c.colModel&&!b.fmatter.isUndefined(c.colModel.formatoptions)&&(f=b.extend({},f,c.colModel.formatoptions));d=!0===f.disabled?'disabled="disabled"': +"";if(b.fmatter.isEmpty(a)||b.fmatter.isUndefined(a))a=b.fn.fmatter.defaultFormat(a,f);a=(a+"").toLowerCase();return'a.search(/(false|0|no|off)/i)?" checked='checked' ":"")+' value="'+a+'" offval="no" '+d+"/>"};b.fn.fmatter.link=function(a,c){var f={target:c.target},d="";void 0!==c.colModel&&!b.fmatter.isUndefined(c.colModel.formatoptions)&&(f=b.extend({},f,c.colModel.formatoptions));f.target&&(d="target="+f.target);return!b.fmatter.isEmpty(a)?"'+ +a+"":b.fn.fmatter.defaultFormat(a,c)};b.fn.fmatter.showlink=function(a,c){var f={baseLinkUrl:c.baseLinkUrl,showAction:c.showAction,addParam:c.addParam||"",target:c.target,idName:c.idName},d="";void 0!==c.colModel&&!b.fmatter.isUndefined(c.colModel.formatoptions)&&(f=b.extend({},f,c.colModel.formatoptions));f.target&&(d="target="+f.target);f=f.baseLinkUrl+f.showAction+"?"+f.idName+"="+c.rowId+f.addParam;return b.fmatter.isString(a)||b.fmatter.isNumber(a)?"'+a+"":b.fn.fmatter.defaultFormat(a, +c)};b.fn.fmatter.integer=function(a,c){var f=b.extend({},c.integer);void 0!==c.colModel&&!b.fmatter.isUndefined(c.colModel.formatoptions)&&(f=b.extend({},f,c.colModel.formatoptions));return b.fmatter.isEmpty(a)?f.defaultValue:b.fmatter.util.NumberFormat(a,f)};b.fn.fmatter.number=function(a,c){var f=b.extend({},c.number);void 0!==c.colModel&&!b.fmatter.isUndefined(c.colModel.formatoptions)&&(f=b.extend({},f,c.colModel.formatoptions));return b.fmatter.isEmpty(a)?f.defaultValue:b.fmatter.util.NumberFormat(a, +f)};b.fn.fmatter.currency=function(a,c){var f=b.extend({},c.currency);void 0!==c.colModel&&!b.fmatter.isUndefined(c.colModel.formatoptions)&&(f=b.extend({},f,c.colModel.formatoptions));return b.fmatter.isEmpty(a)?f.defaultValue:b.fmatter.util.NumberFormat(a,f)};b.fn.fmatter.date=function(a,c,f,d){f=b.extend({},c.date);void 0!==c.colModel&&!b.fmatter.isUndefined(c.colModel.formatoptions)&&(f=b.extend({},f,c.colModel.formatoptions));return!f.reformatAfterEdit&&"edit"==d||b.fmatter.isEmpty(a)?b.fn.fmatter.defaultFormat(a, +c):b.fmatter.util.DateFormat(f.srcformat,a,f.newformat,f)};b.fn.fmatter.select=function(a,c){var a=a+"",f=!1,d=[],e,g;b.fmatter.isUndefined(c.colModel.formatoptions)?b.fmatter.isUndefined(c.colModel.editoptions)||(f=c.colModel.editoptions.value,e=void 0===c.colModel.editoptions.separator?":":c.colModel.editoptions.separator,g=void 0===c.colModel.editoptions.delimiter?";":c.colModel.editoptions.delimiter):(f=c.colModel.formatoptions.value,e=void 0===c.colModel.formatoptions.separator?":":c.colModel.formatoptions.separator, +g=void 0===c.colModel.formatoptions.delimiter?";":c.colModel.formatoptions.delimiter);if(f){var h=!0===c.colModel.editoptions.multiple?!0:!1,i=[];h&&(i=a.split(","),i=b.map(i,function(a){return b.trim(a)}));if(b.fmatter.isString(f))for(var j=f.split(g),k=0,l=0;l0)return a}).join(e)),h)-1"):f.editbutton&&(g="onclick=jQuery.fn.fmatter.rowactions('"+d+"','"+c.gid+"','edit',"+c.pos+"); onmouseover=jQuery(this).addClass('ui-state-hover'); onmouseout=jQuery(this).removeClass('ui-state-hover') ",e=e+"
"); +f.delbutton&&(g="onclick=jQuery.fn.fmatter.rowactions('"+d+"','"+c.gid+"','del',"+c.pos+"); onmouseover=jQuery(this).addClass('ui-state-hover'); onmouseout=jQuery(this).removeClass('ui-state-hover'); ",e=e+"
");g="onclick=jQuery.fn.fmatter.rowactions('"+d+"','"+c.gid+"','save',"+c.pos+"); onmouseover=jQuery(this).addClass('ui-state-hover'); onmouseout=jQuery(this).removeClass('ui-state-hover'); "; +e=e+"";g="onclick=jQuery.fn.fmatter.rowactions('"+d+"','"+c.gid+"','cancel',"+c.pos+"); onmouseover=jQuery(this).addClass('ui-state-hover'); onmouseout=jQuery(this).removeClass('ui-state-hover'); ";e=e+""; +return"
"+e+"
"};b.unformat=function(a,c,f,d){var e,g=c.colModel.formatter,h=c.colModel.formatoptions||{},i=/([\.\*\_\'\(\)\{\}\+\?\\])/g,j=c.colModel.unformat||b.fn.fmatter[g]&&b.fn.fmatter[g].unformat;if("undefined"!==typeof j&&b.isFunction(j))e=j.call(this,b(a).text(),c,a);else if(!b.fmatter.isUndefined(g)&&b.fmatter.isString(g))switch(e=b.jgrid.formatter||{},g){case "integer":h=b.extend({},e.integer,h);c=h.thousandsSeparator.replace(i,"\\$1");e=b(a).text().replace(RegExp(c, +"g"),"");break;case "number":h=b.extend({},e.number,h);c=h.thousandsSeparator.replace(i,"\\$1");e=b(a).text().replace(RegExp(c,"g"),"").replace(h.decimalSeparator,".");break;case "currency":h=b.extend({},e.currency,h);c=h.thousandsSeparator.replace(i,"\\$1");c=RegExp(c,"g");e=b(a).text();h.prefix&&h.prefix.length&&(e=e.substr(h.prefix.length));h.suffix&&h.suffix.length&&(e=e.substr(0,e.length-h.suffix.length));e=e.replace(c,"").replace(h.decimalSeparator,".");break;case "checkbox":h=c.colModel.editoptions? +c.colModel.editoptions.value.split(":"):["Yes","No"];e=b("input",a).is(":checked")?h[0]:h[1];break;case "select":e=b.unformat.select(a,c,f,d);break;case "actions":return"";default:e=b(a).text()}return void 0!==e?e:!0===d?b(a).text():b.jgrid.htmlDecode(b(a).html())};b.unformat.select=function(a,c,f,d){f=[];a=b(a).text();if(!0===d)return a;var d=b.extend({},!b.fmatter.isUndefined(c.colModel.formatoptions)?c.colModel.formatoptions:c.colModel.editoptions),c=void 0===d.separator?":":d.separator,e=void 0=== +d.delimiter?";":d.delimiter;if(d.value){var g=d.value,d=!0===d.multiple?!0:!1,h=[];d&&(h=a.split(","),h=b.map(h,function(a){return b.trim(a)}));if(b.fmatter.isString(g))for(var i=g.split(e),j=0,k=0;k0)return a}).join(c)),d)-1"),g;a.each(c.p.colModel,function(){var i=this,j,q,f,n;q=a("");j=a("
");!0===this.hidden&& +a(q).css("display","none");this.search=!1===this.search?!1:!0;"undefined"==typeof this.stype&&(this.stype="text");f=a.extend({},this.searchoptions||{});if(this.search)switch(this.stype){case "select":if(n=this.surl||f.dataUrl)a.ajax(a.extend({url:n,dataType:"html",success:function(c){if(f.buildSelect!==void 0)(c=f.buildSelect(c))&&a(j).append(c);else a(j).append(c);f.defaultValue!==void 0&&a("select",j).val(f.defaultValue);a("select",j).attr({name:i.index||i.name,id:"gs_"+i.name});f.attr&&a("select", +j).attr(f.attr);a("select",j).css({width:"100%"});f.dataInit!==void 0&&f.dataInit(a("select",j)[0]);f.dataEvents!==void 0&&d(a("select",j)[0],f.dataEvents);b.autosearch===true&&a("select",j).change(function(){e();return false});c=null}},a.jgrid.ajaxOptions,c.p.ajaxSelectOptions||{}));else{var m,o,k;i.searchoptions?(m=void 0===i.searchoptions.value?"":i.searchoptions.value,o=void 0===i.searchoptions.separator?":":i.searchoptions.separator,k=void 0===i.searchoptions.delimiter?";":i.searchoptions.delimiter): +i.editoptions&&(m=void 0===i.editoptions.value?"":i.editoptions.value,o=void 0===i.editoptions.separator?":":i.editoptions.separator,k=void 0===i.editoptions.delimiter?";":i.editoptions.delimiter);if(m){n=document.createElement("select");n.style.width="100%";a(n).attr({name:i.index||i.name,id:"gs_"+i.name});var l;if("string"===typeof m){m=m.split(k);for(var p=0;p"), +f.attr&&a("input",j).attr(f.attr),void 0!==f.dataInit&&f.dataInit(a("input",j)[0]),void 0!==f.dataEvents&&d(a("input",j)[0],f.dataEvents),!0===b.autosearch&&(b.searchOnEnter?a("input",j).keypress(function(a){if((a.charCode?a.charCode:a.keyCode?a.keyCode:0)==13){e();return false}return this}):a("input",j).keydown(function(a){switch(a.which){case 13:return false;case 9:case 16:case 37:case 38:case 39:case 40:case 27:break;default:g&&clearTimeout(g);g=setTimeout(function(){e()},500)}}))}a(q).append(j); +a(h).append(q)});a("table thead",c.grid.hDiv).append(h);this.ftoolbar=!0;this.triggerToolbar=e;this.clearToolbar=function(d){var j={},g=0,f,d="boolean"!=typeof d?!0:d;a.each(c.p.colModel,function(){var b;this.searchoptions&&void 0!==this.searchoptions.defaultValue&&(b=this.searchoptions.defaultValue);f=this.index||this.name;switch(this.stype){case "select":a("#gs_"+a.jgrid.jqID(this.name)+" option",!0===this.frozen&&!0===c.p.frozenColumns?c.grid.fhDiv:c.grid.hDiv).each(function(c){if(c===0)this.selected= +true;if(a(this).val()==b){this.selected=true;return false}});if(void 0!==b)j[f]=b,g++;else try{delete c.p.postData[f]}catch(d){}break;case "text":if(a("#gs_"+a.jgrid.jqID(this.name),!0===this.frozen&&!0===c.p.frozenColumns?c.grid.fhDiv:c.grid.hDiv).val(b),void 0!==b)j[f]=b,g++;else try{delete c.p.postData[f]}catch(e){}}});var h=0",{role:"rowheader"}).addClass("ui-jqgrid-labels");h=c.headers;c=0;for(e=h.length;c",{role:"row", +"aria-hidden":"true"}).addClass("jqg-first-row-header").css("height","auto"):l.empty();var p,r=function(a,b){for(var c=0,d=b.length;c",{role:"rowheader"}).addClass("ui-jqgrid-labels jqg-third-row-header");for(d=0;d",{role:"gridcell"}).css(g).addClass("ui-first-th-"+this.p.direction).appendTo(l),i.style.width="", +g=r(c.name,b.groupHeaders),0<=g){g=b.groupHeaders[g];e=g.numberOfColumns;q=g.titleText;for(g=c=0;g").attr({role:"columnheader"}).addClass("ui-state-default ui-th-column-header ui-th-"+this.p.direction).css({height:"22px","border-top":"0px none"}).html(q);0",{role:"columnheader"}).addClass("ui-state-default ui-th-column-header ui-th-"+ +this.p.direction).css({display:c.hidden?"none":"","border-top":"0px none"}).insertBefore(j),h.append(i)):(h.append(i),e--);f=a(this).children("thead");f.prepend(l);h.insertAfter(k);o.append(f);b.useColSpanStyle&&(o.find("span.ui-jqgrid-resize").each(function(){var b=a(this).parent();b.is(":visible")&&(this.style.cssText="height: "+b.height()+"px !important; cursor: col-resize;")}),o.find("div.ui-jqgrid-sortable").each(function(){var b=a(this),c=b.parent();c.is(":visible")&&c.is(":has(span.ui-jqgrid-resize)")&& +b.css("top",(c.height()-b.outerHeight())/2+"px")}));p=f.find("tr.jqg-first-row-header");a(this).bind("jqGridResizeStop.setGroupHeaders",function(a,b,c){p.find("th").eq(c).width(b)})})},setFrozenColumns:function(){return this.each(function(){if(this.grid){var b=this,d=b.p.colModel,c=0,e=d.length,h=-1,g=!1;if(!(!0===b.p.subGrid||!0===b.p.treeGrid||!0===b.p.cellEdit||b.p.sortable||b.p.scroll||b.p.grouping)){b.p.rownumbers&&c++;for(b.p.multiselect&&c++;c');b.grid.fbDiv=a('
'); +a("#gview_"+a.jgrid.jqID(b.p.id)).append(b.grid.fhDiv);d=a(".ui-jqgrid-htable","#gview_"+a.jgrid.jqID(b.p.id)).clone(!0);if(b.p.groupHeader){a("tr.jqg-first-row-header, tr.jqg-third-row-header",d).each(function(){a("th:gt("+h+")",this).remove()});var i=-1,j=-1;a("tr.jqg-second-row-header th",d).each(function(){var b=parseInt(a(this).attr("colspan"),10);b&&(i+=b,j++);if(i===h)return!1});i!==h&&(j=h);a("tr.jqg-second-row-header",d).each(function(){a("th:gt("+j+")",this).remove()})}else a("tr",d).each(function(){a("th:gt("+ +h+")",this).remove()});a(d).width(1);a(b.grid.fhDiv).append(d).mousemove(function(a){if(b.grid.resizing)return b.grid.dragMove(a),!1});a(b).bind("jqGridResizeStop.setFrozenColumns",function(c,d,e){c=a(".ui-jqgrid-htable",b.grid.fhDiv);a("th:eq("+e+")",c).width(d);c=a(".ui-jqgrid-btable",b.grid.fbDiv);a("tr:first td:eq("+e+")",c).width(d)});a(b).bind("jqGridOnSortCol.setFrozenColumns",function(c,d){var e=a("tr.ui-jqgrid-labels:last th:eq("+b.p.lastsort+")",b.grid.fhDiv),g=a("tr.ui-jqgrid-labels:last th:eq("+ +d+")",b.grid.fhDiv);a("span.ui-grid-ico-sort",e).addClass("ui-state-disabled");a(e).attr("aria-selected","false");a("span.ui-icon-"+b.p.sortorder,g).removeClass("ui-state-disabled");a(g).attr("aria-selected","true");!b.p.viewsortcols[0]&&b.p.lastsort!=d&&(a("span.s-ico",e).hide(),a("span.s-ico",g).show())});a("#gview_"+a.jgrid.jqID(b.p.id)).append(b.grid.fbDiv);jQuery(b.grid.bDiv).scroll(function(){jQuery(b.grid.fbDiv).scrollTop(jQuery(this).scrollTop())});!0===b.p.hoverrows&&a("#"+a.jgrid.jqID(b.p.id)).unbind("mouseover").unbind("mouseout"); +a(b).bind("jqGridAfterGridComplete.setFrozenColumns",function(){a("#"+a.jgrid.jqID(b.p.id)+"_frozen").remove();jQuery(b.grid.fbDiv).height(jQuery(b.grid.bDiv).height()-16);var c=a("#"+a.jgrid.jqID(b.p.id)).clone(!0);a("tr",c).each(function(){a("td:gt("+h+")",this).remove()});a(c).width(1).attr("id",b.p.id+"_frozen");a(b.grid.fbDiv).append(c);!0===b.p.hoverrows&&(a("tr.jqgrow",c).hover(function(){a(this).addClass("ui-state-hover");a("#"+a.jgrid.jqID(this.id),"#"+a.jgrid.jqID(b.p.id)).addClass("ui-state-hover")}, +function(){a(this).removeClass("ui-state-hover");a("#"+a.jgrid.jqID(this.id),"#"+a.jgrid.jqID(b.p.id)).removeClass("ui-state-hover")}),a("tr.jqgrow","#"+a.jgrid.jqID(b.p.id)).hover(function(){a(this).addClass("ui-state-hover");a("#"+a.jgrid.jqID(this.id),"#"+a.jgrid.jqID(b.p.id)+"_frozen").addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover");a("#"+a.jgrid.jqID(this.id),"#"+a.jgrid.jqID(b.p.id)+"_frozen").removeClass("ui-state-hover")}));c=null});b.p.frozenColumns=!0}}}})}, +destroyFrozenColumns:function(){return this.each(function(){if(this.grid&&!0===this.p.frozenColumns){a(this.grid.fhDiv).remove();a(this.grid.fbDiv).remove();this.grid.fhDiv=null;this.grid.fbDiv=null;a(this).unbind(".setFrozenColumns");if(!0===this.p.hoverrows){var b;a("#"+a.jgrid.jqID(this.p.id)).bind("mouseover",function(d){b=a(d.target).closest("tr.jqgrow");"ui-subgrid"!==a(b).attr("class")&&a(b).addClass("ui-state-hover")}).bind("mouseout",function(d){b=a(d.target).closest("tr.jqgrow");a(b).removeClass("ui-state-hover")})}this.p.frozenColumns= +!1}})}})})(jQuery); +(function(a){a.extend(a.jgrid,{showModal:function(a){a.w.show()},closeModal:function(a){a.w.hide().attr("aria-hidden","true");a.o&&a.o.remove()},hideModal:function(d,b){b=a.extend({jqm:!0,gb:""},b||{});if(b.onClose){var c=b.onClose(d);if("boolean"==typeof c&&!c)return}if(a.fn.jqm&&!0===b.jqm)a(d).attr("aria-hidden","true").jqmHide();else{if(""!==b.gb)try{a(".jqgrid-overlay:first",b.gb).hide()}catch(f){}a(d).hide().attr("aria-hidden","true")}},findPos:function(a){var b=0,c=0;if(a.offsetParent){do b+= +a.offsetLeft,c+=a.offsetTop;while(a=a.offsetParent)}return[b,c]},createModal:function(d,b,c,f,g,h,i){var e=document.createElement("div"),l,j=this,i=a.extend({},i||{});l="rtl"==a(c.gbox).attr("dir")?!0:!1;e.className="ui-widget ui-widget-content ui-corner-all ui-jqdialog";e.id=d.themodal;var k=document.createElement("div");k.className="ui-jqdialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix";k.id=d.modalhead;a(k).append(""+c.caption+"");var n=a("").hover(function(){n.addClass("ui-state-hover")}, +function(){n.removeClass("ui-state-hover")}).append("");a(k).append(n);l?(e.dir="rtl",a(".ui-jqdialog-title",k).css("float","right"),a(".ui-jqdialog-titlebar-close",k).css("left","0.3em")):(e.dir="ltr",a(".ui-jqdialog-title",k).css("float","left"),a(".ui-jqdialog-titlebar-close",k).css("right","0.3em"));var m=document.createElement("div");a(m).addClass("ui-jqdialog-content ui-widget-content").attr("id",d.modalcontent);a(m).append(b);e.appendChild(m); +a(e).prepend(k);!0===h?a("body").append(e):"string"==typeof h?a(h).append(e):a(e).insertBefore(f);a(e).css(i);"undefined"===typeof c.jqModal&&(c.jqModal=!0);b={};if(a.fn.jqm&&!0===c.jqModal)0===c.left&&0===c.top&&c.overlay&&(i=[],i=a.jgrid.findPos(g),c.left=i[0]+4,c.top=i[1]+4),b.top=c.top+"px",b.left=c.left;else if(0!==c.left||0!==c.top)b.left=c.left,b.top=c.top+"px";a("a.ui-jqdialog-titlebar-close",k).click(function(){var b=a("#"+a.jgrid.jqID(d.themodal)).data("onClose")||c.onClose,e=a("#"+a.jgrid.jqID(d.themodal)).data("gbox")|| +c.gbox;j.hideModal("#"+a.jgrid.jqID(d.themodal),{gb:e,jqm:c.jqModal,onClose:b});return false});if(0===c.width||!c.width)c.width=300;if(0===c.height||!c.height)c.height=200;c.zIndex||(f=a(f).parents("*[role=dialog]").filter(":first").css("z-index"),c.zIndex=f?parseInt(f,10)+2:950);f=0;l&&b.left&&!h&&(f=a(c.gbox).width()-(!isNaN(c.width)?parseInt(c.width,10):0)-8,b.left=parseInt(b.left,10)+parseInt(f,10));b.left&&(b.left+="px");a(e).css(a.extend({width:isNaN(c.width)?"auto":c.width+"px",height:isNaN(c.height)? +"auto":c.height+"px",zIndex:c.zIndex,overflow:"hidden"},b)).attr({tabIndex:"-1",role:"dialog","aria-labelledby":d.modalhead,"aria-hidden":"true"});"undefined"==typeof c.drag&&(c.drag=!0);"undefined"==typeof c.resize&&(c.resize=!0);if(c.drag)if(a(k).css("cursor","move"),a.fn.jqDrag)a(e).jqDrag(k);else try{a(e).draggable({handle:a("#"+a.jgrid.jqID(k.id))})}catch(o){}if(c.resize)if(a.fn.jqResize)a(e).append("
"), +a("#"+a.jgrid.jqID(d.themodal)).jqResize(".jqResize",d.scrollelm?"#"+a.jgrid.jqID(d.scrollelm):!1);else try{a(e).resizable({handles:"se, sw",alsoResize:d.scrollelm?"#"+a.jgrid.jqID(d.scrollelm):!1})}catch(p){}!0===c.closeOnEscape&&a(e).keydown(function(b){if(b.which==27){b=a("#"+a.jgrid.jqID(d.themodal)).data("onClose")||c.onClose;j.hideModal(this,{gb:c.gbox,jqm:c.jqModal,onClose:b})}})},viewModal:function(d,b){b=a.extend({toTop:!0,overlay:10,modal:!1,overlayClass:"ui-widget-overlay",onShow:a.jgrid.showModal, +onHide:a.jgrid.closeModal,gbox:"",jqm:!0,jqM:!0},b||{});if(a.fn.jqm&&!0===b.jqm)b.jqM?a(d).attr("aria-hidden","false").jqm(b).jqmShow():a(d).attr("aria-hidden","false").jqmShow();else{""!==b.gbox&&(a(".jqgrid-overlay:first",b.gbox).show(),a(d).data("gbox",b.gbox));a(d).show().attr("aria-hidden","false");try{a(":input:visible",d)[0].focus()}catch(c){}}},info_dialog:function(d,b,c,f){var g={width:290,height:"auto",dataheight:"auto",drag:!0,resize:!1,caption:""+d+"",left:250,top:170,zIndex:1E3, +jqModal:!0,modal:!1,closeOnEscape:!0,align:"center",buttonalign:"center",buttons:[]};a.extend(g,f||{});var h=g.jqModal,i=this;a.fn.jqm&&!h&&(h=!1);d="";if(0"+g.buttons[f].text+"";f=isNaN(g.dataheight)?g.dataheight:g.dataheight+"px";b="
"+("
"+b+"
");b+=c?"
"+c+""+d+"
":""!==d?"
"+ +d+"
":"";b+="
";try{"false"==a("#info_dialog").attr("aria-hidden")&&a.jgrid.hideModal("#info_dialog",{jqm:h}),a("#info_dialog").remove()}catch(e){}a.jgrid.createModal({themodal:"info_dialog",modalhead:"info_head",modalcontent:"info_content",scrollelm:"infocnt"},b,g,"","",!0);d&&a.each(g.buttons,function(b){a("#"+a.jgrid.jqID(this.id),"#info_id").bind("click",function(){g.buttons[b].onClick.call(a("#info_dialog"));return!1})});a("#closedialog","#info_id").click(function(){i.hideModal("#info_dialog", +{jqm:h});return!1});a(".fm-button","#info_dialog").hover(function(){a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")});a.isFunction(g.beforeOpen)&&g.beforeOpen();a.jgrid.viewModal("#info_dialog",{onHide:function(a){a.w.hide().remove();a.o&&a.o.remove()},modal:g.modal,jqm:h});a.isFunction(g.afterOpen)&&g.afterOpen();try{a("#info_dialog").focus()}catch(l){}},createEl:function(d,b,c,f,g){function h(b,d){a.isFunction(d.dataInit)&&d.dataInit.call(l,b);d.dataEvents&& +a.each(d.dataEvents,function(){void 0!==this.data?a(b).bind(this.type,this.data,this.fn):a(b).bind(this.type,this.fn)});return d}function i(b,d,c){var e="dataInit,dataEvents,dataUrl,buildSelect,sopt,searchhidden,defaultValue,attr".split(",");"undefined"!=typeof c&&a.isArray(c)&&a.merge(e,c);a.each(d,function(d,c){-1===a.inArray(d,e)&&a(b).attr(d,c)});d.hasOwnProperty("id")||a(b).attr("id",a.jgrid.randId())}var e="",l=this;switch(d){case "textarea":e=document.createElement("textarea");f?b.cols||a(e).css({width:"98%"}): +b.cols||(b.cols=20);b.rows||(b.rows=2);if(" "==c||" "==c||1==c.length&&160==c.charCodeAt(0))c="";e.value=c;i(e,b);b=h(e,b);a(e).attr({role:"textbox",multiline:"true"});break;case "checkbox":e=document.createElement("input");e.type="checkbox";b.value?(d=b.value.split(":"),c===d[0]&&(e.checked=!0,e.defaultChecked=!0),e.value=d[0],a(e).attr("offval",d[1])):(d=c.toLowerCase(),0>d.search(/(false|0|no|off|undefined)/i)&&""!==d?(e.checked=!0,e.defaultChecked=!0,e.value=c):e.value="on",a(e).attr("offval", +"off"));i(e,b,["value"]);b=h(e,b);a(e).attr("role","checkbox");break;case "select":e=document.createElement("select");e.setAttribute("role","select");f=[];!0===b.multiple?(d=!0,e.multiple="multiple",a(e).attr("aria-multiselectable","true")):d=!1;if("undefined"!=typeof b.dataUrl)a.ajax(a.extend({url:b.dataUrl,type:"GET",dataType:"html",context:{elem:e,options:b,vl:c},success:function(d){var b=[],c=this.elem,e=this.vl,f=a.extend({},this.options),g=f.multiple===true;a.isFunction(f.buildSelect)&&(d=f.buildSelect.call(l, +d));if(d=a(d).html()){a(c).append(d);i(c,f);f=h(c,f);if(typeof f.size==="undefined")f.size=g?3:1;if(g){b=e.split(",");b=a.map(b,function(b){return a.trim(b)})}else b[0]=a.trim(e);setTimeout(function(){a("option",c).each(function(d){if(d===0&&c.multiple)this.selected=false;a(this).attr("role","option");if(a.inArray(a.trim(a(this).text()),b)>-1||a.inArray(a.trim(a(this).val()),b)>-1)this.selected="selected"})},0)}}},g||{}));else if(b.value){var j;"undefined"===typeof b.size&&(b.size=d?3:1);d&&(f=c.split(","), +f=a.map(f,function(b){return a.trim(b)}));"function"===typeof b.value&&(b.value=b.value());var k,n,m=void 0===b.separator?":":b.separator,g=void 0===b.delimiter?";":b.delimiter;if("string"===typeof b.value){k=b.value.split(g);for(j=0;j0)return a}).join(m));g=document.createElement("option");g.setAttribute("role","option");g.value=n[0];g.innerHTML=n[1];e.appendChild(g);if(!d&&(a.trim(n[0])==a.trim(c)||a.trim(n[1])==a.trim(c)))g.selected= +"selected";if(d&&(-1l.length||1>c[a[i]]||12l.length||1>c[a[h]]||31(0===c[a[f]]%4&&(0!==c[a[f]]%100||0===c[a[f]]%400)?29:28)||c[a[h]]>e[c[a[i]]]?!1:!0},isEmpty:function(a){return a.match(/^\s+$/)||""===a?!0:!1},checkTime:function(d){var b=/^(\d{1,2}):(\d{2})([ap]m)?$/;if(!a.jgrid.isEmpty(d))if(d=d.match(b)){if(d[3]){if(1>d[1]||12parseFloat(h.maxValue))return[!1,i+": "+a.jgrid.edit.msg.maxValue+" "+h.maxValue,""];if(!0===h.email&&!(!1===f&&a.jgrid.isEmpty(d))&&(g=/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i, +!g.test(d)))return[!1,i+": "+a.jgrid.edit.msg.email,""];if(!0===h.integer&&!(!1===f&&a.jgrid.isEmpty(d))&&(isNaN(d)||0!==d%1||-1!=d.indexOf(".")))return[!1,i+": "+a.jgrid.edit.msg.integer,""];if(!0===h.date&&!(!1===f&&a.jgrid.isEmpty(d))&&(b=c.p.colModel[b].formatoptions&&c.p.colModel[b].formatoptions.newformat?c.p.colModel[b].formatoptions.newformat:c.p.colModel[b].datefmt||"Y-m-d",!$jgrid.checkDate(b,d)))return[!1,i+": "+a.jgrid.edit.msg.date+" - "+b,""];if(!0===h.time&&!(!1===f&&a.jgrid.isEmpty(d))&& +!a.jgrid.checkTime(d))return[!1,i+": "+a.jgrid.edit.msg.date+" - hh:mm (am/pm)",""];if(!0===h.url&&!(!1===f&&a.jgrid.isEmpty(d))&&(g=/^(((https?)|(ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i,!g.test(d)))return[!1,i+": "+a.jgrid.edit.msg.url,""];if(!0===h.custom&&!(!1===f&&a.jgrid.isEmpty(d)))return a.isFunction(h.custom_func)?(d=h.custom_func.call(c,d,i),a.isArray(d)?d:[!1,a.jgrid.edit.msg.customarray,""]):[!1,a.jgrid.edit.msg.customfcheck, +""]}return[!0,"",""]}})})(jQuery); +(function(a){var c={};a.jgrid.extend({searchGrid:function(c){c=a.extend({recreateFilter:!1,drag:!0,sField:"searchField",sValue:"searchString",sOper:"searchOper",sFilter:"filters",loadDefaults:!0,beforeShowSearch:null,afterShowSearch:null,onInitializeSearch:null,afterRedraw:null,afterChange:null,closeAfterSearch:!1,closeAfterReset:!1,closeOnEscape:!1,searchOnEnter:!1,multipleSearch:!1,multipleGroup:!1,top:0,left:0,jqModal:!0,modal:!1,resize:!0,width:450,height:"auto",dataheight:"auto",showQuery:!1, +errorcheck:!0,sopt:null,stringResult:void 0,onClose:null,onSearch:null,onReset:null,toTop:!0,overlay:30,columns:[],tmplNames:null,tmplFilters:null,tmplLabel:" Template: ",showOnLoad:!1,layer:null},a.jgrid.search,c||{});return this.each(function(){function d(b){r=a(e).triggerHandler("jqGridFilterBeforeShow",[b]);"undefined"===typeof r&&(r=!0);r&&a.isFunction(c.beforeShowSearch)&&(r=c.beforeShowSearch.call(e,b));r&&(a.jgrid.viewModal("#"+a.jgrid.jqID(t.themodal),{gbox:"#gbox_"+a.jgrid.jqID(l),jqm:c.jqModal, +modal:c.modal,overlay:c.overlay,toTop:c.toTop}),a(e).triggerHandler("jqGridFilterAfterShow",[b]),a.isFunction(c.afterShowSearch)&&c.afterShowSearch.call(e,b))}var e=this;if(e.grid){var l="fbox_"+e.p.id,r=!0,t={themodal:"searchmod"+l,modalhead:"searchhd"+l,modalcontent:"searchcnt"+l,scrollelm:l},s=e.p.postData[c.sFilter];"string"===typeof s&&(s=a.jgrid.parse(s));!0===c.recreateFilter&&a("#"+a.jgrid.jqID(t.themodal)).remove();if(null!==a("#"+a.jgrid.jqID(t.themodal)).html())d(a("#fbox_"+a.jgrid.jqID(+e.p.id))); +else{var p=a("
").insertBefore("#gview_"+a.jgrid.jqID(e.p.id)),g="left",f="";"rtl"==e.p.direction&&(g="right",f=" style='text-align:left'",p.attr("dir","rtl"));var n=a.extend([],e.p.colModel),w=""+c.Find+"",b=""+ +c.Reset+"",m="",h="",k,j=!1,o=-1;c.showQuery&&(m="Query");c.columns.length?n=c.columns:a.each(n,function(a,b){if(!b.label)b.label=e.p.colNames[a];if(!j){var c=typeof b.search==="undefined"?true:b.search,d=b.hidden===true;if(b.searchoptions&&b.searchoptions.searchhidden===true&&c||c&&!d){j=true;k=b.index||b.name;o=a}}});if(!s&&k||!1=== +c.multipleSearch){var y="eq";0<=o&&n[o].searchoptions&&n[o].searchoptions.sopt?y=n[o].searchoptions.sopt[0]:c.sopt&&c.sopt.length&&(y=c.sopt[0]);s={groupOp:"AND",rules:[{field:k,op:y,data:""}]}}j=!1;c.tmplNames&&c.tmplNames.length&&(j=!0,h=c.tmplLabel,h+="");g="

"+b+h+""+m+w+"
";l=a.jgrid.jqID(l);a("#"+l).jqFilter({columns:n,filter:c.loadDefaults?s:null,showQuery:c.showQuery,errorcheck:c.errorcheck,sopt:c.sopt,groupButton:c.multipleGroup,ruleButtons:c.multipleSearch,afterRedraw:c.afterRedraw,_gridsopt:a.jgrid.search.odata,ajaxSelectOptions:e.p.ajaxSelectOptions, +groupOps:c.groupOps,onChange:function(){this.p.showQuery&&a(".query",this).html(this.toUserFriendlyString());a.isFunction(c.afterChange)&&c.afterChange.call(e,a("#"+l),c)},direction:e.p.direction});p.append(g);j&&c.tmplFilters&&c.tmplFilters.length&&a(".ui-template",p).bind("change",function(){var b=a(this).val();b=="default"?a("#"+l).jqFilter("addFilter",s):a("#"+l).jqFilter("addFilter",c.tmplFilters[parseInt(b,10)]);return false});!0===c.multipleGroup&&(c.multipleSearch=!0);a(e).triggerHandler("jqGridFilterInitialize", +[a("#"+l)]);a.isFunction(c.onInitializeSearch)&&c.onInitializeSearch.call(e,a("#"+l));c.gbox="#gbox_"+l;c.layer?a.jgrid.createModal(t,p,c,"#gview_"+a.jgrid.jqID(e.p.id),a("#gbox_"+a.jgrid.jqID(e.p.id))[0],"#"+a.jgrid.jqID(c.layer),{position:"relative"}):a.jgrid.createModal(t,p,c,"#gview_"+a.jgrid.jqID(e.p.id),a("#gbox_"+a.jgrid.jqID(e.p.id))[0]);(c.searchOnEnter||c.closeOnEscape)&&a("#"+a.jgrid.jqID(t.themodal)).keydown(function(b){var d=a(b.target);if(c.searchOnEnter&&b.which===13&&!d.hasClass("add-group")&& +!d.hasClass("add-rule")&&!d.hasClass("delete-group")&&!d.hasClass("delete-rule")&&(!d.hasClass("fm-button")||!d.is("[id$=_query]"))){a("#"+l+"_search").focus().click();return false}if(c.closeOnEscape&&b.which===27){a("#"+a.jgrid.jqID(t.modalhead)).find(".ui-jqdialog-titlebar-close").focus().click();return false}});m&&a("#"+l+"_query").bind("click",function(){a(".queryresult",p).toggle();return false});void 0===c.stringResult&&(c.stringResult=c.multipleSearch);a("#"+l+"_search").bind("click",function(){var b= +a("#"+l),d={},h,q=b.jqFilter("filterData");if(c.errorcheck){b[0].hideError();c.showQuery||b.jqFilter("toSQLString");if(b[0].p.error){b[0].showError();return false}}if(c.stringResult){try{h=xmlJsonClass.toJson(q,"","",false)}catch(f){try{h=JSON.stringify(q)}catch(g){}}if(typeof h==="string"){d[c.sFilter]=h;a.each([c.sField,c.sValue,c.sOper],function(){d[this]=""})}}else if(c.multipleSearch){d[c.sFilter]=q;a.each([c.sField,c.sValue,c.sOper],function(){d[this]=""})}else{d[c.sField]=q.rules[0].field; +d[c.sValue]=q.rules[0].data;d[c.sOper]=q.rules[0].op;d[c.sFilter]=""}e.p.search=true;a.extend(e.p.postData,d);a(e).triggerHandler("jqGridFilterSearch");a.isFunction(c.onSearch)&&c.onSearch.call(e);a(e).trigger("reloadGrid",[{page:1}]);c.closeAfterSearch&&a.jgrid.hideModal("#"+a.jgrid.jqID(t.themodal),{gb:"#gbox_"+a.jgrid.jqID(e.p.id),jqm:c.jqModal,onClose:c.onClose});return false});a("#"+l+"_reset").bind("click",function(){var b={},d=a("#"+l);e.p.search=false;c.multipleSearch===false?b[c.sField]= +b[c.sValue]=b[c.sOper]="":b[c.sFilter]="";d[0].resetFilter();j&&a(".ui-template",p).val("default");a.extend(e.p.postData,b);a(e).triggerHandler("jqGridFilterReset");a.isFunction(c.onReset)&&c.onReset.call(e);a(e).trigger("reloadGrid",[{page:1}]);return false});d(a("#"+l));a(".fm-button:not(.ui-state-disabled)",p).hover(function(){a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")})}}})},editGridRow:function(u,d){d=a.extend({top:0,left:0,width:300,height:"auto",dataheight:"auto", +modal:!1,overlay:30,drag:!0,resize:!0,url:null,mtype:"POST",clearAfterAdd:!0,closeAfterEdit:!1,reloadAfterSubmit:!0,onInitializeForm:null,beforeInitData:null,beforeShowForm:null,afterShowForm:null,beforeSubmit:null,afterSubmit:null,onclickSubmit:null,afterComplete:null,onclickPgButtons:null,afterclickPgButtons:null,editData:{},recreateForm:!1,jqModal:!0,closeOnEscape:!1,addedrow:"first",topinfo:"",bottominfo:"",saveicon:[],closeicon:[],savekey:[!1,13],navkeys:[!1,38,40],checkOnSubmit:!1,checkOnUpdate:!1, +_savedData:{},processing:!1,onClose:null,ajaxEditOptions:{},serializeEditData:null,viewPagerButtons:!0},a.jgrid.edit,d||{});c[a(this)[0].p.id]=d;return this.each(function(){function e(){a(j+" > tbody > tr > td > .FormElement").each(function(){var d=a(".customelement",this);if(d.length){var c=a(d[0]).attr("name");a.each(b.p.colModel,function(){if(this.name===c&&this.editoptions&&a.isFunction(this.editoptions.custom_value)){try{if(i[c]=this.editoptions.custom_value.call(b,a("#"+a.jgrid.jqID(c),j),"get"), +void 0===i[c])throw"e1";}catch(d){"e1"===d?a.jgrid.info_dialog(jQuery.jgrid.errors.errcap,"function 'custom_value' "+a.jgrid.edit.msg.novalue,jQuery.jgrid.edit.bClose):a.jgrid.info_dialog(jQuery.jgrid.errors.errcap,d.message,jQuery.jgrid.edit.bClose)}return!0}})}else{switch(a(this).get(0).type){case "checkbox":a(this).is(":checked")?i[this.name]=a(this).val():(d=a(this).attr("offval"),i[this.name]=d);break;case "select-one":i[this.name]=a("option:selected",this).val();B[this.name]=a("option:selected", +this).text();break;case "select-multiple":i[this.name]=a(this).val();i[this.name]=i[this.name]?i[this.name].join(","):"";var e=[];a("option:selected",this).each(function(b,d){e[b]=a(d).text()});B[this.name]=e.join(",");break;case "password":case "text":case "textarea":case "button":i[this.name]=a(this).val()}b.p.autoencode&&(i[this.name]=a.jgrid.htmlEncode(i[this.name]))}});return!0}function l(d,e,h,q){var i,f,g,k=0,j,o,l,p=[],n=!1,u="",m;for(m=1;m<=q;m++)u+="  "; +"_empty"!=d&&(n=a(e).jqGrid("getInd",d));a(e.p.colModel).each(function(m){i=this.name;o=(f=this.editrules&&!0===this.editrules.edithidden?!1:!0===this.hidden?!0:!1)?"style='display:none'":"";if("cb"!==i&&"subgrid"!==i&&!0===this.editable&&"rn"!==i){if(!1===n)j="";else if(i==e.p.ExpandColumn&&!0===e.p.treeGrid)j=a("td:eq("+m+")",e.rows[n]).text();else{try{j=a.unformat.call(e,a("td:eq("+m+")",e.rows[n]),{rowId:d,colModel:this},m)}catch(r){j=this.edittype&&"textarea"==this.edittype?a("td:eq("+m+")", +e.rows[n]).text():a("td:eq("+m+")",e.rows[n]).html()}if(!j||" "==j||" "==j||1==j.length&&160==j.charCodeAt(0))j=""}var v=a.extend({},this.editoptions||{},{id:i,name:i}),s=a.extend({},{elmprefix:"",elmsuffix:"",rowabove:!1,rowcontent:""},this.formoptions||{}),t=parseInt(s.rowpos,10)||k+1,y=parseInt(2*(parseInt(s.colpos,10)||1),10);"_empty"==d&&v.defaultValue&&(j=a.isFunction(v.defaultValue)?v.defaultValue.call(b):v.defaultValue);this.edittype||(this.edittype="text");b.p.autoencode&&(j=a.jgrid.htmlDecode(j)); +l=a.jgrid.createEl.call(b,this.edittype,v,j,!1,a.extend({},a.jgrid.ajaxOptions,e.p.ajaxSelectOptions||{}));""===j&&"checkbox"==this.edittype&&(j=a(l).attr("offval"));""===j&&"select"==this.edittype&&(j=a("option:eq(0)",l).text());if(c[b.p.id].checkOnSubmit||c[b.p.id].checkOnUpdate)c[b.p.id]._savedData[i]=j;a(l).addClass("FormElement");("text"==this.edittype||"textarea"==this.edittype)&&a(l).addClass("ui-widget-content ui-corner-all");g=a(h).find("tr[rowpos="+t+"]");s.rowabove&&(v=a(""+s.rowcontent+""),a(h).append(v),v[0].rp=t);0===g.length&&(g=a("").addClass("FormData").attr("id","tr_"+i),a(g).append(u),a(h).append(g),g[0].rp=t);a("td:eq("+(y-2)+")",g[0]).html("undefined"===typeof s.label?e.p.colNames[m]:s.label);a("td:eq("+(y-1)+")",g[0]).append(s.elmprefix).append(l).append(s.elmsuffix);p[k]=m;k++}});if(0