| # 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) |