Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial Commit

  • Loading branch information...
commit 7a825b46e03a96fbe5e4a6576526a057aea7c6ee 0 parents
visiblegovernment authored
Showing with 3,047 additions and 0 deletions.
  1. +17 −0 .project
  2. +7 −0 .pydevproject
  3. 0  __init__.py
  4. BIN  locale/fr/LC_MESSAGES/django.mo
  5. +720 −0 locale/fr/LC_MESSAGES/django.po
  6. 0  mainapp/__init__.py
  7. +32 −0 mainapp/admin.py
  8. +87 −0 mainapp/feeds.py
  9. +478 −0 mainapp/fixtures/initial_data.json
  10. +38 −0 mainapp/forms.py
  11. +524 −0 mainapp/models.py
  12. 0  mainapp/views/__init__.py
  13. +9 −0 mainapp/views/ajax.py
  14. +23 −0 mainapp/views/cities.py
  15. +24 −0 mainapp/views/contact.py
  16. +12 −0 mainapp/views/faq.py
  17. +87 −0 mainapp/views/main.py
  18. +16 −0 mainapp/views/promotion.py
  19. 0  mainapp/views/reports/__init__.py
  20. +25 −0 mainapp/views/reports/flags.py
  21. +69 −0 mainapp/views/reports/main.py
  22. +52 −0 mainapp/views/reports/subscribers.py
  23. +61 −0 mainapp/views/reports/updates.py
  24. +33 −0 mainapp/views/wards.py
  25. +11 −0 manage.py
  26. BIN  media/bug.png
  27. +141 −0 media/css/colors.css
  28. +85 −0 media/css/global.css
  29. +385 −0 media/css/layout-3.css
  30. +111 −0 media/css/typography.css
  31. BIN  media/fixitlogo.png
  32. BIN  media/images/fixmystreetlogo.png
  33. BIN  media/images/marker/default/blank.png
  34. BIN  media/images/marker/default/marker.png
  35. BIN  media/images/marker/default/marker0.png
  36. BIN  media/images/marker/default/marker00.png
  37. BIN  media/images/marker/default/marker01.png
  38. BIN  media/images/marker/default/marker02.png
  39. BIN  media/images/marker/default/marker03.png
  40. BIN  media/images/marker/default/marker04.png
  41. BIN  media/images/marker/default/marker05.png
  42. BIN  media/images/marker/default/marker06.png
  43. BIN  media/images/marker/default/marker07.png
  44. BIN  media/images/marker/default/marker08.png
  45. BIN  media/images/marker/default/marker09.png
  46. BIN  media/images/marker/default/marker1.png
  47. BIN  media/images/marker/default/marker10.png
  48. BIN  media/images/marker/default/marker11.png
  49. BIN  media/images/marker/default/marker12.png
  50. BIN  media/images/marker/default/marker13.png
  51. BIN  media/images/marker/default/marker14.png
  52. BIN  media/images/marker/default/marker15.png
  53. BIN  media/images/marker/default/marker16.png
  54. BIN  media/images/marker/default/marker17.png
  55. BIN  media/images/marker/default/marker18.png
  56. BIN  media/images/marker/default/marker19.png
  57. BIN  media/images/marker/default/marker2.png
  58. BIN  media/images/marker/default/marker20.png
  59. BIN  media/images/marker/default/marker21.png
  60. BIN  media/images/marker/default/marker22.png
  61. BIN  media/images/marker/default/marker23.png
  62. BIN  media/images/marker/default/marker24.png
  63. BIN  media/images/marker/default/marker25.png
  64. BIN  media/images/marker/default/marker26.png
  65. BIN  media/images/marker/default/marker27.png
  66. BIN  media/images/marker/default/marker28.png
  67. BIN  media/images/marker/default/marker29.png
  68. BIN  media/images/marker/default/marker3.png
  69. BIN  media/images/marker/default/marker30.png
  70. BIN  media/images/marker/default/marker31.png
  71. BIN  media/images/marker/default/marker32.png
  72. BIN  media/images/marker/default/marker33.png
  73. BIN  media/images/marker/default/marker34.png
  74. BIN  media/images/marker/default/marker35.png
  75. BIN  media/images/marker/default/marker36.png
  76. BIN  media/images/marker/default/marker37.png
  77. BIN  media/images/marker/default/marker38.png
  78. BIN  media/images/marker/default/marker39.png
  79. BIN  media/images/marker/default/marker4.png
  80. BIN  media/images/marker/default/marker40.png
  81. BIN  media/images/marker/default/marker41.png
  82. BIN  media/images/marker/default/marker42.png
  83. BIN  media/images/marker/default/marker43.png
  84. BIN  media/images/marker/default/marker44.png
  85. BIN  media/images/marker/default/marker45.png
  86. BIN  media/images/marker/default/marker46.png
  87. BIN  media/images/marker/default/marker47.png
  88. BIN  media/images/marker/default/marker48.png
  89. BIN  media/images/marker/default/marker49.png
  90. BIN  media/images/marker/default/marker5.png
  91. BIN  media/images/marker/default/marker50.png
  92. BIN  media/images/marker/default/marker51.png
  93. BIN  media/images/marker/default/marker52.png
  94. BIN  media/images/marker/default/marker53.png
  95. BIN  media/images/marker/default/marker54.png
  96. BIN  media/images/marker/default/marker55.png
  97. BIN  media/images/marker/default/marker56.png
  98. BIN  media/images/marker/default/marker57.png
  99. BIN  media/images/marker/default/marker58.png
  100. BIN  media/images/marker/default/marker59.png
  101. BIN  media/images/marker/default/marker6.png
  102. BIN  media/images/marker/default/marker60.png
  103. BIN  media/images/marker/default/marker61.png
  104. BIN  media/images/marker/default/marker62.png
  105. BIN  media/images/marker/default/marker63.png
  106. BIN  media/images/marker/default/marker64.png
  107. BIN  media/images/marker/default/marker65.png
  108. BIN  media/images/marker/default/marker66.png
  109. BIN  media/images/marker/default/marker67.png
  110. BIN  media/images/marker/default/marker68.png
  111. BIN  media/images/marker/default/marker69.png
  112. BIN  media/images/marker/default/marker7.png
  113. BIN  media/images/marker/default/marker70.png
  114. BIN  media/images/marker/default/marker71.png
  115. BIN  media/images/marker/default/marker72.png
  116. BIN  media/images/marker/default/marker73.png
  117. BIN  media/images/marker/default/marker74.png
  118. BIN  media/images/marker/default/marker75.png
  119. BIN  media/images/marker/default/marker76.png
  120. BIN  media/images/marker/default/marker77.png
  121. BIN  media/images/marker/default/marker78.png
  122. BIN  media/images/marker/default/marker79.png
  123. BIN  media/images/marker/default/marker8.png
  124. BIN  media/images/marker/default/marker80.png
  125. BIN  media/images/marker/default/marker81.png
  126. BIN  media/images/marker/default/marker82.png
  127. BIN  media/images/marker/default/marker83.png
  128. BIN  media/images/marker/default/marker84.png
  129. BIN  media/images/marker/default/marker85.png
  130. BIN  media/images/marker/default/marker86.png
  131. BIN  media/images/marker/default/marker87.png
  132. BIN  media/images/marker/default/marker88.png
  133. BIN  media/images/marker/default/marker89.png
  134. BIN  media/images/marker/default/marker9.png
  135. BIN  media/images/marker/default/marker90.png
  136. BIN  media/images/marker/default/marker91.png
  137. BIN  media/images/marker/default/marker92.png
  138. BIN  media/images/marker/default/marker93.png
  139. BIN  media/images/marker/default/marker94.png
  140. BIN  media/images/marker/default/marker95.png
  141. BIN  media/images/marker/default/marker96.png
  142. BIN  media/images/marker/default/marker97.png
  143. BIN  media/images/marker/default/marker98.png
  144. BIN  media/images/marker/default/marker99.png
  145. BIN  media/images/marker/green/blank.png
  146. BIN  media/images/marker/green/marker0.png
  147. BIN  media/images/marker/green/marker00.png
  148. BIN  media/images/marker/green/marker01.png
  149. BIN  media/images/marker/green/marker02.png
  150. BIN  media/images/marker/green/marker03.png
  151. BIN  media/images/marker/green/marker04.png
  152. BIN  media/images/marker/green/marker05.png
  153. BIN  media/images/marker/green/marker06.png
  154. BIN  media/images/marker/green/marker07.png
  155. BIN  media/images/marker/green/marker08.png
  156. BIN  media/images/marker/green/marker09.png
  157. BIN  media/images/marker/green/marker1.png
  158. BIN  media/images/marker/green/marker10.png
  159. BIN  media/images/marker/green/marker11.png
  160. BIN  media/images/marker/green/marker12.png
  161. BIN  media/images/marker/green/marker13.png
  162. BIN  media/images/marker/green/marker14.png
  163. BIN  media/images/marker/green/marker15.png
  164. BIN  media/images/marker/green/marker16.png
  165. BIN  media/images/marker/green/marker17.png
  166. BIN  media/images/marker/green/marker18.png
  167. BIN  media/images/marker/green/marker19.png
  168. BIN  media/images/marker/green/marker2.png
  169. BIN  media/images/marker/green/marker20.png
  170. BIN  media/images/marker/green/marker21.png
  171. BIN  media/images/marker/green/marker22.png
  172. BIN  media/images/marker/green/marker23.png
  173. BIN  media/images/marker/green/marker24.png
  174. BIN  media/images/marker/green/marker25.png
  175. BIN  media/images/marker/green/marker26.png
  176. BIN  media/images/marker/green/marker27.png
  177. BIN  media/images/marker/green/marker28.png
  178. BIN  media/images/marker/green/marker29.png
  179. BIN  media/images/marker/green/marker3.png
  180. BIN  media/images/marker/green/marker30.png
  181. BIN  media/images/marker/green/marker31.png
  182. BIN  media/images/marker/green/marker32.png
  183. BIN  media/images/marker/green/marker33.png
  184. BIN  media/images/marker/green/marker34.png
  185. BIN  media/images/marker/green/marker35.png
  186. BIN  media/images/marker/green/marker36.png
  187. BIN  media/images/marker/green/marker37.png
  188. BIN  media/images/marker/green/marker38.png
  189. BIN  media/images/marker/green/marker39.png
  190. BIN  media/images/marker/green/marker4.png
  191. BIN  media/images/marker/green/marker40.png
  192. BIN  media/images/marker/green/marker41.png
  193. BIN  media/images/marker/green/marker42.png
  194. BIN  media/images/marker/green/marker43.png
  195. BIN  media/images/marker/green/marker44.png
  196. BIN  media/images/marker/green/marker45.png
  197. BIN  media/images/marker/green/marker46.png
  198. BIN  media/images/marker/green/marker47.png
  199. BIN  media/images/marker/green/marker48.png
  200. BIN  media/images/marker/green/marker49.png
  201. BIN  media/images/marker/green/marker5.png
  202. BIN  media/images/marker/green/marker50.png
  203. BIN  media/images/marker/green/marker51.png
  204. BIN  media/images/marker/green/marker52.png
  205. BIN  media/images/marker/green/marker53.png
  206. BIN  media/images/marker/green/marker54.png
  207. BIN  media/images/marker/green/marker55.png
  208. BIN  media/images/marker/green/marker56.png
  209. BIN  media/images/marker/green/marker57.png
  210. BIN  media/images/marker/green/marker58.png
  211. BIN  media/images/marker/green/marker59.png
  212. BIN  media/images/marker/green/marker6.png
  213. BIN  media/images/marker/green/marker60.png
  214. BIN  media/images/marker/green/marker61.png
  215. BIN  media/images/marker/green/marker62.png
  216. BIN  media/images/marker/green/marker63.png
  217. BIN  media/images/marker/green/marker64.png
  218. BIN  media/images/marker/green/marker65.png
  219. BIN  media/images/marker/green/marker66.png
  220. BIN  media/images/marker/green/marker67.png
  221. BIN  media/images/marker/green/marker68.png
  222. BIN  media/images/marker/green/marker69.png
  223. BIN  media/images/marker/green/marker7.png
  224. BIN  media/images/marker/green/marker70.png
  225. BIN  media/images/marker/green/marker71.png
  226. BIN  media/images/marker/green/marker72.png
  227. BIN  media/images/marker/green/marker73.png
  228. BIN  media/images/marker/green/marker74.png
  229. BIN  media/images/marker/green/marker75.png
  230. BIN  media/images/marker/green/marker76.png
  231. BIN  media/images/marker/green/marker77.png
  232. BIN  media/images/marker/green/marker78.png
  233. BIN  media/images/marker/green/marker79.png
  234. BIN  media/images/marker/green/marker8.png
  235. BIN  media/images/marker/green/marker80.png
  236. BIN  media/images/marker/green/marker81.png
  237. BIN  media/images/marker/green/marker82.png
  238. BIN  media/images/marker/green/marker83.png
  239. BIN  media/images/marker/green/marker84.png
  240. BIN  media/images/marker/green/marker85.png
  241. BIN  media/images/marker/green/marker86.png
  242. BIN  media/images/marker/green/marker87.png
  243. BIN  media/images/marker/green/marker88.png
  244. BIN  media/images/marker/green/marker89.png
  245. BIN  media/images/marker/green/marker9.png
  246. BIN  media/images/marker/green/marker90.png
  247. BIN  media/images/marker/green/marker91.png
  248. BIN  media/images/marker/green/marker92.png
  249. BIN  media/images/marker/green/marker93.png
  250. BIN  media/images/marker/green/marker94.png
  251. BIN  media/images/marker/green/marker95.png
  252. BIN  media/images/marker/green/marker96.png
  253. BIN  media/images/marker/green/marker97.png
  254. BIN  media/images/marker/green/marker98.png
  255. BIN  media/images/marker/green/marker99.png
  256. BIN  media/images/marker/red/blank.png
  257. BIN  media/images/marker/red/marker0.png
  258. BIN  media/images/marker/red/marker00.png
  259. BIN  media/images/marker/red/marker01.png
  260. BIN  media/images/marker/red/marker02.png
  261. BIN  media/images/marker/red/marker03.png
  262. BIN  media/images/marker/red/marker04.png
  263. BIN  media/images/marker/red/marker05.png
  264. BIN  media/images/marker/red/marker06.png
  265. BIN  media/images/marker/red/marker07.png
  266. BIN  media/images/marker/red/marker08.png
  267. BIN  media/images/marker/red/marker09.png
  268. BIN  media/images/marker/red/marker1.png
  269. BIN  media/images/marker/red/marker10.png
  270. BIN  media/images/marker/red/marker11.png
  271. BIN  media/images/marker/red/marker12.png
  272. BIN  media/images/marker/red/marker13.png
  273. BIN  media/images/marker/red/marker14.png
  274. BIN  media/images/marker/red/marker15.png
  275. BIN  media/images/marker/red/marker16.png
  276. BIN  media/images/marker/red/marker17.png
  277. BIN  media/images/marker/red/marker18.png
  278. BIN  media/images/marker/red/marker19.png
  279. BIN  media/images/marker/red/marker2.png
  280. BIN  media/images/marker/red/marker20.png
  281. BIN  media/images/marker/red/marker21.png
  282. BIN  media/images/marker/red/marker22.png
  283. BIN  media/images/marker/red/marker23.png
  284. BIN  media/images/marker/red/marker24.png
  285. BIN  media/images/marker/red/marker25.png
  286. BIN  media/images/marker/red/marker26.png
  287. BIN  media/images/marker/red/marker27.png
  288. BIN  media/images/marker/red/marker28.png
  289. BIN  media/images/marker/red/marker29.png
  290. BIN  media/images/marker/red/marker3.png
  291. BIN  media/images/marker/red/marker30.png
  292. BIN  media/images/marker/red/marker31.png
  293. BIN  media/images/marker/red/marker32.png
  294. BIN  media/images/marker/red/marker33.png
  295. BIN  media/images/marker/red/marker34.png
  296. BIN  media/images/marker/red/marker35.png
  297. BIN  media/images/marker/red/marker36.png
  298. BIN  media/images/marker/red/marker37.png
  299. BIN  media/images/marker/red/marker38.png
  300. BIN  media/images/marker/red/marker39.png
