From d61080228c44fd513db65858aa1bddf9795f21a4 Mon Sep 17 00:00:00 2001
From: jpic <jamespic@gmail.com>
Date: Mon, 2 Nov 2015 23:28:06 +0100
Subject: [PATCH] Project modernization

- continuous integration with travis,
- continuous deployment with openshift,
- add update_score cron,
- sentry for exception tracking,
- continuous documentation build with rtfd,
- configuration made 12factor-ish,
- make use of new django-representatives version for database
  optimization.
---
 .gitignore                                |   4 +-
 .openshift/README.md                      |   5 +
 .openshift/action_hooks/README.md         |   3 +
 .openshift/action_hooks/build             |   1 +
 .openshift/action_hooks/deploy            |  50 +++
 .openshift/cron/README.cron               |  27 ++
 .openshift/cron/daily/.gitignore          |   0
 .openshift/cron/daily/update_database     |   8 +
 .openshift/cron/hourly/.gitignore         |   0
 .openshift/cron/minutely/.gitignore       |   0
 .openshift/cron/monthly/.gitignore        |   0
 .openshift/cron/weekly/README             |  16 +
 .openshift/cron/weekly/chrono.dat         |   1 +
 .openshift/cron/weekly/chronograph        |   3 +
 .openshift/cron/weekly/jobs.allow         |  12 +
 .openshift/cron/weekly/jobs.deny          |   7 +
 .openshift/markers/README.md              |   3 +
 .travis.yml                               |  33 +-
 bin/lib.sh                                |  20 ++
 bin/update_all                            |  16 +
 bin/update_dossiers                       |   6 +
 bin/update_representatives                |   6 +
 bin/update_scores                         |   7 +
 bin/update_votes                          |   6 +
 core/templates/admin/index.html           |  16 -
 docs/Makefile                             | 192 ++++++++++++
 docs/conf.py                              | 354 ++++++++++++++++++++++
 docs/deployment.rst                       |  93 ++++++
 docs/development.rst                      | 180 +++++++++++
 docs/index.rst                            |  23 ++
 legislature/models.py                     |  79 ++---
 memopol/__init__.py                       |   7 +-
 memopol/config.json.sample                |  14 -
 memopol/settings.py                       | 181 +++++++----
 memopol/settings.py.example               | 146 ---------
 requirements.txt                          |   4 +-
 setup.py                                  |  24 ++
 votes/management/commands/update_score.py |   9 +
 38 files changed, 1265 insertions(+), 291 deletions(-)
 create mode 100644 .openshift/README.md
 create mode 100644 .openshift/action_hooks/README.md
 create mode 100755 .openshift/action_hooks/build
 create mode 100755 .openshift/action_hooks/deploy
 create mode 100644 .openshift/cron/README.cron
 create mode 100644 .openshift/cron/daily/.gitignore
 create mode 100755 .openshift/cron/daily/update_database
 create mode 100644 .openshift/cron/hourly/.gitignore
 create mode 100644 .openshift/cron/minutely/.gitignore
 create mode 100644 .openshift/cron/monthly/.gitignore
 create mode 100644 .openshift/cron/weekly/README
 create mode 100644 .openshift/cron/weekly/chrono.dat
 create mode 100755 .openshift/cron/weekly/chronograph
 create mode 100644 .openshift/cron/weekly/jobs.allow
 create mode 100644 .openshift/cron/weekly/jobs.deny
 create mode 100644 .openshift/markers/README.md
 create mode 100644 bin/lib.sh
 create mode 100755 bin/update_all
 create mode 100755 bin/update_dossiers
 create mode 100755 bin/update_representatives
 create mode 100755 bin/update_scores
 create mode 100755 bin/update_votes
 delete mode 100644 core/templates/admin/index.html
 create mode 100644 docs/Makefile
 create mode 100644 docs/conf.py
 create mode 100644 docs/deployment.rst
 create mode 100644 docs/development.rst
 create mode 100644 docs/index.rst
 delete mode 100644 memopol/config.json.sample
 delete mode 100644 memopol/settings.py.example
 create mode 100644 setup.py
 create mode 100644 votes/management/commands/update_score.py

