Skip to content

Commit

Permalink
updating to part 3 of the blog post series
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmakai committed Jun 15, 2015
1 parent e04427c commit 8aa63ed
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 10 deletions.
17 changes: 17 additions & 0 deletions cyoa/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,20 @@ class PresentationForm(Form):
slug = StringField('URL slug', validators=[Required(),
Length(1, 255)])
is_visible = BooleanField()


class DecisionForm(Form):
slug = StringField('URL slug', validators=[Required(),
Length(1, 128)])
first_path_slug = StringField('A word for the first path. Must be '
'lowercase. No spaces.',
validators=[Required(), Length(1, 64),
Regexp('[a-z0-9]+', message=
'Choice must be lowercase '
'with no whitespace.')])
second_path_slug = StringField('A word for the second path. Must be '
'lowercase. No spaces.',
validators=[Required(), Length(1, 64),
Regexp('[a-z-0-9]+', message=
'Choice must be lowercase '
'with no whitespace.')])
17 changes: 17 additions & 0 deletions cyoa/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@ class Presentation(db.Model):
slug = db.Column(db.String(128), unique=True)
filename = db.Column(db.String(256))
is_visible = db.Column(db.Boolean, default=False)
decisions = db.relationship('Decision', lazy='dynamic')

def __repr__(self):
return '<Presentation %r>' % self.name


class Decision(db.Model):
"""
A branch in the storyline that an audience member can vote on.
Must map directly into what is stored in Redis.
"""
__tablename__ = 'choices'
id = db.Column(db.Integer, primary_key=True)
slug = db.Column(db.String(128))
first_path_slug = db.Column(db.String(128))
second_path_slug = db.Column(db.String(128))
presentation = db.Column(db.Integer, db.ForeignKey('presentations.id'))

def __repr__(self):
return '<Decision %r>' % self.slug
8 changes: 8 additions & 0 deletions cyoa/templates/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>
<head>
<title>Sorry, no presentation voting list found.</title>
</head>
<body>
Sorry, no presentation voting list was found at this URL.
</body>
</html>
13 changes: 13 additions & 0 deletions cyoa/templates/decision.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends "base.html" %}

{% block content %}
<div class="container">
<div class="row">
<div class="col-md-6">
<h2>What path do you choose?</h2>
<a href="{{ url_for('web_vote', presentation_slug=presentation.slug, decision_slug=decision.slug, choice_slug=decision.first_path_slug) }}" class="btn btn-lg btn-success">{{ decision.first_path_slug }}</a>
<a href="{{ url_for('web_vote', presentation_slug=presentation.slug, decision_slug=decision.slug, choice_slug=decision.second_path_slug) }}" class="btn btn-lg btn-success">{{ decision.second_path_slug }}</a>
</div>
</div>
</div>
{% endblock %}
34 changes: 34 additions & 0 deletions cyoa/templates/web_vote.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{% extends "base.html" %}

{% block content %}
<div class="container">
<div class="row">
<div class="col-md-6">
<h2>You've chosen <em>{{ choice }}</em>. Stay on
this page until all the votes are counted.</h2>
<h1><span id="vote-counter"></span> votes for
{{ choice }}.</h1>
</div>
</div>
{% endblock %}


{% block js_body %}
<script type="text/javascript"
src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/0.9.16/socket.io.min.js"></script>

<script type="text/javascript">
$(document).ready(function() {
namespace = '/cyoa';
var websocket = io.connect('http://' + document.domain + ':'
+ location.port + namespace);

websocket.emit('join', {'vote': '{{ choice }}'});

websocket.on('msg', function(msg) {
var voteCounter = $('#vote-counter').html(msg.val);
});
});
</script>
{% endblock %}
33 changes: 33 additions & 0 deletions cyoa/templates/wizard/decision.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{% extends "base.html" %}

{% block nav %}
{% include "nav.html" %}
{% endblock %}

{% block content %}
<div class="container">
<div class="row">
<div class="col-md-6">
{% from "partials/_formhelpers.html" import render_field %}
{% if is_new %}
<form action="{{ url_for('wizard_new_decision', pres_id=presentation_id) }}"
method="post">
{% else %}
<form action="{{ url_for('wizard_edit_decision', presentation_id=presentation_id, decision_id=decision.id) }}" method="post">
{% endif %}
<div>
{{ form.csrf_token }}
{{ render_field(form.slug) }}
{{ render_field(form.first_path_slug) }}
{{ render_field(form.second_path_slug) }}
</div>
<div>
<input type="submit" class="btn btn-success btn-top-margin"
value="Save Decision"></input>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

46 changes: 46 additions & 0 deletions cyoa/templates/wizard/decisions.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{% extends "base.html" %}

{% block nav %}
{% include "nav.html" %}
{% endblock %}

