blob: 005ffe0c6830db46f49b38b7af988e54fa122dea [file] [log] [blame]
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import csv
import filters
import jinja2
import logging
import os
import random
import urllib
import webapp2
from google.appengine.api import users
from google.appengine.ext import db
from models import Experiment, Property, Treatment
jinja_environment = jinja2.Environment(autoescape=True,
loader=jinja2.FileSystemLoader(os.path.join(
os.path.dirname(__file__),
'templates')))
jinja_environment.filters['average'] = filters.average
jinja_environment.filters['row_class'] = filters.row_class
jinja_environment.filters['sign_test'] = filters.sign_test
jinja_environment.filters['variance'] = filters.variance
jinja_environment.filters['encode_experiment'] = filters.encode_experiment
def load_experiment(experiment_id):
experiment = Experiment.gql('WHERE __key__ = KEY(:1)', experiment_id).get()
treatments = Treatment.gql('WHERE ANCESTOR IS KEY(:1)', experiment_id)
properties = Property.gql('WHERE ANCESTOR IS KEY(:1)', experiment_id)
return (experiment, treatments, properties)
class MainPage(webapp2.RequestHandler):
""" Displays the Create form and a list of the user's experiments """
def get(self):
user = users.get_current_user()
exps = Experiment.gql('WHERE owner = :1', user)
template = jinja_environment.get_template('index.html')
self.response.out.write(template.render(user=user, experiments=exps))
class Error(webapp2.RequestHandler):
""" Displays an error message """
def get(self):
msg = self.request.get('msg')
template = jinja_environment.get_template('error.html')
self.response.out.write(template.render(msg=msg))
class Logout(webapp2.RequestHandler):
""" Logs the user out and redirects to the main page """
def get(self):
user = users.get_current_user()
self.redirect(users.create_logout_url('/'))
class Participate(webapp2.RequestHandler):
""" Have a participant actually run the experiment """
def get(self):
exp, treatments, props = load_experiment(self.request.get('exp_key'))
# Shuffle the treatments using the user as a seed to provide
# a consistent but random ordering on a per user basis.
shuffler = random.Random(users.get_current_user())
shuffled = [t for t in treatments]
shuffler.shuffle(shuffled)
template = jinja_environment.get_template('participate.html')
self.response.out.write(template.render(user=users.get_current_user(),
experiment=exp,
treatments=shuffled,
properties=props))
class Submit(webapp2.RequestHandler):
""" Accept the rankings a user submitted from their experiment """
def get(self):
exp, treatments, _ = load_experiment(self.request.get('exp_key'))
ranks = self.request.get('ranking').split('>')
ranks = [r.split('=') for r in ranks]
if treatments.count() != sum([len(r) for r in ranks]):
return self.fail('You must rank ALL treatments to submit.')
participant = str(users.get_current_user())
feedback = self.request.get('feedback')
if str(users.get_current_user()) not in exp.participants:
exp.participants.append(participant)
exp.feedback.append(db.Text(''))
participant_number = exp.participants.index(participant)
while participant_number >= len(exp.feedback):
exp.feedback.append(db.Text(''))
exp.feedback[participant_number] = db.Text(feedback)
score = 0
updated_treatments = []
for same_rank in ranks:
for treatment in same_rank:
results = Treatment.gql('WHERE __key__ = KEY(:1) '
'AND ANCESTOR IS KEY(:2)',
treatment, str(exp.key()))
if results.count() != 1:
return self.fail('Unable to find treatment: ' + treatment)
treatment_data = results.get()
if participant_number >= len(treatment_data.scores):
treatment_data.scores.append(score)
else:
treatment_data.scores[participant_number] = score
updated_treatments.append(treatment_data)
score += len(same_rank)
self.save(exp, updated_treatments)
template = jinja_environment.get_template('submit.html')
self.response.out.write(template.render(experiment=exp))
@db.transactional
def save(self, experiment, treatments):
""" Atomically write all the changes """
experiment.put()
for treatment in treatments:
treatment.put()
def fail(self, message):
arguments = urllib.urlencode({'msg': message})
self.redirect('error?' + arguments)
class ViewExperiment(webapp2.RequestHandler):
""" Shows the details for an experiment if the user is it's owner """
def get(self):
exp, treatments, props = load_experiment(self.request.get('exp_key'))
user = users.get_current_user()
has_access = (user == exp.owner or users.is_current_user_admin())
experiment_url = self.request.url.replace('view', 'participate')
template = jinja_environment.get_template('view.html')
self.response.out.write(template.render(has_access=has_access,
experiment_url=experiment_url,
experiment=exp,
treatments=treatments,
properties=props))
class DownloadCSV(webapp2.RequestHandler):
""" Download a CSV file of the results for a given experiment """
def get(self):
exp, treatments, props = load_experiment(self.request.get('exp_key'))
user = users.get_current_user()
has_access = (user == exp.owner or users.is_current_user_admin())
if not has_access:
msg = 'Sorry, you do not have permission to access these results.'
arguments = urllib.urlencode({'msg': msg})
self.redirect('error?' + arguments)
self.response.headers['Content-Type'] = 'application/csv'
self.response.headers['Content-Disposition'] = 'filename="results.csv"'
writer = csv.writer(self.response.out)
writer.writerow(['Name', exp.name])
writer.writerow(['Owner', exp.owner])
writer.writerow([])
writer.writerow(['Treatments:'])
for treatment in treatments:
properties = ['%s = %s' % (prop.name, str(prop.value))
for prop in props
if prop.parent_key() == treatment.key()]
writer.writerow([treatment.name] + properties)
writer.writerow([])
writer.writerow(['Raw Results:'])
writer.writerow([''] + [treatment.name for treatment in treatments])
for i, participant in enumerate(exp.participants):
results = [treatment.scores[i] for treatment in treatments]
writer.writerow([participant] + results + [exp.feedback[i]])
writer.writerow([])
writer.writerow(['Sign-test P-values:'])
writer.writerow([''] + [treatment.name for treatment in treatments])
for treatment1 in treatments:
values = [filters.sign_test(treatment1.scores, treatment2.scores)
for treatment2 in treatments]
writer.writerow([treatment1.name] + values)
app = webapp2.WSGIApplication([
('/', MainPage),
('/csv', DownloadCSV),
('/error', Error),
('/logout', Logout),
('/participate', Participate),
('/submit', Submit),
('/view', ViewExperiment),
('/.+', Error),
], debug=True)