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.
 
 
 
 

334 lines
8.2 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/:body/?' do
  147. @body = Body.first(:slug => params[:body])
  148. @districts = District.all(:body => @body, :order => [:name])
  149. @elections = repository(:default).adapter.select("
  150. SELECT
  151. e.id,
  152. e.kind,
  153. e.d,
  154. SUM(p.ballot_papers_issued)::float / SUM(p.electorate) * 100 AS turnout_percent
  155. FROM elections e
  156. LEFT JOIN polls p
  157. ON e.id = p.election_id
  158. WHERE e.body_id = ?
  159. GROUP BY p.election_id, e.id
  160. ORDER BY e.d DESC
  161. ", @body.id)
  162. haml :body
  163. end
  164. # get '/wards/:slug/postcode/:postcode/?' do
  165. # @ward = Ward.first(:slug => params[:slug])
  166. # @postcode = params[:postcode]
  167. # haml :wards
  168. # end
  169. get '/candidates/:id/?' do
  170. if @deleted_candidate = DeletedCandidate.get(params[:id])
  171. redirect "/candidates/#{@deleted_candidate.candidate_id}", 302 # HTTP 302 Moved Temporarily
  172. end
  173. if @candidate = Candidate.get(params[:id])
  174. @candidacies = repository(:default).adapter.select("
  175. SELECT
  176. e.d,
  177. c.*,
  178. p.name AS party_name,
  179. p.colour AS party_colour,
  180. b.name AS body_name,
  181. b.slug AS body_slug,
  182. b.districts_name AS districts_name,
  183. d.name AS district_name,
  184. d.slug AS district_slug
  185. FROM candidacies c
  186. INNER JOIN elections e
  187. ON c.election_id = e.id
  188. INNER JOIN parties p
  189. ON c.party_id = p.id
  190. INNER JOIN bodies b
  191. ON e.body_id = b.id
  192. INNER JOIN districts d
  193. ON c.district_id = d.id
  194. WHERE c.candidate_id = ?
  195. ORDER BY d
  196. ", @candidate.id)
  197. haml :candidate
  198. else
  199. 404
  200. end
  201. end
  202. get '/candidates/?' do
  203. @candidates = Candidate.all(:order => [ :surname, :forenames ])
  204. haml :candidates
  205. end
  206. get '/bodies/:body/elections/:date/:districts_name/:district' do
  207. @district = District.first(:slug => params[:district])
  208. @body = Body.first(:slug => params[:body])
  209. @election = Election.first(:body => @body, :d => params[:date])
  210. @candidacies = Candidacy.all(:district => @district, :election => @election, :order => [:votes.desc])
  211. @total_votes = Candidacy.sum(:votes, :district => @district, :election => @election)
  212. @total_candidates = Candidacy.count(:district => @district, :election => @election)
  213. @total_seats = Candidacy.sum(:seats, :district => @district, :election => @election)
  214. @districts_in_this_election = @election.candidacies.districts
  215. @poll = Poll.get(@district.id, @election.id)
  216. if @total_seats == 1
  217. @share_denominator = @total_votes
  218. elsif @poll && @poll.valid_ballot_papers
  219. @share_denominator = @poll.valid_ballot_papers
  220. else
  221. @share_denominator = @total_votes / @total_seats
  222. @share_message = "The vote share percentages have been estimated as we don't have data for the number of valid ballot papers in this poll."
  223. end
  224. # Postgres: All the columns selected when using GROUP BY must either be aggregate functions or appear in the GROUP BY clause
  225. @results_by_party = repository(:default).adapter.select("
  226. SELECT
  227. p.name AS party_name,
  228. p.colour AS party_colour,
  229. COUNT(c.id) AS num_candidates,
  230. SUM(c.seats) AS num_seats,
  231. SUM(c.votes) AS total_votes
  232. FROM candidacies c
  233. LEFT JOIN parties p
  234. ON c.party_id = p.id
  235. WHERE c.district_id = ?
  236. AND c.election_id = ?
  237. GROUP BY p.name, p.colour
  238. ORDER BY total_votes DESC
  239. ", @district.id, @election.id)
  240. haml :resultsdistrict
  241. end
  242. get '/bodies/:body/:districts_name/:district' do
  243. @district = District.first(:slug => params[:district])
  244. @body = Body.first(:slug => params[:body])
  245. haml :district
  246. end
  247. get '/how-the-council-election-works' do
  248. haml :election
  249. end
  250. get '/how-the-parliament-election-works' do
  251. haml :parliament
  252. end
  253. get '/error' do
  254. haml :error
  255. end
  256. get '/about' do
  257. haml :about
  258. end
  259. not_found do
  260. haml :not_found
  261. end