{% block content %}
<div class="container">
<div class="row">
<div class="col-md-10">
<h1>{{ presentation.name }} Decisions</h1>
{% if decisions|length == 0 %}
No web browser voting enabled for
<em>{{ presentation.name }}</em>.
<a href="{{ url_for('wizard_new_decision', pres_id=presentation.id) }}">Add a decision point for this presentation</a>.
{% else %}
<table class="table">
<thead>
<tr>
<th>Decision</th>
<th>First story path</th>
<th>Second story path</th>
<th>Delete</th>
<th>View choice</th>
</tr>
</thead>
<tbody>
{% for d in decisions %}
<tr>
<td><a href="{{ url_for('wizard_edit_decision', presentation_id=presentation.id, decision_id=d.id) }}">{{ d.slug }}</a></td>
<td>{{ d.first_path_slug }}</td>
<td>{{ d.second_path_slug }}</td>
<td><a href="{{ url_for('wizard_delete_decision', pres_id=presentation.id, decision_id=d.id) }}" class="btn btn-danger">X</a></td>
<td><a href="{{ url_for('decision', presentation_slug=presentation.slug, decision_slug=d.slug) }}" target="_blank">{{ d.slug }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<div class="btn-top-margin">
<a href="{{ url_for('wizard_new_decision', pres_id=presentation.id) }}" class="btn btn-primary">Add decision point</a>
</div>
</div>
</div>
</div>
{% endblock %}
37 changes: 36 additions & 1 deletion cyoa/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from .config import TWILIO_NUMBER
from .forms import LoginForm
from .models import Wizard
from .models import Wizard, Decision

from . import app, redis_db, socketio, login_manager
from .models import Presentation
Expand Down Expand Up @@ -71,3 +71,38 @@ def sign_out():
return redirect(url_for('sign_in'))


@app.route('/<presentation_slug>/vote/<decision_slug>/', methods=['GET'])
def decision(presentation_slug, decision_slug):
presentations = Presentation.query.filter_by(slug=presentation_slug)
if presentations.count() > 0:
presentation = presentations.first()
decision = Decision.query.filter_by(presentation=presentation.id,
slug=decision_slug).first()
return render_template('decision.html', presentation=presentation,
decision=decision)
return render_template("404.html"), 404


@app.route('/<presentation_slug>/vote/<decision_slug>/<choice_slug>/',
methods=['GET'])
def web_vote(presentation_slug, decision_slug, choice_slug):
presentations = Presentation.query.filter_by(slug=presentation_slug)
if presentations.count() > 0:
presentation = presentations.first()
decision = Decision.query.filter_by(presentation=presentation.id,
slug=decision_slug).first()
if decision:
votes = redis_db.get(choice_slug)
return render_template('web_vote.html', decision=decision,
presentation=presentation, votes=votes,
choice=choice_slug)
return render_template("404.html"), 404


def broadcast_vote_count(key):
total_votes = 0
if redis_db.get(key):
total_votes += int(redis_db.get(key))
total_votes += len(socketio.rooms['/cyoa'][key])
socketio.emit('msg', {'div': key, 'val': total_votes},
namespace='/cyoa')
14 changes: 11 additions & 3 deletions cyoa/websockets.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
from flask.ext.socketio import emit
from flask.ext.socketio import join_room, leave_room

from . import socketio

from .views import broadcast_vote_count

@socketio.on('connect', namespace='/cyoa')
def test_connect():
def ws_connect():
pass

@socketio.on('disconnect', namespace='/cyoa')
def test_disconnect():
def ws_disconnect():
pass


@socketio.on('join', namespace='/cyoa')
def on_join(data):
vote = data['vote']
join_room(vote)
broadcast_vote_count(vote)

43 changes: 37 additions & 6 deletions cyoa/wizard_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from flask.ext.login import login_required

from . import app, db
from .models import Presentation
from .forms import PresentationForm
from .models import Presentation, Decision
from .forms import PresentationForm, DecisionForm


@app.route('/wizard/presentations/')
Expand All @@ -13,6 +13,7 @@ def wizard_list_presentations():
return render_template('wizard/presentations.html',
presentations=presentations)


@app.route('/wizard/presentation/', methods=['GET', 'POST'])
@login_required
def wizard_new_presentation():
Expand All @@ -39,27 +40,57 @@ def wizard_edit_presentation(id):
return render_template('wizard/presentation.html', form=form,
presentation=presentation)


@app.route('/wizard/presentation/<int:pres_id>/decisions/')
@login_required
def wizard_list_presentation_decisions(pres_id):
pass
presentation = Presentation.query.get_or_404(pres_id)
return render_template('wizard/decisions.html', presentation=presentation,
decisions=presentation.decisions.all())


@app.route('/wizard/presentation/<int:pres_id>/decision/',
methods=['GET', 'POST'])
@login_required
def wizard_new_decision(pres_id):
pass
form = DecisionForm()
if form.validate_on_submit():
decision = Decision()
form.populate_obj(decision)
decision.presentation = pres_id
db.session.add(decision)
db.session.commit()
return redirect(url_for('wizard_list_presentation_decisions',
pres_id=pres_id))
return render_template('wizard/decision.html', form=form, is_new=True,
presentation_id=pres_id)


@app.route('/wizard/presentation/<int:presentation_id>/decision/'
'<int:decision_id>/', methods=['GET', 'POST'])
@login_required
def wizard_edit_decision(presentation_id, decision_id):
pass
decision = Decision.query.get_or_404(decision_id)
form = DecisionForm(obj=decision)
if form.validate_on_submit():
form.populate_obj(decision)
decision.presentation = presentation_id
db.session.merge(decision)
db.session.commit()
db.session.refresh(decision)
return redirect(url_for('wizard_list_presentation_decisions',
pres_id=presentation_id))
return render_template('wizard/decision.html', form=form,
decision=decision, presentation_id=presentation_id)


@app.route('/wizard/presentation/<int:pres_id>/decision/'
'<int:decision_id>/delete/')
@login_required
def wizard_delete_decision(pres_id, decision_id):
pass
presentation = Presentation.query.get_or_404(pres_id)
decision = Decision.query.get_or_404(decision_id)
db.session.delete(decision)
db.session.commit()
return redirect(url_for('wizard_list_presentation_decisions',
pres_id=presentation.id))

0 comments on commit 8aa63ed

Please sign in to comment.