View on GitHub

Quizzer

A lightweight writing lab quiz engine for node.js

Download this project as a .zip file Download this project as a tar.gz file

Overview

Quizzer is an online support tool for academic writing instruction. It can be installed with a single command, requires no student IDs or passwords, and works well with classes made up of students at varying stages of language acquisition, and from diverse language backgrounds.

The basic concept is to use student writing as the basis for a flood of pattern-recognition exercises, cast as online quizzes delivered by email. By raising the pace of iteration, the aim is to help students internalize a sense of grammatical anomalies and stylistic infelicities.

While Quizzer can be used to generate multiple-choice quizzes for a variety of purposes, the workflow it was built for runs like this:

  1. Students submit a 400-word essay once each week on an arbitrary topic.
  2. The instructor selects representative errors of style and grammar from the submissions, and composes a multiple-choice question consisting of the student's own sentence, two alternatives that also contain errors, and a corrected version.
  3. After constructing one question from each submitted essay, students are sent personalized links to the resulting quiz.
  4. Students submit their responses, which are recorded on the Quizzer server. Students receive feedback immediately feedback on their incorrect responses. Neither the essays nor the quiz results are assessed.
  5. Class commenters (TAs, instructors, and other experienced writers) post short explanations of why the wrong answers were wrong. Where appropriate, commenters from the students' own language domain can be brought in to provide supplementary native-language guidance.
  6. Class commenters of the target language domain can set persistent "rules" on-the-fly to cover issues that arise frequently. These rules can then to set as comments on specific wrong answers, and they can be translated by native-language commenters, for reference by students of the same language domain.
  7. When students revisit their quiz links, they will find their errors attached with explanatory rule, comments, and a list of classmates who answered the question correctly.
  8. Students are given an assessed, paper-based, multiple-choice supplementary mid-term and final exam, consisting entirely of questions from the quizzes.

Quiz distribution, commenting, exam composition, and marking are all managed by Quizzer. Paper tests are randomized as a hedge against cheating, and marked with a barcode reader for quick assessment.

The initial inspiration for Quizzer was a small code sample posted by Chetan Jain. The code has been refactored and extended considerably for this project, but I gratefully acknowledge the starting point for this frolic. Hats off also to the developers of node.js, and LaTeX and, well, everything else. Quizzer was built on short notice to fill a critical need, and it has been a real pleasure to see how quickly it could be brought together, and how smoothly it could be extended.

Requirements

Quizzer is a node.js module. To get the website running, the minimum requirements are:

  • npm >=1.3.x
  • node >=0.10.x

In addition, the following external utilities are required for the typesetting of exams:

  • pandoc >=1.11.x (preferably compiled with texmath support)
  • either pdflatex, or platex and dvipdfmx (the latter pair is needed only if Japanese text must be handled)

LaTex (pLaTeX) documents created by Quizzer use the following packages:

  • makebarcode
  • marginnote
  • graphicx
  • tikz
  • ctable
  • float
  • hyphenat
  • amsmath

Quizzer must have access to a mail transfer agent (MTA). This can either be a sendmail instance running on the same server, or a mail API to a service such as GMail.

With the above requirements in place, Quizzer can be run on a workstation for initial trials, accessed via a port on localhost (aka 127.0.0.1). For production use, Quizzer should be placed behind a webserver, such as lighttpd or apache. Instructions for setting up the former are given below.

Basic Installation

Install quizzer from the npm repository:

npm install quizzer

Run the server by saving the following code to a file (say, quizServer.js):

var qz = require('quizzer');
qz.run();

Run the script from command line like this:

node ./quizServer.js

The script will whinge on first run, asking for some essential details:

usage: quizServer.js [-h] [-v] [-H PROXY_HOSTNAME] [-Q QUIZZER_PATH]
                     [-p REAL_PORT] [-e EMAIL_ACCOUNT] [-s SMTP_HOST]
                     [-l LOCALE] [-P] [-E]


Quizzer, a quiz server

Optional arguments:
  -h, --help            Show this help message and exit.
  -v, --version         Show program's version number and exit.
  -H PROXY_HOSTNAME, --proxy-hostname PROXY_HOSTNAME
                        Host name for external access
  -Q QUIZZER_PATH, --quizzer-path QUIZZER_PATH
                        Server path to quizzer (default: "/quizzer/")
  -p REAL_PORT, --real-port REAL_PORT
                        Port on which to listen for local connections
                        (defaults to 3498)
  -e EMAIL_ACCOUNT, --email-account EMAIL_ACCOUNT
                        Full username of email account (e.g. useme@gmail.com)
  -s SMTP_HOST, --smtp-host SMTP_HOST
                        SMTP host name (e.g. smtp.gmail.com)
  -l LOCALE, --locale LOCALE
                        Language locale for admin interface ("en" or "ja")
  -P, --use-platex      Use platex engine + dvipdfmx for PDF generation
  -E, --use-euc-jp      Convert input text from UTF8 to legacy EUC-JP 
                        encoding before LaTeX processing
  ERROR: must set option proxy_hostname
  ERROR: must set option email_account

To get Quizzer running, set proxy_hostname to localhost (or 127.0.0.1), and set email_account to your mail address (me@mail.com in the example below):

node ./quizServer -H localhost -e me@mail.com

Quizzer will come up with a message like the following:

Wrote config parameters to quizzer-3498.cfg
Quizzer can now be run with the single option: -p 3498
Message: no mypwd.txt file found, will use local Sendmail transport
Using local Sendmail transport
Admin URL: http://localhost:3498/?admin=179359xq
Adding admin role
Loaded class membership keys
Woke up the mail schedulers
Done. Ready to shake, rattle and roll!

The website can now be accessed at the URL reported in the fifth line. (Note that the admin key is automatically generated, and will differ from that shown in the example above.)

The database and configuration files are created in the directory from which the script is run, named after the port number. The server can be shut down with CTRL-c (SIGINT), and as the startup message says, it can be restarted with the single option -p <REAL_PORT>

A full explanation of the remaining options will be added to this README as time permits.

Running Quizzer behind a Proxy

When PROXY_HOSTNAME is set to a fully qualified domain name (e.g. myschool.edu), it will assume that it is being run behind a reverse proxy, and adjust URLs accordingly. Quizzer itself has only the thinnest concept of security, and should be run behind a proxy in production (and preferably over SSL). Access to the administrator display depends on a key set in the URL of a GET request. Rewrite rules on the front-end web server should be used to assure that attempts to set the key directly are rerouted through a password-protected URL.

If lighttpd is used as the front-end server, and Quizzer is run from a directory quizzer to which the server has access, configuration settings like the following should do the trick:

url.rewrite = (
  "^(?!/quizzer)(.*)\?admin=[^&]+(?:&(.*))*" => "/quizzer/admin.html$1?$2",
  "^(?!/quizzer)(.*)&admin=[^&]+(?:&(.*))*" => "/quizzer/admin.html$1&$2",
  "^/quizzer/admin.html$" => "/quizzer/admin.html?admin=fyvg19vx",
  "^/quizzer/admin.html\?(.*)$" => "/quizzer/admin.html?admin=fyvg19vx&$1"
)

$HTTP["host"] == "faculty.of.things.edu" {
  proxy.server = ( "/quizzer" => ( ( "host" => "127.0.0.1", "port" => 3498 ) ) )
}

auth.backend = "htdigest"
auth.backend.htdigest.userfile = "/etc/lighttpd/lighttpd.user"

auth.require = ( "/quizzer/admin.html" =>
  (
    "method" => "basic",
    "realm" => "Quiz Admin",
    "require" => "user=quizmaster"
  )
)