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.
 
 
 
 

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