From: Radek Czajka Date: Tue, 20 Sep 2011 15:12:24 +0000 (+0200) Subject: initial commit X-Git-Url: https://git.mdrn.pl/koed-quiz.git/commitdiff_plain/2c2b8f122dd789a089ac5054e112f59874c01a70?ds=inline initial commit --- 2c2b8f122dd789a089ac5054e112f59874c01a70 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1146e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +localsettings.py +*.db +*.db-journal +*~ + +# Python garbage +*.pyc +.coverage +pip-log.txt +nosetests.xml + +# Mac OS X garbage +.DS_Store + +# Windows garbage +thumbs.db + +# Eclipse +.project +.settings +.pydevproject +.tmp_* + diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..62a06e2 --- /dev/null +++ b/README.rst @@ -0,0 +1,5 @@ +========= +KOED Quiz +========= + +This project will be used for various openness related tests and quizzes. diff --git a/apps/quiz/__init__.py b/apps/quiz/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/quiz/admin.py b/apps/quiz/admin.py new file mode 100644 index 0000000..849f918 --- /dev/null +++ b/apps/quiz/admin.py @@ -0,0 +1,18 @@ +from django.contrib import admin + +from quiz.models import Question, Result, Answer + + +class AnswerInline(admin.TabularInline): + model = Answer + fk_name = 'question' + extra = 2 + + +class QuestionAdmin(admin.ModelAdmin): + inlines = [AnswerInline] + list_display = ['title', 'quiz'] + + +admin.site.register(Question, QuestionAdmin) +admin.site.register(Result) diff --git a/apps/quiz/forms.py b/apps/quiz/forms.py new file mode 100644 index 0000000..7831806 --- /dev/null +++ b/apps/quiz/forms.py @@ -0,0 +1,14 @@ +from django import forms +from django.forms.widgets import RadioSelect +from django.utils.translation import ugettext_lazy as _ +from quiz.models import Answer + +class QuestionForm(forms.Form): + answer = forms.ModelChoiceField(widget=RadioSelect, + queryset=Answer.objects.all(), empty_label=None, + error_messages={'required': _('Please select an option')}) + + def __init__(self, instance, *args, **kwargs): + r = super(QuestionForm, self).__init__(*args, **kwargs) + self.fields['answer'].queryset = instance.answer_set.all() + return r diff --git a/apps/quiz/locale/pl/LC_MESSAGES/django.mo b/apps/quiz/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 0000000..7b3fdb2 Binary files /dev/null and b/apps/quiz/locale/pl/LC_MESSAGES/django.mo differ diff --git a/apps/quiz/locale/pl/LC_MESSAGES/django.po b/apps/quiz/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 0000000..62433dd --- /dev/null +++ b/apps/quiz/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,71 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Quiz 0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2011-09-20 17:10+0200\n" +"PO-Revision-Date: 2011-09-20 17:10+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: LANGUAGE \n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " +"|| n%100>=20) ? 1 : 2);\n" + +#: forms.py:9 +msgid "Please select an option" +msgstr "Proszę wybrać jedną z odpowiedzi" + +#: models.py:11 +msgid "quiz" +msgstr "quiz" + +#: models.py:12 +msgid "quizzes" +msgstr "quizy" + +#: models.py:40 +msgid "result" +msgstr "rezultat" + +#: models.py:41 +msgid "results" +msgstr "rezultaty" + +#: models.py:60 +msgid "question" +msgstr "pytanie" + +#: models.py:61 +msgid "questions" +msgstr "pytania" + +#: models.py:90 +msgid "answer" +msgstr "odpowiedź" + +#: models.py:91 +msgid "answers" +msgstr "odpowiedzi" + +#: templates/quiz/question_detail.html:26 templates/quiz/result_detail.html:20 +msgid "Back to last question" +msgstr "Wróć do poprzedniego pytania" + +#: templates/quiz/question_detail.html:31 templates/quiz/result_detail.html:24 +msgid "Back to my test" +msgstr "Wróć do testu" + +#: templates/quiz/question_detail.html:33 templates/quiz/result_detail.html:26 +msgid "Start from the beginning" +msgstr "Zacznij od początku" + +#: templates/quiz/result_detail.html:32 +msgid "Start again" +msgstr "Jeszcze raz od początku" diff --git a/apps/quiz/migrations/0001_initial.py b/apps/quiz/migrations/0001_initial.py new file mode 100644 index 0000000..df82327 --- /dev/null +++ b/apps/quiz/migrations/0001_initial.py @@ -0,0 +1,108 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Result' + db.create_table('quiz_result', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('quiz', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'])), + ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('text', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('quiz', ['Result']) + + # Adding model 'Question' + db.create_table('quiz_question', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('quiz', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'])), + ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50, db_index=True)), + ('ordering', self.gf('django.db.models.fields.SmallIntegerField')()), + ('title', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('text', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + )) + db.send_create_signal('quiz', ['Question']) + + # Adding unique constraint on 'Question', fields ['quiz', 'slug'] + db.create_unique('quiz_question', ['quiz_id', 'slug']) + + # Adding unique constraint on 'Question', fields ['quiz', 'ordering'] + db.create_unique('quiz_question', ['quiz_id', 'ordering']) + + # Adding model 'Answer' + db.create_table('quiz_answer', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('question', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['quiz.Question'])), + ('go_to', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='go_tos', null=True, to=orm['quiz.Question'])), + ('result', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['quiz.Result'], null=True, blank=True)), + ('ordering', self.gf('django.db.models.fields.SmallIntegerField')()), + )) + db.send_create_signal('quiz', ['Answer']) + + + def backwards(self, orm): + + # Removing unique constraint on 'Question', fields ['quiz', 'ordering'] + db.delete_unique('quiz_question', ['quiz_id', 'ordering']) + + # Removing unique constraint on 'Question', fields ['quiz', 'slug'] + db.delete_unique('quiz_question', ['quiz_id', 'slug']) + + # Deleting model 'Result' + db.delete_table('quiz_result') + + # Deleting model 'Question' + db.delete_table('quiz_question') + + # Deleting model 'Answer' + db.delete_table('quiz_answer') + + + models = { + 'quiz.answer': { + 'Meta': {'ordering': "['question', 'ordering']", 'object_name': 'Answer'}, + 'go_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'go_tos'", 'null': 'True', 'to': "orm['quiz.Question']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ordering': ('django.db.models.fields.SmallIntegerField', [], {}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['quiz.Question']"}), + 'result': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['quiz.Result']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'quiz.question': { + 'Meta': {'ordering': "['quiz', 'ordering']", 'unique_together': "[['quiz', 'slug'], ['quiz', 'ordering']]", 'object_name': 'Question'}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ordering': ('django.db.models.fields.SmallIntegerField', [], {}), + 'quiz': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'quiz.quiz': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Quiz', 'db_table': "'django_site'", '_ormbases': ['sites.Site'], 'proxy': 'True'} + }, + 'quiz.result': { + 'Meta': {'object_name': 'Result'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'quiz': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + } + } + + complete_apps = ['quiz'] diff --git a/apps/quiz/migrations/__init__.py b/apps/quiz/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/quiz/models.py b/apps/quiz/models.py new file mode 100644 index 0000000..92955fa --- /dev/null +++ b/apps/quiz/models.py @@ -0,0 +1,105 @@ +from django.db import models +from django.contrib.sites.models import Site +from django.utils.translation import ugettext_lazy as _ + +from django.conf import settings + + +class Quiz(Site): + class Meta: + proxy=True + verbose_name = _('quiz') + verbose_name_plural = _('quizzes') + + @classmethod + def current(cls): + return cls.objects.get(id=settings.SITE_ID) + + def start(self): + return self.question_set.all()[0] + + @models.permalink + def get_absolute_url(self): + return ('quiz', ) + + def where_to(self): + try: + return Result.objects.get(quiz=self).get_absolute_url() + except Result.DoesNotExist: + # just go to the beginning + return self.get_absolute_url() + + +class Result(models.Model): + quiz = models.ForeignKey(Quiz) + slug = models.SlugField(db_index=True) + title = models.CharField(max_length=255) + text = models.TextField() + + class Meta: + verbose_name = _('result') + verbose_name_plural = _('results') + + def __unicode__(self): + return self.title + + @models.permalink + def get_absolute_url(self): + return ('quiz_result', [self.slug]) + + +class Question(models.Model): + quiz = models.ForeignKey(Quiz) + slug = models.SlugField(db_index=True) + ordering = models.SmallIntegerField() + title = models.CharField(max_length=255) + text = models.TextField(null=True, blank=True) + description = models.TextField(null=True, blank=True) + + class Meta: + verbose_name = _('question') + verbose_name_plural = _('questions') + ordering = ['quiz', 'ordering'] + unique_together = [['quiz', 'slug'], ['quiz', 'ordering']] + + def __unicode__(self): + return self.title + + @models.permalink + def get_absolute_url(self): + return ('quiz', [self.slug]) + + def where_to(self): + later = self.quiz.question_set.filter(ordering__gt=self.ordering) + if later.exists(): + return later[0].get_absolute_url() + else: + return self.quiz.where_to() + + + +class Answer(models.Model): + title = models.CharField(max_length=255) + question = models.ForeignKey(Question) + go_to = models.ForeignKey(Question, null=True, blank=True, + related_name='go_tos') + result = models.ForeignKey(Result, null=True, blank=True) + ordering = models.SmallIntegerField() + + class Meta: + verbose_name = _('answer') + verbose_name_plural = _('answers') + ordering = ['question', 'ordering'] + + def __unicode__(self): + return self.title + + def where_to(self): + # follow explicit redirects + if self.result: + return self.result.get_absolute_url() + elif self.go_to: + return self.go_to.get_absolute_url() + + # or just get the next question + return self.question.where_to() diff --git a/apps/quiz/templates/quiz/question_detail.html b/apps/quiz/templates/quiz/question_detail.html new file mode 100755 index 0000000..b739a98 --- /dev/null +++ b/apps/quiz/templates/quiz/question_detail.html @@ -0,0 +1,46 @@ +{% extends "base.html" %} +{% load i18n %} +{% load url from future %} + +{% block "body" %} + + +