Sorry, we could not display the entire diff because too many files (452) changed.
17 .project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>fixmystreet google code</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.python.pydev.PyDevBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.python.pydev.pythonNature</nature>
+ </natures>
+</projectDescription>
7 .pydevproject
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?eclipse-pydev version="1.0"?>
+
+<pydev_project>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
+</pydev_project>
0  __init__.py
No changes.
BIN  locale/fr/LC_MESSAGES/django.mo
Binary file not shown
720 locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,720 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-04-29 18:35-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: mainapp/forms.py:11 mainapp/models.py:109 mainapp/models.py:147
+msgid "Name"
+msgstr "Nom"
+
+#: mainapp/forms.py:14 mainapp/models.py:146
+msgid "Email"
+msgstr "Courriel"
+
+#: mainapp/forms.py:16
+msgid "Message"
+msgstr "Message"
+
+#: mainapp/models.py:87
+msgid "Subject"
+msgstr "Sujet"
+
+#: mainapp/models.py:107
+msgid "* Photo"
+msgstr "* Photo"
+
+#: mainapp/models.py:108 mainapp/models.py:141
+msgid "Details"
+msgstr "Détails"
+
+#: mainapp/models.py:148
+msgid "Phone"
+msgstr "Téléphone"
+
+#: mainapp/views/main.py:39
+msgid ""
+"Sorry, we couldn't retreive the coordinates of that location, please use the "
+"Back button on your browser and try something more specific or include the "
+"city name at the end of your search."
+msgstr ""
+"Désolé, nous n'avons pas pu retrouver les coordonnées de cet endroit, s'il "
+"vous plaît cliquer sur le bouton de votre navigateur et essayez quelque "
+"chose de plus spécifique ou inclure le nom de la ville à la fin de votre "
+"recherche."
+
+#: mainapp/views/main.py:50
+msgid ""
+"Sorry, we couldn't find the address you entered. Please try again with "
+"another intersection, address or postal code, or add the name of the city to "
+"the end of the search."
+msgstr ""
+"Désolé, nous n'avons pas pu trouver l'adresse que vous avez entrée. S'il "
+"vous plaît essayer de nouveau avec une autre intersection, adresse ou le "
+"code postal."
+
+#: mainapp/views/main.py:60
+msgid ""
+"Sorry, we don't yet have that area in our database. Please have your area "
+"councillor contact fixmystreet.ca."
+msgstr ""
+"Désolé, nous n'avons pas ce domaine dans notre base de données. S'il vous "
+"plaît demander à votre échevin municipal qu'il contacte fixmystreet.ca."
+
+#: mainapp/views/reports/main.py:41
+msgid "Please select a category"
+msgstr "S'il vous plaît choisir une catégorie"
+
+#: mainapp/views/reports/subscribers.py:18
+msgid "You are already subscribed to this report."
+msgstr "Vous vous avez déjà abonné aux mises à jour de ce rapport."
+
+#: mainapp/views/reports/subscribers.py:50
+msgid "You have unsubscribed from updates to:"
+msgstr "Vous vous avez désabonné des mises à jour de:"
+
+#: stdimage/widgets.py:19
+msgid "Currently:"
+msgstr "Actuellement:"
+
+#: stdimage/widgets.py:20
+msgid "Change:"
+msgstr "Modifier:"
+
+#: stdimage/widgets.py:21
+msgid "Delete"
+msgstr "Supprimer"
+
+#: templates/about.html:4 templates/about.html.py:9 templates/base.html:33
+#: templates/base.html.py:53 templates/faq/show.html:3
+#: templates/faq/show.html.py:7
+msgid "About"
+msgstr "À Propos"
+
+#: templates/about.html:16
+msgid "Credits"
+msgstr "Crédits"
+
+#: templates/about.html:17
+msgid "Thanks to:"
+msgstr "Remerciements À:"
+
+#: templates/about.html:19
+msgid "for the original concept."
+msgstr "pour le concept original."
+
+#: templates/about.html:20
+msgid ""
+"Chris Taggart, of <a href='http://www.openottawa.org/'>OpenOttawa</a>, for "
+"adapting the source code to Canada."
+msgstr ""
+"Chris Taggart, d' <a href='http://www.openottawa.org/'> OpenOttawa </a>, "
+"pourl'adaptation du code source pour le Canada."
+
+#: templates/about.html:21
+msgid ""
+"Brad Elliott, of <a href='http://www.dyad.ca'>dyad infoSys</a>, for the "
+"domain name."
+msgstr ""
+"Brad Elliott, de <a href='http://www.dyad.ca'>dyad infoSys</a>, pour le nom "
+"de domain."
+
+#: templates/about.html:22
+msgid "The City of Ottawa, for being open to new ideas."
+msgstr "La Ville d'Ottawa, pour être ouvert aux idées nouvelles."
+
+#: templates/about.html:23
+msgid "The Rae Family of Montreal, for hosting and IT support."
+msgstr ""
+"La Famille Rae de Montréal, pour l'hébergement et l'assistance informatique."
+
+#: templates/about.html:26
+msgid "In addition, Chris Taggart would like to thank:"
+msgstr "En outre, Chris Taggart tiens à remercier:"
+
+#: templates/about.html:29
+msgid ""
+"Pierre Lewis, for his MTM conversion script. The script enabled conversion "
+"from the City's MTM projection to the Google-friendly WGS-84 format."
+msgstr ""
+"Pierre Lewis, pour son script de conversion MTM. Le script a permis la "
+"conversion des projections MTM de la Ville au format WGS-84 mieux reconnu "
+"par Google."
+
+#: templates/base.html:4
+msgid "FixMyStreet Canada"
+msgstr "FixMyStreet (réparer ma rue) Canada"
+
+#: templates/base.html:4
+msgid "Home"
+msgstr "Page d'accueil"
+
+#: templates/base.html:30 templates/reports/new.html:58
+msgid "Report a Problem"
+msgstr "Signalez un Problème"
+
+#: templates/base.html:31 templates/cities/index.html:3
+#: templates/cities/index.html.py:8
+msgid "All Reports"
+msgstr "Tous les Rapports"
+
+#: templates/base.html:32
+msgid "Contact"
+msgstr "Contact"
+
+#: templates/base.html:53
+msgid "Promotions"
+msgstr "Promotions"
+
+#: templates/base.html:54
+msgid "Find a bug?"
+msgstr "Vous avez trouver un problème?"
+
+#: templates/base.html:54
+msgid "Tell us"
+msgstr "Laissez nous savoir"
+
+#: templates/index.html:9
+msgid "Report, view, or discuss local problems"
+msgstr "Signalez, examinez ou discutez des problèmes locaux"
+
+#: templates/index.html:10
+msgid "(like graffiti, potholes, excessive garbage, or street lighting)"
+msgstr ""
+"(comme les graffitis, les nids de poule, trop de déchets, ou l'éclairage de "
+"rue)"
+
+#: templates/index.html:16
+msgid "Enter a nearby postal code, or street name and city:"
+msgstr ""
+"Entrez un code postal à proximité, ou le nom de la rue et de la région:"
+
+#: templates/index.html:18
+msgid "Go"
+msgstr "Allez"
+
+#: templates/index.html:25
+msgid "Supported Cities"
+msgstr "Villes Incluses"
+
+#: templates/index.html:29
+msgid "Add Your City"
+msgstr "Ajoutez Votre Ville"
+
+#: templates/index.html:37
+msgid "How to report a Problem"
+msgstr "Signalez un Problème"
+
+#: templates/index.html:39
+msgid "Enter a nearby postal code, or street name and city"
+msgstr ""
+"Entrez un code postal à proximité, ou le nom de la rue et de la région:"
+
+#: templates/index.html:40
+msgid "Locate the problem on a map of the area"
+msgstr "Localisez le problème sur une carte de la région"
+
+#: templates/index.html:41
+msgid "Enter details of the problem"
+msgstr "Entrez les détails du problème"
+
+#: templates/index.html:42
+msgid "We send it to the City on your behalf"
+msgstr "Nous le rapporterons à la ville en votre nom<"
+
+#: templates/index.html:46
+msgid "Stats"
+msgstr "Statistiques"
+
+#: templates/index.html:48
+msgid "Reports Filed Last Month"
+msgstr "Rapports de le Mois Dernière"
+
+#: templates/index.html:51
+msgid "Reports Fixed Last Month"
+msgstr "Rapports Résolus le Mois Dernière"
+
+#: templates/index.html:54
+msgid "Reports Updated Last Month"
+msgstr "Rapports Mis à Jour le Mois Dernière"
+
+#: templates/index.html:61
+msgid "Recent Photos"
+msgstr "Dernières Photos"
+
+#: templates/index.html:70
+msgid "Recent Reports"
+msgstr "Derniers Rapports"
+
+#: templates/search_result.html:8
+msgid "Problems near"
+msgstr "Problèmes proche de"
+
+#: templates/search_result.html:10
+msgid ""
+"To report a problem, simply drag the marker to the correct location on the "
+"map."
+msgstr ""
+"Pour signaler un problème, il suffit de faire glisser le marqueur à "
+"l'emplacement sur la carte."
+
+#: templates/search_result.html:15
+msgid "fixed"
+msgstr "r�solu"
+
+#: templates/search_result.html:19
+msgid "Zoom out to see more reports nearby."
+msgstr "Zoom en arrière pour voir d'autres rapports en proximité."
+
+#: templates/search_result.html:21
+msgid "No problems have been reported yet."
+msgstr "Aucun problème n'a été signalé pour le moment."
+
+#: templates/ajax/category_description.html:2 templates/reports/new.html:93
+msgid "Please Note"
+msgstr "Notez S'il Vous Plaît"
+
+#: templates/ajax/category_description.html:5
+msgid "Is it Hate Graffiti?"
+msgstr "Est-ce du Graffiti Haineux?"
+
+#: templates/ajax/category_description.html:6
+msgid ""
+"Please contact the City directly regarding hate or racist graffiti by "
+"calling #311."
+msgstr ""
+"S'il vous plaît contactez directement la ville en ce qui concerne les "
+"graffitis racistes ou haineux en appelant # 311."
+
+#: templates/cities/_report_count_table.html:6
+msgid "New Problems"
+msgstr "Nouveaux Problèmes"
+
+#: templates/cities/_report_count_table.html:7
+msgid "Older Unresolved Problems"
+msgstr "Problèmes Pas Encore Résolu"
+
+#: templates/cities/_report_count_table.html:8
+msgid "Recently Fixed"
+msgstr "Résolu Récemment"
+
+#: templates/cities/_report_count_table.html:9
+msgid "Old Fixed"
+msgstr "Résolu"
+
+#: templates/cities/index.html:9
+msgid "Reports by City"
+msgstr "Rapports par Ville"
+
+#: templates/cities/show.html:4 templates/cities/show.html.py:8
+#: templates/promotions/show.html:4 templates/wards/show.html:3
+#: templates/wards/show.html.py:6
+msgid "Reports for"
+msgstr "Rapports par"
+
+#: templates/cities/show.html:9
+msgid "Reports by Ward"
+msgstr "Rapports par Quartier"
+
+#: templates/contact/new.html:7
+msgid "Contact Us"
+msgstr "Contactez Nous"
+
+#: templates/contact/new.html:8
+msgid ""
+"\n"
+"<p>\n"
+"\tPlease <strong>DO NOT</strong> report problems through this form.\n"
+"\tMessages go to the team behind FixMyStreet.ca, not to City Councillors.\n"
+"</p>\n"
+"<p>\n"
+"\tTo report a problem, please follow the instructions <a href=\"/\">here</"
+"a>.\n"
+"</p>\n"
+"<p>We'd love to get your feedback on this site:</p> \n"
+msgstr ""
+"\n"
+"<p>\n"
+"\tS'il vous plait <strong>NE PAS</strong> signaler de problèmes par "
+"l'entremise de ce formulaire.\n"
+"\tLes messages vont à l'équipe de FixMyStreet.ca, pas aux Échevins "
+"Municipaux.\n"
+"</p>\n"
+"<p>\n"
+"\tPour signaler un problème, s'il vous plait suivre les instructions <a href="
+"\"/\">que vous trouverez ici</a>.\n"
+"</p>\n"
+"<p>Nous voulons vraiment obtenir vos commentaires sur ce site:</p> \n"
+
+#: templates/contact/new.html:24
+msgid "Send"
+msgstr "Envoyez"
+
+#: templates/contact/thanks.html:7
+msgid "Thank you"
+msgstr "Merci"
+
+#: templates/contact/thanks.html:8
+msgid ""
+"\n"
+"<p>Thanks for submitting your comments.</p>\n"
+"<a href=\"/\">Return to FixMyStreet.ca</a>\n"
+msgstr ""
+"\n"
+"<p>Merci pour nous avoir envoyer vos commentaires.</p>\n"
+"<a href=\"/\">Retournez à FixMyStreet.ca</a>\n"
+
+#: templates/promotions/show.html:8
+msgid "First 100 Reports"
+msgstr "Premier 100 Rapports"
+
+#: templates/promotions/show.html:9
+msgid ""
+" out of the first 100 confirmed reports were filed with the promotional code "
+msgstr ""
+" de ces 100 premier rapports confirmé ont été enregistré avec le code de "
+"promotion "
+
+#: templates/reports/new.html:3
+msgid "New Report"
+msgstr "Nouveau Rapport"
+
+#: templates/reports/new.html:67
+msgid "* Optional"
+msgstr "* Facultatif"
+
+#: templates/reports/new.html:70 templates/reports/show.html:37
+msgid "Category:"
+msgstr "Catégorie:"
+
+#: templates/reports/new.html:75
+msgid "Select a Category"
+msgstr "Choisissez une Catégorie"
+
+#: templates/reports/new.html:95
+msgid "Please be polite, concise and to the point."
+msgstr ""
+"S'il vous plaît noter que les mises à jour ne sont pas envoyées à la Ville."
+
+#: templates/reports/new.html:96
+msgid ""
+"Please do not be abusive - abusing the service devalues the service for all "
+"users."
+msgstr ""
+"S'il vous plaît ne pas être abusif, ceci dévalue le service pour tous les "
+"utilisateurs"
+
+#: templates/reports/new.html:97
+msgid ""
+"Writing your message entirely in block capitals makes it hard to read, as "
+"does a lack of punctuation."
+msgstr ""
+"Écrire votre message entièrement en lettres majuscules ou sans ponctuation "
+"rend la lecture difficile."
+
+#: templates/reports/new.html:98
+msgid ""
+"Remember that FixMyStreet is primarily for reporting physical problems that "
+"can be fixed. If your problem is not appropriate for submission via this "
+"site remember that you can contact city officials directly at #311."
+msgstr ""
+"Rappelez-vous que FixMyStreet est surtout pour signaler les problèmes "
+"physiques qui peuvent être résolus. Si votre problème n'est pas approprié "
+"pour cesite, rappelez-vous que vous pouvez contacter directement les "
+"fonctionnaires municipaux en appelant # 311."
+
+#: templates/reports/new.html:106 templates/reports/show.html:78
+msgid "Submit"
+msgstr "Envoyez"
+
+#: templates/reports/show.html:4
+msgid "Problem Report"
+msgstr "Rapport d'un Problème"
+
+#: templates/reports/show.html:29 templates/reports/show.html.py:77
+msgid "This problem has been fixed."
+msgstr "Ce problème a été résolu."
+
+#: templates/reports/show.html:33
+msgid ""
+"Please check your email and click on the confirmation link to complete your "
+"submission. Your report will not be sent to the City's 311 Department or be "
+"visible to other visitors until you have done so."
+msgstr ""
+"S'il vous plaît vérifier votre adresse courriel et cliquez sur le lien de "
+"confirmation pour compléter votresoumission. Votre rapport ne sera pas "
+"envoyé au département 311 de la Ville et ne sera pas visible aux autres "
+"visiteurs jusqu'à ce que vous l'avez fait."
+
+#: templates/reports/show.html:38
+msgid "Filed by:"
+msgstr "Par:"
+
+#: templates/reports/show.html:41
+msgid "Sent to the city of"
+msgstr "Envoyer à la ville de"
+
+#: templates/reports/show.html:41
+msgid "on"
+msgstr " le "
+
+#: templates/reports/show.html:44
+#, python-format
+msgid "This report will be sent to the city of %(city)s in the next 24 hrs."
+msgstr ""
+"Ce rapport va être envoyer à la ville de %(city)s dans les prochaines 24 "
+"heures."
+
+#: templates/reports/show.html:55
+msgid "Offensive? Unsuitable? Tell us"
+msgstr "Offensif? Inappropriées? Dites-le-nous"
+
+#: templates/reports/show.html:59
+msgid "Updates"
+msgstr "Mises à Jour"
+
+#: templates/reports/show.html:61
+msgid "posted by"
+msgstr "signalé par"
+
+#: templates/reports/show.html:70
+msgid "Provide an update"
+msgstr "Fournir une mise à jour"
+
+#: templates/reports/show.html:71
+msgid "Email me updates"
+msgstr "Abonnez-vous aux Mises à Jour des Rapports"
+
+#: templates/reports/show.html:73
+msgid "Please note that updates are not sent to the City."
+msgstr ""
+"S'il vous plaît noter que les mises à jour ne sont pas envoyées à la Ville."
+
+#: templates/reports/show.html:92
+msgid "Councillor"
+msgstr "Échevin"
+
+#: templates/reports/show.html:93
+msgid "Ward"
+msgstr "Quartier"
+
+#: templates/reports/flags/new.html:6
+msgid "Flag an Offensive Report"
+msgstr "Identifiez un Rapport comme étant Offensif"
+
+#: templates/reports/flags/new.html:7
+msgid ""
+"You are reporting the following problem report for being abusive, containing "
+"personal information, or similar to another report:"
+msgstr ""
+"Vous déclarez le problème suivant comme étant abusif, contenant des "
+"renseignements personnels, ou similaire à un autre rapport:"
+
+#: templates/reports/flags/new.html:13
+msgid "Flag this Report"
+msgstr "Identifiez ce Rapport"
+
+#: templates/reports/flags/thanks.html:4
+msgid ""
+"\n"
+"<h2>Thank you</h2>\n"
+"<p>Thank you, your feedback has been received and we will act accordingly as "
+"soon as possible.</p>\n"
+msgstr ""
+"\n"
+"<h2>Merci</h2>\n"
+"<p>Merci, vos commentaires ont été reçus et nous agirons en conséquence dès "
+"que possible.</p>\n"
+
+#: templates/reports/subscribers/confirm.html:5
+msgid "You have subscribed to updates to"
+msgstr "Vous vous avez abonné aux mises à jour de"
+
+#: templates/reports/subscribers/create.html:5
+msgid ""
+"\n"
+"<h2>Nearly Done! Now check your email...</h2>\n"
+"\n"
+"<p>The confirmation email may take a few minutes to arrive — please be "
+"patient.</p>\n"
+"\n"
+"<p>If you use web-based email or have 'junk mail' filters, you may wish to "
+"check your bulk/spam mail folders: sometimes, our messages are marked that "
+"way.\n"
+"You must now click the link in the email we've just sent you — if you do "
+"not, your alert will not be activated.</p>\n"
+"\n"
+"<p>(Don't worry — we'll hang on to your alert while you're checking your "
+"email.)</p>\n"
+msgstr ""
+
+#: templates/reports/subscribers/new.html:3
+msgid "Subscribe to a Report"
+msgstr "Abonnez-vous aux Mises à Jour d'un Rapport"
+
+#: templates/reports/subscribers/new.html:6
+msgid "Subscribe to Updates to Report"
+msgstr "Abonnez-vous aux Mises à Jour des Rapports"
+
+#: templates/reports/subscribers/new.html:7
+msgid "Enter your email address to subscribe to updates:"
+msgstr "Entrez votre adresse courriel pour vous abonner aux mises à jour:"
+
+#: templates/reports/subscribers/new.html:10
+msgid "Subscribe"
+msgstr "Abonnez-vous"
+
+#: templates/reports/updates/create.html:4
+msgid "Update Created"
+msgstr "Mise à Jour"
+
+#: templates/reports/updates/create.html:8
+msgid ""
+"A confirmation email has been sent. You must confirm your update in order "
+"for your update to be displayed. Please check your email."
+msgstr ""
+"Un courriel de confirmation a été envoyé. Vous devez confirmer votre mise à "
+"jour pour que la mise à jour soit affiché. S'il vous plaît vérifier votre "
+"courriel."
+
+#: templates/wards/show.html:7
+msgid "All reports for"
+msgstr "Tous les Rapports pour"
+
+#: templates/wards/show.html:7
+msgid "ward"
+msgstr "quartier"
+
+#~ msgid "Report filed by "
+#~ msgstr "Rapports par "
+
+#~ msgid "Who Runs This Site?"
+#~ msgstr "Qui Gère Ce Site"
+
+#~ msgid "FixMyStreet Canada is maintained by the non-profit "
+#~ msgstr ""
+#~ "FixMyStreet Canada est maintenu par l'organisation à but non lucratif"
+
+#~ msgid ""
+#~ "The site was inspired by <a href='http://mysociety.com'>MySociety's</a> "
+#~ "<a href='http://fixmystreet.com'>example</a>, which was adapted for "
+#~ "Canada by Chris Taggart of"
+#~ msgstr ""
+#~ "Le site a été inspiré par <a href='http://mysociety.com'>MySociety's </a> "
+#~ "<a href='http://fixmystreet.com'>FixMyStreet</a>, et adaptée pour le "
+#~ "Canada par Chris Taggart de"
+
+#~ msgid "Why Isn't My Municipality Listed?"
+#~ msgstr "Pourquoi Ma Municipalité n'Est-Elle Pas Inscrite?"
+
+#~ msgid "To support a municipality, we require:"
+#~ msgstr "Pour inscrire une municipalité, nous avons besoin de:"
+
+#~ msgid "shape files of city ward boundary maps."
+#~ msgstr ""
+#~ "fichiers graphiques décrivant les frontières de ses quartiers de la ville."
+
+#~ msgid "a list of contact email addresses for each city ward."
+#~ msgstr ""
+#~ "une liste d'adresses courriels de contact pour chaque quartier de la "
+#~ "ville."
+
+#~ msgid ""
+#~ "If you would like to see your municipality included on our site, please "
+#~ "have your municipality representative contact"
+#~ msgstr ""
+#~ "Si vous souhaitez voir votre municipalité sur notre site, s'il vous plaît "
+#~ "demander à un représentant de votre municipalité de nous contacter"
+
+#~ msgid "Do you remove silly or illegal content?"
+#~ msgstr "Supprimer-vous le contenu illégal ou stupide?"
+
+#~ msgid ""
+#~ "FixMyStreet Canada is not responsible for the content and accuracy of "
+#~ "material submitted by its users. All reports are accepted on the basis "
+#~ "that they contain no illegal content, and we reserve the right to remove "
+#~ "any problems or updates which we consider to be inappropriate upon being "
+#~ "informed by a user of the site."
+#~ msgstr ""
+#~ "FixMyStreet Canada n'est pas responsable pour le contenu et la précision "
+#~ "des documents soumis par les utilisateurs. Tous les rapports sont "
+#~ "acceptés avec l'assomption qu'ils ne contiennent pas de contenu illégal, "
+#~ "et nous nous réservons le droit de supprimer toutles problèmes ou les "
+#~ "mises à jour que nous considérons comme étant inappropriés une fois "
+#~ "averti par un utilisateur du site."
+
+#~ msgid "Who gets to see my email address?"
+#~ msgstr "Qui peut voir mon adresse courriel?"
+
+#~ msgid ""
+#~ "If you submit a problem, we pass on your contact details, and details of "
+#~ "the problem, to the municipality contact responsible for the area where "
+#~ "you located the problem. When you submit a report or update, your name "
+#~ "is displayed upon the site, but not your email address."
+#~ msgstr ""
+#~ "Si vous soumettez un problème, nous transmettons vos coordonnées et les "
+#~ "détails du problème au contact municipal responsable de la région où vous "
+#~ "avez localisé le problème. Lorsque vous envoyez un rapport ou une mise à "
+#~ "jour, votre nom est affiché sur le site, mais pas votre adresse courriel."
+
+#~ msgid ""
+#~ "We will never give or sell your email address to anyone else, unless we "
+#~ "are obliged to by law."
+#~ msgstr ""
+#~ "Jamais nous ne donnerons ou venderons votre adresse courriel à quiconque, "
+#~ "à moins que nous sommes obligé par la loi."
+
+#~ msgid "Will you send spam to my email address?"
+#~ msgstr "Allez-vous envoyer du spam à mon adresse courriel?"
+
+#~ msgid ""
+#~ "Never. We will email you if someone leaves an update on a problem you’ve "
+#~ "reported, or subscribed to. We will also send you a questionnaire email "
+#~ "four weeks after you submit a problem, asking for a status update."
+#~ msgstr ""
+#~ "Jamais. Nous vous envoyerons un courriel si quelqu'un quitte une mise à "
+#~ "jour sur un problème que vous avez signalé, ou auxquel vous vous êtes "
+#~ "souscrit. Nous allons aussi vous envoyer un questionnaire par courriel "
+#~ "quatre semaines après que vous avez soumis un problème our que vous avez "
+#~ "demander l'état du problème."
+
+#~ msgid ""
+#~ "We’ll only ever send you emails in relation to problems you have reported "
+#~ "or subscribed to."
+#~ msgstr ""
+#~ "Nous vous envoyerons des courriels seulements pour les problèmes que vous "
+#~ "avez signalé ou auxquels vous vous êtes souscrits."
+
+#~ msgid ""
+#~ "Chris Wightman, e-Media Division, Communications and Customer Service, "
+#~ "City of Ottawa"
+#~ msgstr ""
+#~ "Chris Wightman, Division des Médias électroniques, Communications et "
+#~ "Service à la Clientèle, de la Ville d'Ottawa"
+
+#~ msgid "Stephen Perkins, City Mapper, City of Ottawa"
+#~ msgstr "Stephen Perkins, Cartographeur Municipal de la Ville d'Ottawa"
+
+#~ msgid "Privacy Policy"
+#~ msgstr "Politique de Confidentialité"
+
+#~ msgid "Help"
+#~ msgstr "Aide"
+
+#~ msgid "English"
+#~ msgstr "Anglais"
+
+#~ msgid "French"
+#~ msgstr "Francais"
0  mainapp/__init__.py
No changes.
32 mainapp/admin.py
@@ -0,0 +1,32 @@
+from fixmystreet.mainapp.models import Ward,ReportCategory, ReportCategoryClass, FaqEntry, Councillor
+from django.contrib import admin
+from contrib.transmeta import canonical_fieldname
+
+class ReportCategoryClassAdmin(admin.ModelAdmin):
+ list_display = ('name',)
+
+admin.site.register(ReportCategoryClass,ReportCategoryClassAdmin)
+
+class ReportCategoryAdmin(admin.ModelAdmin):
+ list_display = ('name', 'hint')
+
+admin.site.register(ReportCategory, ReportCategoryAdmin)
+
+class FaqEntryAdmin(admin.ModelAdmin):
+ list_display = ('q', 'order')
+# prepopulated_fields = {'slug': ('q') }
+
+admin.site.register(FaqEntry, FaqEntryAdmin)
+
+class CouncillorAdmin(admin.ModelAdmin):
+ list_display = ('last_name', 'first_name', 'email')
+
+admin.site.register(Councillor,CouncillorAdmin)
+
+
+class WardAdmin(admin.ModelAdmin):
+ list_display = ('id','city','number','name')
+ ordering = ['city', 'number']
+
+
+admin.site.register(Ward,WardAdmin)
87 mainapp/feeds.py
@@ -0,0 +1,87 @@
+from django.contrib.syndication.feeds import Feed
+from django.contrib.syndication.feeds import FeedDoesNotExist
+from django.core.exceptions import ObjectDoesNotExist
+from mainapp.models import Report, ReportUpdate, City, Ward
+
+class LatestReports(Feed):
+ title = "All FixMyStreet Reports"
+ link = "/reports/"
+ description = "All FixMyStreet.ca Reports"
+
+ def items(self):
+ return Report.objects.filter(is_confirmed=True).order_by('-created_at')[:30]
+
+class LatestReportsByCity(Feed):
+
+ def get_object(self, bits):
+ # In case of "/rss/beats/0613/foo/bar/baz/", or other such clutter,
+ # check that bits has only one member.
+ if len(bits) != 1:
+ raise ObjectDoesNotExist
+ return City.objects.get(id=bits[0])
+
+ def title(self, obj):
+ return "FixMyStreet.ca: Reports for %s" % obj.name
+
+ def link(self, obj):
+ if not obj:
+ raise FeedDoesNotExist
+ return obj.get_absolute_url()
+
+ def description(self, obj):
+ return "Problems recently reported in the city of %s" % obj.name
+
+ def items(self, obj):
+ return Report.objects.filter(is_confirmed=True,ward__city=obj.id).order_by('-created_at')[:30]
+
+
+class LatestReportsByWard(Feed):
+
+ def get_object(self, bits):
+ # In case of "/rss/beats/0613/foo/bar/baz/", or other such clutter,
+ # check that bits has only one member.
+ if len(bits) != 1:
+ raise ObjectDoesNotExist
+ return Ward.objects.get(id=bits[0])
+
+ def title(self, obj):
+ return "FixMyStreet.ca: Reports for %s" % obj.name
+
+ def link(self, obj):
+ if not obj:
+ raise FeedDoesNotExist
+ return obj.get_absolute_url()
+
+ def description(self, obj):
+ return "Problems recently reports in %s, %s" % ( obj.name, obj.city.name)
+
+ def items(self, obj):
+ return Report.objects.filter(is_confirmed=True,ward=obj.id).order_by('-created_at')[:30]
+
+# Allow subsciption to a particular report.
+
+class LatestUpdatesByReport(Feed):
+
+ def get_object(self, bits):
+ # In case of "/rss/beats/0613/foo/bar/baz/", or other such clutter,
+ # check that bits has only one member.
+ if len(bits) != 1:
+ raise ObjectDoesNotExist
+ return Report.objects.get(id=bits[0])
+
+ def title(self, obj):
+ return "FixMyStreet.ca: Updates for Report %s" % obj.title
+
+ def link(self, obj):
+ if not obj:
+ raise FeedDoesNotExist
+ return obj.get_absolute_url()
+
+ def item_link(self,obj):
+ return( obj.report.get_absolute_url())
+
+ def description(self, obj):
+ return "Updates for FixMySteet.ca Problem Report %s" % obj.title
+
+ def items(self, obj):
+ return obj.reportupdate_set.order_by('created_at')[:30]
478 mainapp/fixtures/initial_data.json
478 additions, 0 deletions not shown
38 mainapp/forms.py
@@ -0,0 +1,38 @@
+from django import forms
+from django.template.loader import render_to_string
+from django.core.mail import send_mail
+from fixmystreet import settings
+from mainapp.models import Report, ReportUpdate, ReportSubscriber
+from django.utils.translation import ugettext_lazy
+
+class ContactForm(forms.Form):
+ name = forms.CharField(max_length=100,
+ widget=forms.TextInput(attrs={ 'class': 'required' }),
+ label=ugettext_lazy('Name'))
+ email = forms.EmailField(widget=forms.TextInput(attrs=dict({ 'class': 'required' },
+ maxlength=200)),
+ label=ugettext_lazy('Email'))
+ body = forms.CharField(widget=forms.Textarea(attrs={ 'class': 'required' }),
+ label=ugettext_lazy('Message'))
+
+ def save(self, fail_silently=False):
+ message = render_to_string("emails/contact/message.txt", self.cleaned_data )
+ send_mail('FixMyStreet.ca User Message from %s' % self.cleaned_data['email'], message,
+ settings.EMAIL_FROM_USER,[settings.ADMIN_EMAIL], fail_silently=False)
+
+
+class ReportUpdateForm(forms.ModelForm):
+ class Meta:
+ model = ReportUpdate
+ fields = ( 'desc','author','email','phone')
+
+class ReportSubscriberForm(forms.ModelForm):
+ class Meta:
+ model = ReportSubscriber
+ fields = ( 'email')
+
+class ReportForm(forms.ModelForm):
+ class Meta:
+ model = Report
+ fields = ('title', 'photo')
+
524 mainapp/models.py
@@ -0,0 +1,524 @@
+from django.db import models, connection
+from django.contrib.gis.db import models
+from django.contrib.gis.maps.google import GoogleMap, GMarker, GEvent, GPolygon
+from django.template.loader import render_to_string
+from fixmystreet import settings
+from django import forms
+from django.core.mail import send_mail, EmailMessage
+import md5
+import time
+from datetime import datetime as dt
+from django.utils.safestring import mark_safe
+from django.utils.translation import ugettext_lazy, ugettext as _
+from contrib.transmeta import TransMeta
+from contrib.stdimage import StdImageField
+
+class Province(models.Model):
+ name = models.CharField(max_length=100)
+ abbrev = models.CharField(max_length=3)
+
+ class Meta:
+ db_table = u'province'
+
+class City(models.Model):
+ province = models.ForeignKey(Province)
+ name = models.CharField(max_length=100)
+ # the city's 311 email, if it has one.
+ email = models.EmailField(blank=True, null=True)
+ # unused, for now.
+ geom = models.PolygonField( null=True)
+
+ objects = models.GeoManager()
+
+ def __unicode__(self):
+ return self.name
+
+
+ def get_absolute_url(self):
+ return settings.SITE_URL + "/cities/" + str(self.id)
+
+ class Meta:
+ db_table = u'cities'
+
+class Councillor(models.Model):
+ first_name = models.CharField(max_length=100)
+ last_name = models.CharField(max_length=100)
+
+ # this email addr. is used to send reports to if there is no 411 email for the city.
+ email = models.EmailField(blank=True, null=True)
+ fax = models.CharField(max_length=20,blank=True, null=True)
+ phone = models.CharField(max_length=20,blank=True, null=True)
+
+ class Meta:
+ db_table = u'councillors'
+
+class Ward(models.Model):
+ name = models.CharField(max_length=100)
+ number = models.IntegerField()
+ councillor = models.ForeignKey(Councillor)
+ city = models.ForeignKey(City)
+ geom = models.MultiPolygonField( null=True)
+ objects = models.GeoManager()
+
+ def get_absolute_url(self):
+ return settings.SITE_URL + "/wards/" + str(self.id)
+
+ class Meta:
+ db_table = u'wards'
+
+class ReportCategoryClass(models.Model):
+ __metaclass__ = TransMeta
+
+ name = models.CharField(max_length=100)
+
+ def __unicode__(self):
+ return self.name
+
+ class Meta:
+ db_table = u'report_category_classes'
+ translate = ('name', )
+
+class ReportCategory(models.Model):
+ __metaclass__ = TransMeta
+
+ name = models.CharField(max_length=100)
+ hint = models.TextField(blank=True, null=True)
+ category_class = models.ForeignKey(ReportCategoryClass)
+
+ class Meta:
+ db_table = u'report_categories'
+ translate = ('name', 'hint', )
+
+class Report(models.Model):
+ title = models.CharField(max_length=100, verbose_name = ugettext_lazy("Subject"))
+ category = models.ForeignKey(ReportCategory,null=True)
+ ward = models.ForeignKey(Ward,null=True)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ # last time report was updated
+ updated_at = models.DateTimeField(auto_now_add=True)
+
+ # time report was marked as 'fixed'
+ fixed_at = models.DateTimeField(null=True)
+ is_fixed = models.BooleanField(default=False)
+ is_hate = models.BooleanField(default=False)
+
+ # last time report was sent to city
+ sent_at = models.DateTimeField(null=True)
+
+ # last time a reminder was sent to the person that filed the report.
+ reminded_at = models.DateTimeField(auto_now_add=True)
+
+ point = models.PointField(null=True)
+ photo = StdImageField(upload_to="photos", blank=True, verbose_name = ugettext_lazy("* Photo"), size=(400, 400), thumbnail_size=(133,100))
+ desc = models.TextField(blank=True, null=True, verbose_name = ugettext_lazy("Details"))
+ author = models.CharField(max_length=255,verbose_name = ugettext_lazy("Name"))
+
+ # true if first update has been confirmed - redundant with
+ # one in ReportUpdate, but makes aggregate SQL queries easier.
+
+ is_confirmed = models.BooleanField(default=False)
+
+ objects = models.GeoManager()
+
+ def is_subscribed(self, email):
+ if len( self.reportsubscriber_set.filter(email=email)) != 0:
+ return( True )
+ return( self.first_update().email == email )
+
+ def sent_at_diff(self):
+ if not self.sent_at:
+ return( None )
+ else:
+ return( self.sent_at - self.created_at )
+
+ def first_update(self):
+ return( ReportUpdate.objects.get(report=self,first_update=True))
+
+ def get_absolute_url(self):
+ return settings.SITE_URL + "/reports/" + str(self.id)
+
+ class Meta:
+ db_table = u'reports'
+
+class ReportCount(object):
+ def __init__(self, interval):
+ self.interval = interval
+
+ def dict(self):
+ return({ "recent_new": "count( case when age(clock_timestamp(), reports.created_at) < interval '%s' THEN 1 ELSE null end )" % self.interval,
+ "recent_fixed": "count( case when age(clock_timestamp(), reports.fixed_at) < interval '%s' AND reports.is_fixed = True THEN 1 ELSE null end )" % self.interval,
+ "recent_updated": "count( case when age(clock_timestamp(), reports.updated_at) < interval '%s' AND reports.is_fixed = False and reports.updated_at != reports.created_at THEN 1 ELSE null end )" % self.interval,
+ "old_fixed": "count( case when age(clock_timestamp(), reports.fixed_at) > interval '%s' AND reports.is_fixed = True THEN 1 ELSE null end )" % self.interval,
+ "old_unfixed": "count( case when age(clock_timestamp(), reports.fixed_at) > interval '%s' AND reports.is_fixed = False THEN 1 ELSE null end )" % self.interval } )
+
+class ReportUpdate(models.Model):
+ report = models.ForeignKey(Report)
+ desc = models.TextField(blank=True, null=True, verbose_name = ugettext_lazy("Details"))
+ created_at = models.DateTimeField(auto_now_add=True)
+ is_confirmed = models.BooleanField(default=False)
+ is_fixed = models.BooleanField(default=False)
+ confirm_token = models.CharField(max_length=255, null=True)
+ email = models.EmailField(max_length=255, verbose_name = ugettext_lazy("Email"))
+ author = models.CharField(max_length=255,verbose_name = ugettext_lazy("Name"))
+ phone = models.CharField(max_length=255, verbose_name = ugettext_lazy("Phone") )
+ first_update = models.BooleanField(default=False)
+
+ def send_emails(self):
+ if self.first_update:
+ self.notify_on_new()
+ else:
+ self.notify_on_update()
+
+ def notify_on_new(self):
+ # send to the city immediately.
+ subject = render_to_string("emails/send_report_to_city/subject.txt", {'update': self })
+ message = render_to_string("emails/send_report_to_city/message.txt", { 'update': self })
+
+ if self.report.ward.city.email:
+ email_addr = self.report.ward.city.email
+ else:
+ email_addr = self.report.ward.councillor.email
+
+ email_msg = EmailMessage(subject,message,settings.EMAIL_FROM_USER,
+ [email_addr], headers = {'Reply-To': self.email })
+ if self.report.photo:
+ email_msg.attach_file( self.report.photo.file.name )
+
+ email_msg.send()
+
+ # update report to show time sent to city.
+ self.report.sent_at=dt.now()
+ self.report.save()
+
+
+ def notify_on_update(self):
+ subject = render_to_string("emails/report_update/subject.txt",
+ { 'update': self })
+
+ # tell our subscribers there was an update.
+ for subscriber in self.report.reportsubscriber_set.all():
+ unsubscribe_url = settings.SITE_URL + "/reports/subscribers/unsubscribe/" + subscriber.confirm_token
+ message = render_to_string("emails/report_update/message.txt",
+ { 'update': self, 'unsubscribe_url': unsubscribe_url })
+ send_mail(subject, message,
+ settings.EMAIL_FROM_USER,[subscriber.email], fail_silently=False)
+
+ # tell the original problem reporter there was an update
+ message = render_to_string("emails/report_update/message.txt",
+ { 'update': self })
+ send_mail(subject, message,
+ settings.EMAIL_FROM_USER,
+ [self.report.first_update().email], fail_silently=False)
+
+
+ def save(self):
+ if not self.confirm_token or self.confirm_token == "":
+ m = md5.new()
+ m.update(self.email)
+ m.update(str(time.time()))
+ self.confirm_token = m.hexdigest()
+ confirm_url = settings.SITE_URL + "/reports/updates/confirm/" + self.confirm_token
+ message = render_to_string("emails/confirm/message.txt",
+ { 'confirm_url': confirm_url, 'update': self })
+ subject = render_to_string("emails/confirm/subject.txt",
+ { 'update': self })
+ send_mail(subject, message,
+ settings.EMAIL_FROM_USER,[self.email], fail_silently=False)
+
+ super(ReportUpdate, self).save()
+
+ def title(self):
+ if self.first_update :
+ return self.report.title
+ if self.is_fixed:
+ return "Reported Fixed"
+ return("Update")
+
+ class Meta:
+ db_table = u'report_updates'
+
+class ReportSubscriber(models.Model):
+ """
+ Report Subscribers are notified when there's an update.
+ """
+
+ report = models.ForeignKey(Report)
+ confirm_token = models.CharField(max_length=255, null=True)
+ is_confirmed = models.BooleanField(default=False)
+ email = models.EmailField(max_length=255)
+
+ class Meta:
+ db_table = u'report_subscribers'
+
+
+ def save(self):
+ if not self.confirm_token or self.confirm_token == "":
+ m = md5.new()
+ m.update(self.email)
+ m.update(str(time.time()))
+ self.confirm_token = m.hexdigest()
+ confirm_url = settings.SITE_URL + "/reports/subscribers/confirm/" + self.confirm_token
+ message = render_to_string("emails/subscribe/message.txt",
+ { 'confirm_url': confirm_url, 'subscriber': self })
+ send_mail('Subscribe to FixMyStreet.ca Report Updates', message,
+ settings.EMAIL_FROM_USER,[self.email], fail_silently=False)
+ super(ReportSubscriber, self).save()
+
+
+class CenterMarker(GMarker):
+ """
+ Override the marker class that comes with geodjango to make
+ the marker draggable.
+ """
+
+ def __init__(self, geom, title=None, draggable=False):
+ self.draggable = draggable
+ super(CenterMarker, self).__init__(geom,title)
+
+ def options(self):
+ result = []
+ if self.draggable: result.append('draggable: true')
+ if self.title: result.append('title: "%s"' % self.title)
+ return '{%s}' % ','.join(result)
+
+ def __unicode__(self):
+ return mark_safe('%s(%s)' % ("GMarker", self.js_params))
+
+ # just use the default icon for the center marker.
+ def icon(self):
+ str = ""
+ return mark_safe(str)
+
+
+class ReportMarker(GMarker):
+ """
+ A marker for an existing report. Override the GMarker class to
+ add a numbered, coloured marker.
+
+ If the report is fixed, show a green marker, otherwise red.
+ """
+ def __init__(self, report, icon_number ):
+ if report.is_fixed:
+ self.color = 'green'
+ else:
+ self.color = 'red'
+ self.icon_number = icon_number
+ super(ReportMarker, self).__init__(geom=(report.point.x,report.point.y), title=report.title)
+
+ def __unicode__(self):
+ return mark_safe('%s(%s)' % ("GMarker", self.js_params))
+
+ def icon(self):
+ str = """
+ letteredIcon%s = new GIcon(baseIcon);
+ letteredIcon%s.image = "/media/images/marker/%s/marker%s.png";
+ """ %( self.icon_number, self.icon_number, self.color, self.icon_number )
+ return mark_safe(str)
+
+ def options(self):
+ result = []
+ if self.title: result.append('title: "%s"' % self.title)
+ if self.icon_number: result.append(' icon: letteredIcon%s' % str(self.icon_number))
+ return '{%s}' % ','.join(result)
+
+
+class FixMyStreetMap(GoogleMap):
+ """
+ Overrides the GoogleMap class that comes with GeoDjango. Optionally,
+ show nearby reports.
+ """
+ def __init__(self,pnt,draggable=False,nearby_reports = [] ):
+ self.icons = []
+ markers = []
+ marker = CenterMarker(geom=(pnt.x,pnt.y), draggable=draggable)
+ if draggable:
+ event = GEvent('dragend',
+ 'function() { window.location.href = "/reports/new?" +"&lat="+marker1.getPoint().lat().toString()+"&lon="+marker1.getPoint().lng().toString(); }')
+ marker.add_event(event)
+ markers.append(marker)
+
+ for i in range( len( nearby_reports ) ):
+ nearby_marker = ReportMarker(nearby_reports[i], str(i+1) )
+ markers.append(nearby_marker)
+
+ GoogleMap.__init__(self,center=(pnt.x,pnt.y),zoom=17,key=settings.GMAP_KEY, template="maps/fixmystreetmap.js", markers=markers, dom_id='map_canvas')
+
+class WardMap(GoogleMap):
+ """
+ Show a single ward as a gmap overlay. Optionally, show reports in the
+ ward.
+ """
+ def __init__(self,ward, reports = []):
+ polygons = []
+ for poly in ward.geom:
+ polygons.append( GPolygon( poly ) )
+ markers = []
+ for i in range( len( reports ) ):
+ marker = ReportMarker(reports[i], str(i+1) )
+ markers.append(marker)
+
+ GoogleMap.__init__(self,zoom=13,markers=markers,key=settings.GMAP_KEY, polygons=polygons, dom_id='map_canvas', template="maps/fixmystreetmap.js")
+
+
+
+class CityMap(GoogleMap):
+ """
+ Show all wards in a city as overlays.
+ """
+
+ def __init__(self,city):
+ polygons = []
+
+ for ward in Ward.objects.filter(city=city):
+ for poly in ward.geom:
+ polygons.append( GPolygon( poly ) )
+ GoogleMap.__init__(self,zoom=13,key=settings.GMAP_KEY, polygons=polygons, dom_id='map_canvas', template="maps/fixmystreetmap.js")
+
+
+class SqlQuery(object):
+ """
+ This is a workaround: django doesn't support our optimized
+ direct SQL queries very well.
+ """
+
+ def __init__(self):
+ self.cursor = None
+ self.index = 0
+ self.results = None
+
+ def next(self):
+ self.index = self.index + 1
+
+ def get_results(self):
+ if not self.cursor:
+ self.cursor = connection.cursor()
+ self.cursor.execute(self.sql)
+ self.results = self.cursor.fetchall()
+ return( self.results )
+
+class ReportCountQuery(SqlQuery):
+
+ def name(self):
+ return self.get_results()[self.index][5]
+
+ def recent_new(self):
+ return self.get_results()[self.index][0]
+
+ def recent_fixed(self):
+ return self.get_results()[self.index][1]
+
+ def recent_updated(self):
+ return self.get_results()[self.index][2]
+
+ def old_fixed(self):
+ return self.get_results()[self.index][3]
+
+ def old_unfixed(self):
+ return self.get_results()[self.index][4]
+
+ def __init__(self, interval = '1 month'):
+ SqlQuery.__init__(self)
+ self.base_query = """select count( case when age(clock_timestamp(), reports.created_at) < interval '%s' and reports.is_confirmed THEN 1 ELSE null end ) as recent_new,\
+ count( case when age(clock_timestamp(), reports.fixed_at) < interval '%s' AND reports.is_fixed = True THEN 1 ELSE null end ) as recent_fixed,\
+ count( case when age(clock_timestamp(), reports.updated_at) < interval '%s' AND reports.is_fixed = False and reports.updated_at != reports.created_at THEN 1 ELSE null end ) as recent_updated,\
+ count( case when age(clock_timestamp(), reports.fixed_at) > interval '%s' AND reports.is_fixed = True THEN 1 ELSE null end ) as old_fixed,\
+ count( case when age(clock_timestamp(), reports.created_at) > interval '%s' AND reports.is_confirmed AND reports.is_fixed = False THEN 1 ELSE null end ) as old_unfixed
+ """ % (interval,interval,interval,interval,interval)
+ self.sql = self.base_query + " from reports where reports.is_confirmed = true"
+
+class CityReportCountQuery(ReportCountQuery):
+
+ def __init__(self, city):
+ ReportCountQuery.__init__(self,"1 month")
+ self.sql = self.base_query
+ field_names = ""
+ self.url_prefix = "/wards/"
+ self.sql += ", wards.name, wards.id, wards.number from wards "
+ self.sql += """left join reports on wards.id = reports.ward_id join cities on wards.city_id = cities.id join province on cities.province_id = province.id
+ """
+ self.sql += "and cities.id = " + str(city.id)
+ self.sql += " group by wards.name, wards.id, wards.number order by wards.number"
+
+ def number(self):
+ return(self.get_results()[self.index][7])
+
+ def get_absolute_url(self):
+ return( self.url_prefix + str(self.get_results()[self.index][6]))
+
+class CitiesReportCountQuery(ReportCountQuery):
+
+ def __init__(self):
+ ReportCountQuery.__init__(self,"1 month")
+ self.sql = self.base_query
+ self.url_prefix = "/cities/"
+ self.sql += ", cities.name, cities.id, province.name from cities "
+ self.sql += """left join wards on wards.city_id = cities.id join province on cities.province_id = province.id left join reports on wards.id = reports.ward_id
+ """
+ self.sql += "group by cities.name, cities.id, province.name order by province.name, cities.name"
+
+ def get_absolute_url(self):
+ return( self.url_prefix + str(self.get_results()[self.index][6]))
+
+ def province(self):
+ return(self.get_results()[self.index][7])
+
+ def province_changed(self):
+ if (self.index ==0 ):
+ return( True )
+ return( self.get_results()[self.index][7] != self.get_results()[self.index-1][7] )
+
+class FaqEntry(models.Model):
+ __metaclass__ = TransMeta
+
+ q = models.CharField(max_length=100)
+ a = models.TextField(blank=True, null=True)
+ slug = models.SlugField(null=True, blank=True)
+ order = models.IntegerField(null=True, blank=True)
+
+ def save(self):
+ super(FaqEntry, self).save()
+ if self.order == None:
+ self.order = self.id + 1
+ super(FaqEntry, self).save()
+
+ class Meta:
+ db_table = u'faq_entries'
+ translate = ('q', 'a', )
+
+
+class FaqMgr(object):
+
+ def incr_order(self, faq_entry ):
+ if faq_entry.order == 1:
+ return
+ other = FaqEntry.objects.get(order=faq_entry.order-1)
+ swap_order(other[0],faq_entry)
+
+ def decr_order(self, faq_entry):
+ other = FaqEntry.objects.filter(order=faq_entry.order+1)
+ if len(other) == 0:
+ return
+ swap_order(other[0],faq_entry)
+
+ def swap_order(self, entry1, entry2 ):
+ entry1.order = entry2.order
+ entry2.order = entry1.order
+ entry1.save()
+ entry2.save()
+
+
+class PollingStation(models.Model):
+ """
+ This is a temporary object. Sometimes, we get maps in the form of
+ polling stations, which have to be combined into wards.
+ """
+ number = models.IntegerField()
+ ward_number = models.IntegerField()
+ city = models.ForeignKey(City)
+ geom = models.MultiPolygonField( null=True)
+ objects = models.GeoManager()
+
+ class Meta:
+ db_table = u'polling_stations'
+
0  mainapp/views/__init__.py
No changes.
9 mainapp/views/ajax.py
@@ -0,0 +1,9 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from django.template import Context, RequestContext
+from mainapp.models import ReportCategory, ReportCategoryClass
+
+def category_desc(request,id):
+ return render_to_response("ajax/category_description.html",
+ {"category": ReportCategory.objects.get(id=id),
+ },
+ context_instance=RequestContext(request))
23 mainapp/views/cities.py
@@ -0,0 +1,23 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from mainapp.models import City, CitiesReportCountQuery, CityReportCountQuery, CityMap
+from django.template import Context, RequestContext
+
+def index(request):
+ return render_to_response("cities/index.html",
+ {"report_counts": CitiesReportCountQuery() },
+ context_instance=RequestContext(request))
+
+
+def show( request, city_id ):
+ city = get_object_or_404(City, id=city_id)
+ if request.GET.has_key('test'):
+ google = CityMap(city)
+ else:
+ google = None
+
+ return render_to_response("cities/show.html",
+ {"city":city,
+ "google": google,
+ "report_counts": CityReportCountQuery(city) },
+ context_instance=RequestContext(request))
+
24 mainapp/views/contact.py
@@ -0,0 +1,24 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from mainapp.models import Report
+from mainapp.forms import ContactForm
+from django.template import Context, RequestContext
+from django.http import HttpResponseRedirect
+
+import settings
+
+def thanks(request):
+ return render_to_response("contact/thanks.html", {},
+ context_instance=RequestContext(request))
+
+def new(request):
+ if request.method == 'POST':
+ form = ContactForm(data=request.POST)
+ if form.is_valid():
+ form.save()
+ return HttpResponseRedirect("/contact/thanks")
+ else:
+ form = ContactForm()
+
+ return render_to_response("contact/new.html",
+ { 'contact_form': form },
+ context_instance=RequestContext(request))
12 mainapp/views/faq.py
@@ -0,0 +1,12 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from django.http import HttpResponseRedirect
+from mainapp.models import FaqEntry
+from django.template import Context, RequestContext
+
+
+def show( request, slug ):
+ faq = get_object_or_404(FaqEntry, slug=slug)
+ return render_to_response("faq/show.html",
+ {"faq_entry":faq },
+ context_instance=RequestContext(request))
+
87 mainapp/views/main.py
@@ -0,0 +1,87 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from django.http import HttpResponseRedirect
+from mainapp.models import Report, ReportUpdate, Ward, FixMyStreetMap, ReportCountQuery, City, FaqEntry
+import urllib
+import libxml2
+from django.template import Context, RequestContext
+from django.contrib.gis.measure import D
+from django.contrib.gis.geos import *
+import settings
+from django.utils.translation import ugettext as _
+import logging
+import os
+
+
+def index(request, error_msg = None, disambiguate=None):
+ reports_with_photos = Report.objects.filter(is_confirmed=True).exclude(photo='').order_by("-created_at")[:3]
+ recent_reports = Report.objects.filter(is_confirmed=True).order_by("-created_at")[:5]
+
+ return render_to_response("index.html",
+ {"report_counts": ReportCountQuery(),
+ "cities": City.objects.all(),
+ "reports_with_photos": reports_with_photos,
+ "recent_reports": recent_reports ,
+ 'error_msg': error_msg,
+ 'disambiguate':disambiguate },
+ context_instance=RequestContext(request))
+
+
+def search_address(request):
+ if request.method == 'POST':
+ address = urllib.urlencode({'x':request.POST["q"]})[2:]
+ return HttpResponseRedirect("/search?q=" + address )
+
+ address = urllib.urlencode({'x':request.GET["q"]})[2:]
+ url = "http://maps.google.ca/maps/geo?q=" + address + "&output=xml&key=" + settings.GMAP_KEY
+
+ try:
+ resp = urllib.urlopen(url).read()
+ except:
+ return index(request, _("Sorry, we couldn\'t retreive the coordinates of that location, please use the Back button on your browser and try something more specific or include the city name at the end of your search."))
+
+ doc = libxml2.parseDoc(resp)
+ xpathContext = doc.xpathNewContext()
+ xpathContext.xpathRegisterNs('google', 'http://earth.google.com/kml/2.0')
+ coord_nodes = xpathContext.xpathEval("//google:coordinates")
+
+ if(len(coord_nodes) == 0):
+ return index( request, _("Sorry, we couldn\'t find the address you entered. Please try again with another intersection, address or postal code, or add the name of the city to the end of the search."))
+
+ coord_index = 0
+ if len(coord_nodes) > 1:
+ if not request.GET.has_key("index"):
+ addr_nodes = xpathContext.xpathEval("//google:address")
+ addr_list = "<ul>"
+ for i in range(0,len(addr_nodes)):
+ link = "/search?q=" + address + "&index=" + str(i)
+ addr_list += "<li><a href='%s'>%s</a></li>" % ( link, addr_nodes[i].content )
+ addr_list += "</ul>"
+ return index(request,disambiguate = addr_list )
+ else:
+ coord_index = int(request.GET["index"])
+
+ coord = coord_nodes[coord_index]
+ coords = coord.content.split(',')
+ lat = coords[1]
+ lon = coords[0]
+ point_str = "POINT(" + lon + " " + lat + ")"
+ pnt = fromstr(point_str, srid=4326)
+ wards = Ward.objects.filter(geom__contains=point_str)
+ if (len(wards) == 0):
+ return( index(request, _("Sorry, we don't yet have that area in our database. Please have your area councillor contact fixmystreet.ca.")))
+
+ #logging.debug( " %d wards returned" % len(wards) )
+ reports = Report.objects.filter(is_confirmed = True,point__distance_lte=(pnt,D(km=4))).distance(pnt).order_by('distance')
+ gmap = FixMyStreetMap(pnt,True,reports)
+
+ return render_to_response("search_result.html",
+ {'google' : gmap,
+ "pnt": pnt,
+ "enable_map": True,
+ "ward" : wards[0],
+ "reports" : reports, },
+ context_instance=RequestContext(request))
+
+def about(request):
+ return render_to_response("about.html",{'faq_entries' : FaqEntry.objects.all().order_by('order')},
+ context_instance=RequestContext(request))
16 mainapp/views/promotion.py
@@ -0,0 +1,16 @@
+from django.template import Context, RequestContext
+from django.shortcuts import render_to_response
+from django.http import HttpResponseRedirect
+from mainapp.models import Report, ReportUpdate
+
+
+def show(request, promo_code):
+ matchstr = "author LIKE '%%" + promo_code + "%%'"
+ reports = Report.objects.filter(is_confirmed=True).extra(select={'match': matchstr }).order_by('created_at')[0:100]
+ count = Report.objects.filter(author__contains=promo_code).count()
+ return render_to_response("promotions/show.html",
+ { "reports": reports,
+ "promo_code":promo_code,
+ "count": count },
+ context_instance=RequestContext(request))
+
0  mainapp/views/reports/__init__.py
No changes.
25 mainapp/views/reports/flags.py
@@ -0,0 +1,25 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from django.http import HttpResponseRedirect
+from mainapp.models import Report
+from django.template import Context, RequestContext
+from fixmystreet import settings
+from django.template.loader import render_to_string
+from django.core.mail import send_mail
+
+def new( request, report_id ):
+ report = get_object_or_404(Report, id=report_id)
+ if request.method == 'GET':
+ return render_to_response("reports/flags/new.html",
+ { "report": report },
+ context_instance=RequestContext(request))
+ else:
+ # send email flagging this report as being potentially offensive.
+ message = render_to_string("emails/flag_report/message.txt",
+ { 'report': report })
+ send_mail('FixMyStreet.ca Report Flagged as Offensive', message,
+ settings.EMAIL_FROM_USER,[settings.ADMIN_EMAIL], fail_silently=False)
+ return HttpResponseRedirect(report.get_absolute_url() + '/flags/thanks')
+
+def thanks( request, report_id ):
+ return render_to_response("reports/flags/thanks.html", {},
+ context_instance=RequestContext(request))
69 mainapp/views/reports/main.py
@@ -0,0 +1,69 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from django.http import HttpResponseRedirect
+from mainapp.models import Report, ReportUpdate, Ward, FixMyStreetMap, ReportCategory
+from mainapp.forms import ReportForm,ReportUpdateForm
+from django.template import Context, RequestContext
+from django.contrib.gis.geos import *
+from fixmystreet import settings
+from django.utils.translation import ugettext as _
+
+
+def new( request ):
+ category_error = None
+
+ if request.method == "POST":
+ pnt = fromstr("POINT(" + request.POST["lon"] + " " + request.POST["lat"] + ")", srid=4326)
+ f = request.POST.copy()
+ update_form = ReportUpdateForm( {'email':request.POST['email'], 'desc':request.POST['desc'],
+ 'author':request.POST['author'], 'phone': request.POST['phone']})
+ report_form = ReportForm({'title' : request.POST['title']}, request.FILES )
+
+ # this is a lot more complicated than it has to be because of the infortmation
+ # spread across two records.
+
+ if request.POST['category_id'] != "" and update_form.is_valid() and report_form.is_valid():
+ report = report_form.save( commit = False )
+ report.point = pnt
+ report.category_id = request.POST['category_id']
+ report.author = request.POST['author']
+ report.desc = request.POST['desc']
+ report.ward = Ward.objects.get(geom__contains=pnt)
+ report.save()
+ update = update_form.save(commit=False)
+ update.report = report
+ update.first_update = True
+ update.created_at = report.created_at
+ update.save()
+ return( HttpResponseRedirect( report.get_absolute_url() ))
+
+ # other form errors are handled by the form objects.
+ if not request.POST['category_id']:
+ category_error = _("Please select a category")
+
+ else:
+ pnt = fromstr("POINT(" + request.GET["lon"] + " " + request.GET["lat"] + ")", srid=4326)
+ report_form = ReportForm()
+ update_form = ReportUpdateForm()
+
+
+ return render_to_response("reports/new.html",
+ { "lat": pnt.y,
+ "lon": pnt.x,
+ "google": FixMyStreetMap(pnt, True),
+ "categories": ReportCategory.objects.all().order_by("category_class"),
+ "report_form": report_form,
+ "update_form": update_form,
+ "category_error" : category_error, },
+ context_instance=RequestContext(request))
+
+def show( request, report_id ):
+ report = get_object_or_404(Report, id=report_id)
+ return render_to_response("reports/show.html",
+ { "report": report,
+ "ward":report.ward,
+ "updates": ReportUpdate.objects.filter(report=report, is_confirmed=True).order_by("created_at")[1:],
+ "update_form": ReportUpdateForm(),
+ "google": FixMyStreetMap((report.point)) },
+ context_instance=RequestContext(request))
+
+
52 mainapp/views/reports/subscribers.py
@@ -0,0 +1,52 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from django.http import HttpResponseRedirect
+from mainapp.models import Report, ReportSubscriber
+from mainapp.forms import ReportSubscriberForm
+from django.template import Context, RequestContext
+from django.utils.translation import ugettext as _
+
+def new( request, report_id ):
+ report = get_object_or_404(Report, id=report_id)
+ error_msg = None
+
+ if request.method == 'POST':
+ form = ReportSubscriberForm( request.POST )
+ if form.is_valid():
+ subscriber = form.save( commit = False )
+ subscriber.report = report;
+ if report.is_subscribed(subscriber.email):
+ error_msg = _("You are already subscribed to this report.")
+ else:
+ subscriber.save()
+ return( HttpResponseRedirect( '/reports/subscribers/create/' ) )
+ else:
+ form = ReportSubscriberForm()
+
+ return render_to_response("reports/subscribers/new.html",
+ { "subscriber_form": form,
+ "report": report,
+ "error_msg": error_msg, },
+ context_instance=RequestContext(request))
+
+def create( request ):
+ return render_to_response("reports/subscribers/create.html",
+ { },
+ context_instance=RequestContext(request))
+
+def confirm( request, confirm_token ):
+ subscriber = get_object_or_404(ReportSubscriber, confirm_token = confirm_token )
+ subscriber.is_confirmed = True
+ subscriber.save()
+
+ return render_to_response("reports/subscribers/confirm.html",
+ { "subscriber": subscriber, },
+ context_instance=RequestContext(request))
+
+def unsubscribe(request, confirm_token ):
+ subscriber = get_object_or_404(ReportSubscriber, confirm_token = confirm_token )
+ report = subscriber.report
+ subscriber.delete()
+ return render_to_response("reports/subscribers/message.html",
+ { "message": _("You have unsubscribed from updates to:") + report.title, },
+ context_instance=RequestContext(request))
+
61 mainapp/views/reports/updates.py
@@ -0,0 +1,61 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from django.http import HttpResponseRedirect
+from mainapp.models import Report, ReportUpdate, Ward, FixMyStreetMap, ReportCategory
+from mainapp.forms import ReportForm,ReportUpdateForm
+from django.template import Context, RequestContext
+
+def new( request, report_id ):
+ report = get_object_or_404(Report, id=report_id)
+ if request.method == 'POST':
+ update_form = ReportUpdateForm( request.POST )
+ if update_form.is_valid():
+ update = update_form.save(commit=False)
+ update.is_fixed = request.POST.has_key('is_fixed')
+ update.report=report
+ update.save()
+ # redirect after a POST
+ return( HttpResponseRedirect( '/reports/updates/create/' ) )
+ else:
+ update_form = ReportUpdateForm()
+
+ return render_to_response("reports/show.html",
+ { "report": report,
+ "google": FixMyStreetMap(report.point),
+ "update_form": update_form,
+ },
+ context_instance=RequestContext(request))
+
+def create( request ):
+ return render_to_response("reports/updates/create.html",
+ { },
+ context_instance=RequestContext(request))
+
+def confirm( request, confirm_token ):
+ update = get_object_or_404(ReportUpdate, confirm_token = confirm_token )
+
+ if update.is_confirmed:
+ return( HttpResponseRedirect( update.report.get_absolute_url() ))
+
+ # is the update fixed?
+
+ if update.is_fixed:
+ update.report.is_fixed = True
+ update.report.fixed_at = update.created_at
+
+ update.is_confirmed = True
+ update.save()
+
+ # we track a last updated time in the report to make statistics
+ # (such as on the front page) easier.
+
+ if not update.first_update:
+ update.report.updated_at = update.created_at
+ else:
+ update.report.updated_at = update.report.created_at
+ update.report.is_confirmed = True
+
+ update.report.save()
+ update.send_emails()
+
+ # redirect to report
+ return( HttpResponseRedirect( update.report.get_absolute_url() ))
33 mainapp/views/wards.py
@@ -0,0 +1,33 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from mainapp.models import City, Ward, WardMap, Report
+from django.template import Context, RequestContext
+from django.db import connection
+
+def show_by_number( request, city_id, ward_no ):
+ city= get_object_or_404(City, id=city_id)
+ wards = Ward.objects.filter( city=city, number=ward_no)
+ google = WardMap(wards[0],[])
+ return render_to_response("wards/show.html",
+ {"ward": wards[0],
+ "google": google,
+ "reports": [] },
+ context_instance=RequestContext(request))
+
+def show( request, ward_id ):
+ ward = get_object_or_404(Ward, id=ward_id)
+ reports = Report.objects.filter( ward = ward, is_confirmed = True ).extra( select = { 'status' : """
+ CASE
+ WHEN age( clock_timestamp(), created_at ) < interval '1 month' AND is_fixed = false THEN 'New Problems'
+ WHEN age( clock_timestamp(), created_at ) > interval '1 month' AND is_fixed = false THEN 'Older Problems'
+ WHEN age( clock_timestamp(), fixed_at ) < interval '1 month' AND is_fixed = true THEN 'Recently Fixed'
+ WHEN age( clock_timestamp(), fixed_at ) > interval '1 month' AND is_fixed = true THEN 'Older Fixed Problems'
+ ELSE 'Unknown Status'
+ END """ }, order_by = ['status'] )
+
+ google = WardMap(ward,reports)
+
+ return render_to_response("wards/show.html",
+ {"ward": ward,
+ "google": google,
+ "reports": reports },
+ context_instance=RequestContext(request))
11 manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+ import settings # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
BIN  media/bug.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
141 media/css/colors.css
@@ -0,0 +1,141 @@
+
+#heading {
+ background: olive;
+}
+
+#page_container {
+ background-color: #fff;
+}
+
+#error-msg, #notice {
+ background-color: #ffffcc;
+ border: solid 2px #999900;
+}
+
+.is_fixed {
+ background-color: #ccffcc;
+ border: solid 2px #009900;
+}
+
+
+.errorlist {
+ color: #990000;
+}
+
+.row-highlight {
+ background: #FFFFAA;
+}
+
+.row-odd {
+ background: #fafafa;
+}
+
+#photo_note {
+ color: #6396b0;
+}
+
+#top_menu {
+ background: #666666;
+}
+
+
+#top_menu {
+ background: #666666 url(/media/images/stat_tl.png) no-repeat;
+}
+
+#top_menu_tr {
+ background: url(/media/images/stat_tr.png) top right no-repeat;
+}
+
+#top_menu_bl {
+ background: url(/media/images/stat_bl.png) bottom left no-repeat;
+}
+
+#top_menu_br {
+ background: url(/media/images/stat_br.png) bottom right no-repeat;
+}
+
+
+.stat {
+ background: #666666 url(/media/images/stat_tl.png) no-repeat;
+}
+
+.stat div {
+ background: url(/media/images/stat_tr.png) top right no-repeat;
+}
+
+.stat div div {
+ background: url(/media/images/stat_bl.png) bottom left no-repeat;
+}
+
+.stat div div div {
+ background: url(/media/images/stat_br.png) bottom right no-repeat;
+}
+
+#footer {
+ color: #666666;
+}
+
+.note strong {
+ color:green;
+}
+
+#start_box {
+ background: #6396b0 url(/media/images/start_tl.png) no-repeat;
+ color: #ffffff;
+}
+
+#start_box div {
+ background: url(/media/images/start_tr.png) top right no-repeat;
+}
+
+#start_box div div {
+ background: url(/media/images/start_bl.png) bottom left no-repeat;
+}
+
+#start_box div div div {
+ background: url(/media/images/start_br.png) bottom right no-repeat;
+ padding: 15px;
+}
+
+a {
+ color: #6396b0;
+}
+
+
+.status-text {
+color:#666666;
+}
+
+
+#ward_box {
+ background-color: #aafbaf;
+}
+
+#content_container h1 {
+ color: #4a8797;
+}
+#content_container h2 {
+ color: #f3865a;
+}
+#content_container h3 {
+ color: #6396b0