Browse files

Merge pull request #39 from tghw/master

Update WebPutty to Python 2.7
  • Loading branch information...
2 parents 442141e + ea7e46d commit 1866f38632282e665dc11cd41476f9153cd66204 @tghw tghw committed Nov 6, 2012
Showing with 1,097 additions and 1,108 deletions.
  1. +89 −88 app.yaml
  2. +29 −29 app/decorators.py
  3. +478 −478 app/models.py
  4. +85 −85 app/rpc.py
  5. +32 −37 bootstrap.py
  6. +160 −154 compress.py
  7. +177 −182 fabfile.py
  8. +47 −55 incoming_email_handler.py
  9. BIN libs/babel/localedata/aa.dat
  10. BIN libs/babel/localedata/aa_DJ.dat
  11. BIN libs/babel/localedata/aa_ER.dat
  12. BIN libs/babel/localedata/aa_ER_SAAHO.dat
  13. BIN libs/babel/localedata/aa_ET.dat
  14. BIN libs/babel/localedata/af.dat
  15. BIN libs/babel/localedata/af_NA.dat
  16. BIN libs/babel/localedata/af_ZA.dat
  17. BIN libs/babel/localedata/ak.dat
  18. BIN libs/babel/localedata/ak_GH.dat
  19. BIN libs/babel/localedata/am.dat
  20. BIN libs/babel/localedata/am_ET.dat
  21. BIN libs/babel/localedata/ar.dat
  22. BIN libs/babel/localedata/ar_AE.dat
  23. BIN libs/babel/localedata/ar_BH.dat
  24. BIN libs/babel/localedata/ar_DZ.dat
  25. BIN libs/babel/localedata/ar_EG.dat
  26. BIN libs/babel/localedata/ar_IQ.dat
  27. BIN libs/babel/localedata/ar_JO.dat
  28. BIN libs/babel/localedata/ar_KW.dat
  29. BIN libs/babel/localedata/ar_LB.dat
  30. BIN libs/babel/localedata/ar_LY.dat
  31. BIN libs/babel/localedata/ar_MA.dat
  32. BIN libs/babel/localedata/ar_OM.dat
  33. BIN libs/babel/localedata/ar_QA.dat
  34. BIN libs/babel/localedata/ar_SA.dat
  35. BIN libs/babel/localedata/ar_SD.dat
  36. BIN libs/babel/localedata/ar_SY.dat
  37. BIN libs/babel/localedata/ar_TN.dat
  38. BIN libs/babel/localedata/ar_YE.dat
  39. BIN libs/babel/localedata/as.dat
  40. BIN libs/babel/localedata/as_IN.dat
  41. BIN libs/babel/localedata/az.dat
  42. BIN libs/babel/localedata/az_AZ.dat
  43. BIN libs/babel/localedata/az_Cyrl.dat
  44. BIN libs/babel/localedata/az_Cyrl_AZ.dat
  45. BIN libs/babel/localedata/az_Latn.dat
  46. BIN libs/babel/localedata/az_Latn_AZ.dat
  47. BIN libs/babel/localedata/be.dat
  48. BIN libs/babel/localedata/be_BY.dat
  49. BIN libs/babel/localedata/bg.dat
  50. BIN libs/babel/localedata/bg_BG.dat
  51. BIN libs/babel/localedata/bn.dat
  52. BIN libs/babel/localedata/bn_BD.dat
  53. BIN libs/babel/localedata/bn_IN.dat
  54. BIN libs/babel/localedata/bs.dat
  55. BIN libs/babel/localedata/bs_BA.dat
  56. BIN libs/babel/localedata/byn.dat
  57. BIN libs/babel/localedata/byn_ER.dat
  58. BIN libs/babel/localedata/ca.dat
  59. BIN libs/babel/localedata/ca_ES.dat
  60. BIN libs/babel/localedata/cch.dat
  61. BIN libs/babel/localedata/cch_NG.dat
  62. BIN libs/babel/localedata/cop.dat
  63. BIN libs/babel/localedata/cs.dat
  64. BIN libs/babel/localedata/cs_CZ.dat
  65. BIN libs/babel/localedata/cy.dat
  66. BIN libs/babel/localedata/cy_GB.dat
  67. BIN libs/babel/localedata/da.dat
  68. BIN libs/babel/localedata/da_DK.dat
  69. BIN libs/babel/localedata/de.dat
  70. BIN libs/babel/localedata/de_AT.dat
  71. BIN libs/babel/localedata/de_BE.dat
  72. BIN libs/babel/localedata/de_CH.dat
  73. BIN libs/babel/localedata/de_DE.dat
  74. BIN libs/babel/localedata/de_LI.dat
  75. BIN libs/babel/localedata/de_LU.dat
  76. BIN libs/babel/localedata/dv.dat
  77. BIN libs/babel/localedata/dv_MV.dat
  78. BIN libs/babel/localedata/dz.dat
  79. BIN libs/babel/localedata/dz_BT.dat
  80. BIN libs/babel/localedata/ee.dat
  81. BIN libs/babel/localedata/ee_GH.dat
  82. BIN libs/babel/localedata/ee_TG.dat
  83. BIN libs/babel/localedata/el.dat
  84. BIN libs/babel/localedata/el_CY.dat
  85. BIN libs/babel/localedata/el_GR.dat
  86. BIN libs/babel/localedata/el_POLYTON.dat
  87. BIN libs/babel/localedata/eo.dat
  88. BIN libs/babel/localedata/es.dat
  89. BIN libs/babel/localedata/es_AR.dat
  90. BIN libs/babel/localedata/es_BO.dat
  91. BIN libs/babel/localedata/es_CL.dat
  92. BIN libs/babel/localedata/es_CO.dat
  93. BIN libs/babel/localedata/es_CR.dat
  94. BIN libs/babel/localedata/es_DO.dat
  95. BIN libs/babel/localedata/es_EC.dat
  96. BIN libs/babel/localedata/es_ES.dat
  97. BIN libs/babel/localedata/es_GT.dat
  98. BIN libs/babel/localedata/es_HN.dat
  99. BIN libs/babel/localedata/es_MX.dat
  100. BIN libs/babel/localedata/es_NI.dat
  101. BIN libs/babel/localedata/es_PA.dat
  102. BIN libs/babel/localedata/es_PE.dat
  103. BIN libs/babel/localedata/es_PR.dat
  104. BIN libs/babel/localedata/es_PY.dat
  105. BIN libs/babel/localedata/es_SV.dat
  106. BIN libs/babel/localedata/es_US.dat
  107. BIN libs/babel/localedata/es_UY.dat
  108. BIN libs/babel/localedata/es_VE.dat
  109. BIN libs/babel/localedata/et.dat
  110. BIN libs/babel/localedata/et_EE.dat
  111. BIN libs/babel/localedata/eu.dat
  112. BIN libs/babel/localedata/eu_ES.dat
  113. BIN libs/babel/localedata/fa.dat
  114. BIN libs/babel/localedata/fa_AF.dat
  115. BIN libs/babel/localedata/fa_IR.dat
  116. BIN libs/babel/localedata/fi.dat
  117. BIN libs/babel/localedata/fi_FI.dat
  118. BIN libs/babel/localedata/fil.dat
  119. BIN libs/babel/localedata/fil_PH.dat
  120. BIN libs/babel/localedata/fo.dat
  121. BIN libs/babel/localedata/fo_FO.dat
  122. BIN libs/babel/localedata/fur.dat
  123. BIN libs/babel/localedata/fur_IT.dat
  124. BIN libs/babel/localedata/ga.dat
  125. BIN libs/babel/localedata/ga_IE.dat
  126. BIN libs/babel/localedata/gaa.dat
  127. BIN libs/babel/localedata/gaa_GH.dat
  128. BIN libs/babel/localedata/gez.dat
  129. BIN libs/babel/localedata/gez_ER.dat
  130. BIN libs/babel/localedata/gez_ET.dat
  131. BIN libs/babel/localedata/gl.dat
  132. BIN libs/babel/localedata/gl_ES.dat
  133. BIN libs/babel/localedata/gu.dat
  134. BIN libs/babel/localedata/gu_IN.dat
  135. BIN libs/babel/localedata/gv.dat
  136. BIN libs/babel/localedata/gv_GB.dat
  137. BIN libs/babel/localedata/ha.dat
  138. BIN libs/babel/localedata/ha_Arab.dat
  139. BIN libs/babel/localedata/ha_Arab_NG.dat
  140. BIN libs/babel/localedata/ha_Arab_SD.dat
  141. BIN libs/babel/localedata/ha_GH.dat
  142. BIN libs/babel/localedata/ha_Latn.dat
  143. BIN libs/babel/localedata/ha_Latn_GH.dat
  144. BIN libs/babel/localedata/ha_Latn_NE.dat
  145. BIN libs/babel/localedata/ha_Latn_NG.dat
  146. BIN libs/babel/localedata/ha_NE.dat
  147. BIN libs/babel/localedata/ha_NG.dat
  148. BIN libs/babel/localedata/ha_SD.dat
  149. BIN libs/babel/localedata/haw.dat
  150. BIN libs/babel/localedata/haw_US.dat
  151. BIN libs/babel/localedata/he.dat
  152. BIN libs/babel/localedata/he_IL.dat
  153. BIN libs/babel/localedata/hi.dat
  154. BIN libs/babel/localedata/hi_IN.dat
  155. BIN libs/babel/localedata/hr.dat
  156. BIN libs/babel/localedata/hr_HR.dat
  157. BIN libs/babel/localedata/hu.dat
  158. BIN libs/babel/localedata/hu_HU.dat
  159. BIN libs/babel/localedata/hy.dat
  160. BIN libs/babel/localedata/hy_AM.dat
  161. BIN libs/babel/localedata/hy_AM_REVISED.dat
  162. BIN libs/babel/localedata/ia.dat
  163. BIN libs/babel/localedata/id.dat
  164. BIN libs/babel/localedata/id_ID.dat
  165. BIN libs/babel/localedata/ig.dat
  166. BIN libs/babel/localedata/ig_NG.dat
  167. BIN libs/babel/localedata/ii.dat
  168. BIN libs/babel/localedata/ii_CN.dat
  169. BIN libs/babel/localedata/in.dat
  170. BIN libs/babel/localedata/is.dat
  171. BIN libs/babel/localedata/is_IS.dat
  172. BIN libs/babel/localedata/it.dat
  173. BIN libs/babel/localedata/it_CH.dat
  174. BIN libs/babel/localedata/it_IT.dat
  175. BIN libs/babel/localedata/iu.dat
  176. BIN libs/babel/localedata/iw.dat
  177. BIN libs/babel/localedata/ja.dat
  178. BIN libs/babel/localedata/ja_JP.dat
  179. BIN libs/babel/localedata/ka.dat
  180. BIN libs/babel/localedata/ka_GE.dat
  181. BIN libs/babel/localedata/kaj.dat
  182. BIN libs/babel/localedata/kaj_NG.dat
  183. BIN libs/babel/localedata/kam.dat
  184. BIN libs/babel/localedata/kam_KE.dat
  185. BIN libs/babel/localedata/kcg.dat
  186. BIN libs/babel/localedata/kcg_NG.dat
  187. BIN libs/babel/localedata/kfo.dat
  188. BIN libs/babel/localedata/kfo_CI.dat
  189. BIN libs/babel/localedata/kk.dat
  190. BIN libs/babel/localedata/kk_Cyrl.dat
  191. BIN libs/babel/localedata/kk_Cyrl_KZ.dat
  192. BIN libs/babel/localedata/kk_KZ.dat
  193. BIN libs/babel/localedata/kl.dat
  194. BIN libs/babel/localedata/kl_GL.dat
  195. BIN libs/babel/localedata/km.dat
  196. BIN libs/babel/localedata/km_KH.dat
  197. BIN libs/babel/localedata/kn.dat
  198. BIN libs/babel/localedata/kn_IN.dat
  199. BIN libs/babel/localedata/ko.dat
  200. BIN libs/babel/localedata/ko_KR.dat
  201. BIN libs/babel/localedata/kok.dat
  202. BIN libs/babel/localedata/kok_IN.dat
  203. BIN libs/babel/localedata/kpe.dat
  204. BIN libs/babel/localedata/kpe_GN.dat
  205. BIN libs/babel/localedata/kpe_LR.dat
  206. BIN libs/babel/localedata/ku.dat
  207. BIN libs/babel/localedata/ku_Arab.dat
  208. BIN libs/babel/localedata/ku_Latn.dat
  209. BIN libs/babel/localedata/ku_Latn_TR.dat
  210. BIN libs/babel/localedata/ku_TR.dat
  211. BIN libs/babel/localedata/kw.dat
  212. BIN libs/babel/localedata/kw_GB.dat
  213. BIN libs/babel/localedata/ky.dat
  214. BIN libs/babel/localedata/ky_KG.dat
  215. BIN libs/babel/localedata/ln.dat
  216. BIN libs/babel/localedata/ln_CD.dat
  217. BIN libs/babel/localedata/ln_CG.dat
  218. BIN libs/babel/localedata/lo.dat
  219. BIN libs/babel/localedata/lo_LA.dat
  220. BIN libs/babel/localedata/lt.dat
  221. BIN libs/babel/localedata/lt_LT.dat
  222. BIN libs/babel/localedata/lv.dat
  223. BIN libs/babel/localedata/lv_LV.dat
  224. BIN libs/babel/localedata/mk.dat
  225. BIN libs/babel/localedata/mk_MK.dat
  226. BIN libs/babel/localedata/ml.dat
  227. BIN libs/babel/localedata/ml_IN.dat
  228. BIN libs/babel/localedata/mn.dat
  229. BIN libs/babel/localedata/mn_CN.dat
  230. BIN libs/babel/localedata/mn_Cyrl.dat
  231. BIN libs/babel/localedata/mn_Cyrl_MN.dat
  232. BIN libs/babel/localedata/mn_MN.dat
  233. BIN libs/babel/localedata/mn_Mong.dat
  234. BIN libs/babel/localedata/mn_Mong_CN.dat
  235. BIN libs/babel/localedata/mo.dat
  236. BIN libs/babel/localedata/mr.dat
  237. BIN libs/babel/localedata/mr_IN.dat
  238. BIN libs/babel/localedata/ms.dat
  239. BIN libs/babel/localedata/ms_BN.dat
  240. BIN libs/babel/localedata/ms_MY.dat
  241. BIN libs/babel/localedata/mt.dat
  242. BIN libs/babel/localedata/mt_MT.dat
  243. BIN libs/babel/localedata/my.dat
  244. BIN libs/babel/localedata/my_MM.dat
  245. BIN libs/babel/localedata/nb.dat
  246. BIN libs/babel/localedata/nb_NO.dat
  247. BIN libs/babel/localedata/ne.dat
  248. BIN libs/babel/localedata/ne_IN.dat
  249. BIN libs/babel/localedata/ne_NP.dat
  250. BIN libs/babel/localedata/nl.dat
  251. BIN libs/babel/localedata/nl_BE.dat
  252. BIN libs/babel/localedata/nl_NL.dat
  253. BIN libs/babel/localedata/nn.dat
  254. BIN libs/babel/localedata/nn_NO.dat
  255. BIN libs/babel/localedata/no.dat
  256. BIN libs/babel/localedata/nr.dat
  257. BIN libs/babel/localedata/nr_ZA.dat
  258. BIN libs/babel/localedata/nso.dat
  259. BIN libs/babel/localedata/nso_ZA.dat
  260. BIN libs/babel/localedata/ny.dat
  261. BIN libs/babel/localedata/ny_MW.dat
  262. BIN libs/babel/localedata/om.dat
  263. BIN libs/babel/localedata/om_ET.dat
  264. BIN libs/babel/localedata/om_KE.dat
  265. BIN libs/babel/localedata/or.dat
  266. BIN libs/babel/localedata/or_IN.dat
  267. BIN libs/babel/localedata/pa.dat
  268. BIN libs/babel/localedata/pa_Arab.dat
  269. BIN libs/babel/localedata/pa_Arab_PK.dat
  270. BIN libs/babel/localedata/pa_Guru.dat
  271. BIN libs/babel/localedata/pa_Guru_IN.dat
  272. BIN libs/babel/localedata/pa_IN.dat
  273. BIN libs/babel/localedata/pa_PK.dat
  274. BIN libs/babel/localedata/pl.dat
  275. BIN libs/babel/localedata/pl_PL.dat
  276. BIN libs/babel/localedata/ps.dat
  277. BIN libs/babel/localedata/ps_AF.dat
  278. BIN libs/babel/localedata/pt.dat
  279. BIN libs/babel/localedata/pt_BR.dat
  280. BIN libs/babel/localedata/pt_PT.dat
  281. BIN libs/babel/localedata/ro.dat
  282. BIN libs/babel/localedata/ro_MD.dat
  283. BIN libs/babel/localedata/ro_RO.dat
  284. BIN libs/babel/localedata/root.dat
  285. BIN libs/babel/localedata/ru.dat
  286. BIN libs/babel/localedata/ru_RU.dat
  287. BIN libs/babel/localedata/ru_UA.dat
  288. BIN libs/babel/localedata/rw.dat
  289. BIN libs/babel/localedata/rw_RW.dat
  290. BIN libs/babel/localedata/sa.dat
  291. BIN libs/babel/localedata/sa_IN.dat
  292. BIN libs/babel/localedata/se.dat
  293. BIN libs/babel/localedata/se_FI.dat
  294. BIN libs/babel/localedata/se_NO.dat
  295. BIN libs/babel/localedata/sh.dat
  296. BIN libs/babel/localedata/sh_BA.dat
  297. BIN libs/babel/localedata/sh_CS.dat
  298. BIN libs/babel/localedata/sh_YU.dat
  299. BIN libs/babel/localedata/si.dat
  300. BIN libs/babel/localedata/si_LK.dat