{{ question.title }}

+
+{{ question.text }} +
+ +
+ + + +
+{% csrf_token %} +{{ form.answer.errors }} +{{ form.answer }} + +{% if valid %} + + + {% if previous_url %} + {% trans "Back to last question" %} + {% endif %} + +{% else %} + {% if valid_url %} + {% trans "Back to my test" %} + {% else %} + {% trans "Start from the beginning" %} + {% endif %} +{% endif %} + +
+
+ + +
+{{ question.description }} +
+ + +{% endblock %} diff --git a/apps/quiz/templates/quiz/result_detail.html b/apps/quiz/templates/quiz/result_detail.html new file mode 100755 index 0000000..89b875c --- /dev/null +++ b/apps/quiz/templates/quiz/result_detail.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} +{% load i18n %} +{% load url from future %} + + +{% block "body" %} + +

{{ result.title }}

+ + + +
+{{ result.text }} +
+ + + +{% if valid %} + {% if previous_url %} + {% trans "Back to last question" %} + {% endif %} +{% else %} + {% if valid_url %} + {% trans "Back to my test" %} + {% else %} + {% trans "Start from the beginning" %} + {% endif %} +{% endif %} + + + +{% trans "Start again" %} + +{% endblock %} diff --git a/apps/quiz/urls.py b/apps/quiz/urls.py new file mode 100644 index 0000000..ae5c683 --- /dev/null +++ b/apps/quiz/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls.defaults import * + + +urlpatterns = patterns('quiz.views', + url(r'^$', 'question', name='quiz'), + url(r'^q/(?P[-a-z0-9]*)/$', 'question', name='quiz'), + url(r'^r/(?P[-a-z0-9]*)/$', 'result', name='quiz_result'), +) diff --git a/apps/quiz/views.py b/apps/quiz/views.py new file mode 100644 index 0000000..7160759 --- /dev/null +++ b/apps/quiz/views.py @@ -0,0 +1,52 @@ +from django.shortcuts import get_object_or_404, render, redirect + +from quiz.forms import QuestionForm +from quiz.models import Quiz + + +def question(request, slug=None): + if slug is None: + question = Quiz.current().start() + request.session['ticket'] = [request.path] + else: + question = get_object_or_404(Quiz.current().question_set, slug=slug) + + ticket = request.session.get('ticket') + valid = request.path in ticket + print ticket, valid + if valid: + cur_index = ticket.index(request.path) + if cur_index: + previous_url = ticket[cur_index - 1] + else: + valid_url = ticket[-1] + + if request.method == 'POST' and valid: + form = QuestionForm(question, request.POST) + if form.is_valid(): + + answer = form.cleaned_data['answer'] + where_to = answer.where_to() + + del ticket[cur_index + 1:] + try: + del ticket[ticket.index(where_to) + 1:] + except ValueError: + ticket.append(where_to) + + request.session['ticket'] = ticket + + return redirect(where_to) + else: + form = QuestionForm(question) + + return render(request, "quiz/question_detail.html", locals()) + + +def result(request, slug=None): + ticket = request.session['ticket'] + valid = request.path in ticket + + result = get_object_or_404(Quiz.current().result_set, slug=slug) + return render(request, "quiz/result_detail.html", locals()) + diff --git a/fabfile.py b/fabfile.py new file mode 100644 index 0000000..c4965de --- /dev/null +++ b/fabfile.py @@ -0,0 +1,211 @@ +from __future__ import with_statement # needed for python 2.5 +from fabric.api import * +from fabric.contrib import files + +import os + + +# ========== +# = Config = +# ========== +# Globals +env.project_name = 'koedquiz' +env.use_south = True + +# Servers +def localhost(): + """SSH to localhost (for debugging). + + This will deploy to `test-deployment` in the project dir. + + """ + import os.path + from getpass import getuser + + env.hosts = ['localhost'] + env.user = getuser() + env.path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test-deployment') + env.virtualenv = '/usr/bin/virtualenv' + # This goes to VHost configuration + env.server_name = 'koedquiz.example.com' + env.server_admin = 'koedquiz ' + # /var/log/apache2/* logs + env.access_log = 'koedquiz.log' + env.error_log = 'koedquiz-errors.log' + + +# add additional servers here + + +servers = [localhost] + +# ========= +# = Tasks = +# ========= +def test(): + "Run the test suite and bail out if it fails" + require('hosts', 'path', provided_by=servers) + require('python', provided_by=[find_python]) + result = run('cd %(path)s/%(project_name)s; %(python)s manage.py test' % env) + +def setup(): + """ + Setup a fresh virtualenv as well as a few useful directories, then run + a full deployment. virtualenv with pip should be already installed. + """ + require('hosts', 'path', 'virtualenv', provided_by=servers) + + run('mkdir -p %(path)s; cd %(path)s; %(virtualenv)s ve;' % env, pty=True) + run('cd %(path)s; mkdir releases; mkdir packages;' % env, pty=True) + run('cd %(path)s/releases; ln -s . current; ln -s . previous' % env, pty=True) + upload_default_localsettings() + deploy() + +def deploy(): + """ + Deploy the latest version of the site to the servers, + install any required third party modules, + install the virtual host and then restart the webserver + """ + + import time + env.release = time.strftime('%Y-%m-%dT%H%M') + + upload_tar_from_git() + find_python() + upload_wsgi_script() + upload_vhost_sample() + install_requirements() + copy_localsettings() + symlink_current_release() + collectstatic() + migrate() + restart_webserver() + +def deploy_version(version): + "Specify a specific version to be made live" + require('hosts', 'path', provided_by=servers) + env.version = version + with cd(env.path): + run('rm releases/previous; mv releases/current releases/previous;', pty=True) + run('ln -s %(version)s releases/current' % env, pty=True) + restart_webserver() + +def rollback(): + """ + Limited rollback capability. Simple loads the previously current + version of the code. Rolling back again will swap between the two. + """ + require('hosts', 'path', provided_by=servers) + with cd(env.path): + run('mv releases/current releases/_previous;', pty=True) + run('mv releases/previous releases/current;', pty=True) + run('mv releases/_previous releases/previous;', pty=True) + restart_webserver() + + +# ===================================================================== +# = Helpers. These are called by other functions rather than directly = +# ===================================================================== +def upload_tar_from_git(): + "Create an archive from the current Git branch and upload it" + print '>>> upload tar from git' + require('path', provided_by=servers) + require('release', provided_by=[deploy]) + local('/bin/bash lib/git-archive-all.sh --format tar %(release)s.tar' % env) + local('gzip %(release)s.tar' % env) + run('mkdir -p %(path)s/releases/%(release)s' % env, pty=True) + run('mkdir -p %(path)s/packages' % env, pty=True) + put('%(release)s.tar.gz' % env, '%(path)s/packages/' % env) + run('cd %(path)s/releases/%(release)s && tar zxf ../../packages/%(release)s.tar.gz' % env, pty=True) + local('rm %(release)s.tar.gz' % env) + +def find_python(): + "Finds where virtualenv Python stuff is" + print ">>> find Python paths" + require('path', provided_by=servers) + env.python = '%(path)s/ve/bin/python' % env + env.pip = '%(path)s/ve/bin/pip' % env + env.site_packages = run('%(python)s -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"' % env) + +def upload_vhost_sample(): + "Create and upload Apache virtual host configuration sample" + print ">>> upload vhost sample" + require('path', 'project_name', 'user', provided_by=servers) + require('access_log', 'error_log', 'server_admin', 'server_name', provided_by=servers) + require('site_packages', provided_by=[find_python]) + files.upload_template('%(project_name)s.vhost.template' % env, '%(path)s/%(project_name)s.vhost' % env, context=env) + +def upload_wsgi_script(): + "Create and upload a wsgi script sample" + print ">>> upload wsgi script sample" + require('path', 'project_name', provided_by=servers) + require('python', 'site_packages', provided_by=[find_python]) + files.upload_template('%(project_name)s.wsgi.template' % env, '%(path)s/%(project_name)s.wsgi' % env, context=env) + run('chmod ug+x %(path)s/%(project_name)s.wsgi' % env) + +def install_requirements(): + "Install the required packages from the requirements file using pip" + print '>>> install requirements' + require('path', provided_by=servers) + require('release', provided_by=[deploy]) + require('pip', provided_by=[find_python]) + run('%(pip)s install -r %(path)s/releases/%(release)s/requirements.txt' % env, pty=True) + +def secret_key(): + """Generates a new SECRET_KEY.""" + from random import Random + import string + + r = Random() + return "".join(r.choice(string.printable) for i in range(64)) + +def upload_default_localsettings(): + "Uploads localsettings.py with media paths and stuff" + print ">>> upload default localsettings.py" + require('path', provided_by=servers) + + env.secret_key = secret_key() + files.upload_template('%(project_name)s/localsettings.py.template' % env, '%(path)s/localsettings.py' % env, context=env) + +def copy_localsettings(): + "Copy localsettings.py from root directory to release directory (if this file exists)" + print ">>> copy localsettings" + require('path', 'project_name', provided_by=servers) + require('release', provided_by=[deploy]) + + with settings(warn_only=True): + run('cp %(path)s/localsettings.py %(path)s/releases/%(release)s/%(project_name)s' % env) + +def symlink_current_release(): + "Symlink our current release" + print '>>> symlink current release' + require('path', provided_by=servers) + require('release', provided_by=[deploy]) + with cd(env.path): + run('rm releases/previous; mv releases/current releases/previous') + run('ln -s %(release)s releases/current' % env) + +def collectstatic(): + """Runs collectstatic management command from Django staticfiles.""" + print '>>> collectstatic' + require('path', 'project_name', provided_by=servers) + require('python', provided_by=[find_python]) + with cd('%(path)s/releases/current/%(project_name)s' % env): + run('%(python)s manage.py collectstatic --noinput' % env, pty=True) + +def migrate(): + "Update the database" + print '>>> migrate' + require('path', 'project_name', provided_by=servers) + require('python', provided_by=[find_python]) + with cd('%(path)s/releases/current/%(project_name)s' % env): + run('%(python)s manage.py syncdb --noinput' % env, pty=True) + if env.use_south: + run('%(python)s manage.py migrate' % env, pty=True) + +def restart_webserver(): + "Restart the web server" + print '>>> restart webserver' + require('path', 'project_name', provided_by=servers) + run('touch %(path)s/%(project_name)s.wsgi' % env) diff --git a/koedquiz.vhost.template b/koedquiz.vhost.template new file mode 100644 index 0000000..3caae7f --- /dev/null +++ b/koedquiz.vhost.template @@ -0,0 +1,31 @@ + + ServerName "%(server_name)s" + ServerAdmin "%(server_admin)s" + + WSGIDaemonProcess %(project_name)s user=%(user)s group=%(user)s processes=2 threads=15 display-name=%%{GROUP} python-path=%(site_packages)s + WSGIProcessGroup %(project_name)s + + WSGIScriptAlias / %(path)s/%(project_name)s.wsgi + + Order allow,deny + allow from all + + + Alias /media %(path)s/media + + Options Indexes + Order allow,deny + Allow from all + + + Alias /static %(path)s/releases/current/%(project_name)s/static + + Options Indexes + Order allow,deny + Allow from all + + + LogLevel warn + ErrorLog /var/log/apache2/%(error_log)s + CustomLog /var/log/apache2/%(access_log)s combined + diff --git a/koedquiz.wsgi.template b/koedquiz.wsgi.template new file mode 100644 index 0000000..af1781c --- /dev/null +++ b/koedquiz.wsgi.template @@ -0,0 +1,26 @@ +#!%(python)s +import site +site.addsitedir('%(site_packages)s') + +import os +from os.path import abspath, dirname, join +import sys + +# Redirect sys.stdout to sys.stderr for bad libraries like geopy that use +# print statements for optional import exceptions. +sys.stdout = sys.stderr + +# Add apps and lib directories to PYTHONPATH +sys.path = [ + '%(path)s/releases/current/%(project_name)s', + '%(path)s/releases/current', + '%(path)s/releases/current/apps', + '%(path)s/releases/current/lib', + # add paths to submodules here +] + sys.path + +# Run Django +os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' + +from django.core.handlers.wsgi import WSGIHandler +application = WSGIHandler() diff --git a/koedquiz/__init__.py b/koedquiz/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/koedquiz/localsettings.py.template b/koedquiz/localsettings.py.template new file mode 100644 index 0000000..3765080 --- /dev/null +++ b/koedquiz/localsettings.py.template @@ -0,0 +1,27 @@ +# This template is uploaded by `fab setup`. +# You should fill out the details in the version deployed on the server. + + +ADMINS = ( + #('Name', 'E-mail'), +) + +MANAGERS = ( + #('Name', 'E-mail'), +) + +# on +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': '', # Or path to database file if using sqlite3. + 'USER': '', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} + + +SECRET_KEY = %(secret_key)r +MEDIA_ROOT = '%(path)s/media/' diff --git a/koedquiz/manage.py b/koedquiz/manage.py new file mode 100755 index 0000000..eb91c2d --- /dev/null +++ b/koedquiz/manage.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +import os.path +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# Add apps and lib directories to PYTHONPATH +sys.path = [ + os.path.join(ROOT, 'apps'), + os.path.join(ROOT, 'lib'), + # add /lib/* paths here for submodules +] + sys.path + +from django.core.management import execute_manager +import imp +try: + imp.find_module('settings') # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) + sys.exit(1) + +import settings + +if __name__ == "__main__": + execute_manager(settings) diff --git a/koedquiz/settings.py b/koedquiz/settings.py new file mode 100644 index 0000000..52cd99a --- /dev/null +++ b/koedquiz/settings.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +# Django settings for koedquiz project. +import os.path + +PROJECT_DIR = os.path.abspath(os.path.dirname(__file__)) + +DEBUG = False +TEMPLATE_DEBUG = DEBUG + +ADMINS = [ + # ('Your Name', 'your_email@domain.com'), +] + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': os.path.join(PROJECT_DIR, 'dev.db'), # Or path to database file if using sqlite3. + 'USER': '', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# On Unix systems, a value of None will cause Django to use the same +# timezone as the operating system. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = None + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'pl' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# If you set this to False, Django will not format dates, numbers and +# calendars according to the current locale +USE_L10N = True + +# Absolute filesystem path to the directory that will hold user-uploaded files. +# Example: "/home/media/media.lawrence.com/media/" +MEDIA_ROOT = os.path.join(PROJECT_DIR, '../media') + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash. +# Examples: "http://media.lawrence.com/media/", "http://example.com/media/" +MEDIA_URL = '/media/' + +# Absolute path to the directory static files should be collected to. +# Don't put anything in this directory yourself; store your static files +# in apps' "static/" subdirectories and in STATICFILES_DIRS. +# Example: "/home/media/media.lawrence.com/static/" +STATIC_ROOT = os.path.join(PROJECT_DIR, '../static') + +# URL prefix for static files. +# Example: "http://media.lawrence.com/static/" +STATIC_URL = '/static/' + +# URL prefix for admin static files -- CSS, JavaScript and images. +# Make sure to use a trailing slash. +# Examples: "http://foo.com/static/admin/", "/static/admin/". +ADMIN_MEDIA_PREFIX = '/static/admin/' + +# Additional locations of static files +STATICFILES_DIRS = ( + # Put strings here, like "/home/html/static" or "C:/www/django/static". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +# List of finder classes that know how to find static files in +# various locations. +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', +# 'django.contrib.staticfiles.finders.DefaultStorageFinder', +) + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = [ + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +# 'django.template.loaders.eggs.Loader', +] + +MIDDLEWARE_CLASSES = [ + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', +] + +ROOT_URLCONF = 'koedquiz.urls' + +TEMPLATE_DIRS = [ + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + os.path.join(PROJECT_DIR, 'templates'), +] + +INSTALLED_APPS = [ + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'django.contrib.admin', + 'django.contrib.admindocs', + + 'south', + + 'quiz', +] + +# A sample logging configuration. The only tangible logging +# performed by this configuration is to send an email to +# the site admins on every HTTP 500 error. +# See http://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'class': 'django.utils.log.AdminEmailHandler' + } + }, + 'loggers': { + 'django.request': { + 'handlers': ['mail_admins'], + 'level': 'ERROR', + 'propagate': True, + }, + } +} + +# Load localsettings, if they exist +try: + from localsettings import * +except ImportError: + pass diff --git a/koedquiz/templates/base.html b/koedquiz/templates/base.html new file mode 100644 index 0000000..c6ffd3c --- /dev/null +++ b/koedquiz/templates/base.html @@ -0,0 +1,13 @@ + + + + + + {% block "title" %}{% endblock %} + + + + {% block "body" %}{% endblock %} + + + diff --git a/koedquiz/templates/home.html b/koedquiz/templates/home.html new file mode 100644 index 0000000..5c856b8 --- /dev/null +++ b/koedquiz/templates/home.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} + +{% block "body" %} + +Start quiz! + +{% endblock %} diff --git a/koedquiz/urls.py b/koedquiz/urls.py new file mode 100644 index 0000000..51fd0d8 --- /dev/null +++ b/koedquiz/urls.py @@ -0,0 +1,13 @@ +from django.conf.urls.defaults import patterns, include, url + +from django.contrib import admin +admin.autodiscover() + +urlpatterns = patterns('', + url(r'^$', 'koedquiz.views.home', name='main_page'), + + url(r'^quiz/', include('quiz.urls')), + + url(r'^admin/doc/', include('django.contrib.admindocs.urls')), + url(r'^admin/', include(admin.site.urls)), +) diff --git a/koedquiz/views.py b/koedquiz/views.py new file mode 100644 index 0000000..d554715 --- /dev/null +++ b/koedquiz/views.py @@ -0,0 +1,10 @@ +from django.shortcuts import render +#from django.contrib.sites import Site + +from django.conf import settings +from quiz.models import Quiz + + +def home(request): + quiz = Quiz.current() + return render(request, "home.html", locals()) diff --git a/lib/git-archive-all.sh b/lib/git-archive-all.sh new file mode 100644 index 0000000..95e8582 --- /dev/null +++ b/lib/git-archive-all.sh @@ -0,0 +1,208 @@ +#!/bin/bash - +# +# File: git-archive-all.sh +# +# Description: A utility script that builds an archive file(s) of all +# git repositories and submodules in the current path. +# Useful for creating a single tarfile of a git super- +# project that contains other submodules. +# +# Examples: Use git-archive-all.sh to create archive distributions +# from git repositories. To use, simply do: +# +# cd $GIT_DIR; git-archive-all.sh +# +# where $GIT_DIR is the root of your git superproject. +# +# License: GPL3 +# +############################################################################### +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +############################################################################### + +# DEBUGGING +set -e +set -C # noclobber + +# TRAP SIGNALS +trap 'cleanup' QUIT EXIT + +# For security reasons, explicitly set the internal field separator +# to newline, space, tab +OLD_IFS=$IFS +IFS=' + ' + +function cleanup () { + rm -f $TMPFILE + rm -f $TOARCHIVE + IFS="$OLD_IFS" +} + +function usage () { + echo "Usage is as follows:" + echo + echo "$PROGRAM <--version>" + echo " Prints the program version number on a line by itself and exits." + echo + echo "$PROGRAM <--usage|--help|-?>" + echo " Prints this usage output and exits." + echo + echo "$PROGRAM [--format ] [--prefix ] [--separate|-s] [output_file]" + echo " Creates an archive for the entire git superproject, and its submodules" + echo " using the passed parameters, described below." + echo + echo " If '--format' is specified, the archive is created with the named" + echo " git archiver backend. Obviously, this must be a backend that git archive" + echo " understands. The format defaults to 'tar' if not specified." + echo + echo " If '--prefix' is specified, the archive's superproject and all submodules" + echo " are created with the prefix named. The default is to not use one." + echo + echo " If '--separate' or '-s' is specified, individual archives will be created" + echo " for each of the superproject itself and its submodules. The default is to" + echo " concatenate individual archives into one larger archive." + echo + echo " If 'output_file' is specified, the resulting archive is created as the" + echo " file named. This parameter is essentially a path that must be writeable." + echo " When combined with '--separate' ('-s') this path must refer to a directory." + echo " Without this parameter or when combined with '--separate' the resulting" + echo " archive(s) are named with a dot-separated path of the archived directory and" + echo " a file extension equal to their format (e.g., 'superdir.submodule1dir.tar')." +} + +function version () { + echo "$PROGRAM version $VERSION" +} + +# Internal variables and initializations. +readonly PROGRAM=`basename "$0"` +readonly VERSION=0.2 + +OLD_PWD="`pwd`" +TMPDIR=${TMPDIR:-/tmp} +TMPFILE=`mktemp "$TMPDIR/$PROGRAM.XXXXXX"` # Create a place to store our work's progress +TOARCHIVE=`mktemp "$TMPDIR/$PROGRAM.toarchive.XXXXXX"` +OUT_FILE=$OLD_PWD # assume "this directory" without a name change by default +SEPARATE=0 + +FORMAT=tar +PREFIX= +TREEISH=HEAD + +# RETURN VALUES/EXIT STATUS CODES +readonly E_BAD_OPTION=254 +readonly E_UNKNOWN=255 + +# Process command-line arguments. +while test $# -gt 0; do + case $1 in + --format ) + shift + FORMAT="$1" + shift + ;; + + --prefix ) + shift + PREFIX="$1" + shift + ;; + + --separate | -s ) + shift + SEPARATE=1 + ;; + + --version ) + version + exit + ;; + + -? | --usage | --help ) + usage + exit + ;; + + -* ) + echo "Unrecognized option: $1" >&2 + usage + exit $E_BAD_OPTION + ;; + + * ) + break + ;; + esac +done + +if [ ! -z "$1" ]; then + OUT_FILE="$1" + shift +fi + +# Validate parameters; error early, error often. +if [ $SEPARATE -eq 1 -a ! -d $OUT_FILE ]; then + echo "When creating multiple archives, your destination must be a directory." + echo "If it's not, you risk being surprised when your files are overwritten." + exit +elif [ `git config -l | grep -q '^core\.bare=false'; echo $?` -ne 0 ]; then + echo "$PROGRAM must be run from a git working copy (i.e., not a bare repository)." + exit +fi + +# Create the superproject's git archive +git archive --format=$FORMAT --prefix="$PREFIX" $TREEISH > $TMPDIR/$(basename $(pwd)).$FORMAT +echo $TMPDIR/$(basename $(pwd)).$FORMAT >| $TMPFILE # clobber on purpose +superfile=`head -n 1 $TMPFILE` + +# find all '.git' dirs, these show us the remaining to-be-archived dirs +find . -name '.git' -type d -print | sed -e 's/^\.\///' -e 's/\.git$//' | (grep -v '^$' || echo -n) >> $TOARCHIVE + +while read path; do + TREEISH=$(git submodule | grep "^ .*${path%/} " | cut -d ' ' -f 2) # git submodule does not list trailing slashes in $path + cd "$path" + git archive --format=$FORMAT --prefix="${PREFIX}$path" ${TREEISH:-HEAD} > "$TMPDIR"/"$(echo "$path" | sed -e 's/\//./g')"$FORMAT + if [ $FORMAT == 'zip' ]; then + # delete the empty directory entry; zipped submodules won't unzip if we don't do this + zip -d "$(tail -n 1 $TMPFILE)" "${PREFIX}${path%/}" >/dev/null # remove trailing '/' + fi + echo "$TMPDIR"/"$(echo "$path" | sed -e 's/\//./g')"$FORMAT >> $TMPFILE + cd "$OLD_PWD" +done < $TOARCHIVE + +# Concatenate archives into a super-archive. +if [ $SEPARATE -eq 0 ]; then + if [ $FORMAT == 'tar' ]; then + sed -e '1d' $TMPFILE | while read file; do + tar --concatenate -f "$superfile" "$file" && rm -f "$file" + done + elif [ $FORMAT == 'zip' ]; then + sed -e '1d' $TMPFILE | while read file; do + # zip incorrectly stores the full path, so cd and then grow + cd `dirname "$file"` + zip -g "$superfile" `basename "$file"` && rm -f "$file" + done + cd "$OLD_PWD" + fi + + echo "$superfile" >| $TMPFILE # clobber on purpose +fi + +while read file; do + mv "$file" "$OUT_FILE" +done < $TMPFILE diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..aec394f --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +django-debug-toolbar diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..20e6116 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +# Django basics +django>=1.3,<1.4 +South>=0.7