diff --git a/.gitignore b/.gitignore
index e6e78994..0e8670cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
 *.sqlite3
 celerybeat-*
 core/static/libs/*
-deploy
 
 # libs
 static/libs
@@ -21,3 +20,6 @@ __pycache__/
 # Installer logs
 pip-log.txt
 pip-delete-this-directory.txt
+.dpl
+db.sqlite
+log/
diff --git a/.openshift/README.md b/.openshift/README.md
new file mode 100644
index 00000000..785566bc
--- /dev/null
+++ b/.openshift/README.md
@@ -0,0 +1,5 @@
+The OpenShift `python` cartridge documentation can be found at:
+http://openshift.github.io/documentation/oo_cartridge_guide.html#python
+
+For information about .openshift directory, consult the documentation:
+http://openshift.github.io/documentation/oo_user_guide.html#the-openshift-directory
diff --git a/.openshift/action_hooks/README.md b/.openshift/action_hooks/README.md
new file mode 100644
index 00000000..54131958
--- /dev/null
+++ b/.openshift/action_hooks/README.md
@@ -0,0 +1,3 @@
+For information about action hooks, consult the documentation:
+
+http://openshift.github.io/documentation/oo_user_guide.html#action-hooks
diff --git a/.openshift/action_hooks/build b/.openshift/action_hooks/build
new file mode 100755
index 00000000..ac65222f
--- /dev/null
+++ b/.openshift/action_hooks/build
@@ -0,0 +1 @@
+export OPENSHIFT_PYTHON_WSGI_APPLICATION=memopol/wsgi.py
diff --git a/.openshift/action_hooks/deploy b/.openshift/action_hooks/deploy
new file mode 100755
index 00000000..35cb182f
--- /dev/null
+++ b/.openshift/action_hooks/deploy
@@ -0,0 +1,50 @@
+#!/bin/bash
+# This deploy hook gets executed after dependencies are resolved and the
+# build hook has been run but before the application has been started back
+# up again.  This script gets executed directly, so it could be python, php,
+# ruby, etc.
+set -xe
+
+source ${OPENSHIFT_HOMEDIR}app-root/runtime/dependencies/python/virtenv/bin/activate
+
+cat ${OPENSHIFT_REPO_DIR}requirements.txt
+
+pip install -U pip
+
+pip install -r ${OPENSHIFT_REPO_DIR}requirements.txt
+
+# We don't have sentry yet
+# python ${OPENSHIFT_REPO_DIR}manage.py raven test
+
+python ${OPENSHIFT_REPO_DIR}manage.py migrate --noinput
+
+pushd ${OPENSHIFT_DATA_DIR}
+if ! [ -d node ]; then
+    wget https://nodejs.org/dist/v4.2.2/node-v4.2.2-linux-x64.tar.gz
+    tar xvzf node-v4.2.2-linux-x64.tar.gz
+    ln -sfn node-v4.2.2-linux-x64 node
+fi
+popd
+
+pushd ${OPENSHIFT_REPO_DIR}
+if [ -f ${OPENSHIFT_DATA_DIR}sentry ]; then
+    pip install raven
+    ./manage.py raven test
+else
+    echo ${OPENSHIFT_DATA_DIR}sentry does not exist, not setting up raven.
+fi
+
+PATH="${OPENSHIFT_DATA_DIR}node/bin:$PATH"
+HOME=$OPENSHIFT_DATA_DIR
+CI=true
+npm install bower
+npm install
+node_modules/.bin/bower install
+node_modules/gulp/bin/gulp.js less
+mkdir -p wsgi
+./manage.py collectstatic --noinput
+popd
+
+mkdir -p ${OPENSHIFT_DATA_DIR}media
+mkdir -p ${OPENSHIFT_REPO_DIR}wsgi/static/media
+ln -sf ${OPENSHIFT_DATA_DIR}media ${OPENSHIFT_REPO_DIR}wsgi/static/media
diff --git a/.openshift/cron/README.cron b/.openshift/cron/README.cron
new file mode 100644
index 00000000..ac77f787
--- /dev/null
+++ b/.openshift/cron/README.cron
@@ -0,0 +1,27 @@
+Run scripts or jobs on a periodic basis
+=======================================
+Any scripts or jobs added to the minutely, hourly, daily, weekly or monthly
+directories will be run on a scheduled basis (frequency is as indicated by the
+name of the directory) using run-parts.
+
+run-parts ignores any files that are hidden or dotfiles (.*) or backup
+files (*~ or *,)  or named *.{rpmsave,rpmorig,rpmnew,swp,cfsaved}
+
+The presence of two specially named files jobs.deny and jobs.allow controls
+how run-parts executes your scripts/jobs.
+   jobs.deny  ===> Prevents specific scripts or jobs from being executed.
+   jobs.allow ===> Only execute the named scripts or jobs (all other/non-named
+                   scripts that exist in this directory are ignored).
+
+The principles of jobs.deny and jobs.allow are the same as those of cron.deny
+and cron.allow and are described in detail at: 
+   http://docs.redhat.com/docs/en-US/Red_Hat_Enterprise_Linux/6/html/Deployment_Guide/ch-Automating_System_Tasks.html#s2-autotasks-cron-access
+
+See: man crontab or above link for more details and see the the weekly/
+     directory for an example.
+
+PLEASE NOTE: The Cron cartridge must be installed in order to run the configured jobs.
+
+For more information about cron, consult the documentation:
+http://openshift.github.io/documentation/oo_cartridge_guide.html#cron
+http://openshift.github.io/documentation/oo_user_guide.html#cron
diff --git a/.openshift/cron/daily/.gitignore b/.openshift/cron/daily/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/.openshift/cron/daily/update_database b/.openshift/cron/daily/update_database
new file mode 100755
index 00000000..aeecfeec
--- /dev/null
+++ b/.openshift/cron/daily/update_database
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -x
+
+cmd=$1
+
+cd $OPENSHIFT_REPO_DIR
+export CLEAN=1
+nohup bin/update_all > $OPENSHIFT_LOG_DIR/update_all.log 2>&1 &
diff --git a/.openshift/cron/hourly/.gitignore b/.openshift/cron/hourly/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/.openshift/cron/minutely/.gitignore b/.openshift/cron/minutely/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/.openshift/cron/monthly/.gitignore b/.openshift/cron/monthly/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/.openshift/cron/weekly/README b/.openshift/cron/weekly/README
new file mode 100644
index 00000000..7c3e659f
--- /dev/null
+++ b/.openshift/cron/weekly/README
@@ -0,0 +1,16 @@
+Run scripts or jobs on a weekly basis
+=====================================
+Any scripts or jobs added to this directory will be run on a scheduled basis
+(weekly) using run-parts.
+
+run-parts ignores any files that are hidden or dotfiles (.*) or backup
+files (*~ or *,)  or named *.{rpmsave,rpmorig,rpmnew,swp,cfsaved} and handles
+the files named jobs.deny and jobs.allow specially.
+
+In this specific example, the chronograph script is the only script or job file
+executed on a weekly basis (due to white-listing it in jobs.allow). And the
+README and chrono.dat file are ignored either as a result of being black-listed
+in jobs.deny or because they are NOT white-listed in the jobs.allow file.
+
+For more details, please see ../README.cron file.
+
diff --git a/.openshift/cron/weekly/chrono.dat b/.openshift/cron/weekly/chrono.dat
new file mode 100644
index 00000000..fc4abb87
--- /dev/null
+++ b/.openshift/cron/weekly/chrono.dat
@@ -0,0 +1 @@
+Time And Relative D...n In Execution (Open)Shift!
diff --git a/.openshift/cron/weekly/chronograph b/.openshift/cron/weekly/chronograph
new file mode 100755
index 00000000..61de949f
--- /dev/null
+++ b/.openshift/cron/weekly/chronograph
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo "`date`: `cat $(dirname \"$0\")/chrono.dat`"
diff --git a/.openshift/cron/weekly/jobs.allow b/.openshift/cron/weekly/jobs.allow
new file mode 100644
index 00000000..8d32abc7
--- /dev/null
+++ b/.openshift/cron/weekly/jobs.allow
@@ -0,0 +1,12 @@
+#
+#  Script or job files listed in here (one entry per line) will be
+#  executed on a weekly-basis.
+#
+#  Example: The chronograph script will be executed weekly but the README 
+#           and chrono.dat files in this directory will be ignored.
+#
+#           The README file is actually ignored due to the entry in the
+#           jobs.deny which is checked before jobs.allow (this file).
+#
+chronograph
+
diff --git a/.openshift/cron/weekly/jobs.deny b/.openshift/cron/weekly/jobs.deny
new file mode 100644
index 00000000..73c94500
--- /dev/null
+++ b/.openshift/cron/weekly/jobs.deny
@@ -0,0 +1,7 @@
+#
+#  Any script or job files listed in here (one entry per line) will NOT be
+#  executed (read as ignored by run-parts).
+#
+
+README
+
diff --git a/.openshift/markers/README.md b/.openshift/markers/README.md
new file mode 100644
index 00000000..45814da3
--- /dev/null
+++ b/.openshift/markers/README.md
@@ -0,0 +1,3 @@
+For information about markers, consult the documentation:
+
+http://openshift.github.io/documentation/oo_user_guide.html#markers
diff --git a/.travis.yml b/.travis.yml
index 79b90f61..51fcdf38 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,16 +1,29 @@
 sudo: false
+env:
+  matrix:
+  - DEBUG=True
 language: python
 python:
-  - "2.7"
+- '2.7'
 install:
-  - pip install django
-  - pip install -U pip
-  - pip install -r requirements.txt
-  - cp memopol/config.json.sample memopol/config.json
+- pip install django
+- pip install -r requirements.txt
 before_script:
-  - npm install -g bower
-  - bower install
-  - npm install
+- npm install -g bower
+- bower install
+- npm install
 script:
-  - ./manage.py migrate
-  - node_modules/gulp/bin/gulp.js less
+- ./manage.py migrate
+- node_modules/gulp/bin/gulp.js less
+deploy:
+- provider: openshift
+  user: jamespic@gmail.com
+  password:
+    secure: W7hQDKAtmpOfwLjBuss6NEKqPSrRhsbgH8a8eV+/Oo6HZxMi1mbNFSi+6WRNSs3Cil0ZZV+awoqC61jIzV4oTwEYcy5bv9NWNSY1QO34DECMS5sY00wA0zKhkdsdTr9Pc3TLRp1cw6x2KNCF356FKZojFTRbjtfJ79rqBc5k5ww=
+  app: dev
+  domain: memopol
+  skip_cleanup: true
+  deployment_branch: pr
+  on:
+    repo: political-memory/political_memory
+    branch: pr
diff --git a/bin/lib.sh b/bin/lib.sh
new file mode 100644
index 00000000..a53f054a
--- /dev/null
+++ b/bin/lib.sh
@@ -0,0 +1,20 @@
+if [ -n "$OPENSHIFT_HOMEDIR" ]; then
+    source ${OPENSHIFT_HOMEDIR}app-root/runtime/dependencies/python/virtenv/bin/activate
+fi
+
+function pipe_download_to_command() {
+    if [ -n "$OPENSHIFT_DATA_DIR" ]; then
+        cd $OPENSHIFT_DATA_DIR
+    fi
+
+    [ -n "$CLEAN" ] && rm -rf $1
+    [ -f "$1" ] || wget http://parltrack.euwiki.org/dumps/$1 || exit 1
+
+    if [ -n "$OPENSHIFT_REPO_DIR" ]; then
+        cd $OPENSHIFT_REPO_DIR
+    fi
+
+    export DJANGO_SETTINGS_MODULE=memopol.settings
+    unxz -c ${OPENSHIFT_DATA_DIR}$1 | $2
+    [ -n "$CLEAN" ] && rm -rf $1
+}
diff --git a/bin/update_all b/bin/update_all
new file mode 100755
index 00000000..5899c9c1
--- /dev/null
+++ b/bin/update_all
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+bin/update_representatives
+
+# grace time for pg
+sleep 120
+
+bin/update_dossiers
+
+sleep 120
+
+bin/update_votes
+
+sleep 120
+
+bin/update_scores
diff --git a/bin/update_dossiers b/bin/update_dossiers
new file mode 100755
index 00000000..0e6f18b7
--- /dev/null
+++ b/bin/update_dossiers
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -ex
+
+source ${OPENSHIFT_REPO_DIR}bin/lib.sh
+
+pipe_download_to_command ep_dossiers.json.xz parltrack_import_dossiers
diff --git a/bin/update_representatives b/bin/update_representatives
new file mode 100755
index 00000000..ef420b9c
--- /dev/null
+++ b/bin/update_representatives
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -ex
+
+source ${OPENSHIFT_REPO_DIR}bin/lib.sh
+
+pipe_download_to_command ep_meps_current.json.xz parltrack_import_representatives
diff --git a/bin/update_scores b/bin/update_scores
new file mode 100755
index 00000000..a3234bc6
--- /dev/null
+++ b/bin/update_scores
@@ -0,0 +1,7 @@
+#!/bin/bash
+set -ex
+
+source ${OPENSHIFT_REPO_DIR}bin/lib.sh
+
+[ -n "$OPENSHIFT_REPO_DIR" ] && cd $OPENSHIFT_REPO_DIR
+./manage.py update_score
diff --git a/bin/update_votes b/bin/update_votes
new file mode 100755
index 00000000..f60a96eb
--- /dev/null
+++ b/bin/update_votes
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -ex
+
+source ${OPENSHIFT_REPO_DIR}bin/lib.sh
+
+pipe_download_to_command ep_votes.json.xz parltrack_import_votes
diff --git a/core/templates/admin/index.html b/core/templates/admin/index.html
deleted file mode 100644
index a35204e8..00000000
--- a/core/templates/admin/index.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{% extends "contrib/admin/templates/admin/index.html" %}
-
-{% block branding %}
-    <h1 id="site-name">Administration for Memopol</h1>
-{% endblock %}
-
-{% block content %}
-    
-    
-    {{ block.super }}
-{% endblock %}
-    
-
-    
-
-    
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 00000000..67106ec5
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,192 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  applehelp  to make an Apple Help Book"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  xml        to make Docutils-native XML files"
+	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+	@echo "  coverage   to run coverage check of the documentation (if enabled)"
+
+clean:
+	rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Memopol.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Memopol.qhc"
+
+applehelp:
+	$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
+	@echo
+	@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
+	@echo "N.B. You won't be able to view it unless you put it in" \
+	      "~/Library/Documentation/Help or install it in your application" \
+	      "bundle."
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/Memopol"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Memopol"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through platex and dvipdfmx..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+coverage:
+	$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
+	@echo "Testing of coverage in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/coverage/python.txt."
+
+xml:
+	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+	@echo
+	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+	@echo
+	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 00000000..39d89698
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,354 @@
+# -*- coding: utf-8 -*-
+#
+# Memopol documentation build configuration file, created by
+# sphinx-quickstart on Thu Nov 12 22:42:47 2015.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys
+import os
+import shlex
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Memopol'
+copyright = u'2015, Laurent Peuch, Mindiell, Arnaud Fabre'
+author = u'Laurent Peuch, Mindiell, Arnaud Fabre'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.0'
+# The full version, including alpha/beta/rc tags.
+release = '1.0.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+#keep_warnings = False
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'alabaster'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+#html_extra_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Language to be used for generating the HTML full-text search index.
+# Sphinx supports the following languages:
+#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
+#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
+#html_search_language = 'en'
+
+# A dictionary with options for the search language support, empty by default.
+# Now only 'ja' uses this config value
+#html_search_options = {'type': 'default'}
+
+# The name of a javascript file (relative to the configuration directory) that
+# implements a search results scorer. If empty, the default will be used.
+#html_search_scorer = 'scorer.js'
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Memopoldoc'
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+
+# Latex figure (float) alignment
+#'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+  (master_doc, 'Memopol.tex', u'Memopol Documentation',
+   u'Laurent Peuch, Mindiell, Arnaud Fabre', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    (master_doc, 'memopol', u'Memopol Documentation',
+     [author], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  (master_doc, 'Memopol', u'Memopol Documentation',
+   author, 'Memopol', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#texinfo_no_detailmenu = False
+
+
+# -- Options for Epub output ----------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+epub_author = author
+epub_publisher = author
+epub_copyright = copyright
+
+# The basename for the epub file. It defaults to the project name.
+#epub_basename = project
+
+# The HTML theme for the epub output. Since the default themes are not optimized
+# for small screen space, using the same theme for HTML and epub output is
+# usually not wise. This defaults to 'epub', a theme designed to save visual
+# space.
+#epub_theme = 'epub'
+
+# The language of the text. It defaults to the language option
+# or 'en' if the language is not set.
+#epub_language = ''
+
+# The scheme of the identifier. Typical schemes are ISBN or URL.
+#epub_scheme = ''
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#epub_identifier = ''
+
+# A unique identification for the text.
+#epub_uid = ''
+
+# A tuple containing the cover image and cover page html template filenames.
+#epub_cover = ()
+
+# A sequence of (type, uri, title) tuples for the guide element of content.opf.
+#epub_guide = ()
+
+# HTML files that should be inserted before the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_pre_files = []
+
+# HTML files shat should be inserted after the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_post_files = []
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+# The depth of the table of contents in toc.ncx.
+#epub_tocdepth = 3
+
+# Allow duplicate toc entries.
+#epub_tocdup = True
+
+# Choose between 'default' and 'includehidden'.
+#epub_tocscope = 'default'
+
+# Fix unsupported image types using the Pillow.
+#epub_fix_images = False
+
+# Scale large images.
+#epub_max_image_width = 0
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#epub_show_urls = 'inline'
+
+# If false, no index is generated.
+#epub_use_index = True
diff --git a/docs/deployment.rst b/docs/deployment.rst
new file mode 100644
index 00000000..d9a36fda
--- /dev/null
+++ b/docs/deployment.rst
@@ -0,0 +1,93 @@
+Deployment on OpenShift
+~~~~~~~~~~~~~~~~~~~~~~~
+
+OpenShift is an Open-Source Platform-as-a-Service software by Red Hat. It is
+also available in its hosted version known as "OpenShift Online" and the first
+three websites ("gears") are free.
+
+Clone the repository
+====================
+
+You should fork the project on github and use the fork's clone url. For the
+sake of the demo, we'll use the main repository URL::
+
+    $ git clone https://github.com/political-memory/political_memory.git
+    Cloning into 'political_memory'...
+    remote: Counting objects: 2516, done.
+    remote: Compressing objects: 100% (109/109), done.
+    remote: Total 2516 (delta 44), reused 0 (delta 0), pack-reused 2402
+    Receiving objects: 100% (2516/2516), 4.40 MiB | 79.00 KiB/s, done.
+    Resolving deltas: 100% (1103/1103), done.
+    Checking connectivity... done.
+
+    $ cd political_memory/
+
+Create your own branch, ie::
+
+    $ git checkout -b yourbranch origin/pr
+    Branch yourbranch set up to track remote branch pr from origin.
+    Switched to a new branch 'yourbranch'
+
+Create an app on OpenShift
+==========================
+
+To deploy the website, use a command like::
+
+    $ rhc app-create \
+        python-2.7 \
+        "http://cartreflect-claytondev.rhcloud.com/reflect?github=smarterclayton/openshift-redis-cart" \
+        cron-1.4 \
+        postgresql-9.2 \
+        -a yourappname \
+        -e OPENSHIFT_PYTHON_WSGI_APPLICATION=memopol/wsgi.py \
+        --from-code https://github.com/political-memory/political_memory.git
+
+This should create an app on openshift. Other commands would deploy it at once
+but in this tutorial we're going to see how to manage it partly manually for
+development.
+
+Add the git remote created by OpenShift
+=======================================
+
+Add the git remote openshift created for you, you can see it with
+``rhc app-show``, ie.::
+
+    $ rhc app-show -a yourappname
+    [snip]
+    Git URL:         ssh://569f5cf500045f6a1839a0a4@yourappname-yourdomain.rhcloud.com/~/git/yourappname.git/
+    Initial Git URL: https://github.com/political-memory/political_memory.git
+    SSH:             569f5cf500045f6a1839a0a4@yourappname-yourdomain.rhcloud.com
+    [snip]
+
+    $ git remote add oo_yourappname ssh://569f5cf500045f6a1839a0a4@yourappname-yourdomain.rhcloud.com/~/git/yourappname.git/
+
+Activate OpenShift's git post-recieve hook
+==========================================
+
+Activate OpenShift's post-receive hook on your branch::
+
+    $ rhc app-configure -a yourappname --deployment-branch yourbranch
+
+Deploy your branch
+==================
+
+OpenShift will deploy when it receives commits on the deployment branch, to
+deploy just do::
+
+    $ git push oo_yourappname yourbranch
+
+If something goes wrong and you want to retry, use the ``rhc app-deploy``
+command, ie::
+
+    $ rhc app-deploy yourbranch -a yourappname
+
+Data provisionning
+==================
+
+To fill up the representatives database table, either wait for the cron script
+to be executed, either do it manually::
+
+    $ rhc ssh -a yourappname 'cd app-root/repo/ && bin/update_all'
+
+OpenShift is fun, login with ssh and look around if you're curious, you'll be
+able to recreate your app without much effort if you break it anyway.
diff --git a/docs/development.rst b/docs/development.rst
new file mode 100644
index 00000000..ddca78a6
--- /dev/null
+++ b/docs/development.rst
@@ -0,0 +1,180 @@
+Local development tutorial
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. warn:: I reverse-engineered this from the source code I inherited, I might
+          not be doing the right way nor be able to defend all of technical
+          decisions.
+
+This tutorial drives through a local installation of the project for
+development on Linux. It requires git, a fairly recent version of nodejs (see
+:file:`.openshift/action_hooks/deploy` for a way to install it), python2 and
+virtualenv.
+
+Make a virtual environment
+==========================
+
+For the sake of the tutorial, we'll do this in the temporary directory, but you
+could do it anywhere::
+
+    $ cd /tmp
+
+Create a python virtual environment and activate it::
+
+    $ virtualenv memopol_env
+    Using real prefix '/usr'
+    New python executable in memopol_env/bin/python2
+    Also creating executable in memopol_env/bin/python
+    Installing setuptools, pip, wheel...done.
+
+    $ source memopol_env/bin/activate
+
+Clone the repository
+====================
+
+You should fork the project on github and use the fork's clone url. For the
+sake of the demo, we'll use the main repository URL::
+
+    $ git clone https://github.com/political-memory/political_memory.git
+    Cloning into 'political_memory'...
+    remote: Counting objects: 2516, done.
+    remote: Compressing objects: 100% (109/109), done.
+    remote: Total 2516 (delta 44), reused 0 (delta 0), pack-reused 2402
+    Receiving objects: 100% (2516/2516), 4.40 MiB | 79.00 KiB/s, done.
+    Resolving deltas: 100% (1103/1103), done.
+    Checking connectivity... done.
+
+    $ cd political_memory/
+
+Create your own branch, ie::
+
+    $ git checkout -b yourbranch origin/pr
+    Branch yourbranch set up to track remote branch pr from origin.
+    Switched to a new branch 'yourbranch'
+
+Install Python dependencies
+===========================
+
+Then, install the package for development::
+
+    $ pip install -e .
+    Obtaining file:///tmp/political_memory
+    Collecting django (from political-memory==0.0.1)
+      Using cached Django-1.9-py2.py3-none-any.whl
+
+    [output snipped for readability]
+
+    Installing collected packages: django, sqlparse, django-debug-toolbar, django-pdb, six, django-extensions, werkzeug, south, pygments, markdown, hamlpy, django-coffeescript, ijson, python-dateutil, pytz, political-memory
+      Running setup.py develop for political-memory
+    Successfully installed django-1.9 django-coffeescript-0.7.2 django-debug-toolbar-1.4 django-extensions-1.5.9 django-pdb-0.4.2 hamlpy-0.82.2 ijson-2.2 markdown-2.6.5 political-memory pygments-2.0.2 python-dateutil-2.4.2 pytz-2015.7 six-1.10.0 south-1.0.2 sqlparse-0.1.18 werkzeug-0.11.2
+
+And install the requirements::
+
+    $ pip install -r requirements.txt
+    Collecting django<1.9,>=1.8 (from -r requirements.txt (line 1))
+
+    [output snipped for readability]
+
+     Using cached Django-1.8.7-py2.py3-none-any.whl
+      Running setup.py develop for django-representatives
+      Running setup.py develop for django-representatives-votes
+    Successfully installed amqp-1.4.8 anyjson-0.3.3 billiard-3.3.0.22 celery-3.1.19 django-1.8.7 django-adminplus-0.5 django-appconf-1.0.1 django-autocomplete-light-2.2.10 django-bootstrap3-6.2.2 django-celery-3.1.17 django-compressor-1.6 django-constance-1.1.1 django-datetime-widget-0.9.3 django-denorm-0.2.0 django-filter-0.11.0 django-picklefield-0.3.2 django-representatives django-representatives-votes django-taggit-0.17.5 django-uuidfield-0.5.0 djangorestframework-3.3.1 kombu-3.0.30 py-dateutil-2.2 pyprind-2.9.3 requests-2.8.1 slugify-0.0.1
+
+Install NodeJS dependencies
+===========================
+
+We'll also need to install bower for the staticfiles::
+
+    $ npm install bower
+    memopol@3.0.0 /tmp/political_memory
+    └── bower@1.7.0  extraneous
+
+As well as all the requirements from :file:`package.json`::
+
+    $ npm install
+    memopol@3.0.0 /tmp/political_memory
+    ├── bower@1.7.0  extraneous
+    ├─┬ gulp@3.9.0
+
+    [output snipped for readability]
+
+    npm WARN In bower@1.7.0 replacing bundled version of configstore with configstore@0.3.2
+    npm WARN In bower@1.7.0 replacing bundled version of latest-version with latest-version@1.0.1
+    npm WARN In bower@1.7.0 replacing bundled version of update-notifier with update-notifier@0.3.2
+
+Don't worry about the warnings, for they are non-critical (as all warnings).
+Then, install the bower packages::
+
+    $ node_modules/.bin/bower install
+    bower bootstrap#~3.3.5          cached git://github.com/twbs/bootstrap.git#3.3.6
+    bootstrap#3.3.6 static/libs/bootstrap
+    └── jquery#2.1.4
+
+    [output snipped for readability]
+
+    jquery#2.1.4 static/libs/jquery
+
+Build the static files with gulp::
+
+    $ node_modules/gulp/bin/gulp.js less
+    [22:26:42] Using gulpfile /tmp/political_memory/gulpfile.js
+    [22:26:42] Starting 'less'...
+    [22:26:44] Finished 'less' after 1.54 s
+
+.. note:: The ``node_modules/gulp/bin/gulp.js watch`` command may be used to
+          have gulp watching for changes and rebuilding static files
+          automatically.
+
+Activate ``DEBUG``
+==================
+
+``DEBUG`` is disabled by default, the development server won't run properly by
+default thnen, to enable it export the ``DEBUG`` variable in the current
+shell::
+
+    $ export DEBUG=True
+
+Database migrations
+===================
+
+Run database migrations, it'll use a file-based sqlite database by default::
+
+    $ ./manage.py migrate
+    Operations to perform:
+      Synchronize unmigrated apps: django_filters, staticfiles, datetimewidget, autocomplete_light, messages, adminplus, compressor, humanize, django_extensions, constance, bootstrap3
+      Apply all migrations: legislature, votes, database, admin, positions, sessions, representatives, auth, contenttypes, representatives_votes, taggit
+    Synchronizing apps without migrations:
+      Creating tables...
+        Running deferred SQL...
+      Installing custom SQL...
+    Running migrations:
+      Rendering model states... DONE
+      Applying contenttypes.0001_initial... OK
+
+    [output snipped for readability]
+
+      Applying taggit.0002_auto_20150616_2121... OK
+
+Run the development server
+==========================
+
+Run the development server::
+
+    $ ./manage.py runserver
+
+    Performing system checks...
+
+    System check identified no issues (0 silenced).
+    December 09, 2015 - 21:26:47
+    Django version 1.8.7, using settings 'memopol.settings'
+    Starting development server at http://127.0.0.1:8000/
+    Quit the server with CONTROL-C.
+    [09/Dec/2015 21:26:48] "GET / HTTP/1.1" 200 13294
+
+The website is running on ``http://127.0.0.1:8000/``.
+
+Provision with data
+===================
+
+To provision it with data (takes a while)::
+
+    $ bin/update_all
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 00000000..d31240cb
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,23 @@
+.. Memopol documentation master file, created by
+   sphinx-quickstart on Thu Nov 12 22:42:47 2015.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to Memopol's documentation!
+===================================
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+   development
+   deployment
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/legislature/models.py b/legislature/models.py
index 88f589fb..7cf00505 100644
--- a/legislature/models.py
+++ b/legislature/models.py
@@ -27,6 +27,8 @@ from django.dispatch import receiver
 # from django.utils.functional import cached_property
 
 from representatives.models import Representative, Mandate, Country
+from representatives.management.commands import (
+        parltrack_import_representatives,)
 from votes.models import MemopolVote
 from core.utils import create_child_instance_from_parent
 
@@ -38,44 +40,14 @@ class MemopolRepresentative(Representative):
 
     def update_score(self):
         score = 0
-        for vote in self.votes.all():
+        for vote in MemopolVote.objects.filter(representative=self):
             score += vote.absolute_score
 
         self.score = score
         self.save()
 
-    def update_country(self):
-        # Create a country if it does'nt exist
-        # The representative's country is the one associated
-        # with the last 'country' mandate
-        try:
-            country_mandate = self.mandates.filter(
-                group__kind='country'
-            ).order_by('-begin_date')[0:1].get()
-
-            country, _ = Country.objects.get_or_create(
-                name=country_mandate.group.name,
-                code=country_mandate.group.abbreviation
-            )
-            self.country = country
-        except ObjectDoesNotExist:
-            self.country = None
-        self.save()
-
-    def update_main_mandate(self):
-        try:
-            self.main_mandate = self.mandates.get(
-                end_date__gte=datetime.now(),
-                group__kind='group'
-            )
-        except Mandate.DoesNotExist:
-            self.main_mandate = None
-        self.save()
-
     def update_all(self):
-        self.update_country()
         self.update_score()
-        self.update_main_mandate()
 
     def active_mandates(self):
         return self.mandates.filter(
@@ -94,7 +66,44 @@ class MemopolRepresentative(Representative):
         ).filter(representative=self)
 
 
-@receiver(post_save, sender=Representative)
-def create_memopolrepresentative_from_representative(instance, **kwargs):
-    memopol_representative = create_child_instance_from_parent(MemopolRepresentative, instance)
-    memopol_representative.save()
+def parltrack_representative_post_save(sender, representative, data, **kwargs):
+    update = False
+    try:
+        memopol_representative = MemopolRepresentative.objects.get(
+                representative_ptr=representative)
+    except MemopolRepresentative.DoesNotExist:
+        memopol_representative = MemopolRepresentative(
+                representative_ptr=representative)
+
+        # Please forgive the horror your are about to witness, but this is
+        # really necessary. Django wants to update the parent model when we
+        # save a child model.
+        memopol_representative.__dict__.update(representative.__dict__)
+
+    try:
+        country = sorted(data.get('Constituencies', []),
+                key=lambda c: c.get('end') if c is not None else 1
+                )[-1]['country']
+    except IndexError:
+        pass
+    else:
+        if sender.cache.get('countries', None) is None:
+            sender.cache['countries'] = {c.name: c.pk for c in
+                    Country.objects.all()}
+        country_id = sender.cache['countries'].get(country)
+
+        if memopol_representative.country_id != country_id:
+            memopol_representative.country_id = country_id
+            update = True
+
+    if sender.mep_cache['groups']:
+        main_mandate = sorted(sender.mep_cache['groups'],
+                key=lambda m: m.end_date)[-1]
+
+        if  memopol_representative.main_mandate_id != main_mandate.pk:
+            memopol_representative.main_mandate_id = main_mandate.pk
+            update = True
+
+    if update:
+        memopol_representative.save()
+parltrack_import_representatives.ParltrackImporter.representative_post_save.connect(parltrack_representative_post_save)
diff --git a/memopol/__init__.py b/memopol/__init__.py
index ce384691..cd2db24e 100644
--- a/memopol/__init__.py
+++ b/memopol/__init__.py
@@ -22,4 +22,9 @@ from __future__ import absolute_import
 
 # This will make sure the app is always imported when
 # Django starts so that shared_task will use this app.
-from .celery import app as celery_app
+try:
+    import celery
+except ImportError:
+    pass
+else:
+    from .celery import app as celery_app
diff --git a/memopol/config.json.sample b/memopol/config.json.sample
deleted file mode 100644
index c8aad54f..00000000
--- a/memopol/config.json.sample
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-    "debug": true,
-    "secret_key": "notsecret",
-    "compotista_server": "http://compotista.dev.laquadrature.net/",
-    "toutatis_server": "http://toutatis.dev.laquadrature.net/",
-    "redis_db": 0,
-    "organization": "local",
-    "local": true,
-    "database_name": "db.sqlite",
-    "database_user": "",
-    "database_password": "",
-    "database_host": "",
-    "database_port": ""
-}
diff --git a/memopol/settings.py b/memopol/settings.py
index ec5779f0..b20df0bd 100644
--- a/memopol/settings.py
+++ b/memopol/settings.py
@@ -9,51 +9,59 @@ https://docs.djangoproject.com/en/1.7/ref/settings/
 """
 
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-import json
 import os
-import django
 
-# Normally you should not import ANYTHING from Django directly
-# into your settings, but ImproperlyConfigured is an exception.
-from django.core.exceptions import ImproperlyConfigured
-from django.conf import settings
+from django.conf import global_settings
 
-# BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.json')
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
-with open(config_file) as f:
-    config = json.loads(f.read())
+DATA_DIR = os.environ.get('OPENSHIFT_DATA_DIR', 'data')
+if not os.path.exists(DATA_DIR):
+    os.makedirs(DATA_DIR)
 
-def get_param(setting, config=config, default=None):
-    """Get the secret variable or return explicit exception."""
-    try:
-        return config[setting]
-    except KeyError:
-        if default:
-            return default
-        error_msg = "Set the {0} config variable".format(setting)
-        raise ImproperlyConfigured(error_msg)
-
-BASE_DIR = os.path.dirname(os.path.dirname(__file__))
+LOG_DIR = os.environ.get('OPENSHIFT_LOG_DIR', 'log')
+if not os.path.exists(LOG_DIR):
+    os.makedirs(LOG_DIR)
 
+PUBLIC_DIR = os.path.join(os.environ.get('OPENSHIFT_REPO_DIR', ''), 'wsgi/static')
 
 # Quick-start development settings - unsuitable for production
 # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
 
 # SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = get_param('secret_key')
+SECRET_FILE = os.path.join(DATA_DIR, 'secret.txt')
+
+from django.utils.crypto import get_random_string
+if not os.path.exists(SECRET_FILE):
+    chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
+    with open(SECRET_FILE, 'w+') as f:
+        f.write(get_random_string(50, chars))
+
+with open(SECRET_FILE, 'r') as f:
+    SECRET_KEY = f.read()
 
-DEBUG = get_param('debug')
+
+DEBUG = os.environ.get('DEBUG', False)
 TEMPLATE_DEBUG = DEBUG
+LOG_LEVEL = os.environ.get('DJANGO_LOG_LEVEL', 'INFO')
+
+if SECRET_KEY == 'notsecret' and not DEBUG:
+    raise Exception('Please export DJANGO_SECRET_KEY or DEBUG')
 
-ALLOWED_HOSTS = []
+from socket import gethostname
+ALLOWED_HOSTS = [
+    gethostname(),
+]
 
-COMPOTISTA_SERVER = get_param('compotista_server')
-TOUTATIS_SERVER = get_param('toutatis_server')
-REDIS_DB = get_param('redis_db')
-ORGANIZATION_NAME = get_param('organization')
+DNS = os.environ.get('OPENSHIFT_APP_DNS', None),
+if DNS:
+    ALLOWED_HOSTS += DNS
 
-# Application definition
+if 'DJANGO_ALLOWED_HOSTS' in os.environ:
+    ALLOWED_HOSTS += os.environ.get('DJANGO_ALLOWED_HOSTS').split(',')
+
+REDIS_DB = os.environ.get('REDIS_DB', 1)
+ORGANIZATION_NAME = os.environ.get('ORGANIZATION', 'Memopol Demo')
 
 INSTALLED_APPS = (
     # 'django.contrib.admin',
@@ -79,20 +87,15 @@ INSTALLED_APPS = (
     'representatives_votes',
     'legislature',
     'votes',
-    'positions'
+    'positions',
+    'django_extensions',
 )
 
 if DEBUG:
-    INSTALLED_APPS += (
-        'django_extensions',
-    )
-
-if get_param('local'):
     INSTALLED_APPS += (
         'debug_toolbar',
     )
 
-
 MIDDLEWARE_CLASSES = (
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.common.CommonMiddleware',
@@ -113,25 +116,32 @@ WSGI_APPLICATION = 'memopol.wsgi.application'
 
 DATABASES = {
     'default': {
-        'NAME': get_param('database_name'),
-        'USER': get_param('database_user'),
-        'PASSWORD': get_param('database_password'),
-        'HOST': get_param('database_host'),
-        'PORT': get_param('database_port'),
+        'NAME': os.environ.get('DJANGO_DATABASE_DEFAULT_NAME', 'db.sqlite'),
+        'USER': os.environ.get('DJANGO_DATABASE_DEFAULT_USER', ''),
+        'PASSWORD': os.environ.get('DJANGO_DATABASE_DEFAULT_PASSWORD', ''),
+        'HOST': os.environ.get('DJANGO_DATABASE_DEFAULT_HOST', ''),
+        'PORT': os.environ.get('DJANGO_DATABASE_DEFAULT_PORT', ''),
+        'ENGINE': os.environ.get('DJANGO_DATABASE_DEFAULT_ENGINE',
+            'django.db.backends.sqlite3'),
+
     }
 }
 
-if get_param('local'):
-    DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'
-elif get_param('database_server') == 'mysql':
-    DATABASES['default']['ENGINE'] = 'django.db.backends.mysql'
-elif get_param('database_server') == 'postgresql':
+if 'OPENSHIFT_DATA_DIR' in os.environ:
+    DATABASES['default']['NAME'] = os.path.join(DATA_DIR, 'db.sqlite')
+
+if 'OPENSHIFT_POSTGRESQL_DB_HOST' in os.environ:
+    DATABASES['default']['NAME'] = os.environ['OPENSHIFT_APP_NAME']
+    DATABASES['default']['USER'] = os.environ['OPENSHIFT_POSTGRESQL_DB_USERNAME']
+    DATABASES['default']['PASSWORD'] = os.environ['OPENSHIFT_POSTGRESQL_DB_PASSWORD']
+    DATABASES['default']['HOST'] = os.environ['OPENSHIFT_POSTGRESQL_DB_HOST']
+    DATABASES['default']['PORT'] = os.environ['OPENSHIFT_POSTGRESQL_DB_PORT']
     DATABASES['default']['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
 
 # Internationalization
 # https://docs.djangoproject.com/en/1.7/topics/i18n/
 
-LANGUAGE_CODE = get_param('language_code', default='en-us')
+LANGUAGE_CODE = os.environ.get('DJANGO_LANGUAGE_CODE', 'en-us')
 
 TIME_ZONE = 'UTC'
 
@@ -145,14 +155,24 @@ USE_TZ = True
 # Static files (CSS, JavaScript, Images)
 # https://docs.djangoproject.com/en/1.7/howto/static-files/
 
+STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
+
 STATIC_URL = '/static/'
+COMPRESS_ROOT = 'static/'
+
+if DATA_DIR:
+    MEDIA_URL = '/static/media/'
+    MEDIA_ROOT = os.path.join(DATA_DIR, 'media')
+
+if PUBLIC_DIR:
+    STATIC_URL = '/static/collected/'
+    STATIC_ROOT = os.path.join(PUBLIC_DIR, 'collected')
 
 # HAML Templates
 # https://github.com/jessemiller/hamlpy
 
 TEMPLATE_DIRS = (
     'core/templates',
-    os.path.dirname(django.__file__)
 )
 
 TEMPLATE_LOADERS = (
@@ -162,10 +182,6 @@ TEMPLATE_LOADERS = (
     'hamlpy.template.loaders.HamlPyAppDirectoriesLoader',
 )
 
-TEMPLATE_CONTEXT_PROCESSORS = settings.TEMPLATE_CONTEXT_PROCESSORS + (
-    'constance.context_processors.config',
-)
-
 """
 TEMPLATE_LOADERS = (
     ('django.template.loaders.cached.Loader', (
@@ -175,10 +191,12 @@ TEMPLATE_LOADERS = (
 )
 """
 
+TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
+    'constance.context_processors.config',
+)
+
 # Static files finders
 
-STATIC_URL = '/static/'
-COMPRESS_ROOT = 'static/'
 
 STATICFILES_FINDERS = (
     'django.contrib.staticfiles.finders.FileSystemFinder',
@@ -212,13 +230,8 @@ LOGGING = {
         },
     },
     'handlers': {
-        'file': {
-            'level': 'DEBUG',
-            'class': 'logging.FileHandler',
-            'filename': '/tmp/compotista-debug.log',
-        },
         'console': {
-            'level': 'DEBUG',
+            'level': LOG_LEVEL,
             'class': 'logging.StreamHandler',
             'formatter': 'simple'
         },
@@ -226,25 +239,61 @@ LOGGING = {
     'loggers': {
         'memopol': {
             'handlers': ['console'],
-            'level': 'DEBUG'
+            'level': LOG_LEVEL,
         },
         'representatives': {
             'handlers': ['console'],
-            'level': 'DEBUG'
+            'level': LOG_LEVEL,
         },
         'representatives_votes': {
             'handlers': ['console'],
-            'level': 'DEBUG'
+            'level': LOG_LEVEL,
         }
     },
 }
 
+if DEBUG:
+    LOGGING['handlers']['debug'] = {
+        'level': 'DEBUG',
+        'class': 'logging.FileHandler',
+        'filename': os.path.join(LOG_DIR, 'debug.log'),
+    }
+    for logger in LOGGING['loggers'].values():
+        logger['handlers'].append('debug')
+
+RAVEN_FILE = os.path.join(DATA_DIR, 'sentry')
+if os.path.exists(RAVEN_FILE):
+    INSTALLED_APPS += ('raven.contrib.django.raven_compat',)
+
+    LOGGING['handlers']['sentry'] = {
+        'level': 'INFO',
+        'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
+    }
+    LOGGING['loggers']['sentry.errors'] = LOGGING['loggers']['raven'] = {
+        'level': 'INFO',
+        'handlers': ['console'],
+        'propagate': False,
+    }
+
+    with open(RAVEN_FILE, 'r') as f:
+        RAVEN_CONFIG = {
+            'dsn': f.read().strip()
+        }
+
 CONSTANCE_BACKEND = 'constance.backends.redisd.RedisBackend'
 CONSTANCE_REDIS_CONNECTION = {
-    'host': 'localhost',
-    'port': 6379,
-    'db': 0,
+
+    'host': os.environ.get('OPENSHIFT_REDIS_HOST', 'localhost'),
+    'port': os.environ.get('OPENSHIFT_REDIS_PORT', 6379),
+    'password': os.environ.get('REDIS_PASSWORD', ''),
+    'db': 1,
 }
+CONSTANCE_REDIS_CONNECTION = 'redis://:%s@%s:%s/%s' % (
+    os.environ.get('REDIS_PASSWORD', ''),
+    os.environ.get('OPENSHIFT_REDIS_HOST', 'localhost'),
+    os.environ.get('OPENSHIFT_REDIS_PORT', 6379),
+    0,
+)
 
 CONSTANCE_CONFIG = {
     'USE_COUNTRY': (True, 'Use country for representative'),
diff --git a/memopol/settings.py.example b/memopol/settings.py.example
deleted file mode 100644
index 46558a7a..00000000
--- a/memopol/settings.py.example
+++ /dev/null
@@ -1,146 +0,0 @@
-"""
-Django settings for memopol project.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/1.7/topics/settings/
-
-For the full list of settings and their values, see
-https://docs.djangoproject.com/en/1.7/ref/settings/
-"""
-
-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-import os
-BASE_DIR = os.path.dirname(os.path.dirname(__file__))
-
-
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
-
-# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = 'zbzzs+8wkdnbo-l9x6+r38)$%h)!&22c^di$^6ap_+9oza#irr'
-
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
-
-TEMPLATE_DEBUG = True
-
-ALLOWED_HOSTS = []
-
-
-# Application definition
-
-INSTALLED_APPS = (
-    'django.contrib.admin',
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.messages',
-    'django.contrib.staticfiles',
-    # ---
-    'compressor',
-    'chronograph',
-    # ---
-    'core',
-    'representatives',
-    'memopol_representatives',
-)
-
-# App settings
-
-REPRESENTATIVES_COMPOTISTA_SERVER = 'http://pi2.octopuce.fr:8081'
-
-MIDDLEWARE_CLASSES = (
-    'django.contrib.sessions.middleware.SessionMiddleware',
-    'django.middleware.common.CommonMiddleware',
-    'django.middleware.csrf.CsrfViewMiddleware',
-    'django.contrib.auth.middleware.AuthenticationMiddleware',
-    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
-    'django.contrib.messages.middleware.MessageMiddleware',
-    'django.middleware.clickjacking.XFrameOptionsMiddleware',
-)
-
-ROOT_URLCONF = 'memopol.urls'
-
-WSGI_APPLICATION = 'memopol.wsgi.application'
-
-
-# Database
-# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
-
-DATABASES = {
-    'sqlite': {
-        'ENGINE': 'django.db.backends.sqlite3',
-        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
-    },
-    'default': {
-        'ENGINE': 'django.db.backends.postgresql_psycopg2',
-        'NAME': 'memopol_dev',
-        'USER': 'dj',
-        'PASSWORD': "test",
-        'HOST': 'localhost',
-        'PORT': '',
-    }
-}
-
-# Internationalization
-# https://docs.djangoproject.com/en/1.7/topics/i18n/
-
-LANGUAGE_CODE = 'en-us'
-
-TIME_ZONE = 'UTC'
-
-USE_I18N = True
-
-USE_L10N = True
-
-USE_TZ = True
-
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/1.7/howto/static-files/
-
-STATIC_URL = '/static/'
-
-# HAML Templates
-# https://github.com/jessemiller/hamlpy
-
-TEMPLATE_DIRS = (
-    'core/templates',
-)
-
-TEMPLATE_LOADERS = (
-    'django.template.loaders.filesystem.Loader',
-    'django.template.loaders.app_directories.Loader',
-    'hamlpy.template.loaders.HamlPyFilesystemLoader',
-    'hamlpy.template.loaders.HamlPyAppDirectoriesLoader',
-)
-
-"""
-TEMPLATE_LOADERS = (
-    ('django.template.loaders.cached.Loader', (
-        'hamlpy.template.loaders.HamlPyFilesystemLoader',
-        'hamlpy.template.loaders.HamlPyAppDirectoriesLoader',
-    )),
-)
-"""
-
-# Static files finders
-
-STATIC_URL = '/static/'
-COMPRESS_ROOT = 'static/'
-
-STATICFILES_FINDERS = (
-    'django.contrib.staticfiles.finders.FileSystemFinder',
-    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
-    # other finders..
-    'compressor.finders.CompressorFinder',
-)
-
-COMPRESS_PRECOMPILERS = (
-    # ('text/coffeescript', 'coffee --compile --stdio'),
-    ('text/less', 'lessc {infile} {outfile}'),
-    ('text/x-sass', 'sass {infile} {outfile}'),
-    ('text/x-scss', 'sass --scss {infile} {outfile}'),
-    # ('text/stylus', 'stylus < {infile} > {outfile}'),
-    # ('text/foobar', 'path.to.MyPrecompilerFilter'),
-)
diff --git a/requirements.txt b/requirements.txt
index 61f9dbea..d9c25d35 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -16,8 +16,8 @@ django-bootstrap3
 django-filter
 django-taggit
 django-datetime-widget
--e git+https://github.com/political-memory/django-representatives.git#egg=django-representatives
--e git+https://github.com/political-memory/django-representatives-votes.git#egg=rdjango-epresentatives-votes
+-e git+https://github.com/political-memory/django-representatives.git@parltrack#egg=django-representatives
+-e git+https://github.com/political-memory/django-representatives-votes.git@parltrack#egg=django-representatives-votes
 django-extensions
 django-debug-toolbar
 redis
diff --git a/setup.py b/setup.py
new file mode 100644
index 00000000..012aea04
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,24 @@
+from setuptools import setup, find_packages
+
+setup(name='political-memory',
+    version='0.0.1',
+    description='OpenShift App',
+    packages=['political_memory'],
+    package_dir={'political_memory': '.'},
+    author='Laurent Peuch',
+    author_email='cortex@worlddomination.be',
+    url='http://github.com/political-memory/political_memory/',
+    install_requires=[
+        'django',
+        'django-debug-toolbar',
+        'django_pdb',
+        'django_extensions',
+        'werkzeug',
+        'south',
+        'hamlpy',
+        'django-coffeescript',
+        'ijson',
+        'python-dateutil',
+        'pytz',
+    ],
+)
diff --git a/votes/management/commands/update_score.py b/votes/management/commands/update_score.py
new file mode 100644
index 00000000..6cbd6844
--- /dev/null
+++ b/votes/management/commands/update_score.py
@@ -0,0 +1,9 @@
+from django.core.management.base import BaseCommand
+
+from legislature.models import MemopolRepresentative
+
+
+class Command(BaseCommand):
+    def handle(self, *args, **options):
+        for rep in MemopolRepresentative.objects.all():
+            rep.update_score()
-- 
GitLab