Election results in the London Borough of Sutton.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

343 lines
8.4 KiB

  1. require 'rubygems'
  2. require 'sinatra'
  3. require 'haml'
  4. require './models'
  5. require 'rack-flash'
  6. set :root, File.dirname(__FILE__)
  7. enable :sessions
  8. use Rack::Flash
  9. class String
  10. def pluralize(num)
  11. if num == 1
  12. return self
  13. end
  14. case self[-1]
  15. when 'y'
  16. self[0..-2] + 'ies'
  17. when 's'
  18. self + "es"
  19. else
  20. self + "s"
  21. end
  22. end
  23. end
  24. helpers do
  25. # Format a number with commas for every ^3
  26. def commify(num)
  27. num.to_s.reverse.gsub(/(\d\d\d)(?=\d)(?!\d*\.)/,'\1,').reverse
  28. end
  29. # From http://snippets.dzone.com/posts/show/593
  30. def to_ordinal(num)
  31. num = num.to_i
  32. if (10...20) === num
  33. "#{num}th"
  34. else
  35. g = %w{ th st nd rd th th th th th th }
  36. a = num.to_s
  37. c = a[-1..-1].to_i
  38. a + g[c]
  39. end
  40. end
  41. def format_percent(num)
  42. sprintf("%.0f%%", num)
  43. end
  44. def short_date(d)
  45. d.strftime("%e %b %Y")
  46. end
  47. def long_date(d)
  48. d.strftime("%e %B %Y")
  49. end
  50. # Exception for Labour/Co-operative candidacies
  51. def party_name(labcoop, party_name)
  52. labcoop ? "Labour and Co-operative Party" : party_name
  53. end
  54. end
  55. get '/' do
  56. @election = Election.get(9) # FIXME magic number
  57. @election_title = "#{@election.body.name} #{@election.kind} #{long_date(@election.d)}"
  58. if params[:postcode]
  59. if @p = Postcode.get(params[:postcode].strip.upcase)
  60. # Postcode is valid and in LB Sutton
  61. if @election.body.district_name == 'constituency'
  62. @district = District.get(@p.constituency_id)
  63. else
  64. @district = District.get(@p.ward_id)
  65. end
  66. flash[:notice] = "Postcode <strong>#{@postcode}</strong> is in #{@district.name} #{@election.body.district_name}"
  67. if @p.polling_station
  68. @ps_postcode = Postcode.get(@p.polling_station.postcode)
  69. @polling_station = "Your polling station is \
  70. <a href=\"http://www.openstreetmap.org/?mlat=%s&mlon=%s&zoom=16\">%s, %s, %s</a>" \
  71. % [ @ps_postcode.lat, @ps_postcode.lng, @p.polling_station.name, \
  72. @p.polling_station.address, @p.polling_station.postcode]
  73. end
  74. redirect "/bodies/#{@election.body.slug}/elections/#{@election.d}/#{@election.body.districts_name}/#{@district.slug}"
  75. else
  76. flash.now[:error] = "<strong>#{@postcode}</strong> is not a postcode in Sutton"
  77. end
  78. end
  79. # Display a random postcode as default search term
  80. @random_pc = repository(:default).adapter.select("
  81. SELECT postcode
  82. FROM postcodes
  83. ORDER BY RANDOM()
  84. LIMIT 1
  85. ")
  86. @default_pc = @random_pc[0]
  87. @future_elections = Election.future
  88. @past_elections = Election.past
  89. haml :index
  90. end
  91. get '/bodies/:body/elections/:date' do
  92. @body = Body.first(:slug => params[:body])
  93. @election = Election.first(:body => @body, :d => params[:date])
  94. @elections_for_this_body = Election.all(:body => @body, :order => [:d])
  95. @total_seats = Candidacy.sum(:seats, :election => @election)
  96. @total_votes = Candidacy.sum(:votes, :election => @election)
  97. # There's got to be a better way to do this, either with SQL or Datamapper
  98. @total_districts = repository(:default).adapter.select("
  99. SELECT district_id
  100. FROM candidacies
  101. WHERE election_id = ?
  102. GROUP BY district_id
  103. ORDER BY district_id
  104. ", @election.id).count
  105. @results_by_party = repository(:default).adapter.select("
  106. SELECT
  107. p.colour,
  108. p.name,
  109. SUM(c.votes) AS votez,
  110. SUM(c.seats) AS seatz,
  111. COUNT(*) AS cands
  112. FROM candidacies c
  113. LEFT JOIN parties p ON p.id = c.party_id
  114. WHERE c.election_id = ?
  115. GROUP BY c.party_id, p.colour, p.name
  116. ORDER BY seatz DESC, votez DESC
  117. ", @election.id)
  118. @results_by_district = repository(:default).adapter.select("
  119. SELECT
  120. d.name,
  121. d.slug AS district_slug,
  122. SUM(c.seats) AS seats,
  123. SUM(c.votes) AS votez,
  124. COUNT(c.id) AS num_candidates
  125. FROM districts d, candidacies c
  126. WHERE
  127. c.district_id = d.id
  128. AND c.election_id = ?
  129. GROUP BY c.district_id, d.name, d.slug
  130. ORDER BY d.name
  131. ", @election.id)
  132. # For elections that haven't yet been held
  133. @districts_in_this_election = repository(:default).adapter.select("
  134. SELECT DISTINCT d.name, d.slug
  135. FROM candidacies c
  136. LEFT JOIN districts d
  137. ON c.district_id = d.id
  138. WHERE c.election_id = ?
  139. ORDER BY d.name
  140. ", @election.id)
  141. haml :electionsummary
  142. end
  143. # get '/bodies/:body/elections/:date/parties/:party' do
  144. # Not written yet. Show how this party did at this election.
  145. # end
  146. get '/bodies/?' do
  147. @bodies = Body.all
  148. haml :bodies
  149. end
  150. get '/bodies/:body/?' do
  151. @body = Body.first(:slug => params[:body])
  152. @elections = Election.all(:body => @body, :order => [:d.desc])
  153. @districts = District.all(:body => @body, :order => [:name])
  154. haml :body
  155. end
  156. # get '/wards/:slug/postcode/:postcode/?' do
  157. # @ward = Ward.first(:slug => params[:slug])
  158. # @postcode = params[:postcode]
  159. # haml :wards
  160. # end
  161. get '/candidates/:id/?' do
  162. if @deleted_candidate = DeletedCandidate.get(params[:id])
  163. redirect "/candidates/#{@deleted_candidate.candidate_id}", 301 # HTTP 301 Moved Permanently
  164. end
  165. if @candidate = Candidate.get(params[:id])
  166. @candidacies = repository(:default).adapter.select("
  167. SELECT
  168. e.d,
  169. c.*,
  170. p.name AS party_name,
  171. p.colour AS party_colour,
  172. b.name AS body_name,
  173. b.slug AS body_slug,
  174. b.districts_name AS districts_name,
  175. d.name AS district_name,
  176. d.slug AS district_slug
  177. FROM candidacies c
  178. INNER JOIN elections e
  179. ON c.election_id = e.id
  180. INNER JOIN parties p
  181. ON c.party_id = p.id
  182. INNER JOIN bodies b
  183. ON e.body_id = b.id
  184. INNER JOIN districts d
  185. ON c.district_id = d.id
  186. WHERE c.candidate_id = ?
  187. ORDER BY d
  188. ", @candidate.id)
  189. haml :candidate
  190. else
  191. 404
  192. end
  193. end
  194. get '/candidates/?' do
  195. @candidates = Candidate.all(:order => [ :surname, :forenames ])
  196. haml :candidates
  197. end
  198. get '/bodies/:body/elections/:date/:districts_name/:district' do
  199. @district = District.first(:slug => params[:district])
  200. @body = Body.first(:slug => params[:body])
  201. @election = Election.first(:body => @body, :d => params[:date])
  202. @candidacies = Candidacy.all(:district => @district, :election => @election, :order => [:votes.desc])
  203. @total_votes = Candidacy.sum(:votes, :district => @district, :election => @election)
  204. @total_candidates = Candidacy.count(:district => @district, :election => @election)
  205. @total_seats = Candidacy.sum(:seats, :district => @district, :election => @election)
  206. @districts_in_this_election = @election.candidacies.districts
  207. @poll = Poll.get(@district.id, @election.id)
  208. # Postgres: All the columns selected when using GROUP BY must either be aggregate functions or appear in the GROUP BY clause
  209. @results_by_party = repository(:default).adapter.select("
  210. SELECT
  211. p.name AS party_name,
  212. p.colour AS party_colour,
  213. COUNT(c.id) AS num_candidates,
  214. SUM(c.seats) AS num_seats,
  215. SUM(c.votes) AS total_votes
  216. FROM candidacies c
  217. LEFT JOIN parties p
  218. ON c.party_id = p.id
  219. WHERE c.district_id = ?
  220. AND c.election_id = ?
  221. GROUP BY p.name, p.colour
  222. ORDER BY total_votes DESC
  223. ", @district.id, @election.id)
  224. if params[:wiki] == 'dokuwiki'
  225. o = "^ #{@district.name} #{@district.body.district_name} results, #{@body.name} election #{short_date(@election.d)} ^^^^^^\n"
  226. o += "^ Position ^ Candidate ^ Party ^ Votes ^ % Share ^ ^\n"
  227. count = 0
  228. @candidacies.each do |c|
  229. count += 1
  230. o += "| %2d | [[%-30s]] | [[%-40s]] | %6s | %3s | %-7s |\n" %
  231. [ count,
  232. c.candidate.short_name,
  233. party_name(c.labcoop, c.party.name),
  234. commify(c.votes),
  235. format_percent(c.votes.to_f / @total_votes * 100),
  236. c.seats == 1 ? 'Elected' : ''
  237. ]
  238. end
  239. o
  240. else
  241. haml :resultsdistrict
  242. end
  243. end
  244. get '/bodies/:body/:districts_name/:district' do
  245. @district = District.first(:slug => params[:district])
  246. @body = Body.first(:slug => params[:body])
  247. haml :district
  248. end
  249. get '/how-the-council-election-works' do
  250. haml :election
  251. end
  252. get '/how-the-parliament-election-works' do
  253. haml :parliament
  254. end
  255. # get '/voting' do
  256. # haml :voting
  257. # end
  258. get '/error' do
  259. haml :error
  260. end
  261. get '/about' do
  262. haml :about
  263. end
  264. # get '/aliens' do
  265. # haml :aliens
  266. # end
  267. get '/polling-stations' do
  268. @stations = PollingStation.all
  269. haml :pollingstations
  270. end
  271. not_found do
  272. haml :not_found
  273. end