Sorry, we could not display the entire diff because too many files (441) changed.
View
177 app.yaml
@@ -1,88 +1,89 @@
-application: your-gae-application
-version: 1
-runtime: python
-api_version: 1
-default_expiration: "365d"
-
-inbound_services:
-- mail
-- channel_presence
-- warmup
-
-handlers:
-
-- url: /favicon\.ico
- static_files: static/img/favicon.ico
- upload: static/img/favicon\.ico
- mime_type: image/x-icon
-
-- url: /respond-proxy\.html
- static_files: static/respond/respond-proxy.html
- upload: static/respond/respond-proxy\.html
-
-- url: /static/img/(.*\.(gif|png|jpg))
- static_files: static/img/\1
- upload: static/img/(.*\.(gif|png|jpg))
-
-- url: /static/img/docs/(.*\.(gif|png|jpg))
- static_files: static/img/docs/\1
- upload: static/img/docs/(.*\.(gif|png|jpg))
-
-- url: /static/css/(.*\.css)
- mime_type: text/css
- static_files: static/css/\1
- upload: static/css/(.*\.css)
-
-- url: /static/js/(.*\.js)
- mime_type: text/javascript
- static_files: static/js/\1
- upload: static/js/(.*\.js)
-
-- url: /static/codemirror/(.*\.css)
- mime_type: text/css
- static_files: static/codemirror/\1
- upload: static/codemirror/(.*\.css)
-
-- url: /static/codemirror/(.*\.js)
- mime_type: text/javascript
- static_files: static/codemirror/\1
- upload: static/codemirror/(.*\.js)
-
-- url: /_ah/mail/.+
- script: incoming_email_handler.py
- login: admin
-
-- url: /_migrate
- script: bootstrap.py
- login: admin
-
-- url: /gae_mini_profiler/static/js/(.*\.tmpl)
- mime_type: text/html
- static_files: libs/gae_mini_profiler/static/js/\1
- upload: libs/gae_mini_profiler/static/js/(.*\.tmpl)
-
-- url: /gae_mini_profiler/static
- static_dir: libs/gae_mini_profiler/static
-
-- url: /gae_mini_profiler/.*
- script: libs/gae_mini_profiler/main.py
-
-- url: /livecount/counter_admin
- script: libs/livecount/counter_admin.py
- login: admin
-
-- url: /livecount/.*
- script: libs/livecount/counter.py
- login: admin
-
-- url: /tasks/.*
- script: bootstrap.py
- login: admin
-
-- url: .*\.(jpg|gif|png)
- static_files: static/img/404.png
- upload: static/img/404.png
-
-- url: .*
- script: bootstrap.py
-
+application: tghwputty
+version: 1
+runtime: python27
+threadsafe: yes
+api_version: 1
+default_expiration: "365d"
+
+inbound_services:
+- mail
+- channel_presence
+- warmup
+
+handlers:
+
+- url: /favicon\.ico
+ static_files: static/img/favicon.ico
+ upload: static/img/favicon\.ico
+ mime_type: image/x-icon
+
+- url: /respond-proxy\.html
+ static_files: static/respond/respond-proxy.html
+ upload: static/respond/respond-proxy\.html
+
+- url: /static/img/(.*\.(gif|png|jpg))
+ static_files: static/img/\1
+ upload: static/img/(.*\.(gif|png|jpg))
+
+- url: /static/img/docs/(.*\.(gif|png|jpg))
+ static_files: static/img/docs/\1
+ upload: static/img/docs/(.*\.(gif|png|jpg))
+
+- url: /static/css/(.*\.css)
+ mime_type: text/css
+ static_files: static/css/\1
+ upload: static/css/(.*\.css)
+
+- url: /static/js/(.*\.js)
+ mime_type: text/javascript
+ static_files: static/js/\1
+ upload: static/js/(.*\.js)
+
+- url: /static/codemirror/(.*\.css)
+ mime_type: text/css
+ static_files: static/codemirror/\1
+ upload: static/codemirror/(.*\.css)
+
+- url: /static/codemirror/(.*\.js)
+ mime_type: text/javascript
+ static_files: static/codemirror/\1
+ upload: static/codemirror/(.*\.js)
+
+- url: /_ah/mail/.+
+ script: incoming_email_handler.application
+ login: admin
+
+- url: /_migrate
+ script: bootstrap.app
+ login: admin
+
+- url: /gae_mini_profiler/static/js/(.*\.tmpl)
+ mime_type: text/html
+ static_files: libs/gae_mini_profiler/static/js/\1
+ upload: libs/gae_mini_profiler/static/js/(.*\.tmpl)
+
+- url: /gae_mini_profiler/static
+ static_dir: libs/gae_mini_profiler/static
+
+- url: /gae_mini_profiler/.*
+ script: libs.gae_mini_profiler.main.app
+
+- url: /livecount/counter_admin
+ script: libs.livecount.counter_admin.application
+ login: admin
+
+- url: /livecount/.*
+ script: libs.livecount.counter.application
+ login: admin
+
+- url: /tasks/.*
+ script: bootstrap.app
+ login: admin
+
+- url: .*\.(jpg|gif|png)
+ static_files: static/img/404.png
+ upload: static/img/404.png
+
+- url: .*
+ script: bootstrap.app
+
View
58 app/decorators.py
@@ -1,29 +1,29 @@
-import simplejson as json
-from functools import wraps
-from flask import Response, redirect, request
-from google.appengine.api import users
-
-def requires_auth(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- if not users.get_current_user():
- return redirect(users.create_login_url(request.path))
- return func(*args, **kwargs)
- return wrapper
-
-def requires_admin(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- if not users.get_current_user() or not users.is_current_user_admin():
- return redirect(users.create_login_url(request.path))
- return func(*args, **kwargs)
- return wrapper
-
-def as_json(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- res = func(*args, **kwargs)
- if isinstance(res, Response):
- return res
- return json.dumps(res)
- return wrapper
+import json
+from functools import wraps
+from flask import Response, redirect, request
+from google.appengine.api import users
+
+def requires_auth(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ if not users.get_current_user():
+ return redirect(users.create_login_url(request.path))
+ return func(*args, **kwargs)
+ return wrapper
+
+def requires_admin(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ if not users.get_current_user() or not users.is_current_user_admin():
+ return redirect(users.create_login_url(request.path))
+ return func(*args, **kwargs)
+ return wrapper
+
+def as_json(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ res = func(*args, **kwargs)
+ if isinstance(res, Response):
+ return res
+ return json.dumps(res)
+ return wrapper
View
956 app/models.py
@@ -1,478 +1,478 @@
-from logging import Formatter, Filter, StreamHandler
-import scss
-import settings
-import simplejson as json
-from datetime import datetime
-from StringIO import StringIO
-from google.appengine.ext import db
-from google.appengine.api import users as gae_users
-from google.appengine.api import taskqueue
-from google.appengine.api import channel as gae_channels
-from google.appengine.api import files
-from google.appengine.api.datastore_errors import BadKeyError
-from flask import abort, url_for, render_template
-
-def dt_handler(obj):
- if isinstance(obj, datetime):
- return obj.isoformat() + 'Z'
- return None
-
-class ExceptionFilter(Filter):
- def filter(self, record):
- if record.getMessage().lower().startswith('exception'):
- return 0
- return 1
-
-# Old and dead...
-class UserGroup(db.Model):
- name = db.StringProperty(required=True)
- users = db.ListProperty(gae_users.User)
- admins = db.ListProperty(gae_users.User)
-
-class SchemaVersion(db.Model):
- version = db.IntegerProperty(required=True, default=0)
-
-class Site(db.Model):
- name = db.StringProperty(required=True)
- owner = db.UserProperty()
- users = db.ListProperty(gae_users.User)
- admins = db.ListProperty(gae_users.User)
- example = db.BooleanProperty(default=False)
-
- def delete(self):
- for page in self.page_set.fetch(10000):
- page.delete()
- db.delete(self)
-
- @staticmethod
- def get_or_404(id):
- site = Site.get_by_id(id)
- if not site or gae_users.get_current_user() not in site.users:
- abort(404)
- return site
-
- @staticmethod
- def get_admin_or_404(id):
- site = Site.get_by_id(id)
- if not site or gae_users.get_current_user() not in site.admins:
- abort(404)
- return site
-
-class Invitation(db.Model):
- hash = db.StringProperty(required=True)
- site = db.ReferenceProperty(Site, required=True)
- email = db.EmailProperty(required=True)
- admin = db.BooleanProperty(default=False)
- inviter = db.UserProperty(required=True)
- has_been_logged_out = db.BooleanProperty(default=False)
-
-class Page(db.Model):
- name = db.StringProperty(required=True)
- url = db.LinkProperty(required=True)
- site = db.ReferenceProperty(Site)
- _styles = db.ListProperty(db.Key)
- channels = db.ListProperty(db.Key)
- preview_img = db.BlobProperty(required=False, default=None)
- preview_urls = db.ListProperty(db.Link, default=None) # *additional* preview urls
- import_state = db.IntegerProperty(default=0)
- on_cdn = db.BooleanProperty(default=False)
-
- _style_cache = None
- def _set_styles(self, styles):
- self._style_cache = styles
- self._styles = [style.key() for style in styles]
- def _get_styles(self):
- if not self._style_cache:
- self._style_cache = [Style.get(k) for k in self._styles]
- return self._style_cache
- styles = property(_get_styles, _set_styles)
-
- def delete(self):
- for key in self.channels:
- channel = PageChannel.get(key)
- if channel:
- channel.send_message({'cmd': 'lock'})
- channel.delete()
- for style in self.styles:
- style.delete()
- db.delete(self)
-
- def clean_channels(self):
- stale = []
- for key in self.channels:
- channel = PageChannel.get(key)
- if not channel or channel.is_stale():
- stale.append(key)
- if stale:
- for key in stale:
- self.channels.remove(key)
- channel = PageChannel.get(key)
- if channel:
- # If the channel is still here, it's probably stale.
- # Send 'lock' and remove, so it can't clobber anyone else.
- channel.send_message({'cmd': 'lock'})
- channel.delete()
- self.put()
-
- def get_channels(self):
- channels = []
- stale = []
- for key in self.channels:
- channel = PageChannel.get(key)
- if channel:
- channels.append(channel)
- else:
- stale.append(key)
- if stale:
- for key in stale:
- self.channels.remove(key)
- self.put()
- return channels
-
- def update_locks(self):
- owner = None
- channels = self.get_channels()
- if channels:
- owner_user = channels[0].user
- owner = dict(name=owner_user.nickname(), email=owner_user.email())
- channels[0].send_message(dict(cmd='unlock', user=owner))
- lock_msg = dict(cmd='lock', user=owner)
- for channel in channels[1:]:
- channel.send_message(lock_msg)
-
- def add_channel(self, channel):
- self.remove_channel(channel)
- self.channels.append(channel.key())
- self.put()
-
- def add_channel_first(self, channel):
- self.remove_channel(channel)
- self.channels.insert(0, channel.key())
- self.put()
-
- def remove_channel(self, channel, delete=False):
- if channel.key() in self.channels:
- self.channels.remove(channel.key())
- self.put()
- if delete:
- channel.delete()
-
- def put(self, *args, **kwargs):
- self._set_styles(self.styles)
- super(Page, self).put(*args, **kwargs)
-
- def queue_preview(self):
- taskqueue.add(queue_name='fetch-preview', url=url_for('tasks.fetch_preview'), params={'page_key': self.key()})
-
- def queue_upload(self):
- taskqueue.add(queue_name='upload-css', url=url_for('tasks.upload_style'), params={'page_key': self.key()})
-
- def queue_refresh(self):
- self.queue_upload()
- self.queue_preview()
-
- def _css(self, preview, compress):
- css = StringIO()
- for style in self.styles:
- rev = style.preview_rev if (preview and style.preview_rev) else style.published_rev
- if compress:
- css.write(rev.compressed)
- else:
- css.write(scss.Scss().compile('@option compress:no;' + rev.raw))
- return css.getvalue()
-
- def compressed_css(self, preview):
- return self._css(preview, compress=True)
-
- def uncompressed_css(self, preview):
- return self._css(preview, compress=False)
-
- def last_modified(self, preview):
- max_last_edit = datetime.min
- for style in self.styles:
- rev = style.preview_rev if (preview and style.preview_rev) else style.published_rev
- max_last_edit = max(max_last_edit, rev.dt_last_edit)
- return max_last_edit
-
- def styles_json(self):
- # NOTE: It is okay to return an array here because we only display this
- # to users via editor.html. If we ever return this directly as the
- # response, we'll want to wrap it to avoid the exploit described at
- # http://haacked.com/archive/2009/06/25/json-hijacking.aspx
- styles_obj = [style.json_obj() for style in self.styles]
- return json.dumps(styles_obj, default=dt_handler, sort_keys=True, indent=4*' ' if settings.debug else None)
-
- def upload_to_cdn(self):
- if not settings.use_google_cloud_storage:
- return
- path = files.gs.create('/gs/%s/%s.css' % (settings.google_bucket, str(self.key())), mime_type='text/css', acl='public-read', cache_control='private,max-age=300')
- try:
- fd = files.open(path, 'a')
- fd.write(self.compressed_css(False).encode('utf-8'))
- self.on_cdn = True
- self.save()
- except Exception:
- self.on_cdn = False
- self.save()
- raise
- finally:
- fd.close()
- files.finalize(path)
-
- @staticmethod
- def get_or_404(key):
- page = None
- if isinstance(key, int) or (isinstance(key, basestring) and key.isdigit()):
- page = Page.get_by_id(int(key))
- else:
- try:
- key_obj = db.Key(key)
- except BadKeyError:
- abort(404)
- if(key_obj.kind() == 'Style'):
- page = Page.gql('WHERE _styles=:1', key_obj).get()
- else:
- page = Page.get(key)
- if not page:
- abort(404)
- return page
-
- @staticmethod
- def get_edit_or_404(page_id):
- page = Page.get_or_404(page_id)
- if gae_users.get_current_user() not in page.site.users:
- abort(404)
- return page
-
- @staticmethod
- def get_admin_or_404(page_id):
- page = Page.get_or_404(page_id)
- site = page.site
- if not site or gae_users.get_current_user() not in site.admins:
- abort(404)
- return page
-
- @staticmethod
- def new_page(site, name, url):
- '''
- Do all the work in adding a new page to a site.
- '''
- style = Style(name = name, site = site)
- style.put()
- first_rev = StyleRevision(parent=style)
- first_rev.raw = render_template('first_run.css')
- first_rev.put()
- style.published_rev = first_rev
- style.put()
- page = Page(
- name = name,
- url = url,
- site = site,
- _styles = [style.key()]
- )
- page.put()
- page.queue_refresh()
- return page
-
-class StyleRevision(db.Model):
- # parent = Style
- rev = db.IntegerProperty(required=True, default=0)
- dt_created = db.DateTimeProperty(auto_now_add=True)
- dt_last_edit = db.DateTimeProperty(auto_now=True)
- raw = db.TextProperty(required=False, default='')
- compressed = db.TextProperty(required=False, default=None)
- # Old and dead...
- css = db.TextProperty(required=False)
- _cached = db.TextProperty(required=False)
-
- def update(self, raw):
- self.raw = raw
- log = StringIO()
- handler = StreamHandler(log)
- handler.addFilter(ExceptionFilter())
- handler.setFormatter(Formatter('<span class="level">%(levelname)s</span>: <span class="message">%(message)s</span><br />'))
- scss.log.addHandler(handler)
- self.compressed = scss.Scss().compile(self.raw)
- scss.log.removeHandler(handler)
- handler.flush()
- self.put()
- return log.getvalue()
-
-class Style(db.Model):
- site = db.ReferenceProperty(Site)
- name = db.StringProperty(required=True)
- published_rev = db.ReferenceProperty(StyleRevision, default=None, collection_name='style_published')
- preview_rev = db.ReferenceProperty(StyleRevision, default=None, collection_name='style_preview')
-
- # Old and dead
- user_group = db.ReferenceProperty(UserGroup)
- url = db.LinkProperty(required=False)
-
- def delete(self, *args, **kwargs):
- revisions = StyleRevision.all(keys_only=True).ancestor(self).fetch(10000)
- db.delete(revisions)
- db.delete(self)
-
- def json_obj(self):
- if self.preview_rev:
- preview_rev = self.preview_rev
- else:
- if not self.published_rev:
- rev = StyleRevision(parent=self)
- rev.put()
- self.published_rev = rev
- self.put()
- preview_rev = self.published_rev
- return {
- 'id': self.key().id(),
- 'name': self.name,
- 'preview_scss': preview_rev.raw,
- 'preview_dt_last_edit': preview_rev.dt_last_edit,
- 'published_scss': self.published_rev.raw,
- 'published_dt_last_edit': self.published_rev.dt_last_edit,
- }
-
- @staticmethod
- def get_or_404(style_id):
- if isinstance(style_id, basestring) and not style_id.isdigit():
- style = Style.get(style_id)
- else:
- style = Style.get_by_id(style_id)
- if not style:
- abort(404)
- return style
-
- @staticmethod
- def get_edit_or_404(style_id):
- style = Style.get_or_404(style_id)
- if gae_users.get_current_user() not in style.site.users:
- abort(404)
- return style
-
- @staticmethod
- def get_admin_or_404(style_id):
- style = Style.get_or_404(style_id)
- site = style.site
- if not site or gae_users.get_current_user() not in site.admins:
- abort(404)
- return style
-
-class PageChannel(db.Model):
- user = db.UserProperty(required=True)
- page = db.ReferenceProperty(Page, required=True)
- token = db.StringProperty(required=True)
- client_id = db.StringProperty(required=True)
- dt_connected = db.DateTimeProperty(auto_now_add=True)
- dt_last_update = db.DateTimeProperty(auto_now_add=True)
-
- def is_stale(self):
- return (datetime.utcnow() - self.dt_last_update).seconds > 3600
-
- def send_message(self, message):
- if not isinstance(message, basestring):
- message = json.dumps(message, default=dt_handler, sort_keys=True, indent=4*' ' if settings.debug else None)
- gae_channels.send_message(self.client_id, message)
-
- @staticmethod
- def get_or_404(token=None, client_id=None):
- channel = None
- if token:
- channel = PageChannel.gql('WHERE token=:1', token).get()
- elif client_id:
- channel = PageChannel.gql('WHERE client_id=:1', client_id).get()
- if not channel:
- abort(404)
- return channel
-
-class UserSettings(db.Model):
- user = db.UserProperty(required=True)
- seen_example = db.BooleanProperty(default=False)
- seen_guiders = db.StringListProperty()
- # the last version (list of ints) this person has viewed the release notes for
- seen_version = db.ListProperty(int, default=None)
- locale = db.StringProperty(default=None)
- chimped = db.BooleanProperty(default=False)
-
- @staticmethod
- def has_seen_example():
- user = gae_users.get_current_user()
- if not user or not user.user_id():
- raise Exception("Logged in user expected")
- settings = UserSettings.get_or_insert(user.user_id(), user=user)
- return settings.seen_example
-
- @staticmethod
- def mark_example_as_seen():
- user = gae_users.get_current_user()
- if not user or not user.user_id():
- raise Exception("Logged in user expected")
- settings = UserSettings.get_or_insert(user.user_id(), user=user)
- settings.seen_example = True
- settings.put()
-
- @staticmethod
- def show_guider(guider_name):
- user = gae_users.get_current_user()
- if not user or not user.user_id():
- return False
- settings = UserSettings.get_or_insert(user.user_id(), user=user)
- return (guider_name not in settings.seen_guiders)
-
- @staticmethod
- def mark_guider_as_seen(guider_name):
- user = gae_users.get_current_user()
- if not user or not user.user_id():
- return
- settings = UserSettings.get_or_insert(user.user_id(), user=user)
- if not guider_name in settings.seen_guiders:
- settings.seen_guiders.append(guider_name)
- settings.put()
-
- @staticmethod
- def has_seen_version(version):
- user = gae_users.get_current_user()
- if not user or not user.user_id():
- return True # don't bother displaying "new version available" to non-authenticated users
- settings = UserSettings.get_or_insert(user.user_id(), user=user)
- if not settings.seen_version:
- settings.seen_version = [0, 0, 0]
- settings.put()
- return settings.seen_version >= version
-
- @staticmethod
- def mark_version_as_seen(version):
- user = gae_users.get_current_user()
- if not user or not user.user_id():
- return
- settings = UserSettings.get_or_insert(user.user_id(), user=user)
- settings.seen_version = version
- settings.put()
-
- @staticmethod
- def get_locale():
- user = gae_users.get_current_user()
- if not user or not user.user_id():
- return None
- settings = UserSettings.get_or_insert(user.user_id(), user=user)
- return settings.locale
-
- @staticmethod
- def set_locale(locale):
- user = gae_users.get_current_user()
- if not user or not user.user_id():
- return
- settings = UserSettings.get_or_insert(user.user_id(), user=user)
- settings.locale = locale
- settings.put()
-
-class Importer(db.Model):
- page = db.ReferenceProperty(Page)
- urls = db.StringListProperty()
- style = db.TextProperty()
- errors = db.StringListProperty()
-
-class Credential(db.Model):
- name = db.StringProperty()
- user = db.StringProperty(default='')
- passwd = db.StringProperty(default='')
- api_key = db.StringProperty(default='')
+from logging import Formatter, Filter, StreamHandler
+import scss
+import settings
+import json
+from datetime import datetime
+from StringIO import StringIO
+from google.appengine.ext import db
+from google.appengine.api import users as gae_users
+from google.appengine.api import taskqueue
+from google.appengine.api import channel as gae_channels
+from google.appengine.api import files
+from google.appengine.api.datastore_errors import BadKeyError
+from flask import abort, url_for, render_template
+
+def dt_handler(obj):
+ if isinstance(obj, datetime):
+ return obj.isoformat() + 'Z'
+ return None
+
+class ExceptionFilter(Filter):
+ def filter(self, record):
+ if record.getMessage().lower().startswith('exception'):
+ return 0
+ return 1
+
+# Old and dead...
+class UserGroup(db.Model):
+ name = db.StringProperty(required=True)
+ users = db.ListProperty(gae_users.User)
+ admins = db.ListProperty(gae_users.User)
+
+class SchemaVersion(db.Model):
+ version = db.IntegerProperty(required=True, default=0)
+
+class Site(db.Model):
+ name = db.StringProperty(required=True)
+ owner = db.UserProperty()
+ users = db.ListProperty(gae_users.User)
+ admins = db.ListProperty(gae_users.User)
+ example = db.BooleanProperty(default=False)
+
+ def delete(self):
+ for page in self.page_set.fetch(10000):
+ page.delete()
+ db.delete(self)
+
+ @staticmethod
+ def get_or_404(id):
+ site = Site.get_by_id(id)
+ if not site or gae_users.get_current_user() not in site.users:
+ abort(404)
+ return site
+
+ @staticmethod
+ def get_admin_or_404(id):
+ site = Site.get_by_id(id)
+ if not site or gae_users.get_current_user() not in site.admins:
+ abort(404)
+ return site
+
+class Invitation(db.Model):
+ hash = db.StringProperty(required=True)
+ site = db.ReferenceProperty(Site, required=True)
+ email = db.EmailProperty(required=True)
+ admin = db.BooleanProperty(default=False)
+ inviter = db.UserProperty(required=True)
+ has_been_logged_out = db.BooleanProperty(default=False)
+
+class Page(db.Model):
+ name = db.StringProperty(required=True)
+ url = db.LinkProperty(required=True)
+ site = db.ReferenceProperty(Site)
+ _styles = db.ListProperty(db.Key)
+ channels = db.ListProperty(db.Key)
+ preview_img = db.BlobProperty(required=False, default=None)
+ preview_urls = db.ListProperty(db.Link, default=None) # *additional* preview urls
+ import_state = db.IntegerProperty(default=0)
+ on_cdn = db.BooleanProperty(default=False)
+
+ _style_cache = None
+ def _set_styles(self, styles):
+ self._style_cache = styles
+ self._styles = [style.key() for style in styles]
+ def _get_styles(self):
+ if not self._style_cache:
+ self._style_cache = [Style.get(k) for k in self._styles]
+ return self._style_cache
+ styles = property(_get_styles, _set_styles)
+
+ def delete(self):
+ for key in self.channels:
+ channel = PageChannel.get(key)
+ if channel:
+ channel.send_message({'cmd': 'lock'})
+ channel.delete()
+ for style in self.styles:
+ style.delete()
+ db.delete(self)
+
+ def clean_channels(self):
+ stale = []
+ for key in self.channels:
+ channel = PageChannel.get(key)
+ if not channel or channel.is_stale():
+ stale.append(key)
+ if stale:
+ for key in stale:
+ self.channels.remove(key)
+ channel = PageChannel.get(key)
+ if channel:
+ # If the channel is still here, it's probably stale.
+ # Send 'lock' and remove, so it can't clobber anyone else.
+ channel.send_message({'cmd': 'lock'})
+ channel.delete()
+ self.put()
+
+ def get_channels(self):
+ channels = []
+ stale = []
+ for key in self.channels:
+ channel = PageChannel.get(key)
+ if channel:
+ channels.append(channel)
+ else:
+ stale.append(key)
+ if stale:
+ for key in stale:
+ self.channels.remove(key)
+ self.put()
+ return channels
+
+ def update_locks(self):
+ owner = None
+ channels = self.get_channels()
+ if channels:
+ owner_user = channels[0].user
+ owner = dict(name=owner_user.nickname(), email=owner_user.email())
+ channels[0].send_message(dict(cmd='unlock', user=owner))
+ lock_msg = dict(cmd='lock', user=owner)
+ for channel in channels[1:]:
+ channel.send_message(lock_msg)
+
+ def add_channel(self, channel):
+ self.remove_channel(channel)
+ self.channels.append(channel.key())
+ self.put()
+
+ def add_channel_first(self, channel):
+ self.remove_channel(channel)
+ self.channels.insert(0, channel.key())
+ self.put()
+
+ def remove_channel(self, channel, delete=False):
+ if channel.key() in self.channels:
+ self.channels.remove(channel.key())
+ self.put()
+ if delete:
+ channel.delete()
+
+ def put(self, *args, **kwargs):
+ self._set_styles(self.styles)
+ super(Page, self).put(*args, **kwargs)
+
+ def queue_preview(self):
+ taskqueue.add(queue_name='fetch-preview', url=url_for('tasks.fetch_preview'), params={'page_key': self.key()})
+
+ def queue_upload(self):
+ taskqueue.add(queue_name='upload-css', url=url_for('tasks.upload_style'), params={'page_key': self.key()})
+
+ def queue_refresh(self):
+ self.queue_upload()
+ self.queue_preview()
+
+ def _css(self, preview, compress):
+ css = StringIO()
+ for style in self.styles:
+ rev = style.preview_rev if (preview and style.preview_rev) else style.published_rev
+ if compress:
+ css.write(rev.compressed)
+ else:
+ css.write(scss.Scss().compile('@option compress:no;' + rev.raw))
+ return css.getvalue()
+
+ def compressed_css(self, preview):
+ return self._css(preview, compress=True)
+
+ def uncompressed_css(self, preview):
+ return self._css(preview, compress=False)
+
+ def last_modified(self, preview):
+ max_last_edit = datetime.min
+ for style in self.styles:
+ rev = style.preview_rev if (preview and style.preview_rev) else style.published_rev
+ max_last_edit = max(max_last_edit, rev.dt_last_edit)
+ return max_last_edit
+
+ def styles_json(self):
+ # NOTE: It is okay to return an array here because we only display this
+ # to users via editor.html. If we ever return this directly as the
+ # response, we'll want to wrap it to avoid the exploit described at
+ # http://haacked.com/archive/2009/06/25/json-hijacking.aspx
+ styles_obj = [style.json_obj() for style in self.styles]
+ return json.dumps(styles_obj, default=dt_handler, sort_keys=True, indent=4*' ' if settings.debug else None)
+
+ def upload_to_cdn(self):
+ if not settings.use_google_cloud_storage:
+ return
+ path = files.gs.create('/gs/%s/%s.css' % (settings.google_bucket, str(self.key())), mime_type='text/css', acl='public-read', cache_control='private,max-age=300')
+ try:
+ fd = files.open(path, 'a')
+ fd.write(self.compressed_css(False).encode('utf-8'))
+ self.on_cdn = True
+ self.save()
+ except Exception:
+ self.on_cdn = False
+ self.save()
+ raise
+ finally:
+ fd.close()
+ files.finalize(path)
+
+ @staticmethod
+ def get_or_404(key):
+ page = None
+ if isinstance(key, int) or (isinstance(key, basestring) and key.isdigit()):
+ page = Page.get_by_id(int(key))
+ else:
+ try:
+ key_obj = db.Key(key)
+ except BadKeyError:
+ abort(404)
+ if(key_obj.kind() == 'Style'):
+ page = Page.gql('WHERE _styles=:1', key_obj).get()
+ else:
+ page = Page.get(key)
+ if not page:
+ abort(404)
+ return page
+
+ @staticmethod
+ def get_edit_or_404(page_id):
+ page = Page.get_or_404(page_id)
+ if gae_users.get_current_user() not in page.site.users:
+ abort(404)
+ return page
+
+ @staticmethod
+ def get_admin_or_404(page_id):
+ page = Page.get_or_404(page_id)
+ site = page.site
+ if not site or gae_users.get_current_user() not in site.admins:
+ abort(404)
+ return page
+
+ @staticmethod
+ def new_page(site, name, url):
+ '''
+ Do all the work in adding a new page to a site.
+ '''
+ style = Style(name = name, site = site)
+ style.put()
+ first_rev = StyleRevision(parent=style)
+ first_rev.raw = render_template('first_run.css')
+ first_rev.put()
+ style.published_rev = first_rev
+ style.put()
+ page = Page(
+ name = name,
+ url = url,
+ site = site,
+ _styles = [style.key()]
+ )
+ page.put()
+ page.queue_refresh()
+ return page
+
+class StyleRevision(db.Model):
+ # parent = Style
+ rev = db.IntegerProperty(required=True, default=0)
+ dt_created = db.DateTimeProperty(auto_now_add=True)
+ dt_last_edit = db.DateTimeProperty(auto_now=True)
+ raw = db.TextProperty(required=False, default='')
+ compressed = db.TextProperty(required=False, default=None)
+ # Old and dead...
+ css = db.TextProperty(required=False)
+ _cached = db.TextProperty(required=False)
+
+ def update(self, raw):
+ self.raw = raw
+ log = StringIO()
+ handler = StreamHandler(log)
+ handler.addFilter(ExceptionFilter())
+ handler.setFormatter(Formatter('<span class="level">%(levelname)s</span>: <span class="message">%(message)s</span><br />'))
+ scss.log.addHandler(handler)
+ self.compressed = scss.Scss().compile(self.raw)
+ scss.log.removeHandler(handler)
+ handler.flush()
+ self.put()
+ return log.getvalue()
+
+class Style(db.Model):
+ site = db.ReferenceProperty(Site)
+ name = db.StringProperty(required=True)
+ published_rev = db.ReferenceProperty(StyleRevision, default=None, collection_name='style_published')
+ preview_rev = db.ReferenceProperty(StyleRevision, default=None, collection_name='style_preview')
+
+ # Old and dead
+ user_group = db.ReferenceProperty(UserGroup)
+ url = db.LinkProperty(required=False)
+
+ def delete(self, *args, **kwargs):
+ revisions = StyleRevision.all(keys_only=True).ancestor(self).fetch(10000)
+ db.delete(revisions)
+ db.delete(self)
+
+ def json_obj(self):
+ if self.preview_rev:
+ preview_rev = self.preview_rev
+ else:
+ if not self.published_rev:
+ rev = StyleRevision(parent=self)
+ rev.put()
+ self.published_rev = rev
+ self.put()
+ preview_rev = self.published_rev
+ return {
+ 'id': self.key().id(),
+ 'name': self.name,
+ 'preview_scss': preview_rev.raw,
+ 'preview_dt_last_edit': preview_rev.dt_last_edit,
+ 'published_scss': self.published_rev.raw,
+ 'published_dt_last_edit': self.published_rev.dt_last_edit,
+ }
+
+ @staticmethod
+ def get_or_404(style_id):
+ if isinstance(style_id, basestring) and not style_id.isdigit():
+ style = Style.get(style_id)
+ else:
+ style = Style.get_by_id(style_id)
+ if not style:
+ abort(404)
+ return style
+
+ @staticmethod
+ def get_edit_or_404(style_id):
+ style = Style.get_or_404(style_id)
+ if gae_users.get_current_user() not in style.site.users:
+ abort(404)
+ return style
+
+ @staticmethod
+ def get_admin_or_404(style_id):
+ style = Style.get_or_404(style_id)
+ site = style.site
+ if not site or gae_users.get_current_user() not in site.admins:
+ abort(404)
+ return style
+
+class PageChannel(db.Model):
+ user = db.UserProperty(required=True)
+ page = db.ReferenceProperty(Page, required=True)
+ token = db.StringProperty(required=True)
+ client_id = db.StringProperty(required=True)
+ dt_connected = db.DateTimeProperty(auto_now_add=True)
+ dt_last_update = db.DateTimeProperty(auto_now_add=True)
+
+ def is_stale(self):
+ return (datetime.utcnow() - self.dt_last_update).seconds > 3600
+
+ def send_message(self, message):
+ if not isinstance(message, basestring):
+ message = json.dumps(message, default=dt_handler, sort_keys=True, indent=4*' ' if settings.debug else None)
+ gae_channels.send_message(self.client_id, message)
+
+ @staticmethod
+ def get_or_404(token=None, client_id=None):
+ channel = None
+ if token:
+ channel = PageChannel.gql('WHERE token=:1', token).get()
+ elif client_id:
+ channel = PageChannel.gql('WHERE client_id=:1', client_id).get()
+ if not channel:
+ abort(404)
+ return channel
+
+class UserSettings(db.Model):
+ user = db.UserProperty(required=True)
+ seen_example = db.BooleanProperty(default=False)
+ seen_guiders = db.StringListProperty()
+ # the last version (list of ints) this person has viewed the release notes for
+ seen_version = db.ListProperty(int, default=None)
+ locale = db.StringProperty(default=None)
+ chimped = db.BooleanProperty(default=False)
+
+ @staticmethod
+ def has_seen_example():
+ user = gae_users.get_current_user()
+ if not user or not user.user_id():
+ raise Exception("Logged in user expected")
+ settings = UserSettings.get_or_insert(user.user_id(), user=user)
+ return settings.seen_example
+
+ @staticmethod
+ def mark_example_as_seen():
+ user = gae_users.get_current_user()
+ if not user or not user.user_id():
+ raise Exception("Logged in user expected")
+ settings = UserSettings.get_or_insert(user.user_id(), user=user)
+ settings.seen_example = True
+ settings.put()
+
+ @staticmethod
+ def show_guider(guider_name):
+ user = gae_users.get_current_user()
+ if not user or not user.user_id():
+ return False
+ settings = UserSettings.get_or_insert(user.user_id(), user=user)
+ return (guider_name not in settings.seen_guiders)
+
+ @staticmethod
+ def mark_guider_as_seen(guider_name):
+ user = gae_users.get_current_user()
+ if not user or not user.user_id():
+ return
+ settings = UserSettings.get_or_insert(user.user_id(), user=user)
+ if not guider_name in settings.seen_guiders:
+ settings.seen_guiders.append(guider_name)
+ settings.put()
+
+ @staticmethod
+ def has_seen_version(version):
+ user = gae_users.get_current_user()
+ if not user or not user.user_id():
+ return True # don't bother displaying "new version available" to non-authenticated users
+ settings = UserSettings.get_or_insert(user.user_id(), user=user)
+ if not settings.seen_version:
+ settings.seen_version = [0, 0, 0]
+ settings.put()
+ return settings.seen_version >= version
+
+ @staticmethod
+ def mark_version_as_seen(version):
+ user = gae_users.get_current_user()
+ if not user or not user.user_id():
+ return
+ settings = UserSettings.get_or_insert(user.user_id(), user=user)
+ settings.seen_version = version
+ settings.put()
+
+ @staticmethod
+ def get_locale():
+ user = gae_users.get_current_user()
+ if not user or not user.user_id():
+ return None
+ settings = UserSettings.get_or_insert(user.user_id(), user=user)
+ return settings.locale
+
+ @staticmethod
+ def set_locale(locale):
+ user = gae_users.get_current_user()
+ if not user or not user.user_id():
+ return
+ settings = UserSettings.get_or_insert(user.user_id(), user=user)
+ settings.locale = locale
+ settings.put()
+
+class Importer(db.Model):
+ page = db.ReferenceProperty(Page)
+ urls = db.StringListProperty()
+ style = db.TextProperty()
+ errors = db.StringListProperty()
+
+class Credential(db.Model):
+ name = db.StringProperty()
+ user = db.StringProperty(default='')
+ passwd = db.StringProperty(default='')
+ api_key = db.StringProperty(default='')
View
170 app/rpc.py
@@ -1,85 +1,85 @@
-import logging
-from datetime import datetime
-import simplejson as json
-from google.appengine.api import memcache
-from flask import Module, request, abort, jsonify
-from flaskext.csrf import csrf_exempt
-from models import Page, PageChannel, Style, StyleRevision
-from decorators import requires_auth, as_json
-
-rpc = Module(__name__, 'rpc')
-
-@rpc.route('/page/<int:page_id>/rpc', methods=['POST'])
-@requires_auth
-@as_json
-def page_rpc(page_id):
- page = Page.get_edit_or_404(page_id)
- try:
- message = json.loads(request.form.get('message', ''))
- except Exception:
- abort(400)
- data = message.get('data', None)
- token = message.get('from', None)
- if not token or not data:
- logging.warn('RPC received no token or data.')
- abort(400)
- cmd = data.get('cmd', None)
- if not cmd:
- logging.warn('RPC received no cmd.')
- abort(400)
-
- channel = PageChannel.gql('WHERE token=:1', token).get()
- if not channel:
- # We've timed out the channel. User should refresh the page.
- logging.debug('Could not find token: %s', token)
- return dict(cmd='refresh')
- channel.dt_last_update = datetime.utcnow()
- channel.put()
-
- # Commands
- if cmd == 'open':
- page.add_channel(channel)
- page.update_locks()
- return 'OK'
- elif cmd == 'claimLock':
- page.clean_channels()
- page.add_channel_first(channel)
- page.update_locks()
- return 'OK'
- elif cmd == 'save':
- style_id = data.get('style_id', '')
- style = Style.get_edit_or_404(style_id)
- if not style.preview_rev:
- preview_rev = StyleRevision(parent=style, rev=style.published_rev.rev + 1)
- preview_rev.put()
- style.preview_rev = preview_rev
- style.put()
- log = style.preview_rev.update(data.get('scss', ''))
- publish = data.get('fPublish', False)
- preview = not publish
- if publish:
- style.published_rev = style.preview_rev
- style.preview_rev = None
- style.put()
- page_key = str(page.key())
- memcache.delete(page_key + '-css')
- memcache.delete(page_key + '-css-etag')
- page.queue_refresh()
- return jsonify({'css': page.compressed_css(preview), 'log': log})
- else:
- logging.warn('Got a bad command: %s', cmd)
- abort(400) # Bad cmd
-
-@rpc.route('/_ah/channel/<presence>/', methods=['POST'])
-@csrf_exempt
-def _channel_presence(presence):
- client_id = request.form.get('from', '')
- channel = PageChannel.get_or_404(client_id=client_id)
- page = channel.page
- if presence == 'connected':
- page.add_channel(channel)
- elif presence == 'disconnected':
- page.remove_channel(channel, True)
- page.update_locks()
- return 'OK'
-
+import logging
+from datetime import datetime
+import json
+from google.appengine.api import memcache
+from flask import Module, request, abort, jsonify
+from flaskext.csrf import csrf_exempt
+from models import Page, PageChannel, Style, StyleRevision
+from decorators import requires_auth, as_json
+
+rpc = Module(__name__, 'rpc')
+
+@rpc.route('/page/<int:page_id>/rpc', methods=['POST'])
+@requires_auth
+@as_json
+def page_rpc(page_id):
+ page = Page.get_edit_or_404(page_id)
+ try:
+ message = json.loads(request.form.get('message', ''))
+ except Exception:
+ abort(400)
+ data = message.get('data', None)
+ token = message.get('from', None)
+ if not token or not data:
+ logging.warn('RPC received no token or data.')
+ abort(400)
+ cmd = data.get('cmd', None)
+ if not cmd:
+ logging.warn('RPC received no cmd.')
+ abort(400)
+
+ channel = PageChannel.gql('WHERE token=:1', token).get()
+ if not channel:
+ # We've timed out the channel. User should refresh the page.
+ logging.debug('Could not find token: %s', token)
+ return dict(cmd='refresh')
+ channel.dt_last_update = datetime.utcnow()
+ channel.put()
+
+ # Commands
+ if cmd == 'open':
+ page.add_channel(channel)
+ page.update_locks()
+ return 'OK'
+ elif cmd == 'claimLock':
+ page.clean_channels()
+ page.add_channel_first(channel)
+ page.update_locks()
+ return 'OK'
+ elif cmd == 'save':
+ style_id = data.get('style_id', '')
+ style = Style.get_edit_or_404(style_id)
+ if not style.preview_rev:
+ preview_rev = StyleRevision(parent=style, rev=style.published_rev.rev + 1)
+ preview_rev.put()
+ style.preview_rev = preview_rev
+ style.put()
+ log = style.preview_rev.update(data.get('scss', ''))
+ publish = data.get('fPublish', False)
+ preview = not publish
+ if publish:
+ style.published_rev = style.preview_rev
+ style.preview_rev = None
+ style.put()
+ page_key = str(page.key())
+ memcache.delete(page_key + '-css')
+ memcache.delete(page_key + '-css-etag')
+ page.queue_refresh()
+ return jsonify({'css': page.compressed_css(preview), 'log': log})
+ else:
+ logging.warn('Got a bad command: %s', cmd)
+ abort(400) # Bad cmd
+
+@rpc.route('/_ah/channel/<presence>/', methods=['POST'])
+@csrf_exempt
+def _channel_presence(presence):
+ client_id = request.form.get('from', '')
+ channel = PageChannel.get_or_404(client_id=client_id)
+ page = channel.page
+ if presence == 'connected':
+ page.add_channel(channel)
+ elif presence == 'disconnected':
+ page.remove_channel(channel, True)
+ page.update_locks()
+ return 'OK'
+
View
69 bootstrap.py
@@ -1,37 +1,32 @@
-#!/usr/bin/env python
-
-"""Google App Engine uses this file to run your Flask application."""
-
-import os
-from wsgiref.handlers import CGIHandler
-import settings
-from utils import adjust_sys_path
-
-adjust_sys_path()
-if settings.debug:
- adjust_sys_path('ziplibs')
- # Enable ctypes for Jinja debugging
- from google.appengine.tools.dev_appserver import HardenedModulesHook
- HardenedModulesHook._WHITE_LIST_C_MODULES += ['_ctypes', 'gestalt']
-else:
- adjust_sys_path(os.path.join('ziplibs.zip', 'ziplibs'))
-
-from app import create_app
-from werkzeug_debugger_appengine import get_debugged_app
-from flaskext.csrf import csrf
-from gae_mini_profiler import profiler, config as profiler_config
-
-def main():
- app = create_app()
- csrf(app)
- # If we're on the local server, let's enable Flask debugging.
- # For more information: http://goo.gl/RNofH
- if settings.debug:
- app.debug = True
- app = get_debugged_app(app)
- settings.debug_profiler_enabled = profiler_config.should_profile(app)
- app = profiler.ProfilerWSGIMiddleware(app)
- CGIHandler().run(app)
-
-if __name__ == '__main__':
- main()
+#!/usr/bin/env python
+
+"""Google App Engine uses this file to run your Flask application."""
+
+import os
+import settings
+from utils import adjust_sys_path
+
+adjust_sys_path()
+if settings.debug:
+ adjust_sys_path('ziplibs')
+ # Enable ctypes for Jinja debugging
+ from google.appengine.tools.dev_appserver import HardenedModulesHook
+ HardenedModulesHook._WHITE_LIST_C_MODULES += ['_ctypes', 'gestalt']
+else:
+ adjust_sys_path(os.path.join('ziplibs.zip', 'ziplibs'))
+
+from app import create_app
+from werkzeug_debugger_appengine import get_debugged_app
+from flaskext.csrf import csrf
+from gae_mini_profiler import profiler, config as profiler_config
+
+profiler_config.enabled_profiler_emails = settings.admin_emails
+
+app = create_app()
+csrf(app)
+# If we're on the local server, let's enable Flask debugging.
+# For more information: http://goo.gl/RNofH
+if settings.debug:
+ app.debug = True
+ app = get_debugged_app(app)
+app = profiler.ProfilerWSGIMiddleware(app)
View
314 compress.py
@@ -1,154 +1,160 @@
-import os
-import shutil
-import subprocess
-import sys
-import md5
-import re
-
-sys.path.append(os.path.abspath("."))
-from js_css_packages import packages
-
-COMBINED_FILENAME = "combined"
-COMPRESSED_FILENAME = "compressed"
-PACKAGE_SUFFIX = "-package"
-HASHED_FILENAME_PREFIX = "hashed-"
-PATH_PACKAGES = "js_css_packages/packages.py"
-PATH_PACKAGES_TEMP = "js_css_packages/packages.compresstemp.py"
-
-def revert_js_css_hashes(base_path=None):
- path = PATH_PACKAGES
- if base_path:
- path = os.path.join(base_path, PATH_PACKAGES)
- print "Reverting %s" % path
- popen_results(['hg', 'revert', '--no-backup', path])
-
-def compress_all_javascript(base_path):
- path = os.path.join(base_path, 'static', 'js')
- dict_packages = packages.javascript
- compress_all_packages(base_path, path, dict_packages, ".js")
-
-def compress_all_stylesheets(base_path):
- path = os.path.join(base_path, 'static', 'css')
- dict_packages = packages.stylesheets
- compress_all_packages(base_path, path, dict_packages, ".css")
-
-# Combine all .js\.css files in all "-package" suffixed directories
-# into a single combined.js\.css file for each package, then
-# minify into a single compressed.js\.css file.
-def compress_all_packages(base_path, path, dict_packages, suffix):
- if not os.path.exists(path):
- raise Exception("Path does not exist: %s" % path)
-
- for package_name in dict_packages:
- package = dict_packages[package_name]
-
- dir_name = "%s-package" % package_name
- package_path = os.path.join(path, dir_name)
-
- compress_package(base_path, package_name, package_path, package["files"], suffix)
-
-def compress_package(base_path, name, path, files, suffix):
- if not os.path.exists(path):
- raise Exception("Path does not exist: %s" % path)
-
- remove_working_files(path, suffix)
-
- path_combined = combine_package(path, files, suffix)
- path_compressed = minify_package(path, path_combined, suffix)
- path_hashed = hash_package(base_path, name, path, path_compressed, suffix)
-
- if not os.path.exists(path_hashed):
- raise Exception("Did not successfully compress and hash: %s" % path)
-
- return path_hashed
-
-# Remove previous combined.js\.css and compress.js\.css files
-def remove_working_files(path, suffix):
- filenames = os.listdir(path)
- for filename in filenames:
- if filename.endswith(COMBINED_FILENAME + suffix) \
- or filename.endswith(COMPRESSED_FILENAME + suffix) \
- or filename.startswith(HASHED_FILENAME_PREFIX):
- os.remove(os.path.join(path, filename))
-
-# Use YUICompressor to minify the combined file
-def minify_package(path, path_combined, suffix):
- path_compressed = os.path.join(path, COMPRESSED_FILENAME + suffix)
- path_compressor = os.path.join(os.path.dirname(__file__), "yuicompressor-2.4.6.jar")
-
- print "Compressing %s into %s" % (path_combined, path_compressed)
- print popen_results(["java", "-jar", path_compressor, "--charset", "utf-8", path_combined, "-o", path_compressed])
-
- if not os.path.exists(path_compressed):
- raise Exception("Unable to YUICompress: %s" % path_combined)
-
- return path_compressed
-
-def hash_package(base_path, name, path, path_compressed, suffix):
- f = open(path_compressed, "r")
- content = f.read()
- f.close()
-
- hash_sig = md5.new(content).hexdigest()
- path_hashed = os.path.join(path, "hashed-%s%s" % (hash_sig, suffix))
-
- print "Copying %s into %s" % (path_compressed, path_hashed)
- shutil.copyfile(path_compressed, path_hashed)
-
- if not os.path.exists(path_hashed):
- raise Exception("Unable to copy to hashed file: %s" % path_compressed)
-
- insert_hash_sig(base_path, name, hash_sig, suffix)
-
- return path_hashed
-
-def insert_hash_sig(base_path, name, hash_sig, suffix):
- package_file_path = os.path.join(base_path, PATH_PACKAGES)
- temp_file_path = os.path.join(base_path, PATH_PACKAGES_TEMP)
- print "Inserting %s sig (%s) into %s\n" % (name, hash_sig, package_file_path)
-
- f = open(package_file_path, "r")
- content = f.read()
- f.close()
-
- re_search = "@%s@%s" % (name, suffix)
- re_replace = "%s%s" % (hash_sig, suffix)
- hashed_content = re.sub(re_search, re_replace, content)
-
- if content == hashed_content:
- raise Exception("Hash sig insertion failed: %s" % name)
-
- f = open(temp_file_path, "w")
- f.write(hashed_content)
- f.close()
-
- shutil.move(temp_file_path, package_file_path)
-
-# Combine all files into a single combined.js\.css
-def combine_package(path, files, suffix):
- path_combined = os.path.join(path, COMBINED_FILENAME + suffix)
-
- print "Building %s" % path_combined
-
- content = []
- for static_filename in files:
- path_static = os.path.join(path, static_filename)
- print " ...adding %s" % path_static
- f = open(path_static, 'r')
- content.append(f.read())
- f.close()
-
- if os.path.exists(path_combined):
- raise Exception("File about to be compressed already exists: %s" % path_combined)
-
- f = open(path_combined, "w")
- separator = "\n" if suffix.endswith(".css") else ";\n"
- f.write(separator.join(content))
- f.close()
-
- return path_combined
-
-def popen_results(args):
- proc = subprocess.Popen(args, stdout=subprocess.PIPE)
- return proc.communicate()[0]
-
+import os
+import shutil
+import subprocess
+import sys
+import md5
+import re
+
+sys.path.append(os.path.abspath("."))
+from js_css_packages import packages
+
+COMBINED_FILENAME = "combined"
+COMPRESSED_FILENAME = "compressed"
+PACKAGE_SUFFIX = "-package"
+HASHED_FILENAME_PREFIX = "hashed-"
+PATH_PACKAGES = "js_css_packages/packages.py"
+PATH_PACKAGES_TEMP = "js_css_packages/packages.compresstemp.py"
+
+def revert_js_css_hashes(base_path=None):
+ path = PATH_PACKAGES
+ if base_path:
+ path = os.path.join(base_path, PATH_PACKAGES)
+ root = os.path.abspath(os.path.dirname(__file__))
+ if os.path.exists(os.path.join(root, '.hg')):
+ print "Reverting %s" % path
+ popen_results(['hg', 'revert', '--no-backup', path])
+ elif os.path.exists(os.path.join(root, '.git')):
+ print "Reverting %s" % path
+ popen_results(['git', 'checkout', '--', path])
+
+
+def compress_all_javascript(base_path):
+ path = os.path.join(base_path, 'static', 'js')
+ dict_packages = packages.javascript
+ compress_all_packages(base_path, path, dict_packages, ".js")
+
+def compress_all_stylesheets(base_path):
+ path = os.path.join(base_path, 'static', 'css')
+ dict_packages = packages.stylesheets
+ compress_all_packages(base_path, path, dict_packages, ".css")
+
+# Combine all .js\.css files in all "-package" suffixed directories
+# into a single combined.js\.css file for each package, then
+# minify into a single compressed.js\.css file.
+def compress_all_packages(base_path, path, dict_packages, suffix):
+ if not os.path.exists(path):
+ raise Exception("Path does not exist: %s" % path)
+
+ for package_name in dict_packages:
+ package = dict_packages[package_name]
+
+ dir_name = "%s-package" % package_name
+ package_path = os.path.join(path, dir_name)
+
+ compress_package(base_path, package_name, package_path, package["files"], suffix)
+
+def compress_package(base_path, name, path, files, suffix):
+ if not os.path.exists(path):
+ raise Exception("Path does not exist: %s" % path)
+
+ remove_working_files(path, suffix)
+
+ path_combined = combine_package(path, files, suffix)
+ path_compressed = minify_package(path, path_combined, suffix)
+ path_hashed = hash_package(base_path, name, path, path_compressed, suffix)
+
+ if not os.path.exists(path_hashed):
+ raise Exception("Did not successfully compress and hash: %s" % path)
+
+ return path_hashed
+
+# Remove previous combined.js\.css and compress.js\.css files
+def remove_working_files(path, suffix):
+ filenames = os.listdir(path)
+ for filename in filenames:
+ if filename.endswith(COMBINED_FILENAME + suffix) \
+ or filename.endswith(COMPRESSED_FILENAME + suffix) \
+ or filename.startswith(HASHED_FILENAME_PREFIX):
+ os.remove(os.path.join(path, filename))
+
+# Use YUICompressor to minify the combined file
+def minify_package(path, path_combined, suffix):
+ path_compressed = os.path.join(path, COMPRESSED_FILENAME + suffix)
+ path_compressor = os.path.join(os.path.dirname(__file__), "yuicompressor-2.4.6.jar")
+
+ print "Compressing %s into %s" % (path_combined, path_compressed)
+ print popen_results(["java", "-jar", path_compressor, "--charset", "utf-8", path_combined, "-o", path_compressed])
+
+ if not os.path.exists(path_compressed):
+ raise Exception("Unable to YUICompress: %s" % path_combined)
+
+ return path_compressed
+
+def hash_package(base_path, name, path, path_compressed, suffix):
+ f = open(path_compressed, "r")
+ content = f.read()
+ f.close()
+
+ hash_sig = md5.new(content).hexdigest()
+ path_hashed = os.path.join(path, "hashed-%s%s" % (hash_sig, suffix))
+
+ print "Copying %s into %s" % (path_compressed, path_hashed)
+ shutil.copyfile(path_compressed, path_hashed)
+
+ if not os.path.exists(path_hashed):
+ raise Exception("Unable to copy to hashed file: %s" % path_compressed)
+
+ insert_hash_sig(base_path, name, hash_sig, suffix)
+
+ return path_hashed
+
+def insert_hash_sig(base_path, name, hash_sig, suffix):
+ package_file_path = os.path.join(base_path, PATH_PACKAGES)
+ temp_file_path = os.path.join(base_path, PATH_PACKAGES_TEMP)
+ print "Inserting %s sig (%s) into %s\n" % (name, hash_sig, package_file_path)
+
+ f = open(package_file_path, "r")
+ content = f.read()
+ f.close()
+
+ re_search = "@%s@%s" % (name, suffix)
+ re_replace = "%s%s" % (hash_sig, suffix)
+ hashed_content = re.sub(re_search, re_replace, content)
+
+ if content == hashed_content:
+ raise Exception("Hash sig insertion failed: %s" % name)
+
+ f = open(temp_file_path, "w")
+ f.write(hashed_content)
+ f.close()
+
+ shutil.move(temp_file_path, package_file_path)
+
+# Combine all files into a single combined.js\.css
+def combine_package(path, files, suffix):
+ path_combined = os.path.join(path, COMBINED_FILENAME + suffix)
+
+ print "Building %s" % path_combined
+
+ content = []
+ for static_filename in files:
+ path_static = os.path.join(path, static_filename)
+ print " ...adding %s" % path_static
+ f = open(path_static, 'r')
+ content.append(f.read())
+ f.close()
+
+ if os.path.exists(path_combined):
+ raise Exception("File about to be compressed already exists: %s" % path_combined)
+
+ f = open(path_combined, "w")
+ separator = "\n" if suffix.endswith(".css") else ";\n"
+ f.write(separator.join(content))
+ f.close()
+
+ return path_combined
+
+def popen_results(args):
+ proc = subprocess.Popen(args, stdout=subprocess.PIPE)
+ return proc.communicate()[0]
+
View
359 fabfile.py
@@ -1,182 +1,177 @@
-from __future__ import with_statement
-
-import functools
-import os
-import sys
-import shutil
-import getpass
-import re
-import webbrowser
-import zipfile
-from fabric.api import env, local, abort, prompt
-
-import compress
-
-#Some environment information to customize
-if os.name == 'posix':
- APPENGINE_PATH = '/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine'
- PYTHON = '/usr/bin/python2.5'
-else:
- APPENGINE_PATH = r'C:\Program Files (x86)\Google\google_appengine'
- PYTHON = r'C:\Python25\python.exe'
-APPENGINE_APP_CFG = os.path.join(APPENGINE_PATH, 'appcfg.py')
-print APPENGINE_APP_CFG
-
-env.gae_email = None
-env.gae_src = os.path.dirname(__file__)
-
-#default values
-env.dryrun = False
-
-EXTRA_PATHS = [
- APPENGINE_PATH,
- os.path.join(APPENGINE_PATH, <