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.
 
 
 
 

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