├── runtime.txt ├── Procfile ├── requirements.txt ├── templates └── index.html └── server.py /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.4.1 2 | 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: python server.py 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | Jinja2==2.7.3 3 | MarkupSafe==0.23 4 | Werkzeug==0.9.6 5 | itsdangerous==0.24 6 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Yote - vote with Yo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 34 | 35 | 36 | 37 |
38 | 39 |
40 |

41 | Yote vote with Yo 42 |

43 | 44 |

To vote, just Yo at your first choice team then your second choice team. Only your first 2 votes will count :)

45 | 46 |

Just Yo at 2 of the following accounts, in order of preference:

47 |

(buttons on their webpages)

48 | 55 |

Alternatively, Yo their webpage URL to PRODUCTFORGE!

56 | 57 | 58 |
59 | 60 |
61 |

This page refreshes automatically to display the current winner and runner-up.

62 |

63 | winning: {{ winner }}! 64 |

65 |

66 | almost there: {{ runnerup }} 67 |

68 |
69 | 70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | {% for teamvote in sorted_teamvotes %} 81 | 82 | 83 | 84 | 85 | 86 | {% endfor %} 87 | 88 | 89 |
Teamprimary votescontingent votes
{{ teamvote.team_str }}{{ teamvote.votes[1] | count }}{{ teamvote.votes[2] | count }}
90 |
91 |
92 | 93 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | #!python 2 | # -*- coding: utf-8 -*- 3 | """Server for rinse.benjeffrey.com""" 4 | import logging 5 | import os.path 6 | from collections import namedtuple 7 | from datetime import datetime, timedelta 8 | from os import environ 9 | 10 | from flask import Flask, render_template, send_from_directory, request, redirect 11 | 12 | 13 | 14 | logging.basicConfig(level=logging.DEBUG) if bool(environ.get('DEBUG', False)) else logging.basicConfig(level=logging.INFO) 15 | 16 | app = Flask(__name__) 17 | app.config.update( 18 | DEBUG=bool(environ.get('DEBUG', False)), 19 | ) 20 | 21 | 22 | Vote = namedtuple('Vote', ['for_choice', 'by']) 23 | TeamVotes = namedtuple('TeamVotes', ['team_str', 'votes']) 24 | 25 | # Just wanna match on these strings, which are the team names AND substrings in each's URL: 26 | TEAM_VOTES_DICT = {'unichance': None, 'textitdone': None, 'goya': None, 'skillmatrix': None, 'weatherselfie': None} 27 | for t in TEAM_VOTES_DICT: 28 | # we only count 1st and 2nd votes, i.e. 1st and 2nd ranks 29 | TEAM_VOTES_DICT[t] = {1: [], 2: []} 30 | 31 | 32 | HAS_VOTED = set() 33 | FINISHED_VOTING = set() 34 | 35 | 36 | @app.route('/') 37 | def index(): 38 | """Serve a self-refreshing page of the ranks. 39 | """ 40 | sorted_teamvotes = sorted([TeamVotes(team_str=team, votes=TEAM_VOTES_DICT[team]) for team in TEAM_VOTES_DICT], 41 | key=lambda t: len(t.votes[1]), 42 | reverse=True) 43 | 44 | # in the case of a winner/runner-up draw, use supplementary votes 45 | if len(sorted_teamvotes[0].votes[1]) == len(sorted_teamvotes[1].votes[2]): 46 | rank_a = len(sorted_teamvotes[0].votes[1]) + len(sorted_teamvotes[0].votes[2]) 47 | rank_b = len(sorted_teamvotes[1].votes[1]) + len(sorted_teamvotes[1].votes[2]) 48 | if rank_a < rank_b: 49 | sorted_teamvotes[0], sorted_teamvotes[1] = sorted_teamvotes[1], sorted_teamvotes[0] 50 | 51 | winner = sorted_teamvotes[0].team_str if len(sorted_teamvotes[0].votes[1]) > 0 else None 52 | runnerup = sorted_teamvotes[1].team_str if len(sorted_teamvotes[0].votes[1]) > 0 else None 53 | 54 | 55 | return render_template('index.html', winner=winner, runnerup=runnerup, sorted_teamvotes=sorted_teamvotes) 56 | 57 | 58 | @app.route('/yote') 59 | def yote(): 60 | """Callback URL to vote by sending a link to the PRODUCTFORGE account 61 | """ 62 | logging.debug('incoming Yote on PRODUCTFORGE account') 63 | 64 | username = request.args.get('username') 65 | link = request.args.get('link') 66 | vote = None 67 | 68 | for team_str in TEAM_VOTES_DICT: 69 | if team_str in link: 70 | vote = Vote(for_choice=team_str, by=username) 71 | 72 | if username in FINISHED_VOTING: 73 | logging.info('rejected >2nd URL Yote - ' + str(vote)) 74 | pass 75 | elif vote in TEAM_VOTES_DICT[team_str][1]: 76 | logging.info('rejected double URL Yote - ' + str(vote)) 77 | pass 78 | elif username in HAS_VOTED: 79 | logging.info('recorded URL Yote 2 - ' + str(vote)) 80 | TEAM_VOTES_DICT[team_str][2].append(vote) 81 | FINISHED_VOTING.add(username) 82 | else: 83 | logging.info('recorded URL Yote 1 - ' + str(vote)) 84 | TEAM_VOTES_DICT[team_str][1].append(vote) 85 | HAS_VOTED.add(username) 86 | 87 | break 88 | 89 | return vote 90 | 91 | 92 | @app.route('/yote/') 93 | def yote_channel(team_account): 94 | """Callback URL to vote by Yo-ing a PF3- account (used by website buttons) 95 | """ 96 | logging.debug('incoming Yote on ' + team_account + ' account') 97 | 98 | username = request.args.get('username') 99 | vote = None 100 | 101 | for team_str in TEAM_VOTES_DICT: 102 | if team_str in team_account: 103 | vote = Vote(for_choice=team_str, by=username) 104 | 105 | if username in FINISHED_VOTING: 106 | logging.info('rejected >2nd team account Yote - ' + str(vote)) 107 | pass 108 | elif vote in TEAM_VOTES_DICT[team_str][1]: 109 | logging.info('rejected double team account Yote - ' + str(vote)) 110 | pass 111 | elif username in HAS_VOTED: 112 | logging.info('recorded team account Yote 2 - ' + str(vote)) 113 | TEAM_VOTES_DICT[team_str][2].append(vote) 114 | FINISHED_VOTING.add(username) 115 | else: 116 | logging.info('recorded team account Yote 1 - ' + str(vote)) 117 | TEAM_VOTES_DICT[team_str][1].append(vote) 118 | HAS_VOTED.add(username) 119 | 120 | break 121 | 122 | return vote 123 | 124 | 125 | 126 | 127 | @app.route('/reset') 128 | def reset(): 129 | """Resets all votes to 0. 130 | """ 131 | logging.warn('reset TEAM_VOTES_DICT count') 132 | TEAM_VOTES_DICT = {} 133 | for team_str in TEAM_VOTES_DICT: 134 | TEAM_VOTES_DICT[team_str] = {1: [], 2: []} 135 | 136 | return redirect('/') 137 | 138 | 139 | @app.route('/favicon.ico') 140 | def favicon(): 141 | return send_from_directory(os.path.join(app.root_path, 'static'), 142 | 'favicon.ico', mimetype='image/vnd.microsoft.icon') 143 | 144 | 145 | if __name__ == '__main__': 146 | # Bind to PORT if defined, otherwise default to 5000. 147 | port = int(environ.get('PORT', 5000)) 148 | logging.debug('Launching Flask app...') 149 | app.run(host='0.0.0.0', port=port) 150 | --------------------------------------------------------------------------------