Merge branch 'master' into production
commit
ed1f5b5693
|
@ -24,7 +24,7 @@ from django.db.models.signals import post_save, pre_delete
|
|||
import regluit
|
||||
from regluit.marc.models import MARCRecord as NewMARC
|
||||
from regluit.utils.localdatetime import now
|
||||
from regluit.questionnaire.models import Landing
|
||||
from questionnaire.models import Landing
|
||||
|
||||
from regluit.core import mobi
|
||||
import regluit.core.cc as cc
|
||||
|
|
|
@ -71,7 +71,7 @@ from regluit.utils.text import sanitize_line, remove_badxml
|
|||
from regluit.mobi import Mobi
|
||||
from regluit.pyepub import EPUB
|
||||
from regluit.bisac.models import BisacHeading
|
||||
from regluit.questionnaire.models import Questionnaire
|
||||
from questionnaire.models import Questionnaire
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
nulls = [False, 'delete', '']
|
||||
|
|
|
@ -134,8 +134,8 @@ from regluit.libraryauth.forms import UserNamePass
|
|||
from regluit.libraryauth.views import Authenticator, superlogin, login_user
|
||||
from regluit.libraryauth.models import Library
|
||||
from regluit.marc.views import qs_marc_records
|
||||
from regluit.questionnaire.models import Landing, Questionnaire
|
||||
from regluit.questionnaire.views import export_summary as answer_summary, export_csv as export_answers
|
||||
from questionnaire.models import Landing, Questionnaire
|
||||
from questionnaire.views import export_summary as answer_summary, export_csv as export_answers
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
"""
|
||||
questionnaire - Django Questionnaire App
|
||||
========================================
|
||||
|
||||
Create flexible questionnaires.
|
||||
|
||||
Author: Robert Thomson <git AT corporatism.org>
|
||||
"""
|
||||
|
||||
from django.dispatch import Signal
|
||||
|
||||
__all__ = ['question_proc', 'answer_proc', 'add_type', 'AnswerException',
|
||||
'questionset_done', 'questionnaire_done', ]
|
||||
|
||||
default_app_config = '{}.apps.QuestionnaireConfig'.format(__name__)
|
||||
|
||||
QuestionChoices = []
|
||||
QuestionProcessors = {} # supply additional information to the templates
|
||||
Processors = {} # for processing answers
|
||||
|
||||
questionnaire_start = Signal(providing_args=["runinfo", "questionnaire"])
|
||||
questionset_start = Signal(providing_args=["runinfo", "questionset"])
|
||||
questionset_done = Signal(providing_args=["runinfo", "questionset"])
|
||||
questionnaire_done = Signal(providing_args=["runinfo", "questionnaire"])
|
||||
|
||||
|
||||
class AnswerException(Exception):
|
||||
"""Thrown from an answer processor to generate an error message"""
|
||||
pass
|
||||
|
||||
def question_proc(*names):
|
||||
"""
|
||||
Decorator to create a question processor for one or more
|
||||
question types.
|
||||
|
||||
Usage:
|
||||
@question_proc('typename1', 'typename2')
|
||||
def qproc_blah(request, question):
|
||||
...
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
global QuestionProcessors
|
||||
for name in names:
|
||||
QuestionProcessors[name] = func
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def answer_proc(*names):
|
||||
"""
|
||||
Decorator to create an answer processor for one or more
|
||||
question types.
|
||||
|
||||
Usage:
|
||||
@question_proc('typename1', 'typename2')
|
||||
def qproc_blah(request, question):
|
||||
...
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
global Processors
|
||||
for name in names:
|
||||
Processors[name] = func
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def add_type(id, name):
|
||||
"""
|
||||
Register a new question type in the admin interface.
|
||||
At least an answer processor must also be defined for this
|
||||
type.
|
||||
|
||||
Usage:
|
||||
add_type('mysupertype', 'My Super Type [radio]')
|
||||
"""
|
||||
global QuestionChoices
|
||||
QuestionChoices.append((id, name))
|
||||
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
from django.utils.translation import ugettext as _
|
||||
from django.contrib import admin
|
||||
from django.core.urlresolvers import reverse
|
||||
from .models import (Choice, Questionnaire, Question, QuestionSet, Subject,
|
||||
RunInfo, RunInfoHistory, Answer, DBStylesheet, Landing)
|
||||
|
||||
adminsite = admin.site
|
||||
|
||||
|
||||
class SubjectAdmin(admin.ModelAdmin):
|
||||
search_fields = ['surname', 'givenname', 'email']
|
||||
list_display = ['surname', 'givenname', 'email']
|
||||
|
||||
|
||||
class ChoiceAdmin(admin.ModelAdmin):
|
||||
list_display = ['sortid', 'text', 'value', 'question']
|
||||
|
||||
|
||||
class ChoiceInline(admin.TabularInline):
|
||||
ordering = ['sortid']
|
||||
model = Choice
|
||||
extra = 5
|
||||
|
||||
|
||||
class QuestionSetAdmin(admin.ModelAdmin):
|
||||
ordering = ['questionnaire', 'sortid', ]
|
||||
list_filter = ['questionnaire', ]
|
||||
list_display = ['questionnaire', 'heading', 'sortid', ]
|
||||
list_editable = ['sortid', ]
|
||||
|
||||
|
||||
class QuestionAdmin(admin.ModelAdmin):
|
||||
ordering = ['questionset__questionnaire', 'questionset', 'sort_id', 'number']
|
||||
inlines = [ChoiceInline]
|
||||
list_filter = ['questionset__questionnaire']
|
||||
|
||||
def changelist_view(self, request, extra_context=None):
|
||||
"Hack to have Questionnaire list accessible for custom changelist template"
|
||||
if not extra_context:
|
||||
extra_context = {}
|
||||
|
||||
questionnaire_id = request.GET.get('questionset__questionnaire__id__exact', None)
|
||||
if questionnaire_id:
|
||||
args = {"id": questionnaire_id}
|
||||
else:
|
||||
args = {}
|
||||
extra_context['questionnaires'] = Questionnaire.objects.filter(**args).order_by('name')
|
||||
return super(QuestionAdmin, self).changelist_view(request, extra_context)
|
||||
|
||||
|
||||
class QuestionnaireAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'redirect_url', 'export')
|
||||
readonly_fields = ('export',)
|
||||
|
||||
def export(self, obj):
|
||||
csv_url = reverse("export_csv", args=[obj.id,])
|
||||
summary_url = reverse("export_summary", args=[obj.id,])
|
||||
return '<a href="{}">{}</a> <a href="{}">{}</a>'.format(
|
||||
csv_url, _("Download data"), summary_url, _("Show summary")
|
||||
)
|
||||
|
||||
export.allow_tags = True
|
||||
export.short_description = _('Export to CSV')
|
||||
|
||||
|
||||
class RunInfoAdmin(admin.ModelAdmin):
|
||||
list_display = ['random', 'run', 'subject', 'created', 'emailsent', 'lastemailerror']
|
||||
pass
|
||||
|
||||
|
||||
class RunInfoHistoryAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class AnswerAdmin(admin.ModelAdmin):
|
||||
search_fields = ['subject__email', 'run__id', 'question__number', 'answer']
|
||||
list_display = ['id', 'run', 'subject', 'question']
|
||||
list_filter = ['subject', 'run__id']
|
||||
ordering = [ 'id', 'subject', 'run__id', 'question', ]
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
# new in dj1.7
|
||||
# @admin.register(Landing)
|
||||
class LandingAdmin(admin.ModelAdmin):
|
||||
list_display = ('label', 'content_type', 'object_id', )
|
||||
ordering = [ 'object_id', ]
|
||||
|
||||
adminsite.register(Questionnaire, QuestionnaireAdmin)
|
||||
adminsite.register(Question, QuestionAdmin)
|
||||
adminsite.register(QuestionSet, QuestionSetAdmin)
|
||||
adminsite.register(Subject, SubjectAdmin)
|
||||
adminsite.register(RunInfo, RunInfoAdmin)
|
||||
adminsite.register(RunInfoHistory, RunInfoHistoryAdmin)
|
||||
adminsite.register(Answer, AnswerAdmin)
|
||||
adminsite.register(Landing, LandingAdmin)
|
||||
adminsite.register(DBStylesheet)
|
|
@ -1,29 +0,0 @@
|
|||
# questionnaire/apps.py
|
||||
import imp
|
||||
from django.conf import settings
|
||||
|
||||
from . import qprocessors, add_type # make sure ours are imported first # noqa
|
||||
from . import __name__ as app_name
|
||||
from django.apps import AppConfig
|
||||
|
||||
class QuestionnaireConfig(AppConfig):
|
||||
name = app_name
|
||||
verbose_name = "FEF Questionnaire"
|
||||
label = 'questionnaire'
|
||||
|
||||
def ready(self):
|
||||
|
||||
add_type('sameas', 'Same as Another Question (put sameas=question.number in checks or sameasid=question.id)')
|
||||
|
||||
for app in settings.INSTALLED_APPS:
|
||||
try:
|
||||
app_path = __import__(app, {}, {}, [app.split('.')[-1]]).__path__
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
try:
|
||||
imp.find_module('qprocessors', app_path)
|
||||
except ImportError:
|
||||
continue
|
||||
|
||||
__import__("%s.qprocessors" % app)
|
|
@ -1,23 +0,0 @@
|
|||
from django.conf import settings
|
||||
|
||||
|
||||
def load_settings(request):
|
||||
"""
|
||||
A template context processor that adds the Questionnaire
|
||||
Settings to the template context.
|
||||
"""
|
||||
try:
|
||||
use_session = settings.QUESTIONNAIRE_USE_SESSION
|
||||
except AttributeError:
|
||||
use_session = False
|
||||
|
||||
try:
|
||||
debug_questionnaire = settings.QUESTIONNAIRE_DEBUG
|
||||
except AttributeError:
|
||||
debug_questionnaire = False
|
||||
|
||||
context = {
|
||||
'debug_questionnaire': debug_questionnaire,
|
||||
'use_session': use_session
|
||||
}
|
||||
return context
|
|
@ -1,147 +0,0 @@
|
|||
from .models import Question, Answer
|
||||
import logging
|
||||
|
||||
|
||||
def explode_answer_into_list(answers):
|
||||
answer_list = []
|
||||
|
||||
for answer in answers:
|
||||
if type(answer) == type(list()):
|
||||
string_list = filter(lambda v: not isinstance(v, int), answer)
|
||||
# remove all number values from list - this means choice-multiple-values's values
|
||||
answer_list.extend(string_list)
|
||||
else:
|
||||
answer_list.append(answer)
|
||||
|
||||
return answer_list
|
||||
|
||||
|
||||
def check_actual_answers_against_expression(check_answer, actual_answer, check_question):
|
||||
# Numeric Value Expressions
|
||||
if check_answer[0:1] in "<>":
|
||||
try:
|
||||
actual_answer = float(actual_answer)
|
||||
if check_answer[1:2] == "=":
|
||||
check_value = float(check_answer[2:])
|
||||
else:
|
||||
check_value = float(check_answer[1:])
|
||||
except:
|
||||
logging.error("ERROR: must use numeric values with < <= => > checks (%r)" % check_question)
|
||||
return False
|
||||
if check_answer.startswith("<="):
|
||||
return actual_answer <= check_value
|
||||
if check_answer.startswith(">="):
|
||||
return actual_answer >= check_value
|
||||
if check_answer.startswith("<"):
|
||||
return actual_answer < check_value
|
||||
if check_answer.startswith(">"):
|
||||
return actual_answer > check_value
|
||||
|
||||
# Convert answer to list if not already one
|
||||
if type(actual_answer) != type(list()):
|
||||
actual_answer = [actual_answer]
|
||||
|
||||
# Negative Value Expressions
|
||||
if check_answer.startswith("!"):
|
||||
if len(actual_answer) == 0:
|
||||
return False
|
||||
for actual_answer in actual_answer:
|
||||
if actual_answer == '':
|
||||
return False
|
||||
if check_answer[1:].strip() == actual_answer.strip():
|
||||
return False
|
||||
return True
|
||||
|
||||
# Positive Value Expressions
|
||||
for actual_answer in actual_answer:
|
||||
if check_answer.strip() == actual_answer.strip():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def dep_check(expr, runinfo, answerdict):
|
||||
"""
|
||||
Given a comma separated question number and expression, determine if the
|
||||
provided answer to the question number satisfies the expression.
|
||||
|
||||
If the expression starts with >, >=, <, or <=, compare the rest of
|
||||
the expression numerically and return False if it's not able to be
|
||||
converted to an integer.
|
||||
|
||||
If the expression starts with !, return true if the rest of the expression
|
||||
does not match the answer.
|
||||
|
||||
Otherwise return true if the expression matches the answer.
|
||||
|
||||
If there is no comma and only a question number, it checks if the answer
|
||||
is "yes"
|
||||
|
||||
When looking up the answer, it first checks if it's in the answerdict,
|
||||
then it checks runinfo's cookies, then it does a database lookup to find
|
||||
the answer.
|
||||
|
||||
The use of the comma separator is purely historical.
|
||||
"""
|
||||
|
||||
if hasattr(runinfo, 'questionset'):
|
||||
questionnaire = runinfo.questionset.questionnaire
|
||||
elif hasattr(runinfo, 'questionnaire'):
|
||||
questionnaire = runinfo.questionnaire
|
||||
else:
|
||||
assert False
|
||||
|
||||
# Parse expression
|
||||
if "," not in expr:
|
||||
expr += ",yes"
|
||||
|
||||
check_question_number, check_answer = expr.split(",", 1)
|
||||
|
||||
# Get question to check
|
||||
try:
|
||||
check_question = Question.objects.get(number=check_question_number,
|
||||
questionset__questionnaire=questionnaire)
|
||||
except Question.DoesNotExist:
|
||||
return False
|
||||
|
||||
# Parse & load actual answer(s) from user
|
||||
if check_question in answerdict:
|
||||
# test for membership in multiple choice questions
|
||||
# FIXME: only checking answerdict
|
||||
for k, v in answerdict[check_question].items():
|
||||
if not k.startswith('multiple_'):
|
||||
continue
|
||||
if check_answer.startswith("!"):
|
||||
if check_answer[1:].strip() == v.strip():
|
||||
return False
|
||||
elif check_answer.strip() == v.strip():
|
||||
return True
|
||||
actual_answer = answerdict[check_question].get('ANSWER', '')
|
||||
elif hasattr(runinfo, 'get_cookie') and runinfo.get_cookie(check_question_number, False):
|
||||
actual_answer = runinfo.get_cookie(check_question_number)
|
||||
else:
|
||||
# retrieve from database
|
||||
answer_object = Answer.objects.filter(question=check_question,
|
||||
run=runinfo.run,
|
||||
subject=runinfo.subject)
|
||||
if answer_object:
|
||||
actual_answer = answer_object[0].split_answer()
|
||||
logging.warn("Put `store` in checks field for question %s" % check_question_number)
|
||||
else:
|
||||
actual_answer = None
|
||||
|
||||
if not actual_answer:
|
||||
if check_question.getcheckdict():
|
||||
actual_answer = check_question.getcheckdict().get('default')
|
||||
|
||||
if actual_answer is None:
|
||||
actual_answer = u''
|
||||
|
||||
# Convert freeform question type answers from a list of lists to just a single list.
|
||||
# FIXME: Figure out why the hell freeform questions store their values as lists within lists.
|
||||
#Don't shrink to one list if it isn't even a list though, otherwise we could get a string
|
||||
#becoming a character array and falsely failing the comparison.
|
||||
answer_list = actual_answer #might not actually be a list but meh
|
||||
if type(actual_answer) == type(list()):
|
||||
answer_list = explode_answer_into_list(actual_answer)
|
||||
|
||||
return check_actual_answers_against_expression(check_answer, answer_list, check_question)
|
|
@ -1,156 +0,0 @@
|
|||
# -*- coding: utf-8
|
||||
"""
|
||||
Functions to send email reminders to users.
|
||||
"""
|
||||
|
||||
import random, time, smtplib, rfc822
|
||||
from datetime import datetime
|
||||
from email.Header import Header
|
||||
from email.Utils import formataddr, parseaddr
|
||||
from django.core.mail import get_connection, EmailMessage
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.template import loader
|
||||
from django.utils import translation
|
||||
from django.conf import settings
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import render_to_response, get_object_or_404
|
||||
from .models import Subject, QuestionSet, RunInfo, Questionnaire
|
||||
|
||||
try: from hashlib import md5
|
||||
except: from md5 import md5
|
||||
|
||||
|
||||
def encode_emailaddress(address):
|
||||
"""
|
||||
Encode an email address as ASCII using the Encoded-Word standard.
|
||||
Needed to work around http://code.djangoproject.com/ticket/11144
|
||||
"""
|
||||
try: return address.encode('ascii')
|
||||
except UnicodeEncodeError: pass
|
||||
nm, addr = parseaddr(address)
|
||||
return formataddr( (str(Header(nm, settings.DEFAULT_CHARSET)), addr) )
|
||||
|
||||
|
||||
def _new_random(subject):
|
||||
"""
|
||||
Create a short unique randomized string.
|
||||
Returns: subject_id + 'z' +
|
||||
md5 hexdigest of subject's surname, nextrun date, and a random number
|
||||
"""
|
||||
return "%dz%s" % (subject.id, md5(subject.surname + str(subject.nextrun) + hex(random.randint(1,999999))).hexdigest()[:6])
|
||||
|
||||
|
||||
def _new_runinfo(subject, questionset):
|
||||
"""
|
||||
Create a new RunInfo entry with a random code
|
||||
|
||||
If a unique subject+runid entry already exists, return that instead..
|
||||
That should only occurs with manual database changes
|
||||
"""
|
||||
nextrun = subject.nextrun
|
||||
runid = str(nextrun.year)
|
||||
(run, created) = Run.objects.get_or_create(runid=runid)
|
||||
(r, created) = RunInfo.objects.get_or_create(run=run, subject=subject)
|
||||
if created:
|
||||
r.random = _new_random(subject)
|
||||
r.emailcount = 0
|
||||
r.questionset = questionset
|
||||
r.save()
|
||||
if nextrun.month == 2 and nextrun.day == 29: # the only exception?
|
||||
subject.nextrun = datetime(nextrun.year + 1, 2, 28)
|
||||
else:
|
||||
subject.nextrun = datetime(nextrun.year + 1, nextrun.month, nextrun.day)
|
||||
subject.save()
|
||||
return r
|
||||
|
||||
def _send_email(runinfo):
|
||||
"Send the email for a specific runinfo entry"
|
||||
subject = runinfo.subject
|
||||
translation.activate(subject.language)
|
||||
tmpl = loader.get_template(settings.QUESTIONNAIRE_EMAIL_TEMPLATE)
|
||||
c = {}
|
||||
c['surname'] = subject.surname
|
||||
c['givenname'] = subject.givenname
|
||||
c['gender'] = subject.gender
|
||||
c['email'] = subject.email
|
||||
c['random'] = runinfo.random
|
||||
c['runid'] = runinfo.run.runid
|
||||
c['created'] = runinfo.created
|
||||
c['site'] = getattr(settings, 'QUESTIONNAIRE_URL', '(settings.QUESTIONNAIRE_URL not set)')
|
||||
email = tmpl.render(c)
|
||||
emailFrom = settings.QUESTIONNAIRE_EMAIL_FROM
|
||||
emailSubject, email = email.split("\n",1) # subject must be on first line
|
||||
emailSubject = emailSubject.strip()
|
||||
emailFrom = emailFrom.replace("$RUNINFO", runinfo.random)
|
||||
emailTo = '"%s, %s" <%s>' % (subject.surname, subject.givenname, subject.email)
|
||||
|
||||
emailTo = encode_emailaddress(emailTo)
|
||||
emailFrom = encode_emailaddress(emailFrom)
|
||||
|
||||
try:
|
||||
conn = get_connection()
|
||||
msg = EmailMessage(emailSubject, email, emailFrom, [ emailTo ],
|
||||
connection=conn)
|
||||
msg.send()
|
||||
runinfo.emailcount = 1 + runinfo.emailcount
|
||||
runinfo.emailsent = datetime.now()
|
||||
runinfo.lastemailerror = "OK, accepted by server"
|
||||
runinfo.save()
|
||||
return True
|
||||
except smtplib.SMTPRecipientsRefused:
|
||||
runinfo.lastemailerror = "SMTP Recipient Refused"
|
||||
except smtplib.SMTPHeloError:
|
||||
runinfo.lastemailerror = "SMTP Helo Error"
|
||||
except smtplib.SMTPSenderRefused:
|
||||
runinfo.lastemailerror = "SMTP Sender Refused"
|
||||
except smtplib.SMTPDataError:
|
||||
runinfo.lastemailerror = "SMTP Data Error"
|
||||
runinfo.save()
|
||||
return False
|
||||
|
||||
|
||||
def send_emails(request=None, qname=None):
|
||||
"""
|
||||
1. Create a runinfo entry for each subject who is due and has state 'active'
|
||||
2. Send an email for each runinfo entry whose subject receives email,
|
||||
providing that the last sent email was sent more than a week ago.
|
||||
|
||||
This can be called either by "./manage.py questionnaire_emails" (without
|
||||
request) or through the web, if settings.EMAILCODE is set and matches.
|
||||
"""
|
||||
if request and request.GET.get('code') != getattr(settings,'EMAILCODE', False):
|
||||
raise Http404
|
||||
if not qname:
|
||||
qname = getattr(settings, 'QUESTIONNAIRE_DEFAULT', None)
|
||||
if not qname:
|
||||
raise Exception("QUESTIONNAIRE_DEFAULT not in settings")
|
||||
questionnaire = Questionnaire.objects.get(name=qname)
|
||||
questionset = QuestionSet.objects.filter(questionnaire__name=qname).order_by('sortid')
|
||||
if not questionset:
|
||||
raise Exception("No questionsets for questionnaire '%s' (in settings.py)" % qname)
|
||||
return
|
||||
questionset = questionset[0]
|
||||
|
||||
viablesubjects = Subject.objects.filter(nextrun__lte = datetime.now(), state='active')
|
||||
for s in viablesubjects:
|
||||
r = _new_runinfo(s, questionset)
|
||||
runinfos = RunInfo.objects.filter(subject__formtype='email', questionset__questionnaire=questionnaire)
|
||||
WEEKAGO = time.time() - (60 * 60 * 24 * 7) # one week ago
|
||||
outlog = []
|
||||
for r in runinfos:
|
||||
if r.run.runid.startswith('test:'):
|
||||
continue
|
||||
if r.emailcount == -1:
|
||||
continue
|
||||
if r.emailcount == 0 or time.mktime(r.emailsent.timetuple()) < WEEKAGO:
|
||||
try:
|
||||
if _send_email(r):
|
||||
outlog.append(u"[%s] %s, %s: OK" % (r.run.runid, r.subject.surname, r.subject.givenname))
|
||||
else:
|
||||
outlog.append(u"[%s] %s, %s: %s" % (r.run.runid, r.subject.surname, r.subject.givenname, r.lastemailerror))
|
||||
except Exception, e:
|
||||
outlog.append("Exception: [%s] %s: %s" % (r.run.runid, r.subject.surname, str(e)))
|
||||
if request:
|
||||
return HttpResponse("Sent Questionnaire Emails:\n "
|
||||
+"\n ".join(outlog), content_type="text/plain")
|
||||
return "\n".join(outlog)
|
|
@ -1,512 +0,0 @@
|
|||
[
|
||||
{
|
||||
"fields": {
|
||||
"anonymous": false,
|
||||
"email": "test@example.com",
|
||||
"formtype": "email",
|
||||
"gender": "male",
|
||||
"givenname": "TestGivenname",
|
||||
"ip_address": null,
|
||||
"language": "de",
|
||||
"nextrun": "2009-05-15",
|
||||
"state": "active",
|
||||
"surname": "TestSurname"
|
||||
},
|
||||
"model": "questionnaire.subject",
|
||||
"pk": 1
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"admin_access_only": false,
|
||||
"html": "survey html here",
|
||||
"name": "MappingSurvey",
|
||||
"parse_html": false,
|
||||
"redirect_url": ""
|
||||
},
|
||||
"model": "questionnaire.questionnaire",
|
||||
"pk": 3
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"content_type": 22,
|
||||
"label": "Open Book Publishers",
|
||||
"nonce": "xxxxxx",
|
||||
"object_id": 81834,
|
||||
"questionnaire": 3
|
||||
},
|
||||
"model": "questionnaire.landing",
|
||||
"pk": 1234
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"checks": "",
|
||||
"heading": "Open Access Ebooks (Part 1)",
|
||||
"parse_html": true,
|
||||
"questionnaire": 3,
|
||||
"sortid": 1,
|
||||
"text_en": " <h1> Introduction </h1> \r\n <p> \r\nWelcome, reader of <i>{{ landing_object.title }}</i>! And thanks for visiting Unglue.it to help us out with this \u2026\r\n </p> \r\n <p> \r\nAs Open Access publishers, {{ landing_object.claim.all.0.rights_holder }} are truly committed to making academic research broadly accessible - so we want to understand how people like you are actually accessing and using our Open Access titles. \r\n </p> \r\n <p> \r\nWe have a bunch of questions for you (well - only 9 actually) about how you found this book and what you\u2019re going to do with it. Please tell us the things you think are interesting or relevant. We really want to know!\r\n </p> \r\n <p> \r\n[Privacy policy: There are no marketing traps, we\u2019re not going to surreptitiously drop cookies on you to carry around for us, or swamp you with emails afterwards, or tell our \u201cfriends\u201d about you - we\u2019re just going to store your answers to create a database of usage examples that can be used to understand what Open Access publishing enables."
|
||||
},
|
||||
"model": "questionnaire.questionset",
|
||||
"pk": 5
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"checks": "",
|
||||
"heading": "Now About You...",
|
||||
"parse_html": true,
|
||||
"questionnaire": 3,
|
||||
"sortid": 2,
|
||||
"text_en": " <p> And now, three questions about you as well ... </p> "
|
||||
},
|
||||
"model": "questionnaire.questionset",
|
||||
"pk": 6
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"checks": "",
|
||||
"heading": "Follow-up",
|
||||
"parse_html": true,
|
||||
"questionnaire": 3,
|
||||
"sortid": 3,
|
||||
"text_en": " <p> We would really like to be able to follow up with some of the respondents to this questionnaire to ask them a few more questions - particularly if you\u2019ve told us something really interesting in a comment (for example). [There will also be a little reward (a free book no less!) for those of you we do contact in this way.] </p> \r\n\r\n <p> Thanks so much for your time and efforts answering these questions for us - we love you for it! </p> \r\n\r\n <p> We hope you enjoy <i>{{ landing_object.title }}</i>. </p> \r\n\r\n <p> {{ landing_object.claim.all.0.rights_holder }} and Unglue.it </p> \r\n"
|
||||
},
|
||||
"model": "questionnaire.questionset",
|
||||
"pk": 7
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"checks": "",
|
||||
"extra_en": "",
|
||||
"footer_en": "",
|
||||
"number": "1",
|
||||
"parse_html": true,
|
||||
"questionset": 5,
|
||||
"sort_id": 1,
|
||||
"text_en": "How did you find out about this book in the first place? <br /> <br /> \r\n\r\nFor example: Was it from a Google search? Following a wikipedia link? A tweet? Referenced in another book? A late night session with a friend (we don\u2019t need to know much more about that!)? - or in some other way?\r\n",
|
||||
"type": "open"
|
||||
},
|
||||
"model": "questionnaire.question",
|
||||
"pk": 16
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"checks": "",
|
||||
"extra_en": "",
|
||||
"footer_en": "",
|
||||
"number": "2",
|
||||
"parse_html": false,
|
||||
"questionset": 5,
|
||||
"sort_id": 2,
|
||||
"text_en": "How did you get hold of this particular copy? \r\n\r\nFor example: Did you download it from the publisher's website? Amazon or another retailer? Find it on academia.edu? Or somewhere like aaaaarg? Get it from a friend? ",
|
||||
"type": "open"
|
||||
},
|
||||
"model": "questionnaire.question",
|
||||
"pk": 17
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"checks": "",
|
||||
"extra_en": "",
|
||||
"footer_en": "",
|
||||
"number": "3",
|
||||
"parse_html": true,
|
||||
"questionset": 5,
|
||||
"sort_id": 3,
|
||||
"text_en": "Why are you interested in this book?",
|
||||
"type": "choice-multiple-freeform"
|
||||
},
|
||||
"model": "questionnaire.question",
|
||||
"pk": 18
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"checks": "",
|
||||
"extra_en": "If Yes - why are you using this edition and not one of the other ones?",
|
||||
"footer_en": "",
|
||||
"number": "4",
|
||||
"parse_html": false,
|
||||
"questionset": 5,
|
||||
"sort_id": 4,
|
||||
"text_en": "Are you aware that this title is available in multiple different digital and printed formats?",
|
||||
"type": "choice-yesnocomment"
|
||||
},
|
||||
"model": "questionnaire.question",
|
||||
"pk": 19
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"checks": "",
|
||||
"extra_en": "Please tell us in more detail:",
|
||||
"footer_en": "\r\n\r\n\r\n\r\n\r\n\r\n\r\n",
|
||||
"number": "5",
|
||||
"parse_html": false,
|
||||
"questionset": 5,
|
||||
"sort_id": 5,
|
||||
"text_en": "What are you going to do with it now you have it?",
|
||||
"type": "choice-multiple-freeform"
|
||||
},
|
||||
"model": "questionnaire.question",
|
||||
"pk": 20
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"checks": "",
|
||||
"extra_en": "",
|
||||
"footer_en": "",
|
||||
"number": "1",
|
||||
"parse_html": false,
|
||||
"questionset": 6,
|
||||
"sort_id": null,
|
||||
"text_en": "Where do you live?",
|
||||
"type": "choice-freeform"
|
||||
},
|
||||
"model": "questionnaire.question",
|
||||
"pk": 21
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"checks": "",
|
||||
"extra_en": "",
|
||||
"footer_en": "",
|
||||
"number": "2",
|
||||
"parse_html": false,
|
||||
"questionset": 6,
|
||||
"sort_id": null,
|
||||
"text_en": "What do you do for a living?",
|
||||
"type": "open"
|
||||
},
|
||||
"model": "questionnaire.question",
|
||||
"pk": 22
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"checks": "",
|
||||
"extra_en": "",
|
||||
"footer_en": "\r\n\r\n \r\n\r\n",
|
||||
"number": "3",
|
||||
"parse_html": false,
|
||||
"questionset": 6,
|
||||
"sort_id": null,
|
||||
"text_en": "When did you finish your formal education?",
|
||||
"type": "choice-freeform"
|
||||
},
|
||||
"model": "questionnaire.question",
|
||||
"pk": 23
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"checks": "required-no",
|
||||
"extra_en": "",
|
||||
"footer_en": "",
|
||||
"number": "4",
|
||||
"parse_html": false,
|
||||
"questionset": 6,
|
||||
"sort_id": null,
|
||||
"text_en": "Is there anything else you would like to tell us, or think we should know?",
|
||||
"type": "open-textfield"
|
||||
},
|
||||
"model": "questionnaire.question",
|
||||
"pk": 24
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"checks": "",
|
||||
"extra_en": "",
|
||||
"footer_en": "",
|
||||
"number": "1",
|
||||
"parse_html": false,
|
||||
"questionset": 7,
|
||||
"sort_id": null,
|
||||
"text_en": "If you\u2019re willing, then please leave us an email address where we could make contact with you (information which we won\u2019t share or make public).\r\n",
|
||||
"type": "open"
|
||||
},
|
||||
"model": "questionnaire.question",
|
||||
"pk": 25
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 18,
|
||||
"sortid": 1,
|
||||
"tags": "",
|
||||
"text_en": "For personal use - I\u2019m interested in the topic ",
|
||||
"value": "personal"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 17
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 18,
|
||||
"sortid": 2,
|
||||
"tags": "",
|
||||
"text_en": "For my job - it relates to what I do ",
|
||||
"value": "job"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 18
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 18,
|
||||
"sortid": 3,
|
||||
"tags": "",
|
||||
"text_en": "I need to read it for a course",
|
||||
"value": "course"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 19
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 20,
|
||||
"sortid": 1,
|
||||
"tags": "",
|
||||
"text_en": "Save it, in case I need to use it in the future",
|
||||
"value": "save"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 20
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 20,
|
||||
"sortid": 2,
|
||||
"tags": "",
|
||||
"text_en": "Skim through it and see if it\u2019s at all interesting",
|
||||
"value": "skim"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 21
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 20,
|
||||
"sortid": 3,
|
||||
"tags": "",
|
||||
"text_en": "There\u2019s only really a section/chapter I\u2019m interested in - I\u2019ll probably just read that",
|
||||
"value": "section"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 22
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 20,
|
||||
"sortid": 4,
|
||||
"tags": "",
|
||||
"text_en": "The whole book looks fascinating - I\u2019m going to read it all!",
|
||||
"value": "whole"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 23
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 20,
|
||||
"sortid": 5,
|
||||
"tags": "",
|
||||
"text_en": "I\u2019m going to adapt it and use it (or, at least, parts of it) for another purpose (eg a student coursepack, lecture/briefing notes \u2026)",
|
||||
"value": "adapt"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 24
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 20,
|
||||
"sortid": 6,
|
||||
"tags": "",
|
||||
"text_en": "Share it with my friends ",
|
||||
"value": "share"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 25
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 20,
|
||||
"sortid": 7,
|
||||
"tags": "",
|
||||
"text_en": "Print it out and ceremoniously burn it!",
|
||||
"value": "print"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 26
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 20,
|
||||
"sortid": 8,
|
||||
"tags": "",
|
||||
"text_en": "I\u2019m creating/collating a (online) library",
|
||||
"value": "catalog"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 27
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 20,
|
||||
"sortid": 9,
|
||||
"tags": "",
|
||||
"text_en": "Something else entirely \u2026. ",
|
||||
"value": "else"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 28
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 21,
|
||||
"sortid": 1,
|
||||
"tags": "",
|
||||
"text_en": "USA/Canada",
|
||||
"value": "us"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 29
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 21,
|
||||
"sortid": 2,
|
||||
"tags": "",
|
||||
"text_en": "Europe",
|
||||
"value": "eu"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 30
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 21,
|
||||
"sortid": 3,
|
||||
"tags": "",
|
||||
"text_en": "South America",
|
||||
"value": "sa"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 31
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 21,
|
||||
"sortid": 4,
|
||||
"tags": "",
|
||||
"text_en": "Central America/ Caribbean",
|
||||
"value": "ca"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 32
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 21,
|
||||
"sortid": 5,
|
||||
"tags": "",
|
||||
"text_en": "Asia",
|
||||
"value": "as"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 33
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 21,
|
||||
"sortid": 6,
|
||||
"tags": "",
|
||||
"text_en": "Africa",
|
||||
"value": "af"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 34
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 21,
|
||||
"sortid": 7,
|
||||
"tags": "",
|
||||
"text_en": "Middle East",
|
||||
"value": "me"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 35
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 21,
|
||||
"sortid": 8,
|
||||
"tags": "",
|
||||
"text_en": "Another Planet",
|
||||
"value": "ap"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 36
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 23,
|
||||
"sortid": 1,
|
||||
"tags": "",
|
||||
"text_en": "I haven\u2019t - I\u2019m still a student",
|
||||
"value": "x"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 37
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 23,
|
||||
"sortid": 2,
|
||||
"tags": "",
|
||||
"text_en": "At primary school",
|
||||
"value": "8"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 38
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 23,
|
||||
"sortid": 3,
|
||||
"tags": "",
|
||||
"text_en": "At high school",
|
||||
"value": "h"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 39
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 23,
|
||||
"sortid": 4,
|
||||
"tags": "",
|
||||
"text_en": "After trade qualifications",
|
||||
"value": "t"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 40
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 23,
|
||||
"sortid": 5,
|
||||
"tags": "",
|
||||
"text_en": "At College/Undergraduate Degree ",
|
||||
"value": "c"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 41
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"question": 23,
|
||||
"sortid": 6,
|
||||
"tags": "",
|
||||
"text_en": "At Grad School/post-graduate university",
|
||||
"value": "g"
|
||||
},
|
||||
"model": "questionnaire.choice",
|
||||
"pk": 42
|
||||
}
|
||||
]
|
|
@ -1,455 +0,0 @@
|
|||
- fields: {
|
||||
state: active,
|
||||
surname: TestSurname,
|
||||
givenname: TestGivenname,
|
||||
email: test@example.com,
|
||||
gender: male,
|
||||
formtype: email,
|
||||
language: de,
|
||||
nextrun: 2009-05-15
|
||||
}
|
||||
model: questionnaire.subject
|
||||
pk: 1
|
||||
- fields: {
|
||||
name: Test,
|
||||
redirect_url: /
|
||||
}
|
||||
model: questionnaire.questionnaire
|
||||
pk: 1
|
||||
- fields: {
|
||||
name: TagTest,
|
||||
redirect_url: /
|
||||
}
|
||||
model: questionnaire.questionnaire
|
||||
pk: 2
|
||||
- fields: {
|
||||
checks: '',
|
||||
heading: 001_TestQS,
|
||||
questionnaire: 1,
|
||||
sortid: 1,
|
||||
text_de: Test Fragebogenseite 1,
|
||||
text_en: Test QuestionSet 1,
|
||||
}
|
||||
model: questionnaire.questionset
|
||||
pk: 1
|
||||
- fields: {
|
||||
checks: '',
|
||||
heading: 002_TestQS,
|
||||
questionnaire: 1,
|
||||
sortid: 2,
|
||||
text_de: Test Fragebogenseite 1,
|
||||
text_en: Test QuestionSet 1,
|
||||
}
|
||||
model: questionnaire.questionset
|
||||
pk: 2
|
||||
- fields: {
|
||||
checks: '',
|
||||
heading: always,
|
||||
questionnaire: 2,
|
||||
sortid: 1,
|
||||
text_de: always shown,
|
||||
text_en: always shown,
|
||||
}
|
||||
model: questionnaire.questionset
|
||||
pk: 3
|
||||
- fields: {
|
||||
checks: 'iftag=testtag',
|
||||
heading: shown with testtag,
|
||||
questionnaire: 2,
|
||||
sortid: 2,
|
||||
text_de: shown with testtag,
|
||||
text_en: shown with testtag,
|
||||
}
|
||||
model: questionnaire.run
|
||||
pk: 1
|
||||
- fields: {
|
||||
runid: 'test:test',
|
||||
}
|
||||
model: questionnaire.run
|
||||
pk: 2
|
||||
- fields: {
|
||||
runid: 'test:withtags',
|
||||
}
|
||||
model: questionnaire.run
|
||||
pk: 3
|
||||
- fields: {
|
||||
runid: 'test:withouttags',
|
||||
}
|
||||
model: questionnaire.questionset
|
||||
pk: 4
|
||||
- fields: {
|
||||
cookies: null,
|
||||
created: !!timestamp '2009-05-15 10:19:03.905232',
|
||||
emailcount: 0,
|
||||
emailsent: !!timestamp '2009-05-16 15:05:11.775082',
|
||||
lastemailerror: null,
|
||||
questionset: 1,
|
||||
random: 'test:test',
|
||||
run: 1,
|
||||
state: '',
|
||||
subject: 1
|
||||
}
|
||||
model: questionnaire.runinfo
|
||||
pk: 1
|
||||
- fields: {
|
||||
cookies: null,
|
||||
created: !!timestamp '2009-05-15 10:19:03.905232',
|
||||
emailcount: 0,
|
||||
emailsent: !!timestamp '2009-05-16 15:05:11.775082',
|
||||
lastemailerror: null,
|
||||
questionset: 3,
|
||||
random: 'test:withtags',
|
||||
run: 2,
|
||||
state: '',
|
||||
tags: 'testtag',
|
||||
subject: 1
|
||||
}
|
||||
model: questionnaire.runinfo
|
||||
pk: 2
|
||||
- fields: {
|
||||
cookies: null,
|
||||
created: !!timestamp '2009-05-15 10:19:03.905232',
|
||||
emailcount: 0,
|
||||
emailsent: !!timestamp '2009-05-16 15:05:11.775082',
|
||||
lastemailerror: null,
|
||||
questionset: 3,
|
||||
random: 'test:withouttags',
|
||||
run: 3,
|
||||
state: '',
|
||||
tags: '',
|
||||
subject: 1
|
||||
}
|
||||
model: questionnaire.runinfo
|
||||
pk: 3
|
||||
- fields: {
|
||||
completed: 2009-05-16,
|
||||
run: 1,
|
||||
subject: 1,
|
||||
questionnaire: 1,
|
||||
}
|
||||
model: questionnaire.runinfohistory
|
||||
pk: 1
|
||||
- fields: {
|
||||
checks: '',
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '1',
|
||||
questionset: 1,
|
||||
text_de: '[de] Question 1',
|
||||
text_en: '[en] Question 1',
|
||||
type: open
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 1
|
||||
- fields: {
|
||||
checks: '',
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '2',
|
||||
questionset: 1,
|
||||
text_de: '[de] Question 2',
|
||||
text_en: '[en] Question 2',
|
||||
type: open-textfield
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 2
|
||||
- fields: {
|
||||
checks: '',
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '3',
|
||||
questionset: 1,
|
||||
text_de: '[de] Question 3',
|
||||
text_en: '[en] Question 3',
|
||||
type: choice-yesno
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 3
|
||||
- fields: {
|
||||
checks: '',
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '4',
|
||||
questionset: 1,
|
||||
text_de: '[de] Question 4',
|
||||
text_en: '[en] Question 4',
|
||||
type: choice-yesnodontknow
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 4
|
||||
- fields: {
|
||||
checks: required-yes,
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '5',
|
||||
questionset: 1,
|
||||
text_de: '[de] Question 5',
|
||||
text_en: '[en] Question 5',
|
||||
type: choice-yesnocomment
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 5
|
||||
- fields: {
|
||||
checks: required-no,
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '6',
|
||||
questionset: 1,
|
||||
text_de: '[de] Question 6',
|
||||
text_en: '[en] Question 6',
|
||||
type: choice-yesnocomment
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 6
|
||||
- fields: {
|
||||
checks: 'range=5-10 requiredif=3,yes',
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '7',
|
||||
questionset: 1,
|
||||
text_de: '[de] Question 7, requiredif 3,yes',
|
||||
text_en: '[en] Question 7, requiredif 3,yes',
|
||||
type: range
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 7
|
||||
- fields: {
|
||||
checks: 'units=day,week,month,year',
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '8',
|
||||
questionset: 1,
|
||||
text_de: '[de] Question 8',
|
||||
text_en: '[en] Question 8',
|
||||
type: timeperiod
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 8
|
||||
- fields: {
|
||||
checks: '',
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '9',
|
||||
questionset: 2,
|
||||
text_de: '[de] Question 9',
|
||||
text_en: '[en] Question 9',
|
||||
type: choice
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 9
|
||||
- fields: {
|
||||
checks: '',
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '10',
|
||||
questionset: 2,
|
||||
text_de: '[de] Question 10',
|
||||
text_en: '[en] Question 10',
|
||||
type: choice-freeform
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 10
|
||||
- fields: {
|
||||
checks: '',
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '11',
|
||||
questionset: 2,
|
||||
text_de: '[de] Question 11',
|
||||
text_en: '[en] Question 11',
|
||||
type: choice-multiple
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 11
|
||||
- fields: {
|
||||
checks: '',
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '12',
|
||||
questionset: 2,
|
||||
text_de: '[de] Question 12',
|
||||
text_en: '[en] Question 12',
|
||||
type: choice-multiple-freeform
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 12
|
||||
- fields: {
|
||||
checks: '',
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '12',
|
||||
questionset: 3,
|
||||
text_de: '[de] tag test always',
|
||||
text_en: '[en] tag test always',
|
||||
type: choice-multiple-freeform
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 13
|
||||
- fields: {
|
||||
checks: 'iftag=testtag',
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '12',
|
||||
questionset: 3,
|
||||
text_de: '[de] tag test checked',
|
||||
text_en: '[en] tag test checked',
|
||||
type: choice-multiple-freeform
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 14
|
||||
- fields: {
|
||||
checks: '',
|
||||
extra_de: '',
|
||||
extra_en: '',
|
||||
number: '12',
|
||||
questionset: 4,
|
||||
text_de: '[de] tag test always',
|
||||
text_en: '[en] tag test always',
|
||||
type: choice-multiple-freeform
|
||||
}
|
||||
model: questionnaire.question
|
||||
pk: 15
|
||||
- fields: {
|
||||
question: 9,
|
||||
sortid: 1,
|
||||
text_de: Option 1,
|
||||
text_en: Choice 1,
|
||||
value: q9_choice1
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 1
|
||||
- fields: {
|
||||
question: 9,
|
||||
sortid: 2,
|
||||
text_de: Option 2,
|
||||
text_en: Choice 2,
|
||||
value: q9_choice2
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 2
|
||||
- fields: {
|
||||
question: 9,
|
||||
sortid: 3,
|
||||
text_de: Option 3,
|
||||
text_en: Choice 3,
|
||||
value: q9_choice3
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 3
|
||||
- fields: {
|
||||
question: 9,
|
||||
sortid: 4,
|
||||
text_de: Option 4,
|
||||
text_en: Choice 4,
|
||||
value: q9_choice4
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 4
|
||||
- fields: {
|
||||
question: 10,
|
||||
sortid: 1,
|
||||
text_de: Option 1,
|
||||
text_en: Choice 1,
|
||||
value: q10_choice1
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 5
|
||||
- fields: {
|
||||
question: 10,
|
||||
sortid: 2,
|
||||
text_de: Option 2,
|
||||
text_en: Choice 2,
|
||||
value: q10_choice2
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 6
|
||||
- fields: {
|
||||
question: 10,
|
||||
sortid: 3,
|
||||
text_de: Option 3,
|
||||
text_en: Choice 3,
|
||||
value: q10_choice3
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 7
|
||||
- fields: {
|
||||
question: 10,
|
||||
sortid: 4,
|
||||
text_de: Option 4,
|
||||
text_en: Choice 4,
|
||||
value: q10_choice4
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 8
|
||||
- fields: {
|
||||
question: 11,
|
||||
sortid: 1,
|
||||
text_de: Option 1,
|
||||
text_en: Choice 1,
|
||||
value: q11_choice1
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 9
|
||||
- fields: {
|
||||
question: 11,
|
||||
sortid: 2,
|
||||
text_de: Option 2,
|
||||
text_en: Choice 2,
|
||||
value: q11_choice2
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 10
|
||||
- fields: {
|
||||
question: 11,
|
||||
sortid: 3,
|
||||
text_de: Option 3,
|
||||
text_en: Choice 3,
|
||||
value: q11_choice3
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 11
|
||||
- fields: {
|
||||
question: 11,
|
||||
sortid: 4,
|
||||
text_de: Option 4,
|
||||
text_en: Choice 4,
|
||||
value: q11_choice4
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 12
|
||||
- fields: {
|
||||
question: 12,
|
||||
sortid: 1,
|
||||
text_de: Option 1,
|
||||
text_en: Choice 1,
|
||||
value: q12_choice1
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 13
|
||||
- fields: {
|
||||
question: 12,
|
||||
sortid: 2,
|
||||
text_de: Option 2,
|
||||
text_en: Choice 2,
|
||||
value: q12_choice2
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 14
|
||||
- fields: {
|
||||
question: 12,
|
||||
sortid: 3,
|
||||
text_de: Option 3,
|
||||
text_en: Choice 3,
|
||||
value: q12_choice3
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 15
|
||||
- fields: {
|
||||
question: 12,
|
||||
sortid: 4,
|
||||
text_de: Option 4,
|
||||
text_en: Choice 4,
|
||||
value: q12_choice4
|
||||
}
|
||||
model: questionnaire.choice
|
||||
pk: 16
|
|
@ -1,58 +0,0 @@
|
|||
"""
|
||||
Wrapper for loading templates from the filesystem.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.template import TemplateDoesNotExist
|
||||
from django.utils._os import safe_join
|
||||
from django.utils import translation
|
||||
|
||||
def get_template_sources(template_name, template_dirs=None):
|
||||
"""
|
||||
Returns the absolute paths to "template_name", when appended to each
|
||||
directory in "template_dirs". Any paths that don't lie inside one of the
|
||||
template dirs are excluded from the result set, for security reasons.
|
||||
"""
|
||||
if not template_dirs:
|
||||
template_dirs = settings.TEMPLATE_DIRS
|
||||
for template_dir in template_dirs:
|
||||
try:
|
||||
yield safe_join(template_dir, template_name)
|
||||
except UnicodeDecodeError:
|
||||
# The template dir name was a bytestring that wasn't valid UTF-8.
|
||||
raise
|
||||
except ValueError:
|
||||
# The joined path was located outside of this particular
|
||||
# template_dir (it might be inside another one, so this isn't
|
||||
# fatal).
|
||||
pass
|
||||
|
||||
def _load_template_source(template_name, template_dirs=None):
|
||||
tried = []
|
||||
for filepath in get_template_sources(template_name, template_dirs):
|
||||
try:
|
||||
return (open(filepath).read().decode(settings.FILE_CHARSET), filepath)
|
||||
except IOError:
|
||||
tried.append(filepath)
|
||||
if tried:
|
||||
error_msg = "Tried %s" % tried
|
||||
else:
|
||||
error_msg = "Your TEMPLATE_DIRS setting is empty. Change it to point to at least one template directory."
|
||||
raise TemplateDoesNotExist, error_msg
|
||||
|
||||
def load_template_source(template_name, template_dirs=None):
|
||||
"""Assuming the current language is German.
|
||||
If template_name is index.$LANG.html, try index.de.html then index.html
|
||||
Also replaces .. with . when attempting fallback.
|
||||
"""
|
||||
if "$LANG" in template_name:
|
||||
lang = translation.get_language()
|
||||
try:
|
||||
t = template_name.replace("$LANG", lang)
|
||||
res = _load_template_source(t, template_dirs)
|
||||
return res
|
||||
except TemplateDoesNotExist:
|
||||
t = template_name.replace("$LANG", "").replace("..",".")
|
||||
return _load_template_source(t, template_dirs)
|
||||
return _load_template_source(template_name, template_dirs)
|
||||
load_template_source.is_usable = True
|
|
@ -1,17 +0,0 @@
|
|||
# vim: set fileencoding=utf-8
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^q/(?P<runcode>[^/]+)/(?P<qs>\d+)/$',
|
||||
views.questionnaire, name='questionset'),
|
||||
url(r'^q/([^/]+)/',
|
||||
views.questionnaire, name='questionset'),
|
||||
url(r'^q/manage/csv/(\d+)/',
|
||||
'views.export_csv),
|
||||
url(r'^q/manage/sendemail/(\d+)/$',
|
||||
views.send_email),
|
||||
url(r'^q/manage/manage/sendemails/$',
|
||||
views.send_emails),
|
||||
]
|
|
@ -1,192 +0,0 @@
|
|||
"""
|
||||
Basic Test Suite for Questionnaire Application
|
||||
|
||||
Unfortunately Django 1.0 only has TestCase and not TransactionTestCase
|
||||
so we can't test that a submitted page with an error does not have any
|
||||
answers submitted to the DB.
|
||||
"""
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from .models import *
|
||||
from datetime import datetime
|
||||
import os
|
||||
|
||||
class TypeTest(TestCase):
|
||||
fixtures = ( 'testQuestions.yaml', )
|
||||
urls = 'questionnaire.test_urls'
|
||||
|
||||
def setUp(self):
|
||||
self.ansdict1 = {
|
||||
'questionset_id' : '1',
|
||||
'question_1' : 'Open Answer 1',
|
||||
'question_2' : 'Open Answer 2\r\nMultiline',
|
||||
'question_3' : 'yes',
|
||||
'question_4' : 'dontknow',
|
||||
'question_5' : 'yes',
|
||||
'question_5_comment' : 'this comment is required because of required-yes check',
|
||||
'question_6' : 'no',
|
||||
'question_6_comment' : 'this comment is required because of required-no check',
|
||||
'question_7' : '5',
|
||||
'question_8_unit' : 'week',
|
||||
'question_8' : '2',
|
||||
}
|
||||
self.ansdict2 = {
|
||||
'questionset_id' : '2',
|
||||
'question_9' : 'q9_choice1', # choice
|
||||
'question_10' : '_entry_', # choice-freeform
|
||||
'question_10_comment' : 'my freeform',
|
||||
'question_11_multiple_2' : 'q11_choice2', # choice-multiple
|
||||
'question_11_multiple_4' : 'q11_choice4', # choice-multiple
|
||||
'question_12_multiple_1' : 'q12_choice1',# choice-multiple-freeform
|
||||
'question_12_more_1' : 'blah', # choice-multiple-freeform
|
||||
}
|
||||
runinfo = self.runinfo = RunInfo.objects.get(run__runid='test:test')
|
||||
self.runid = runinfo.run.runid
|
||||
self.subject_id = runinfo.subject_id
|
||||
|
||||
|
||||
def test010_redirect(self):
|
||||
"Check redirection from generic questionnaire to questionset"
|
||||
response = self.client.get('/q/test:test/')
|
||||
self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/')
|
||||
|
||||
|
||||
def test020_get_questionset_1(self):
|
||||
"Get first page of Questions"
|
||||
response = self.client.get('/q/test:test/1/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.template[0].name, 'questionnaire/questionset.html')
|
||||
|
||||
|
||||
def test030_language_setting(self):
|
||||
"Set the language and confirm it is set in DB"
|
||||
response = self.client.get('/q/test:test/1/', {"lang" : "en"})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/')
|
||||
response = self.client.get('/q/test:test/1/')
|
||||
assert "Don't Know" in response.content
|
||||
self.assertEqual(response.status_code, 200)
|
||||
runinfo = RunInfo.objects.get(run__runid='test:test')
|
||||
self.assertEqual(runinfo.subject.language, 'en')
|
||||
response = self.client.get('/q/test:test/1/', {"lang" : "de"})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/')
|
||||
response = self.client.get('/q/test:test/1/')
|
||||
assert "Weiss nicht" in response.content
|
||||
self.assertEqual(response.status_code, 200)
|
||||
runinfo = RunInfo.objects.get(run__runid='test:test')
|
||||
self.assertEqual(runinfo.subject.language, 'de')
|
||||
|
||||
|
||||
def test040_missing_question(self):
|
||||
"Post questions with a mandatory field missing"
|
||||
c = self.client
|
||||
ansdict = self.ansdict1.copy()
|
||||
del ansdict['question_3']
|
||||
response = c.post('/q/test:test/1/', ansdict)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
errors = response.context[-1]['errors']
|
||||
self.assertEqual(len(errors), 1) and errors.has_key('3')
|
||||
|
||||
|
||||
def test050_missing_question(self):
|
||||
"Post questions with a mandatory field missing"
|
||||
c = self.client
|
||||
ansdict = self.ansdict1.copy()
|
||||
del ansdict['question_5_comment']
|
||||
# first set language to english
|
||||
response = self.client.get('/q/test:test/1/', {"lang" : "en"})
|
||||
response = c.post('/q/test:test/1/', ansdict)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.context[-1]['errors']), 1)
|
||||
|
||||
|
||||
def test060_successful_questionnaire(self):
|
||||
"POST complete answers for QuestionSet 1"
|
||||
c = self.client
|
||||
ansdict1 = self.ansdict1
|
||||
runinfo = RunInfo.objects.get(run__runid='test:test')
|
||||
runid = runinfo.random = runinfo.run.runid = '1real'
|
||||
runinfo.save()
|
||||
runinfo.run.save()
|
||||
|
||||
response = c.get('/q/1real/1/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.template[0].name, 'questionnaire/questionset.html')
|
||||
response = c.post('/q/1real/', ansdict1)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response['Location'], 'http://testserver/q/1real/2/')
|
||||
"POST complete answers for QuestionSet 2"
|
||||
c = self.client
|
||||
|
||||
ansdict2 = self.ansdict2
|
||||
response = c.get('/q/1real/2/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.template[0].name, 'questionnaire/questionset.html')
|
||||
response = c.post('/q/1real/', ansdict2)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response['Location'], 'http://testserver/')
|
||||
|
||||
self.assertEqual(RunInfo.objects.filter(run__runid='1real').count(), 0)
|
||||
|
||||
# TODO: The format of these answers seems very strange to me. It was
|
||||
# simpler before I changed it to get the test to work.
|
||||
# I'll have to revisit this once I figure out how this is meant to work
|
||||
# for now it is more important to me that all tests pass
|
||||
|
||||
dbvalues = {
|
||||
'1' : u'["%s"]' % ansdict1['question_1'],
|
||||
'2' : u'["%s"]' % ansdict1['question_2'],
|
||||
'3' : u'["%s"]' % ansdict1['question_3'],
|
||||
'4' : u'["%s"]' % ansdict1['question_4'],
|
||||
'5' : u'["%s", ["%s"]]' % (ansdict1['question_5'], ansdict1['question_5_comment']),
|
||||
'6' : u'["%s", ["%s"]]' % (ansdict1['question_6'], ansdict1['question_6_comment']),
|
||||
'7' : u'[%s]' % ansdict1['question_7'],
|
||||
'8' : u'%s; %s' % (ansdict1['question_8'], ansdict1['question_8_unit']),
|
||||
'9' : u'["q9_choice1"]',
|
||||
'10' : u'[["my freeform"]]',
|
||||
'11' : u'["q11_choice2", "q11_choice4"]',
|
||||
'12' : u'["q12_choice1", ["blah"]]',
|
||||
}
|
||||
for k, v in dbvalues.items():
|
||||
ans = Answer.objects.get(run__runid=runid, subject__id=self.subject_id,
|
||||
question__number=k)
|
||||
|
||||
v = v.replace('\r', '\\r').replace('\n', '\\n')
|
||||
self.assertEqual(ans.answer, v)
|
||||
|
||||
def test070_tags(self):
|
||||
c = self.client
|
||||
|
||||
# the first questionset in questionnaire 2 is always shown,
|
||||
# but one of its 2 questions is tagged with testtag
|
||||
with_tags = c.get('/q/test:withtags/1/')
|
||||
|
||||
# so we'll get two questions shown if the run is tagged
|
||||
self.assertEqual(with_tags.status_code, 200)
|
||||
self.assertEqual(len(with_tags.context['qlist']), 2)
|
||||
|
||||
# one question, if the run is not tagged
|
||||
without_tags = c.get('/q/test:withouttags/1/')
|
||||
|
||||
self.assertEqual(without_tags.status_code, 200)
|
||||
self.assertEqual(len(without_tags.context['qlist']), 1)
|
||||
|
||||
# the second questionset is only shown if the run is tagged
|
||||
with_tags = c.get('/q/test:withtags/2/')
|
||||
|
||||
self.assertEqual(with_tags.status_code, 200)
|
||||
self.assertEqual(len(with_tags.context['qlist']), 1)
|
||||
|
||||
# meaning it'll be skipped on the untagged run
|
||||
without_tags = c.get('/q/test.withouttags/2/')
|
||||
|
||||
self.assertEqual(without_tags.status_code, 302) # redirect
|
||||
|
||||
# the progress values of the first questionset should reflect
|
||||
# the fact that in one run there's only one questionset
|
||||
with_tags = c.get('/q/test:withtags/1/')
|
||||
without_tags = c.get('/q/test:withouttags/1/')
|
||||
|
||||
self.assertEqual(with_tags.context['progress'], 50)
|
||||
self.assertEqual(without_tags.context['progress'], 100)
|
|
@ -1,158 +0,0 @@
|
|||
# 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 <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2009-06-04 10:11+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: models.py:16
|
||||
msgid "Active"
|
||||
msgstr "Aktiv"
|
||||
|
||||
#: models.py:17
|
||||
msgid "Inactive"
|
||||
msgstr "Inaktiv"
|
||||
|
||||
#: models.py:22
|
||||
msgid "State"
|
||||
msgstr "Status"
|
||||
|
||||
#: models.py:24
|
||||
msgid "Surname"
|
||||
msgstr "Nachname"
|
||||
|
||||
#: models.py:26
|
||||
msgid "Given name"
|
||||
msgstr "Vorname"
|
||||
|
||||
#: models.py:27
|
||||
msgid "Email"
|
||||
msgstr "Email"
|
||||
|
||||
#: models.py:29
|
||||
msgid "Gender"
|
||||
msgstr "Geschlecht"
|
||||
|
||||
#: models.py:30
|
||||
msgid "Unset"
|
||||
msgstr "Keine Angabe"
|
||||
|
||||
#: models.py:31
|
||||
msgid "Male"
|
||||
msgstr "Männlich"
|
||||
|
||||
#: models.py:32
|
||||
msgid "Female"
|
||||
msgstr "Weiblich"
|
||||
|
||||
#: models.py:35
|
||||
msgid "Next Run"
|
||||
msgstr "Nächste Ausführung"
|
||||
|
||||
#: models.py:37
|
||||
msgid "Form Type"
|
||||
msgstr "Art des Formulars"
|
||||
|
||||
#: models.py:39
|
||||
msgid "Subject receives emails"
|
||||
msgstr "Person bekommt Emails"
|
||||
|
||||
#: models.py:40
|
||||
msgid "Subject is sent paper form"
|
||||
msgstr "Person bekommt Papierformular"
|
||||
|
||||
#: models.py:43
|
||||
msgid "Language"
|
||||
msgstr "Sprache"
|
||||
|
||||
#: qprocessors/choice.py:38 qprocessors/simple.py:66
|
||||
msgid "You must select an option"
|
||||
msgstr "Sie müssen eine Option auswählen"
|
||||
|
||||
#: qprocessors/choice.py:42 qprocessors/simple.py:70 qprocessors/simple.py:72
|
||||
#: qprocessors/simple.py:74 qprocessors/simple.py:77
|
||||
#: qprocessors/timeperiod.py:56
|
||||
msgid "Field cannot be blank"
|
||||
msgstr "Dieses Feld muss ausgefüllt werden"
|
||||
|
||||
#: qprocessors/choice.py:46
|
||||
msgid "Invalid option!"
|
||||
msgstr "Ungültige Angabe"
|
||||
|
||||
#: qprocessors/choice.py:107
|
||||
#, python-format
|
||||
msgid "You must select at least %d option"
|
||||
msgid_plural "You must select at least %d options"
|
||||
msgstr[0] "Sie müssen mindestens %d Option auswählen"
|
||||
msgstr[1] "Sie müssen mindestens %d Optionen auswählen"
|
||||
|
||||
#: qprocessors/custom.py:27
|
||||
msgid "Processor not defined for this question"
|
||||
msgstr ""
|
||||
|
||||
#: qprocessors/range.py:35
|
||||
msgid "Out of range"
|
||||
msgstr ""
|
||||
|
||||
#: qprocessors/timeperiod.py:5
|
||||
msgid "second(s)"
|
||||
msgstr "Sekunde(n)"
|
||||
|
||||
#: qprocessors/timeperiod.py:6
|
||||
msgid "minute(s)"
|
||||
msgstr "Minute(n)"
|
||||
|
||||
#: qprocessors/timeperiod.py:7
|
||||
msgid "hour(s)"
|
||||
msgstr "Stunde(n)"
|
||||
|
||||
#: qprocessors/timeperiod.py:8
|
||||
msgid "day(s)"
|
||||
msgstr "Tag(e)"
|
||||
|
||||
#: qprocessors/timeperiod.py:9
|
||||
msgid "week(s)"
|
||||
msgstr "Woche(n)"
|
||||
|
||||
#: qprocessors/timeperiod.py:10
|
||||
msgid "month(s)"
|
||||
msgstr "Monat(e)"
|
||||
|
||||
#: qprocessors/timeperiod.py:11
|
||||
msgid "year(s)"
|
||||
msgstr "Jahr(e)"
|
||||
|
||||
#: qprocessors/timeperiod.py:42 qprocessors/timeperiod.py:58
|
||||
msgid "Invalid time period"
|
||||
msgstr "Ungültiger Zeitraum"
|
||||
|
||||
#: qprocessors/timeperiod.py:48
|
||||
msgid "Time period must be a whole number"
|
||||
msgstr "Angabe muss aus einer ganzen Zahl bestehen"
|
||||
|
||||
#: templates/questionnaire/choice-yesnocomment.html:3
|
||||
msgid "Yes"
|
||||
msgstr "Ja"
|
||||
|
||||
#: templates/questionnaire/choice-yesnocomment.html:5
|
||||
msgid "No"
|
||||
msgstr "Nein"
|
||||
|
||||
#: templates/questionnaire/choice-yesnocomment.html:8
|
||||
msgid "Don't Know"
|
||||
msgstr "Weiss nicht"
|
||||
|
||||
#: templates/questionnaire/questionset.html:72
|
||||
msgid "Continue"
|
||||
msgstr "Weiter"
|
|
@ -1,160 +0,0 @@
|
|||
# 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 <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2009-06-04 10:11+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: models.py:16
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:17
|
||||
msgid "Inactive"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:22
|
||||
msgid "State"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:24
|
||||
msgid "Surname"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:26
|
||||
msgid "Given name"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:27
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:29
|
||||
msgid "Gender"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:30
|
||||
msgid "Unset"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:31
|
||||
msgid "Male"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:32
|
||||
msgid "Female"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:35
|
||||
msgid "Next Run"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:37
|
||||
msgid "Form Type"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:39
|
||||
msgid "Subject receives emails"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:40
|
||||
msgid "Subject is sent paper form"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:43
|
||||
msgid "Language"
|
||||
msgstr ""
|
||||
|
||||
#: qprocessors/choice.py:38 qprocessors/simple.py:66
|
||||
msgid "You must select an option"
|
||||
msgstr "Veuillez choisir une option!"
|
||||
|
||||
#: qprocessors/choice.py:42 qprocessors/simple.py:70 qprocessors/simple.py:72
|
||||
#: qprocessors/simple.py:74 qprocessors/simple.py:77
|
||||
#: qprocessors/timeperiod.py:56
|
||||
msgid "Field cannot be blank"
|
||||
msgstr ""
|
||||
"Merci de donner une réponse à cette question; ce champs ne peut rester vide."
|
||||
|
||||
#: qprocessors/choice.py:46
|
||||
#, fuzzy
|
||||
msgid "Invalid option!"
|
||||
msgstr "Option non valable"
|
||||
|
||||
#: qprocessors/choice.py:107
|
||||
#, python-format
|
||||
msgid "You must select at least %d option"
|
||||
msgid_plural "You must select at least %d options"
|
||||
msgstr[0] "Vous devez cocher au moins %d option"
|
||||
msgstr[1] "Vous devez cocher au moins %d options"
|
||||
|
||||
#: qprocessors/custom.py:27
|
||||
msgid "Processor not defined for this question"
|
||||
msgstr ""
|
||||
|
||||
#: qprocessors/range.py:35
|
||||
msgid "Out of range"
|
||||
msgstr ""
|
||||
|
||||
#: qprocessors/timeperiod.py:5
|
||||
msgid "second(s)"
|
||||
msgstr "seconde(s)"
|
||||
|
||||
#: qprocessors/timeperiod.py:6
|
||||
msgid "minute(s)"
|
||||
msgstr "minute(s)"
|
||||
|
||||
#: qprocessors/timeperiod.py:7
|
||||
msgid "hour(s)"
|
||||
msgstr "heure(s)"
|
||||
|
||||
#: qprocessors/timeperiod.py:8
|
||||
msgid "day(s)"
|
||||
msgstr "jour(s)"
|
||||
|
||||
#: qprocessors/timeperiod.py:9
|
||||
msgid "week(s)"
|
||||
msgstr "semaine(s)"
|
||||
|
||||
#: qprocessors/timeperiod.py:10
|
||||
msgid "month(s)"
|
||||
msgstr "mois"
|
||||
|
||||
#: qprocessors/timeperiod.py:11
|
||||
msgid "year(s)"
|
||||
msgstr "année(s)"
|
||||
|
||||
#: qprocessors/timeperiod.py:42 qprocessors/timeperiod.py:58
|
||||
msgid "Invalid time period"
|
||||
msgstr "Période non valable"
|
||||
|
||||
#: qprocessors/timeperiod.py:48
|
||||
msgid "Time period must be a whole number"
|
||||
msgstr "La période doit être un chiffre entier"
|
||||
|
||||
#: templates/questionnaire/choice-yesnocomment.html:3
|
||||
msgid "Yes"
|
||||
msgstr "Oui"
|
||||
|
||||
#: templates/questionnaire/choice-yesnocomment.html:5
|
||||
msgid "No"
|
||||
msgstr "Non"
|
||||
|
||||
#: templates/questionnaire/choice-yesnocomment.html:8
|
||||
msgid "Don't Know"
|
||||
msgstr "Ne sais pas"
|
||||
|
||||
#: templates/questionnaire/questionset.html:72
|
||||
msgid "Continue"
|
||||
msgstr "Continuer"
|
|
@ -1,174 +0,0 @@
|
|||
# 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 <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-11-29 15:51+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: models.py:16
|
||||
msgid "Active"
|
||||
msgstr "Attivo"
|
||||
|
||||
#: models.py:17
|
||||
msgid "Inactive"
|
||||
msgstr "Inattivo"
|
||||
|
||||
#: models.py:22
|
||||
msgid "State"
|
||||
msgstr "Stato"
|
||||
|
||||
#: models.py:24
|
||||
msgid "Surname"
|
||||
msgstr "Cognome"
|
||||
|
||||
#: models.py:26
|
||||
msgid "Given name"
|
||||
msgstr "Nome"
|
||||
|
||||
#: models.py:27
|
||||
msgid "Email"
|
||||
msgstr "Email"
|
||||
|
||||
#: models.py:29
|
||||
msgid "Gender"
|
||||
msgstr "Sesso"
|
||||
|
||||
#: models.py:30
|
||||
msgid "Unset"
|
||||
msgstr "Nessun parere"
|
||||
|
||||
#: models.py:31
|
||||
msgid "Male"
|
||||
msgstr "Maschio"
|
||||
|
||||
#: models.py:32
|
||||
msgid "Female"
|
||||
msgstr "Femmina"
|
||||
|
||||
#: models.py:35
|
||||
msgid "Next Run"
|
||||
msgstr "Prossimo"
|
||||
|
||||
#: models.py:37
|
||||
msgid "Form Type"
|
||||
msgstr "Tipo di domanda"
|
||||
|
||||
#: models.py:39
|
||||
msgid "Subject receives emails"
|
||||
msgstr "Persona contattata via email"
|
||||
|
||||
#: models.py:40
|
||||
msgid "Subject is sent paper form"
|
||||
msgstr "Persona contattata via questionario cartaceo"
|
||||
|
||||
#: models.py:43
|
||||
msgid "Language"
|
||||
msgstr "Lingua"
|
||||
|
||||
#: views.py:608 templates/questionnaire/choice-yesnocomment.html:2
|
||||
msgid "Yes"
|
||||
msgstr "Si"
|
||||
|
||||
#: views.py:608 templates/questionnaire/choice-yesnocomment.html:3
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
#: views.py:610 templates/questionnaire/choice-yesnocomment.html:5
|
||||
msgid "Don't Know"
|
||||
msgstr "Non so"
|
||||
|
||||
#: qprocessors/choice.py:39 qprocessors/simple.py:67
|
||||
msgid "You must select an option"
|
||||
msgstr "Devi scegliere un'opzione"
|
||||
|
||||
#: qprocessors/choice.py:43 qprocessors/simple.py:71 qprocessors/simple.py:73
|
||||
#: qprocessors/simple.py:75 qprocessors/simple.py:78
|
||||
#: qprocessors/timeperiod.py:56
|
||||
msgid "Field cannot be blank"
|
||||
msgstr "Domanda richiesta"
|
||||
|
||||
#: qprocessors/choice.py:48
|
||||
msgid "Invalid option!"
|
||||
msgstr "Opzione non valida"
|
||||
|
||||
#: qprocessors/choice.py:110
|
||||
#, python-format
|
||||
msgid "You must select at least %d option"
|
||||
msgid_plural "You must select at least %d options"
|
||||
msgstr[0] "Devi sciegliere almeno %d opzione"
|
||||
msgstr[1] "Devi sciegliere almeno %d opzioni"
|
||||
|
||||
#: qprocessors/custom.py:27
|
||||
msgid "Processor not defined for this question"
|
||||
msgstr ""
|
||||
|
||||
#: qprocessors/range.py:36
|
||||
msgid "Out of range"
|
||||
msgstr ""
|
||||
|
||||
#: qprocessors/timeperiod.py:5
|
||||
msgid "second(s)"
|
||||
msgstr "secondi"
|
||||
|
||||
#: qprocessors/timeperiod.py:6
|
||||
msgid "minute(s)"
|
||||
msgstr "minuti"
|
||||
|
||||
#: qprocessors/timeperiod.py:7
|
||||
msgid "hour(s)"
|
||||
msgstr "ora(e)"
|
||||
|
||||
#: qprocessors/timeperiod.py:8
|
||||
msgid "day(s)"
|
||||
msgstr "giorno(i)"
|
||||
|
||||
#: qprocessors/timeperiod.py:9
|
||||
msgid "week(s)"
|
||||
msgstr "settimana(e)"
|
||||
|
||||
#: qprocessors/timeperiod.py:10
|
||||
msgid "month(s)"
|
||||
msgstr "mese(i)"
|
||||
|
||||
#: qprocessors/timeperiod.py:11
|
||||
msgid "year(s)"
|
||||
msgstr "anno(i)"
|
||||
|
||||
#: qprocessors/timeperiod.py:42 qprocessors/timeperiod.py:58
|
||||
msgid "Invalid time period"
|
||||
msgstr "Intervallo di tempo non valido"
|
||||
|
||||
#: qprocessors/timeperiod.py:48
|
||||
msgid "Time period must be a whole number"
|
||||
msgstr "Angabe muss aus einer ganzen Zahl bestehen"
|
||||
|
||||
#: templates/admin/questionnaire/question/change_form.html:10
|
||||
msgid "Choices"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/questionnaire/question/change_form.html:12
|
||||
msgid "Sortid determines the choice order."
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/questionnaire/question/change_form.html:13
|
||||
msgid ""
|
||||
"Short values will appear in exported data and are \n"
|
||||
"referenced in 'Additional checks' fields. Numbers, letters \n"
|
||||
"or single words are valid."
|
||||
msgstr ""
|
||||
|
||||
#: templates/questionnaire/questionset.html:77
|
||||
msgid "Continue"
|
||||
msgstr "Continua"
|
Binary file not shown.
|
@ -1,190 +0,0 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# Victor Safronivich <vsafronovich@gmail.com>, 2012.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 2.0\n"
|
||||
"Report-Msgid-Bugs-To: Victor Safronivich <vsafronovich@gmail.com>\n"
|
||||
"POT-Creation-Date: 2012-08-24 09:15+0600\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Victor Safronivich <vsafronovich@gmail.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\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%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
|
||||
|
||||
#: .\models.py:16
|
||||
msgid "Active"
|
||||
msgstr "Активно"
|
||||
|
||||
#: .\models.py:17
|
||||
msgid "Inactive"
|
||||
msgstr "Не активно"
|
||||
|
||||
#: .\models.py:22
|
||||
msgid "State"
|
||||
msgstr "Состояние"
|
||||
|
||||
#: .\models.py:24
|
||||
msgid "Surname"
|
||||
msgstr "Фамилия"
|
||||
|
||||
#: .\models.py:26
|
||||
msgid "Given name"
|
||||
msgstr "Имя"
|
||||
|
||||
#: .\models.py:27
|
||||
msgid "Email"
|
||||
msgstr "Эл. почта"
|
||||
|
||||
#: .\models.py:29
|
||||
msgid "Gender"
|
||||
msgstr "Пол"
|
||||
|
||||
#: .\models.py:30
|
||||
msgid "Unset"
|
||||
msgstr "Неважно"
|
||||
|
||||
#: .\models.py:31
|
||||
msgid "Male"
|
||||
msgstr "Мужской"
|
||||
|
||||
#: .\models.py:32
|
||||
msgid "Female"
|
||||
msgstr "Женский"
|
||||
|
||||
#: .\models.py:35
|
||||
msgid "Next Run"
|
||||
msgstr "Следующая попытка"
|
||||
|
||||
#: .\models.py:37
|
||||
msgid "Form Type"
|
||||
msgstr "Тип формы"
|
||||
|
||||
#: .\models.py:39
|
||||
msgid "Subject receives emails"
|
||||
msgstr "Тема полученного письма"
|
||||
|
||||
#: .\models.py:40
|
||||
msgid "Subject is sent paper form"
|
||||
msgstr ""
|
||||
|
||||
#: .\models.py:43
|
||||
msgid "Language"
|
||||
msgstr "Язык"
|
||||
|
||||
#: .\views.py:805 .\templates\questionnaire\choice-yesnocomment.html.py:10
|
||||
msgid "Yes"
|
||||
msgstr "Да"
|
||||
|
||||
#: .\views.py:805 .\templates\questionnaire\choice-yesnocomment.html.py:18
|
||||
msgid "No"
|
||||
msgstr "Нет"
|
||||
|
||||
#: .\views.py:807 .\templates\questionnaire\choice-yesnocomment.html.py:27
|
||||
msgid "Don't Know"
|
||||
msgstr "Не известно"
|
||||
|
||||
#: .\qprocessors\choice.py:38 .\qprocessors\simple.py:68
|
||||
msgid "You must select an option"
|
||||
msgstr "Вы должны выбрать ответ"
|
||||
|
||||
#: .\qprocessors\choice.py:42 .\qprocessors\simple.py:72
|
||||
#: .\qprocessors\simple.py:74 .\qprocessors\simple.py:76
|
||||
#: .\qprocessors\simple.py:79 .\qprocessors\timeperiod.py:56
|
||||
msgid "Field cannot be blank"
|
||||
msgstr "Поле не может быть пустым"
|
||||
|
||||
#: .\qprocessors\choice.py:47
|
||||
msgid "Invalid option!"
|
||||
msgstr "Не правильный ответ!"
|
||||
|
||||
#: .\qprocessors\choice.py:108
|
||||
#, python-format
|
||||
msgid "You must select at least %d option"
|
||||
msgid_plural "You must select at least %d options"
|
||||
msgstr[0] "Вы должны выбрать как минимум %d ответ"
|
||||
msgstr[1] "Вы должны выбрать как минимум %d ответа"
|
||||
msgstr[2] "Вы должны выбрать как минимум %d ответов"
|
||||
|
||||
#: .\qprocessors\custom.py:27
|
||||
msgid "Processor not defined for this question"
|
||||
msgstr "Для этого вопроса не определен обработчик"
|
||||
|
||||
#: .\qprocessors\range.py:41
|
||||
msgid "Out of range"
|
||||
msgstr "Вне диапазона значений"
|
||||
|
||||
#: .\qprocessors\timeperiod.py:5
|
||||
msgid "second(s)"
|
||||
msgstr "Секунды"
|
||||
|
||||
#: .\qprocessors\timeperiod.py:6
|
||||
msgid "minute(s)"
|
||||
msgstr "Минуты"
|
||||
|
||||
#: .\qprocessors\timeperiod.py:7
|
||||
msgid "hour(s)"
|
||||
msgstr "часов"
|
||||
|
||||
#: .\qprocessors\timeperiod.py:8
|
||||
msgid "day(s)"
|
||||
msgstr "дней"
|
||||
|
||||
#: .\qprocessors\timeperiod.py:9
|
||||
msgid "week(s)"
|
||||
msgstr "недель"
|
||||
|
||||
#: .\qprocessors\timeperiod.py:10
|
||||
msgid "month(s)"
|
||||
msgstr "месяцев"
|
||||
|
||||
#: .\qprocessors\timeperiod.py:11
|
||||
msgid "year(s)"
|
||||
msgstr "лет"
|
||||
|
||||
#: .\qprocessors\timeperiod.py:42 .\qprocessors\timeperiod.py:58
|
||||
msgid "Invalid time period"
|
||||
msgstr "Неправильный временной интервал"
|
||||
|
||||
#: .\qprocessors\timeperiod.py:48
|
||||
msgid "Time period must be a whole number"
|
||||
msgstr "Временной интервал должен быть целым числом"
|
||||
|
||||
#: .\templates\admin\questionnaire\question\change_form.html.py:10
|
||||
msgid "Choices"
|
||||
msgstr "Варианты ответов"
|
||||
|
||||
#: .\templates\admin\questionnaire\question\change_form.html.py:12
|
||||
msgid "Sortid determines the choice order."
|
||||
msgstr "Поле обозначает порядок ответов"
|
||||
|
||||
#: .\templates\admin\questionnaire\question\change_form.html.py:13
|
||||
msgid ""
|
||||
"Short values will appear in exported data and are \n"
|
||||
"referenced in 'Additional checks' fields. Numbers, letters \n"
|
||||
"or single words are valid."
|
||||
msgstr ""
|
||||
"Краткие значения появятся в данных экспорта. \n"
|
||||
"Допустимы числа, буквы или простые слова."
|
||||
|
||||
#: .\templates\questionnaire\choice-yesnocomment.html.py:35
|
||||
msgid "comment"
|
||||
msgstr "комментарий"
|
||||
|
||||
#: .\templates\questionnaire\questionset.html.py:58
|
||||
msgid "edit"
|
||||
msgstr "редактировать"
|
||||
|
||||
#: .\templates\questionnaire\questionset.html.py:95
|
||||
msgid "Continue"
|
||||
msgstr "далее"
|
||||
|
||||
#: .\templates\questionnaire\questionset.html.py:100
|
||||
msgid "return to previous page"
|
||||
msgstr "вернуть на предыдущую страницу"
|
|
@ -1,14 +0,0 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from ...models import Landing
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "make survey nonces with the specified label"
|
||||
args = "<how_many> <label>"
|
||||
|
||||
def handle(self, how_many=1, label="no label yet", **options):
|
||||
how_many=int(how_many)
|
||||
while how_many > 0:
|
||||
landing = Landing.objects.create(label = label)
|
||||
print landing.nonce
|
||||
how_many -= 1
|
|
@ -1,8 +0,0 @@
|
|||
from django.core.management.base import NoArgsCommand
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
def handle_noargs(self, **options):
|
||||
from ...emails import send_emails
|
||||
res = send_emails()
|
||||
if res:
|
||||
print res
|
|
@ -1,208 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Answer',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('runid', models.CharField(help_text='The RunID (ie. year)', max_length=32, verbose_name='RunID')),
|
||||
('answer', models.TextField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Choice',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('sortid', models.IntegerField()),
|
||||
('value', models.CharField(max_length=64, verbose_name='Short Value')),
|
||||
('text_en', models.CharField(max_length=200, null=True, verbose_name='Choice Text', blank=True)),
|
||||
('tags', models.CharField(max_length=64, verbose_name='Tags', blank=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DBStylesheet',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('inclusion_tag', models.CharField(max_length=128)),
|
||||
('content', models.TextField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='GlobalStyles',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('content', models.TextField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Landing',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('nonce', models.CharField(max_length=32, null=True, blank=True)),
|
||||
('object_id', models.PositiveIntegerField(null=True, blank=True)),
|
||||
('label', models.CharField(max_length=64, blank=True)),
|
||||
('content_type', models.ForeignKey(related_name='landings', blank=True, to='contenttypes.ContentType', null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Question',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('number', models.CharField(help_text=b'eg. <tt>1</tt>, <tt>2a</tt>, <tt>2b</tt>, <tt>3c</tt><br /> Number is also used for ordering questions.', max_length=8)),
|
||||
('sort_id', models.IntegerField(help_text=b'Questions within a questionset are sorted by sort order first, question number second', null=True, blank=True)),
|
||||
('text_en', models.TextField(null=True, verbose_name='Text', blank=True)),
|
||||
('type', models.CharField(help_text="Determines the means of answering the question. An open question gives the user a single-line textfield, multiple-choice gives the user a number of choices he/she can choose from. If a question is multiple-choice, enter the choices this user can choose from below'.", max_length=32, verbose_name='Type of question', choices=[(b'open', b'Open Answer, single line [input]'), (b'open-textfield', b'Open Answer, multi-line [textarea]'), (b'choice-yesno', b'Yes/No Choice [radio]'), (b'choice-yesnocomment', b'Yes/No Choice with optional comment [radio, input]'), (b'choice-yesnodontknow', b"Yes/No/Don't know Choice [radio]"), (b'choice-yesno-optional', b'Optional Yes/No Choice [radio]'), (b'choice-yesnocomment-optional', b'Optional Yes/No Choice with optional comment [radio, input]'), (b'choice-yesnodontknow-optional', b"Optional Yes/No/Don't know Choice [radio]"), (b'comment', b'Comment Only'), (b'choice', b'Choice [radio]'), (b'choice-freeform', b'Choice with a freeform option [radio]'), (b'choice-optional', b'Optional choice [radio]'), (b'choice-freeform-optional', b'Optional choice with a freeform option [radio]'), (b'dropdown', b'Dropdown choice [select]'), (b'choice-multiple', b'Multiple-Choice, Multiple-Answers [checkbox]'), (b'choice-multiple-freeform', b'Multiple-Choice, Multiple-Answers, plus freeform [checkbox, input]'), (b'choice-multiple-values', b'Multiple-Choice, Multiple-Answers [checkboxes], plus value box [input] for each selected answer'), (b'range', b'Range of numbers [select]'), (b'number', b'Number [input]'), (b'timeperiod', b'Time Period [input, select]'), (b'custom', b'Custom field'), (b'sameas', b'Same as Another Question (put sameas=question.number in checks or sameasid=question.id)')])),
|
||||
('extra_en', models.CharField(help_text='Extra information (use on question type)', max_length=512, null=True, verbose_name='Extra information', blank=True)),
|
||||
('checks', models.CharField(help_text=b'Additional checks to be performed for this value (space separated) <br /><br />For text fields, <tt>required</tt> is a valid check.<br />For yes/no choice, <tt>required</tt>, <tt>required-yes</tt>, and <tt>required-no</tt> are valid.<br /><br />If this question is required only if another question\'s answer is something specific, use <tt>requiredif="QuestionNumber,Value"</tt> or <tt>requiredif="QuestionNumber,!Value"</tt> for anything but a specific value. You may also combine tests appearing in <tt>requiredif</tt> by joining them with the words <tt>and</tt> or <tt>or</tt>, eg. <tt>requiredif="Q1,A or Q2,B"</tt>', max_length=512, null=True, verbose_name='Additional checks', blank=True)),
|
||||
('footer_en', models.TextField(help_text=b'Footer rendered below the question', null=True, verbose_name='Footer', blank=True)),
|
||||
('parse_html', models.BooleanField(default=False, verbose_name=b'Render html in Footer?')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Questionnaire',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=128)),
|
||||
('redirect_url', models.CharField(default=b'', help_text=b"URL to redirect to when Questionnaire is complete. Macros: $SUBJECTID, $RUNID, $LANG. Leave blank to render the 'complete.$LANG.html' template.", max_length=128, blank=True)),
|
||||
('html', models.TextField(verbose_name='Html', blank=True)),
|
||||
('parse_html', models.BooleanField(default=False, verbose_name=b'Render html instead of name for survey?')),
|
||||
('admin_access_only', models.BooleanField(default=False, verbose_name=b'Only allow access to logged in users? (This allows entering paper surveys without allowing new external submissions)')),
|
||||
],
|
||||
options={
|
||||
'permissions': (('export', 'Can export questionnaire answers'), ('management', 'Management Tools')),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='QuestionSet',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('sortid', models.IntegerField()),
|
||||
('heading', models.CharField(max_length=64)),
|
||||
('checks', models.CharField(help_text=b'Current options are \'femaleonly\' or \'maleonly\' and shownif="QuestionNumber,Answer" which takes the same format as <tt>requiredif</tt> for questions.', max_length=256, blank=True)),
|
||||
('text_en', models.TextField(help_text=b'HTML or Text', null=True, verbose_name='Text', blank=True)),
|
||||
('parse_html', models.BooleanField(default=False, verbose_name=b'Render html in heading?')),
|
||||
('questionnaire', models.ForeignKey(to='questionnaire.Questionnaire')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RunInfo',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('random', models.CharField(max_length=32)),
|
||||
('runid', models.CharField(max_length=32)),
|
||||
('emailcount', models.IntegerField(default=0)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('emailsent', models.DateTimeField(null=True, blank=True)),
|
||||
('lastemailerror', models.CharField(max_length=64, null=True, blank=True)),
|
||||
('state', models.CharField(max_length=16, null=True, blank=True)),
|
||||
('cookies', models.TextField(null=True, blank=True)),
|
||||
('tags', models.TextField(help_text='Tags active on this run, separated by commas', blank=True)),
|
||||
('skipped', models.TextField(help_text='A comma sepearted list of questions to skip', blank=True)),
|
||||
('landing', models.ForeignKey(blank=True, to='questionnaire.Landing', null=True)),
|
||||
('questionset', models.ForeignKey(blank=True, to='questionnaire.QuestionSet', null=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Run Info',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RunInfoHistory',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('runid', models.CharField(max_length=32)),
|
||||
('completed', models.DateTimeField()),
|
||||
('tags', models.TextField(help_text='Tags used on this run, separated by commas', blank=True)),
|
||||
('skipped', models.TextField(help_text='A comma sepearted list of questions skipped by this run', blank=True)),
|
||||
('landing', models.ForeignKey(blank=True, to='questionnaire.Landing', null=True)),
|
||||
('questionnaire', models.ForeignKey(to='questionnaire.Questionnaire')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Run Info History',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Subject',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('state', models.CharField(default=b'inactive', max_length=16, verbose_name='State', choices=[(b'active', 'Active'), (b'inactive', 'Inactive')])),
|
||||
('anonymous', models.BooleanField(default=False)),
|
||||
('ip_address', models.GenericIPAddressField(null=True, blank=True)),
|
||||
('surname', models.CharField(max_length=64, null=True, verbose_name='Surname', blank=True)),
|
||||
('givenname', models.CharField(max_length=64, null=True, verbose_name='Given name', blank=True)),
|
||||
('email', models.EmailField(max_length=254, null=True, verbose_name='Email', blank=True)),
|
||||
('gender', models.CharField(default=b'unset', max_length=8, verbose_name='Gender', blank=True, choices=[(b'unset', 'Unset'), (b'male', 'Male'), (b'female', 'Female')])),
|
||||
('nextrun', models.DateField(null=True, verbose_name='Next Run', blank=True)),
|
||||
('formtype', models.CharField(default=b'email', max_length=16, verbose_name='Form Type', choices=[(b'email', 'Subject receives emails'), (b'paperform', 'Subject is sent paper form')])),
|
||||
('language', models.CharField(default=b'en-us', max_length=5, verbose_name='Language', choices=[(b'en', b'English')])),
|
||||
],
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='subject',
|
||||
index_together=set([('givenname', 'surname')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='runinfohistory',
|
||||
name='subject',
|
||||
field=models.ForeignKey(to='questionnaire.Subject'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='runinfo',
|
||||
name='subject',
|
||||
field=models.ForeignKey(to='questionnaire.Subject'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='questionset',
|
||||
field=models.ForeignKey(to='questionnaire.QuestionSet'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='landing',
|
||||
name='questionnaire',
|
||||
field=models.ForeignKey(related_name='landings', blank=True, to='questionnaire.Questionnaire', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='choice',
|
||||
name='question',
|
||||
field=models.ForeignKey(to='questionnaire.Question'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='answer',
|
||||
name='question',
|
||||
field=models.ForeignKey(help_text='The question that this is an answer to', to='questionnaire.Question'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='answer',
|
||||
name='subject',
|
||||
field=models.ForeignKey(help_text='The user who supplied this answer', to='questionnaire.Subject'),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='runinfo',
|
||||
index_together=set([('random',)]),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='questionset',
|
||||
index_together=set([('questionnaire', 'sortid'), ('sortid',)]),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='question',
|
||||
index_together=set([('number', 'questionset')]),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='choice',
|
||||
index_together=set([('value',)]),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='answer',
|
||||
index_together=set([('subject', 'runid', 'id'), ('subject', 'runid')]),
|
||||
),
|
||||
]
|
|
@ -1,36 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Run',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('runid', models.CharField(max_length=32, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='answer',
|
||||
name='run',
|
||||
field=models.ForeignKey(related_name='answers', to='questionnaire.Run', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='runinfo',
|
||||
name='run',
|
||||
field=models.ForeignKey(related_name='run_infos', to='questionnaire.Run', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='runinfohistory',
|
||||
name='run',
|
||||
field=models.ForeignKey(related_name='run_info_histories', to='questionnaire.Run', null=True),
|
||||
),
|
||||
]
|
|
@ -1,36 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def models_to_migrate(apps):
|
||||
return [
|
||||
apps.get_model('questionnaire', 'RunInfo'),
|
||||
apps.get_model('questionnaire', 'RunInfoHistory'),
|
||||
apps.get_model('questionnaire', 'Answer'),
|
||||
]
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
def move_runids(apps, schema_editor):
|
||||
Run = apps.get_model('questionnaire', 'Run')
|
||||
for model in models_to_migrate(apps):
|
||||
for instance in model.objects.all():
|
||||
(run, created) = Run.objects.get_or_create(runid=instance.runid)
|
||||
instance.run = run
|
||||
instance.save()
|
||||
|
||||
def unmove_runids(apps, schema_editor):
|
||||
for model in models_to_migrate(apps):
|
||||
for instance in model.objects.all():
|
||||
instance.runid = instance.run.runid
|
||||
instance.save()
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0002_auto_20160929_1320'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(move_runids, reverse_code=unmove_runids, hints={'questionnaire': 'Run'}),
|
||||
]
|
|
@ -1,47 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0003_auto_20160929_1321'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='runinfo',
|
||||
name='runid',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='runinfohistory',
|
||||
name='runid',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='answer',
|
||||
name='run',
|
||||
field=models.ForeignKey(related_name='answers', default=1, to='questionnaire.Run'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='runinfo',
|
||||
name='run',
|
||||
field=models.ForeignKey(related_name='run_infos', default=1, to='questionnaire.Run'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='runinfohistory',
|
||||
name='run',
|
||||
field=models.ForeignKey(related_name='run_info_histories', to='questionnaire.Run'),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='answer',
|
||||
index_together=set([('subject', 'run'), ('subject', 'run', 'id')]),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='answer',
|
||||
name='runid',
|
||||
),
|
||||
]
|
|
@ -1,542 +0,0 @@
|
|||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from transmeta import TransMeta
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_save
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from . import QuestionChoices
|
||||
from .utils import split_numal
|
||||
from .parsers import parse_checks, ParseException
|
||||
|
||||
_numre = re.compile("(\d+)([a-z]+)", re.I)
|
||||
|
||||
class Subject(models.Model):
|
||||
STATE_CHOICES = [
|
||||
("active", _("Active")),
|
||||
("inactive", _("Inactive")),
|
||||
# Can be changed from elsewhere with
|
||||
# Subject.STATE_CHOICES[:] = [ ('blah', 'Blah') ]
|
||||
]
|
||||
state = models.CharField(max_length=16, default="inactive",
|
||||
choices = STATE_CHOICES, verbose_name=_('State'))
|
||||
anonymous = models.BooleanField(default=False)
|
||||
ip_address = models.GenericIPAddressField(null=True, blank=True)
|
||||
surname = models.CharField(max_length=64, blank=True, null=True,
|
||||
verbose_name=_('Surname'))
|
||||
givenname = models.CharField(max_length=64, blank=True, null=True,
|
||||
verbose_name=_('Given name'))
|
||||
email = models.EmailField(null=True, blank=True, verbose_name=_('Email'))
|
||||
gender = models.CharField(max_length=8, default="unset", blank=True,
|
||||
verbose_name=_('Gender'),
|
||||
choices = ( ("unset", _("Unset")),
|
||||
("male", _("Male")),
|
||||
("female", _("Female")),
|
||||
)
|
||||
)
|
||||
nextrun = models.DateField(verbose_name=_('Next Run'), blank=True, null=True)
|
||||
formtype = models.CharField(max_length=16, default='email',
|
||||
verbose_name = _('Form Type'),
|
||||
choices = (
|
||||
("email", _("Subject receives emails")),
|
||||
("paperform", _("Subject is sent paper form"),))
|
||||
)
|
||||
language = models.CharField(max_length=5, default=settings.LANGUAGE_CODE,
|
||||
verbose_name = _('Language'), choices = settings.LANGUAGES)
|
||||
|
||||
def __unicode__(self):
|
||||
if self.anonymous:
|
||||
return self.ip_address
|
||||
else:
|
||||
return u'%s, %s (%s)' % (self.surname, self.givenname, self.email)
|
||||
|
||||
def next_runid(self):
|
||||
"Return the string form of the runid for the upcoming run"
|
||||
return str(self.nextrun.year)
|
||||
|
||||
def last_run(self):
|
||||
"Returns the last completed run or None"
|
||||
try:
|
||||
query = RunInfoHistory.objects.filter(subject=self)
|
||||
return query.order_by('-completed')[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def history(self):
|
||||
return RunInfoHistory.objects.filter(subject=self).order_by('run__runid')
|
||||
|
||||
def pending(self):
|
||||
return RunInfo.objects.filter(subject=self).order_by('run__runid')
|
||||
|
||||
class Meta:
|
||||
index_together = [
|
||||
["givenname", "surname"],
|
||||
]
|
||||
|
||||
class GlobalStyles(models.Model):
|
||||
content = models.TextField()
|
||||
|
||||
class Questionnaire(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
redirect_url = models.CharField(max_length=128, help_text="URL to redirect to when Questionnaire is complete. Macros: $SUBJECTID, $RUNID, $LANG. Leave blank to render the 'complete.$LANG.html' template.", default="", blank=True)
|
||||
html = models.TextField(u'Html', blank=True)
|
||||
parse_html = models.BooleanField("Render html instead of name for survey?", null=False, default=False)
|
||||
admin_access_only = models.BooleanField("Only allow access to logged in users? (This allows entering paper surveys without allowing new external submissions)", null=False, default=False)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def questionsets(self):
|
||||
if not hasattr(self, "__qscache"):
|
||||
self.__qscache = \
|
||||
QuestionSet.objects.filter(questionnaire=self).order_by('sortid')
|
||||
return self.__qscache
|
||||
|
||||
def questions(self):
|
||||
questions = []
|
||||
for questionset in self.questionsets():
|
||||
questions += questionset.questions()
|
||||
return questions
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("export", "Can export questionnaire answers"),
|
||||
("management", "Management Tools")
|
||||
)
|
||||
|
||||
class Landing(models.Model):
|
||||
# defines an entry point to a Feedback session
|
||||
nonce = models.CharField(max_length=32, null=True,blank=True)
|
||||
content_type = models.ForeignKey(ContentType, null=True,blank=True, related_name='landings')
|
||||
object_id = models.PositiveIntegerField(null=True,blank=True)
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
label = models.CharField(max_length=64, blank=True)
|
||||
questionnaire = models.ForeignKey(Questionnaire, null=True, blank=True, related_name='landings')
|
||||
def _hash(self):
|
||||
return uuid.uuid4().hex
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
||||
def url(self):
|
||||
return settings.BASE_URL_SECURE + reverse('landing', args=[self.nonce])
|
||||
|
||||
def config_landing(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
instance.nonce=instance._hash()
|
||||
instance.save()
|
||||
|
||||
post_save.connect(config_landing,sender=Landing)
|
||||
|
||||
class DBStylesheet(models.Model):
|
||||
#Questionnaire max length of name is 128; Questionset max length of heading
|
||||
#is 64, and Question associative information is id which is less than 128
|
||||
#in length
|
||||
inclusion_tag = models.CharField(max_length=128)
|
||||
content = models.TextField()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.inclusion_tag
|
||||
|
||||
|
||||
class QuestionSet(models.Model):
|
||||
__metaclass__ = TransMeta
|
||||
|
||||
"Which questions to display on a question page"
|
||||
questionnaire = models.ForeignKey(Questionnaire)
|
||||
sortid = models.IntegerField() # used to decide which order to display in
|
||||
heading = models.CharField(max_length=64)
|
||||
checks = models.CharField(max_length=256, blank=True,
|
||||
help_text = """Current options are 'femaleonly' or 'maleonly' and shownif="QuestionNumber,Answer" which takes the same format as <tt>requiredif</tt> for questions.""")
|
||||
text = models.TextField(u'Text', help_text="HTML or Text")
|
||||
|
||||
parse_html = models.BooleanField("Render html in heading?", null=False, default=False)
|
||||
|
||||
def questions(self):
|
||||
if not hasattr(self, "__qcache"):
|
||||
def numeric_number(val):
|
||||
matches = re.findall(r'^\d+', val)
|
||||
return int(matches[0]) if matches else 0
|
||||
|
||||
questions_with_sort_id = sorted(Question.objects.filter(questionset=self.id).exclude(sort_id__isnull=True), key=lambda q: q.sort_id)
|
||||
|
||||
questions_with_out_sort_id = sorted(Question.objects.filter(questionset=self.id, sort_id__isnull=True), key=lambda q: (numeric_number(q.number), q.number))
|
||||
self.__qcache = questions_with_sort_id + questions_with_out_sort_id
|
||||
return self.__qcache
|
||||
|
||||
def sorted_questions(self):
|
||||
questions = self.questions()
|
||||
return sorted(questions, key = lambda question : (question.sort_id, question.number))
|
||||
|
||||
def next(self):
|
||||
qs = self.questionnaire.questionsets()
|
||||
retnext = False
|
||||
for q in qs:
|
||||
if retnext:
|
||||
return q
|
||||
if q == self:
|
||||
retnext = True
|
||||
return None
|
||||
|
||||
def prev(self):
|
||||
qs = self.questionnaire.questionsets()
|
||||
last = None
|
||||
for q in qs:
|
||||
if q == self:
|
||||
return last
|
||||
last = q
|
||||
|
||||
def is_last(self):
|
||||
try:
|
||||
return self.questionnaire.questionsets()[-1] == self
|
||||
except NameError:
|
||||
# should only occur if not yet saved
|
||||
return True
|
||||
|
||||
def is_first(self):
|
||||
try:
|
||||
return self.questionnaire.questionsets()[0] == self
|
||||
except NameError:
|
||||
# should only occur if not yet saved
|
||||
return True
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s: %s' % (self.questionnaire.name, self.heading)
|
||||
|
||||
class Meta:
|
||||
translate = ('text',)
|
||||
index_together = [
|
||||
["questionnaire", "sortid"],
|
||||
["sortid",]
|
||||
]
|
||||
class Run(models.Model):
|
||||
runid = models.CharField(max_length=32, null=True)
|
||||
|
||||
class RunInfo(models.Model):
|
||||
"Store the active/waiting questionnaire runs here"
|
||||
subject = models.ForeignKey(Subject)
|
||||
random = models.CharField(max_length=32) # probably a randomized md5sum
|
||||
run = models.ForeignKey(Run, related_name='run_infos')
|
||||
landing = models.ForeignKey(Landing, null=True, blank=True)
|
||||
# questionset should be set to the first QuestionSet initially, and to null on completion
|
||||
# ... although the RunInfo entry should be deleted then anyway.
|
||||
questionset = models.ForeignKey(QuestionSet, blank=True, null=True) # or straight int?
|
||||
emailcount = models.IntegerField(default=0)
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
emailsent = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
lastemailerror = models.CharField(max_length=64, null=True, blank=True)
|
||||
|
||||
state = models.CharField(max_length=16, null=True, blank=True)
|
||||
cookies = models.TextField(null=True, blank=True)
|
||||
|
||||
tags = models.TextField(
|
||||
blank=True,
|
||||
help_text=u"Tags active on this run, separated by commas"
|
||||
)
|
||||
|
||||
skipped = models.TextField(
|
||||
blank=True,
|
||||
help_text=u"A comma sepearted list of questions to skip"
|
||||
)
|
||||
|
||||
def save(self, **kwargs):
|
||||
self.random = (self.random or '').lower()
|
||||
super(RunInfo, self).save(**kwargs)
|
||||
|
||||
def add_tags(self, tags):
|
||||
for tag in tags:
|
||||
if self.tags:
|
||||
self.tags += ','
|
||||
self.tags += tag
|
||||
|
||||
def remove_tags(self, tags):
|
||||
if not self.tags:
|
||||
return
|
||||
|
||||
current_tags = self.tags.split(',')
|
||||
|
||||
for tag in tags:
|
||||
try:
|
||||
current_tags.remove(tag)
|
||||
except ValueError:
|
||||
pass
|
||||
self.tags = ",".join(current_tags)
|
||||
|
||||
def set_cookie(self, key, value):
|
||||
"runinfo.set_cookie(key, value). If value is None, delete cookie"
|
||||
key = key.lower().strip()
|
||||
cookies = self.get_cookiedict()
|
||||
if type(value) not in (int, float, str, unicode, type(None)):
|
||||
raise Exception("Can only store cookies of type integer or string")
|
||||
if value is None:
|
||||
if key in cookies:
|
||||
del cookies[key]
|
||||
else:
|
||||
if type(value) in ('int', 'float'):
|
||||
value=str(value)
|
||||
cookies[key] = value
|
||||
cstr = json.dumps(cookies)
|
||||
self.cookies=cstr
|
||||
self.save()
|
||||
self.__cookiecache = cookies
|
||||
|
||||
def get_cookie(self, key, default=None):
|
||||
if not self.cookies:
|
||||
return default
|
||||
d = self.get_cookiedict()
|
||||
return d.get(key.lower().strip(), default)
|
||||
|
||||
def get_cookiedict(self):
|
||||
if not self.cookies:
|
||||
return {}
|
||||
if not hasattr(self, '__cookiecache'):
|
||||
self.__cookiecache = json.loads(self.cookies)
|
||||
return self.__cookiecache
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s: %s, %s" % (self.run.runid, self.subject.surname, self.subject.givenname)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = 'Run Info'
|
||||
index_together = [
|
||||
["random"],
|
||||
]
|
||||
|
||||
class RunInfoHistory(models.Model):
|
||||
subject = models.ForeignKey(Subject)
|
||||
run = models.ForeignKey(Run, related_name='run_info_histories')
|
||||
completed = models.DateTimeField()
|
||||
landing = models.ForeignKey(Landing, null=True, blank=True)
|
||||
tags = models.TextField(
|
||||
blank=True,
|
||||
help_text=u"Tags used on this run, separated by commas"
|
||||
)
|
||||
skipped = models.TextField(
|
||||
blank=True,
|
||||
help_text=u"A comma sepearted list of questions skipped by this run"
|
||||
)
|
||||
questionnaire = models.ForeignKey(Questionnaire)
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s: %s on %s" % (self.run.runid, self.subject, self.completed)
|
||||
|
||||
def answers(self):
|
||||
"Returns the query for the answers."
|
||||
return Answer.objects.filter(subject=self.subject, run=self.run)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = 'Run Info History'
|
||||
|
||||
|
||||
class Question(models.Model):
|
||||
__metaclass__ = TransMeta
|
||||
|
||||
questionset = models.ForeignKey(QuestionSet)
|
||||
number = models.CharField(max_length=8, help_text=
|
||||
"eg. <tt>1</tt>, <tt>2a</tt>, <tt>2b</tt>, <tt>3c</tt><br /> "
|
||||
"Number is also used for ordering questions.")
|
||||
sort_id = models.IntegerField(null=True, blank=True, help_text="Questions within a questionset are sorted by sort order first, question number second")
|
||||
text = models.TextField(blank=True, verbose_name=_("Text"))
|
||||
type = models.CharField(u"Type of question", max_length=32,
|
||||
choices = QuestionChoices,
|
||||
help_text = u"Determines the means of answering the question. " \
|
||||
"An open question gives the user a single-line textfield, " \
|
||||
"multiple-choice gives the user a number of choices he/she can " \
|
||||
"choose from. If a question is multiple-choice, enter the choices " \
|
||||
"this user can choose from below'.")
|
||||
extra = models.CharField(u"Extra information", max_length=512, blank=True, null=True, help_text=u"Extra information (use on question type)")
|
||||
checks = models.CharField(u"Additional checks", max_length=512, blank=True,
|
||||
null=True, help_text="Additional checks to be performed for this "
|
||||
"value (space separated) <br /><br />"
|
||||
"For text fields, <tt>required</tt> is a valid check.<br />"
|
||||
"For yes/no choice, <tt>required</tt>, <tt>required-yes</tt>, "
|
||||
"and <tt>required-no</tt> are valid.<br /><br />"
|
||||
"If this question is required only if another question's answer is "
|
||||
'something specific, use <tt>requiredif="QuestionNumber,Value"</tt> '
|
||||
'or <tt>requiredif="QuestionNumber,!Value"</tt> for anything but '
|
||||
"a specific value. "
|
||||
"You may also combine tests appearing in <tt>requiredif</tt> "
|
||||
"by joining them with the words <tt>and</tt> or <tt>or</tt>, "
|
||||
'eg. <tt>requiredif="Q1,A or Q2,B"</tt>')
|
||||
footer = models.TextField(u"Footer", help_text="Footer rendered below the question", blank=True)
|
||||
|
||||
parse_html = models.BooleanField("Render html in Footer?", null=False, default=False)
|
||||
|
||||
def questionnaire(self):
|
||||
return self.questionset.questionnaire
|
||||
|
||||
def getcheckdict(self):
|
||||
"""getcheckdict returns a dictionary of the values in self.checks"""
|
||||
if(hasattr(self, '__checkdict_cached')):
|
||||
return self.__checkdict_cached
|
||||
try:
|
||||
self.__checkdict_cached = d = parse_checks(self.sameas().checks or '')
|
||||
except ParseException:
|
||||
raise Exception("Error Parsing Checks for Question %s: %s" % (
|
||||
self.number, self.sameas().checks))
|
||||
return d
|
||||
|
||||
def __unicode__(self):
|
||||
return u'{%s} (%s) %s' % (unicode(self.questionset), self.number, self.text)
|
||||
|
||||
def sameas(self):
|
||||
if self.type == 'sameas':
|
||||
try:
|
||||
kwargs = {}
|
||||
for check, value in parse_checks(self.checks):
|
||||
if check == 'sameasid':
|
||||
kwargs['id'] = value
|
||||
break
|
||||
elif check == 'sameas':
|
||||
kwargs['number'] = value
|
||||
kwargs['questionset__questionnaire'] = self.questionset.questionnaire
|
||||
break
|
||||
|
||||
self.__sameas = res = getattr(self, "__sameas", Question.objects.get(**kwargs))
|
||||
return res
|
||||
except Question.DoesNotExist:
|
||||
return Question(type='comment') # replace with something benign
|
||||
return self
|
||||
|
||||
def display_number(self):
|
||||
"Return either the number alone or the non-number part of the question number indented"
|
||||
m = _numre.match(self.number)
|
||||
if m:
|
||||
sub = m.group(2)
|
||||
return " " + sub
|
||||
return self.number
|
||||
|
||||
def choices(self):
|
||||
if self.type == 'sameas':
|
||||
return self.sameas().choices()
|
||||
|
||||
res = None
|
||||
if 'samechoicesas' in parse_checks(self.checks):
|
||||
number_to_grab_from = parse_checks(self.checks)['samechoicesas']
|
||||
choicesource = Question.objects.get(number=number_to_grab_from)
|
||||
if not choicesource == None:
|
||||
res = Choice.objects.filter(question=choicesource).order_by('sortid')
|
||||
else:
|
||||
res = Choice.objects.filter(question=self).order_by('sortid')
|
||||
return res
|
||||
|
||||
def is_custom(self):
|
||||
return "custom" == self.sameas().type
|
||||
|
||||
def get_type(self):
|
||||
"Get the type name, treating sameas and custom specially"
|
||||
t = self.sameas().type
|
||||
if t == 'custom':
|
||||
cd = self.sameas().getcheckdict()
|
||||
if 'type' not in cd:
|
||||
raise Exception("When using custom types, you must have type=<name> in the additional checks field")
|
||||
return cd.get('type')
|
||||
return t
|
||||
|
||||
def questioninclude(self):
|
||||
return "questionnaire/" + self.get_type() + ".html"
|
||||
|
||||
@property
|
||||
def is_comment(self):
|
||||
return self.type == 'comment'
|
||||
|
||||
def get_value_for_run_question(self, runid):
|
||||
runanswer = Answer.objects.filter(run__runid=runid, question=self)
|
||||
if len(runanswer) > 0:
|
||||
return runanswer[0].answer
|
||||
else:
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
translate = ('text', 'extra', 'footer')
|
||||
index_together = [
|
||||
["number", "questionset"],
|
||||
]
|
||||
|
||||
class Choice(models.Model):
|
||||
__metaclass__ = TransMeta
|
||||
|
||||
question = models.ForeignKey(Question)
|
||||
sortid = models.IntegerField()
|
||||
value = models.CharField(u"Short Value", max_length=64)
|
||||
text = models.CharField(u"Choice Text", max_length=200)
|
||||
tags = models.CharField(u"Tags", max_length=64, blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'(%s) %d. %s' % (self.question.number, self.sortid, self.text)
|
||||
|
||||
class Meta:
|
||||
translate = ('text',)
|
||||
index_together = [
|
||||
['value'],
|
||||
]
|
||||
|
||||
class Answer(models.Model):
|
||||
subject = models.ForeignKey(Subject, help_text = u'The user who supplied this answer')
|
||||
question = models.ForeignKey(Question, help_text = u"The question that this is an answer to")
|
||||
run = models.ForeignKey(Run, related_name='answers')
|
||||
answer = models.TextField()
|
||||
|
||||
def __unicode__(self):
|
||||
return "Answer(%s: %s, %s)" % (self.question.number, self.subject.surname, self.subject.givenname)
|
||||
|
||||
def split_answer(self):
|
||||
"""
|
||||
Decode stored answer value and return as a list of choices.
|
||||
Any freeform value will be returned in a list as the last item.
|
||||
|
||||
Calling code should be tolerant of freeform answers outside
|
||||
of additional [] if data has been stored in plain text format
|
||||
"""
|
||||
try:
|
||||
return json.loads(self.answer)
|
||||
except ValueError:
|
||||
# this was likely saved as plain text, try to guess what the
|
||||
# value(s) were
|
||||
if 'multiple' in self.question.type:
|
||||
return self.answer.split('; ')
|
||||
else:
|
||||
return [self.answer]
|
||||
|
||||
def check_answer(self):
|
||||
"Confirm that the supplied answer matches what we expect"
|
||||
return True
|
||||
|
||||
def save(self, runinfo=None, **kwargs):
|
||||
self._update_tags(runinfo)
|
||||
super(Answer, self).save(**kwargs)
|
||||
|
||||
def _update_tags(self, runinfo):
|
||||
if not runinfo:
|
||||
return
|
||||
|
||||
tags_to_add = []
|
||||
|
||||
for choice in self.question.choices():
|
||||
tags = choice.tags
|
||||
if not tags:
|
||||
continue
|
||||
tags = tags.split(',')
|
||||
runinfo.remove_tags(tags)
|
||||
|
||||
for split_answer in self.split_answer():
|
||||
if unicode(split_answer) == choice.value:
|
||||
tags_to_add.extend(tags)
|
||||
|
||||
runinfo.add_tags(tags_to_add)
|
||||
runinfo.save()
|
||||
|
||||
class Meta:
|
||||
index_together = [
|
||||
['subject', 'run'],
|
||||
['subject', 'run', 'id'],
|
||||
]
|
|
@ -1,5 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
|
@ -1,8 +0,0 @@
|
|||
|
||||
from django.contrib import admin
|
||||
from .models import Page
|
||||
|
||||
class PageAdmin(admin.ModelAdmin):
|
||||
list_display = ('slug', 'title',)
|
||||
|
||||
admin.site.register(Page, PageAdmin)
|
|
@ -1,21 +0,0 @@
|
|||
from django.db import models
|
||||
from django.core.urlresolvers import reverse
|
||||
from transmeta import TransMeta
|
||||
|
||||
class Page(models.Model):
|
||||
__metaclass__ = TransMeta
|
||||
|
||||
slug = models.SlugField(unique=True, primary_key=True)
|
||||
title = models.CharField(u"Title", max_length=256)
|
||||
body = models.TextField(u"Body")
|
||||
public = models.BooleanField(default=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"Page[%s]" % self.slug
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('questionnaire.page.views.page', kwargs={'page_to_render':self.slug})
|
||||
|
||||
|
||||
class Meta:
|
||||
translate = ('title','body',)
|
|
@ -1,40 +0,0 @@
|
|||
# Create your views here.
|
||||
from django.shortcuts import render, render_to_response
|
||||
from django.conf import settings
|
||||
from django.template import RequestContext
|
||||
from django import http
|
||||
from django.utils import translation
|
||||
from .models import Page
|
||||
|
||||
def page(request, page_to_render):
|
||||
try:
|
||||
p = Page.objects.get(slug=page_to_render, public=True)
|
||||
except Page.DoesNotExist:
|
||||
return render(request, "pages/{}.html".format(page_to_render),
|
||||
{ "request" : request,},
|
||||
)
|
||||
|
||||
return render(request, "page.html",
|
||||
{ "request" : request, "page" : p, },
|
||||
)
|
||||
|
||||
def langpage(request, lang, page_to_trans):
|
||||
translation.activate(lang)
|
||||
return page(request, page_to_trans)
|
||||
|
||||
def set_language(request):
|
||||
next = request.POST.get('next', request.GET.get('next', None))
|
||||
if not next:
|
||||
next = request.META.get('HTTP_REFERER', None)
|
||||
if not next:
|
||||
next = '/'
|
||||
response = http.HttpResponseRedirect(next)
|
||||
if request.method == 'GET':
|
||||
lang_code = request.GET.get('language', None)
|
||||
if lang_code and translation.check_for_language(lang_code):
|
||||
if hasattr(request, 'session'):
|
||||
request.session['django_language'] = lang_code
|
||||
else:
|
||||
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code)
|
||||
return response
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
__all__ = ('parse_checks', 'BooleanParser')
|
||||
|
||||
try: from pyparsing import *
|
||||
except ImportError: from utils.pyparsing import *
|
||||
|
||||
def __make_parser():
|
||||
key = Word(alphas, alphanums+"_-")
|
||||
value = Word(alphanums + "-.,_=<>!@$%^&*[]{}:;|/'") | QuotedString('"')
|
||||
return Dict(ZeroOrMore(Group( key + Optional( Suppress("=") + value, default=True ) ) ))
|
||||
__checkparser = __make_parser()
|
||||
|
||||
def parse_checks(string):
|
||||
"""
|
||||
from parsers import parse_checks
|
||||
>>> parse_checks('dependent=5a,no dependent="5a && 4a" dog="Roaming Rover" name=Robert foo bar')
|
||||
([(['dependent', '5a,no'], {}), (['dependent', '5a && 4a'], {}), (['dog', 'Roaming Rover'], {}), (['name', 'Robert'], {}), (['foo', True], {}), (['bar', True], {})], {'dependent': [('5a,no', 0), ('5a && 4a', 1)], 'foo': [(True, 4)], 'bar': [(True, 5)], 'dog': [('Roaming Rover', 2)], 'name': [('Robert', 3)]})
|
||||
"""
|
||||
return __checkparser.parseString(string, parseAll=True)
|
||||
|
||||
|
||||
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
# - Boolean Expression Parser -
|
||||
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
||||
|
||||
class BoolOperand(object):
|
||||
def __init__(self, t):
|
||||
self.args = t[0][0::2]
|
||||
def __str__(self):
|
||||
sep = " %s " % self.reprsymbol
|
||||
return "(" + sep.join(map(str, self.args)) + ")"
|
||||
|
||||
class BoolAnd(BoolOperand):
|
||||
reprsymbol = '&&'
|
||||
def __nonzero__(self):
|
||||
for a in self.args:
|
||||
if not bool(a):
|
||||
return False
|
||||
return True
|
||||
|
||||
class BoolOr(BoolOperand):
|
||||
reprsymbol = '||'
|
||||
def __nonzero__(self):
|
||||
for a in self.args:
|
||||
if bool(a):
|
||||
return True
|
||||
return False
|
||||
|
||||
class BoolNot(BoolOperand):
|
||||
def __init__(self,t):
|
||||
self.arg = t[0][1]
|
||||
def __str__(self):
|
||||
return "!" + str(self.arg)
|
||||
def __nonzero__(self):
|
||||
return not bool(self.arg)
|
||||
|
||||
class Checker(object):
|
||||
"Simple wrapper to call a specific function, passing in args and kwargs each time"
|
||||
def __init__(self, func, expr, *args, **kwargs):
|
||||
self.func = func
|
||||
self.expr = expr
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __nonzero__(self):
|
||||
return self.func(self.expr, *self.args, **self.kwargs)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.expr)
|
||||
|
||||
def __unicode__(self):
|
||||
try: fname=self.func.func_name
|
||||
except: fname="TestExpr"
|
||||
return "%s('%s')" % (fname, self.expr)
|
||||
__str__ = __unicode__
|
||||
|
||||
|
||||
class BooleanParser(object):
|
||||
"""Simple boolean parser
|
||||
|
||||
>>> def foo(x):
|
||||
... if x == '1': return True
|
||||
... return False
|
||||
...
|
||||
>>> foo('1')
|
||||
True
|
||||
>>> foo('0')
|
||||
False
|
||||
>>> p = BooleanParser(foo)
|
||||
>>> p.parse('1 and 0')
|
||||
False
|
||||
>>> p.parse('1 and 1')
|
||||
True
|
||||
>>> p.parse('1 or 1')
|
||||
True
|
||||
>>> p.parse('0 or 1')
|
||||
True
|
||||
>>> p.parse('0 or 0')
|
||||
False
|
||||
>>> p.parse('(0 or 0) and 1')
|
||||
False
|
||||
>>> p.parse('(0 or 0) and (1)')
|
||||
False
|
||||
>>> p.parse('(0 or 1) and (1)')
|
||||
True
|
||||
>>> p.parse('(0 or 0) or (1)')
|
||||
True
|
||||
"""
|
||||
|
||||
def __init__(self, func, *args, **kwargs): # treats kwarg boolOperand specially!
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.func = func
|
||||
if "boolOperand" in kwargs:
|
||||
boolOperand = kwargs["boolOperand"]
|
||||
del kwargs["boolOperand"]
|
||||
else:
|
||||
boolOperand = Word(alphanums + "-.,_=<>!@$%^&*[]{}:;|/\\")
|
||||
boolOperand = boolOperand.setParseAction(self._check)
|
||||
self.boolExpr = operatorPrecedence( boolOperand,
|
||||
[
|
||||
("not ", 1, opAssoc.RIGHT, BoolNot),
|
||||
("or", 2, opAssoc.LEFT, BoolOr),
|
||||
("and", 2, opAssoc.LEFT, BoolAnd),
|
||||
])
|
||||
|
||||
def _check(self, string, location, tokens):
|
||||
checker = Checker(self.func, tokens[0], *self.args, **self.kwargs)
|
||||
tokens[0] = checker
|
||||
|
||||
def parse(self, code):
|
||||
if not code or not code.strip():
|
||||
return False
|
||||
return bool(self.boolExpr.parseString(code)[0])
|
||||
|
||||
def toString(self, code):
|
||||
return str(self.boolExpr.parseString(code)[0])
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
|
@ -1,68 +0,0 @@
|
|||
import hotshot
|
||||
import os
|
||||
import time
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from django.db import connection
|
||||
|
||||
try:
|
||||
PROFILE_LOG_BASE = settings.PROFILE_LOG_BASE
|
||||
except:
|
||||
PROFILE_LOG_BASE = "/tmp"
|
||||
|
||||
|
||||
def profile(log_file):
|
||||
"""Profile some callable.
|
||||
|
||||
This decorator uses the hotshot profiler to profile some callable (like
|
||||
a view function or method) and dumps the profile data somewhere sensible
|
||||
for later processing and examination.
|
||||
|
||||
It takes one argument, the profile log name. If it's a relative path, it
|
||||
places it under the PROFILE_LOG_BASE. It also inserts a time stamp into the
|
||||
file name, such that 'my_view.prof' become 'my_view-20100211T170321.prof',
|
||||
where the time stamp is in UTC. This makes it easy to run and compare
|
||||
multiple trials.
|
||||
"""
|
||||
|
||||
if not os.path.isabs(log_file):
|
||||
log_file = os.path.join(PROFILE_LOG_BASE, log_file)
|
||||
|
||||
def _outer(f):
|
||||
def _inner(*args, **kwargs):
|
||||
# Add a timestamp to the profile output when the callable
|
||||
# is actually called.
|
||||
(base, ext) = os.path.splitext(log_file)
|
||||
base = base + "-" + time.strftime("%Y%m%dT%H%M%S", time.gmtime())
|
||||
final_log_file = base + ext
|
||||
|
||||
prof = hotshot.Profile(final_log_file)
|
||||
try:
|
||||
ret = prof.runcall(f, *args, **kwargs)
|
||||
finally:
|
||||
prof.close()
|
||||
return ret
|
||||
|
||||
return _inner
|
||||
return _outer
|
||||
|
||||
def timethis(fn):
|
||||
def wrapper(*args, **kwargs):
|
||||
start = time.time()
|
||||
result = fn(*args, **kwargs)
|
||||
print fn.__name__, 'took', time.time() - start
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
from pprint import pprint
|
||||
|
||||
def sqlprint(fn):
|
||||
def wrapper(*args, **kwargs):
|
||||
connection.queries = list()
|
||||
result = fn(*args, **kwargs)
|
||||
print fn.__name__, 'issued'
|
||||
pprint(connection.queries)
|
||||
return result
|
||||
return wrapper
|
|
@ -1,10 +0,0 @@
|
|||
# add all the question types to QuestionChoices before anything else
|
||||
from .. import add_type
|
||||
|
||||
from . import simple # store value as returned
|
||||
from . import choice # multiple choice, do checks
|
||||
from . import range_or_number # range of numbers
|
||||
from . import timeperiod # time periods
|
||||
from . import custom # backwards compatibility support
|
||||
|
||||
add_type('custom', 'Custom field')
|
|
@ -1,224 +0,0 @@
|
|||
from json import dumps
|
||||
import ast
|
||||
from django.utils.translation import ugettext as _, ungettext
|
||||
|
||||
from .. import add_type, question_proc, answer_proc, AnswerException
|
||||
from ..utils import get_runid_from_request
|
||||
|
||||
|
||||
@question_proc('choice', 'choice-freeform', 'dropdown', 'choice-optional', 'choice-freeform-optional')
|
||||
def question_choice(request, question):
|
||||
choices = []
|
||||
jstriggers = []
|
||||
|
||||
cd = question.getcheckdict()
|
||||
key = "question_%s" % question.number
|
||||
key2 = "question_%s_comment" % question.number
|
||||
val = None
|
||||
possibledbvalue = question.get_value_for_run_question(get_runid_from_request(request))
|
||||
if key in request.POST:
|
||||
val = request.POST[key]
|
||||
elif not possibledbvalue == None:
|
||||
valueaslist = ast.literal_eval(possibledbvalue)
|
||||
val = valueaslist[0]
|
||||
else:
|
||||
if 'default' in cd:
|
||||
val = cd['default']
|
||||
for choice in question.choices():
|
||||
choices.append( ( choice.value == val, choice, ) )
|
||||
|
||||
if question.type in ( 'choice-freeform','choice-freeform-optional'):
|
||||
jstriggers.append('%s_comment' % question.number)
|
||||
template = question.type[:-9] if question.type.endswith('-optional') else question.type
|
||||
|
||||
|
||||
return {
|
||||
'choices' : choices,
|
||||
'sel_entry' : val == '_entry_',
|
||||
'qvalue' : val or '',
|
||||
"template" : "questionnaire/{}.html".format(template),
|
||||
'required' : not question.type in ( 'choice-optional', 'choice-freeform-optional'),
|
||||
'comment' : request.POST.get(key2, ""),
|
||||
'jstriggers': jstriggers,
|
||||
}
|
||||
|
||||
@answer_proc('choice', 'choice-freeform', 'dropdown', 'choice-optional', 'choice-freeform-optional')
|
||||
def process_choice(question, answer):
|
||||
opt = answer['ANSWER'] or ''
|
||||
if not opt and not question.type.endswith( '-optional'):
|
||||
raise AnswerException(_(u'You must select an option'))
|
||||
if opt == '_entry_' and question.type.startswith('choice-freeform'):
|
||||
opt = answer.get('comment','')
|
||||
if not opt:
|
||||
raise AnswerException(_(u'Field cannot be blank'))
|
||||
return dumps([[opt]])
|
||||
else:
|
||||
valid = [c.value for c in question.choices()]
|
||||
if opt and opt not in valid:
|
||||
raise AnswerException(_(u'Invalid option!'))
|
||||
return dumps([opt])
|
||||
add_type('choice', 'Choice [radio]')
|
||||
add_type('choice-freeform', 'Choice with a freeform option [radio]')
|
||||
add_type('choice-optional', 'Optional choice [radio]')
|
||||
add_type('choice-freeform-optional', 'Optional choice with a freeform option [radio]')
|
||||
add_type('dropdown', 'Dropdown choice [select]')
|
||||
|
||||
@question_proc('choice-multiple', 'choice-multiple-freeform', 'choice-multiple-values')
|
||||
def question_multiple(request, question):
|
||||
key = "question_%s" % question.number
|
||||
choices = []
|
||||
jstriggers = []
|
||||
counter = 0
|
||||
qvalues = []
|
||||
cd = question.getcheckdict()
|
||||
defaults = cd.get('default','').split(',')
|
||||
possibledbvalue = question.get_value_for_run_question(get_runid_from_request(request))
|
||||
possiblelist = []
|
||||
if not possibledbvalue == None:
|
||||
possiblelist = ast.literal_eval(possibledbvalue)
|
||||
prev_vals = {}
|
||||
if question.type == 'choice-multiple-values':
|
||||
pl = []
|
||||
for choice_value, prev_value in possiblelist:
|
||||
pl.append(choice_value)
|
||||
prev_vals[choice_value] = str(prev_value)
|
||||
possiblelist = pl
|
||||
|
||||
# print 'possible value is ', possibledbvalue, ', possiblelist is ', possiblelist
|
||||
|
||||
for choice in question.choices():
|
||||
counter += 1
|
||||
key = "question_%s_multiple_%d" % (question.number, choice.sortid)
|
||||
if question.type == "choice-multiple-values":
|
||||
jstriggers.append("q%s_%s_box" % (question.number, choice.value))
|
||||
# so that the number box will be activated when item is checked
|
||||
|
||||
#try database first and only after that fall back to post choices
|
||||
# print 'choice multiple checking for match for choice ', choice
|
||||
checked = ' checked'
|
||||
prev_value = ''
|
||||
qvalue = "%s_%s" % (question.number, choice.value)
|
||||
if key in request.POST or \
|
||||
(request.method == 'GET' and choice.value in defaults):
|
||||
qvalues.append(qvalue)
|
||||
value_key = "question_%s_%s_value" % (question.number, choice.value)
|
||||
if value_key in request.POST:
|
||||
prev_value = request.POST[value_key]
|
||||
elif choice.value in possiblelist:
|
||||
qvalues.append(qvalue)
|
||||
# so that this choice being checked will trigger anything that depends on it -
|
||||
# for choice-multiple-values right now
|
||||
if choice.value in prev_vals.keys():
|
||||
prev_value = prev_vals[choice.value]
|
||||
else:
|
||||
checked = ''
|
||||
# bug: you can have one item checked from database and another from POST data
|
||||
|
||||
choices.append( (choice, key, checked, prev_value,) )
|
||||
|
||||
extracount = int(cd.get('extracount', 0))
|
||||
if not extracount and question.type == 'choice-multiple-freeform':
|
||||
extracount = 1
|
||||
extras = []
|
||||
for x in range(1, extracount+1):
|
||||
key = "question_%s_more%d" % (question.number, x)
|
||||
if key in request.POST:
|
||||
extras.append( (key, request.POST[key],) )
|
||||
else:
|
||||
extras.append( (key, '',) )
|
||||
# right now does not retrieve extra fields from database
|
||||
return {
|
||||
"choices": choices,
|
||||
"extras": extras,
|
||||
"type": question.type,
|
||||
"template" : "questionnaire/choice-multiple-freeform.html",
|
||||
"required" : cd.get("required", False) and cd.get("required") != "0",
|
||||
"jstriggers": jstriggers,
|
||||
"qvalues": qvalues
|
||||
}
|
||||
|
||||
@answer_proc('choice-multiple', 'choice-multiple-freeform', 'choice-multiple-values')
|
||||
def process_multiple(question, answer):
|
||||
multiple = []
|
||||
multiple_freeform = []
|
||||
|
||||
#this is the same thing as a minimum count so use it for that..
|
||||
requiredcount = 0
|
||||
required = question.getcheckdict().get('required', 0)
|
||||
if required:
|
||||
try:
|
||||
requiredcount = int(required)
|
||||
except ValueError:
|
||||
requiredcount = 1
|
||||
if requiredcount and requiredcount > question.choices().count():
|
||||
requiredcount = question.choices().count()
|
||||
|
||||
#added support for a max number of choices
|
||||
maxcount = 9999
|
||||
maxdict = question.getcheckdict().get('max', 0)
|
||||
if maxdict:
|
||||
try:
|
||||
maxcount = int(maxdict)
|
||||
except ValueError:
|
||||
pass #leave maxcount at 9999
|
||||
|
||||
if maxcount and maxcount > question.choices().count():
|
||||
maxcount = question.choices().count()
|
||||
|
||||
for k, v in answer.items():
|
||||
if k.startswith('multiple') and not k.endswith('value'):
|
||||
multiple.append(v)
|
||||
if k.startswith('more') and len(v.strip()) > 0:
|
||||
multiple_freeform.append(v)
|
||||
|
||||
if question.type == 'choice-multiple-values':
|
||||
# check for associated values for choice-multiple-values
|
||||
total = 0
|
||||
floats = False
|
||||
notnumbers = False
|
||||
for k, v in answer.items():
|
||||
if k.endswith('value'):
|
||||
choice_text = k.rsplit("_", 2)[0]
|
||||
if choice_text in multiple:
|
||||
val = v
|
||||
multiple.remove(choice_text)
|
||||
try:
|
||||
val = int(v)
|
||||
total += val
|
||||
except ValueError: # not an int
|
||||
try:
|
||||
val = float(v)
|
||||
floats = True
|
||||
except ValueError: # not a float or int
|
||||
notnumbers = True
|
||||
multiple.append([choice_text, val])
|
||||
|
||||
if floats:
|
||||
raise AnswerException(ungettext(u"Please enter a whole number - no decimal places", u"Please enter whole numbers - no decimal places", len(multiple)))
|
||||
|
||||
if notnumbers:
|
||||
raise AnswerException(ungettext(u"Value must be a number (with no decimal places)", u"All values must be numbers (with no decimal places)", len(multiple)))
|
||||
|
||||
if len(multiple) > 0 and total != 100:
|
||||
raise AnswerException(ungettext(u"Did you mean 100% for one choice? Please enter 100 or add other choices.", u"Values must add up to 100.", len(multiple)))
|
||||
|
||||
|
||||
if len(multiple) + len(multiple_freeform) < requiredcount:
|
||||
raise AnswerException(ungettext(u"You must select at least %d option",
|
||||
u"You must select at least %d options",
|
||||
requiredcount) % requiredcount)
|
||||
|
||||
if len(multiple) + len(multiple_freeform) > maxcount:
|
||||
raise AnswerException(ungettext(u"You must select at most %d options",
|
||||
u"You must select at most %d options",
|
||||
maxcount) % maxcount)
|
||||
multiple.sort()
|
||||
if multiple_freeform:
|
||||
multiple.append(multiple_freeform)
|
||||
|
||||
return dumps(multiple)
|
||||
add_type('choice-multiple', 'Multiple-Choice, Multiple-Answers [checkbox]')
|
||||
add_type('choice-multiple-freeform', 'Multiple-Choice, Multiple-Answers, plus freeform [checkbox, input]')
|
||||
add_type('choice-multiple-values', 'Multiple-Choice, Multiple-Answers [checkboxes], plus value box [input] for each selected answer')
|
||||
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#
|
||||
# Custom type exists for backwards compatibility. All custom types should now
|
||||
# exist in the drop down list of the management interface.
|
||||
#
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from .. import add_type, question_proc, answer_proc, AnswerException
|
||||
from .. import Processors, QuestionProcessors
|
||||
|
||||
@question_proc('custom')
|
||||
def question_custom(request, question):
|
||||
cd = question.getcheckdict()
|
||||
_type = cd['type']
|
||||
d = {}
|
||||
if _type in QuestionProcessors:
|
||||
d = QuestionProcessors[_type](request, question)
|
||||
if 'template' not in d:
|
||||
d['template'] = 'questionnaire/%s.html' % _type
|
||||
return d
|
||||
|
||||
@answer_proc('custom')
|
||||
def process_custom(question, answer):
|
||||
cd = question.getcheckdict()
|
||||
_type = cd['type']
|
||||
if _type in Processors:
|
||||
return Processors[_type](question, answer)
|
||||
raise AnswerException(_(u"Processor not defined for this question"))
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
import ast
|
||||
from json import dumps
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from ..utils import get_runid_from_request
|
||||
from .. import add_type, question_proc, answer_proc, AnswerException
|
||||
|
||||
@question_proc('range', 'number')
|
||||
def question_range_or_number(request, question):
|
||||
cd = question.getcheckdict()
|
||||
|
||||
rmin, rmax = parse_range(cd)
|
||||
rstep = parse_step(cd)
|
||||
runit = cd.get('unit', '')
|
||||
|
||||
#try loading current from database before just setting to min
|
||||
possibledbvalue = question.get_value_for_run_question(get_runid_from_request(request))
|
||||
|
||||
#you can't eval none nor can you eval empty
|
||||
if not possibledbvalue == None and len(possibledbvalue) > 0:
|
||||
valueaslist = ast.literal_eval(possibledbvalue)
|
||||
current = valueaslist[0]
|
||||
else:
|
||||
current = request.POST.get('question_%s' % question.number, rmin)
|
||||
|
||||
jsinclude = []
|
||||
if question.type == 'range':
|
||||
jsinclude = [settings.STATIC_URL+'range.js']
|
||||
|
||||
return {
|
||||
'required' : True,
|
||||
'type': question.type,
|
||||
'rmin' : rmin,
|
||||
'rmax' : rmax,
|
||||
'rstep' : rstep,
|
||||
'runit' : runit,
|
||||
'current' : current,
|
||||
'jsinclude' : jsinclude
|
||||
}
|
||||
|
||||
@answer_proc('range', 'number')
|
||||
def process_range_or_number(question, answer):
|
||||
cd = question.getcheckdict()
|
||||
|
||||
rmin, rmax = parse_range(cd)
|
||||
rstep = parse_step(cd)
|
||||
|
||||
convert = range_type(rmin, rmax, rstep)
|
||||
|
||||
ans = answer['ANSWER']
|
||||
if not ans:
|
||||
required = question.getcheckdict().get('required', 0)
|
||||
if required:
|
||||
raise AnswerException(_(u"Field cannot be blank"))
|
||||
else:
|
||||
return []
|
||||
|
||||
try:
|
||||
ans = convert(ans)
|
||||
except:
|
||||
raise AnswerException(_(u"Could not convert the number"))
|
||||
|
||||
if ans > convert(rmax) or ans < convert(rmin):
|
||||
raise AnswerException(_(u"Out of range"))
|
||||
|
||||
return dumps([ans])
|
||||
|
||||
add_type('range', 'Range of numbers [select]')
|
||||
add_type('number', 'Number [input]')
|
||||
|
||||
def parse_range(checkdict):
|
||||
"Given a checkdict for a range widget return the min and max string values."
|
||||
|
||||
Range = checkdict.get('range', '1-5')
|
||||
|
||||
try:
|
||||
rmin, rmax = Range.split('-', 1)
|
||||
except ValueError:
|
||||
rmin, rmax = '1', '5'
|
||||
|
||||
return rmin, rmax
|
||||
|
||||
def parse_step(checkdict):
|
||||
"Given a checkdict for a range widget return the step as string value."
|
||||
|
||||
return checkdict.get('step', '1')
|
||||
|
||||
def range_type(rmin, rmax, step):
|
||||
"""Given the min, max and step value return float or int depending on
|
||||
the number of digits after 0.
|
||||
|
||||
"""
|
||||
|
||||
if any((digits(rmin), digits(rmax), digits(step))):
|
||||
return float
|
||||
else:
|
||||
return int
|
||||
|
||||
def digits(number):
|
||||
"Given a number as string return the number of digits after 0."
|
||||
if '.' in number or ',' in number:
|
||||
if '.' in number:
|
||||
return len(number.split('.')[1])
|
||||
else:
|
||||
return len(number.split(',')[1])
|
||||
else:
|
||||
return 0
|
|
@ -1,137 +0,0 @@
|
|||
import ast
|
||||
from json import dumps
|
||||
from django.utils.translation import ugettext as _
|
||||
from .. import add_type, question_proc, answer_proc, AnswerException
|
||||
from ..utils import get_runid_from_request
|
||||
|
||||
|
||||
#true if either 'required' or if 'requiredif' is satisfied
|
||||
#def is_required
|
||||
|
||||
@question_proc('choice-yesno', 'choice-yesnocomment', 'choice-yesnodontknow','choice-yesno-optional', 'choice-yesnocomment-optional', 'choice-yesnodontknow-optional')
|
||||
def question_yesno(request, question):
|
||||
key = "question_%s" % question.number
|
||||
key2 = "question_%s_comment" % question.number
|
||||
val = request.POST.get(key, None)
|
||||
cmt = request.POST.get(key2, '')
|
||||
qtype = question.get_type()
|
||||
cd = question.getcheckdict()
|
||||
jstriggers = []
|
||||
|
||||
if qtype.startswith('choice-yesnocomment'):
|
||||
hascomment = True
|
||||
else:
|
||||
hascomment = False
|
||||
if qtype.startswith( 'choice-yesnodontknow') or 'dontknow' in cd:
|
||||
hasdontknow = True
|
||||
else:
|
||||
hasdontknow = False
|
||||
|
||||
#try the database before reverting to default
|
||||
possiblevalue = question.get_value_for_run_question(get_runid_from_request(request))
|
||||
if not possiblevalue == None:
|
||||
#save process always listifies the answer so we unlistify it to put it back in the field
|
||||
valueaslist = ast.literal_eval(possiblevalue)
|
||||
if len(valueaslist) > 0:
|
||||
val = valueaslist[0]
|
||||
|
||||
if not val:
|
||||
if cd.get('default', None):
|
||||
val = cd['default']
|
||||
|
||||
checks = ''
|
||||
if hascomment:
|
||||
if cd.get('required-yes'):
|
||||
jstriggers = ['%s_comment' % question.number]
|
||||
checks = ' checks="dep_check(\'%s,yes\')"' % question.number
|
||||
elif cd.get('required-no'):
|
||||
checks = ' checks="dep_check(\'%s,no\')"' % question.number
|
||||
elif cd.get('required-dontknow'):
|
||||
checks = ' checks="dep_check(\'%s,dontknow\')"' % question.number
|
||||
|
||||
return {
|
||||
'required': not qtype.endswith("-optional"),
|
||||
'checks': checks,
|
||||
'value': val,
|
||||
'qvalue': val,
|
||||
'hascomment': hascomment,
|
||||
'hasdontknow': hasdontknow,
|
||||
'comment': cmt,
|
||||
'jstriggers': jstriggers,
|
||||
'template': 'questionnaire/choice-yesnocomment.html',
|
||||
}
|
||||
|
||||
|
||||
@question_proc('open', 'open-textfield')
|
||||
def question_open(request, question):
|
||||
key = "question_%s" % question.number
|
||||
value = question.getcheckdict().get('default', '')
|
||||
if key in request.POST:
|
||||
value = request.POST[key]
|
||||
else:
|
||||
#also try to get it from the database so we can handle back/forward in which post has been cleared
|
||||
possiblevalue = question.get_value_for_run_question(get_runid_from_request(request))
|
||||
if not possiblevalue == None:
|
||||
#save process always listifies the answer so we unlistify it to put it back in the field
|
||||
valueaslist = ast.literal_eval(possiblevalue)
|
||||
if len(valueaslist) > 0:
|
||||
value = valueaslist[0]
|
||||
return {
|
||||
'required': question.getcheckdict().get('required', False),
|
||||
'value': value,
|
||||
}
|
||||
|
||||
|
||||
@answer_proc('open', 'open-textfield', 'choice-yesno', 'choice-yesnocomment', 'choice-yesnodontknow','choice-yesno-optional', 'choice-yesnocomment-optional', 'choice-yesnodontknow-optional')
|
||||
def process_simple(question, ansdict):
|
||||
# print 'process_simple has question, ansdict ', question, ',', ansdict
|
||||
checkdict = question.getcheckdict()
|
||||
ans = ansdict['ANSWER'] or ''
|
||||
qtype = question.get_type()
|
||||
if qtype.startswith('choice-yesno'):
|
||||
if ans not in ('yes', 'no', 'dontknow') and not qtype.endswith('-optional'):
|
||||
raise AnswerException(_(u'You must select an option'))
|
||||
if qtype == 'choice-yesnocomment' \
|
||||
and len(ansdict.get('comment', '').strip()) == 0:
|
||||
if checkdict.get('required', False):
|
||||
raise AnswerException(_(u'Field cannot be blank'))
|
||||
if checkdict.get('required-yes', False) and ans == 'yes':
|
||||
raise AnswerException(_(u'Field cannot be blank'))
|
||||
if checkdict.get('required-no', False) and ans == 'no':
|
||||
raise AnswerException(_(u'Field cannot be blank'))
|
||||
else:
|
||||
#the key here is to note that requiredif has already been evaluated or we wouldn't have reached this point, so we don't have to recheck
|
||||
if not ans.strip() and (checkdict.get('required', False) or checkdict.get('requiredif', False)):
|
||||
raise AnswerException(_(u'Field cannot be blank'))
|
||||
maxwords = checkdict.get('maxwords', False)
|
||||
if maxwords:
|
||||
maxwords = int(maxwords)
|
||||
answords = len(ans.split())
|
||||
if answords > maxwords:
|
||||
raise AnswerException(_(u'Answer is ' + str(answords) + ' words. Please shorten answer to ' + str(maxwords) + ' words or less'))
|
||||
if ansdict.has_key('comment') and len(ansdict['comment']) > 0:
|
||||
return dumps([ans, [ansdict['comment']]])
|
||||
if ans:
|
||||
return dumps([ans])
|
||||
return dumps([])
|
||||
|
||||
|
||||
add_type('open', 'Open Answer, single line [input]')
|
||||
add_type('open-textfield', 'Open Answer, multi-line [textarea]')
|
||||
add_type('choice-yesno', 'Yes/No Choice [radio]')
|
||||
add_type('choice-yesnocomment', 'Yes/No Choice with optional comment [radio, input]')
|
||||
add_type('choice-yesnodontknow', 'Yes/No/Don\'t know Choice [radio]')
|
||||
add_type('choice-yesno-optional', 'Optional Yes/No Choice [radio]')
|
||||
add_type('choice-yesnocomment-optional', 'Optional Yes/No Choice with optional comment [radio, input]')
|
||||
add_type('choice-yesnodontknow-optional', 'Optional Yes/No/Don\'t know Choice [radio]')
|
||||
|
||||
|
||||
@answer_proc('comment')
|
||||
def process_comment(question, answer):
|
||||
pass
|
||||
|
||||
|
||||
add_type('comment', 'Comment Only')
|
||||
|
||||
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from .. import add_type, question_proc, answer_proc, AnswerException
|
||||
|
||||
perioddict = {
|
||||
"second" : ugettext_lazy("second(s)"),
|
||||
"minute" : ugettext_lazy("minute(s)"),
|
||||
"hour" : ugettext_lazy("hour(s)"),
|
||||
"day" : ugettext_lazy("day(s)"),
|
||||
"week" : ugettext_lazy("week(s)"),
|
||||
"month" : ugettext_lazy("month(s)"),
|
||||
"year" : ugettext_lazy("year(s)"),
|
||||
}
|
||||
|
||||
@question_proc('timeperiod')
|
||||
def question_timeperiod(request, question):
|
||||
cd = question.getcheckdict()
|
||||
if "units" in cd:
|
||||
units = cd["units"].split(',')
|
||||
else:
|
||||
units = ["day","week","month","year"]
|
||||
timeperiods = []
|
||||
if not units:
|
||||
units = ["day","week","month","year"]
|
||||
|
||||
key1 = "question_%s" % question.number
|
||||
key2 = "question_%s_unit" % question.number
|
||||
value = request.POST.get(key1, '')
|
||||
unitselected = request.POST.get(key2, units[0])
|
||||
|
||||
for x in units:
|
||||
if x in perioddict:
|
||||
timeperiods.append( (x, unicode(perioddict[x]), unitselected==x) )
|
||||
return {
|
||||
"required" : "required" in cd,
|
||||
"timeperiods" : timeperiods,
|
||||
"value" : value,
|
||||
}
|
||||
|
||||
@answer_proc('timeperiod')
|
||||
def process_timeperiod(question, answer):
|
||||
if not answer['ANSWER'] or not answer.has_key('unit'):
|
||||
raise AnswerException(_(u"Invalid time period"))
|
||||
period = answer['ANSWER'].strip()
|
||||
if period:
|
||||
try:
|
||||
period = str(int(period))
|
||||
except ValueError:
|
||||
raise AnswerException(_(u"Time period must be a whole number"))
|
||||
unit = answer['unit']
|
||||
checkdict = question.getcheckdict()
|
||||
if checkdict and 'units' in checkdict:
|
||||
units = checkdict['units'].split(',')
|
||||
else:
|
||||
units = ('day', 'hour', 'week', 'month', 'year')
|
||||
if not period and "required" in checkdict:
|
||||
raise AnswerException(_(u'Field cannot be blank'))
|
||||
if unit not in units:
|
||||
raise AnswerException(_(u"Invalid time period"))
|
||||
return "%s; %s" % (period, unit)
|
||||
|
||||
add_type('timeperiod', 'Time Period [input, select]')
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
# Per request cache middleware
|
||||
|
||||
# Provides a simple cache dictionary only existing for the request's lifetime
|
||||
|
||||
# The middleware provides a threadsafe LocMemCache which can be used just
|
||||
# like any other django cache facility
|
||||
|
||||
from functools import wraps
|
||||
from threading import currentThread
|
||||
from django.core.cache.backends.locmem import LocMemCache
|
||||
|
||||
_request_cache = {}
|
||||
_installed_middleware = False
|
||||
|
||||
def get_request_cache():
|
||||
assert _installed_middleware, 'RequestCacheMiddleware not loaded'
|
||||
return _request_cache[currentThread()]
|
||||
|
||||
def clear_request_cache():
|
||||
_request_cache[currentThread()].clear()
|
||||
|
||||
class RequestCache(LocMemCache):
|
||||
def __init__(self):
|
||||
name = 'locmemcache@%i' % hash(currentThread())
|
||||
params = dict()
|
||||
super(RequestCache, self).__init__(name, params)
|
||||
|
||||
class RequestCacheMiddleware(object):
|
||||
def __init__(self):
|
||||
global _installed_middleware
|
||||
_installed_middleware = True
|
||||
|
||||
def process_request(self, request):
|
||||
cache = _request_cache.get(currentThread()) or RequestCache()
|
||||
_request_cache[currentThread()] = cache
|
||||
|
||||
cache.clear()
|
||||
|
||||
class request_cache(object):
|
||||
""" A decorator for use around functions that should be cached for the current
|
||||
request. Use like this:
|
||||
|
||||
@request_cache()
|
||||
def cached(name):
|
||||
print "My name is %s and I'm cached" % name
|
||||
|
||||
@request_cache(keyfn=lambda p: p['id'])
|
||||
def cached(param):
|
||||
print "My id is %s" % p['id']
|
||||
|
||||
If no keyfn is provided the decorator expects the args to be hashable.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, keyfn=None):
|
||||
self.keyfn = keyfn or (lambda *args: hash(args))
|
||||
|
||||
def __call__(self, func):
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
cache = get_request_cache()
|
||||
|
||||
cachekey = self.keyfn(*args)
|
||||
cacheval = cache.get(cachekey, 'expired')
|
||||
|
||||
if not cacheval == 'expired':
|
||||
return cacheval
|
||||
|
||||
result = func(*args, **kwargs)
|
||||
cache.set(cachekey, result)
|
||||
|
||||
return result
|
||||
|
||||
return wrapper
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,17 +0,0 @@
|
|||
(function($){
|
||||
$(document).ready(function() {
|
||||
var request = {
|
||||
type: 'GET',
|
||||
url: progress_url + '?nocache=' + new Date().getTime(),
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
var progress = data.progress;
|
||||
|
||||
var bar = $('#progress_bar').find('.ui-progress');
|
||||
bar.animate({'width': progress.toString() + '%'});
|
||||
}
|
||||
};
|
||||
|
||||
$.ajax(request);
|
||||
});
|
||||
})(jQuery);
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
Copyright (c) 2010 Ivan Vanderbyl
|
||||
Originally found at http://ivan.ly/ui
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
/* Bar which is placed behind the progress */
|
||||
.ui-progress-bar {
|
||||
/* Usual setup stuff */
|
||||
position: relative;
|
||||
top: 27px;
|
||||
height: 18px;
|
||||
width: 200px;
|
||||
margin: 0 0 0 auto;
|
||||
|
||||
/* Pad right so we don't cover the borders when fully progressed */
|
||||
padding-right: 2px;
|
||||
|
||||
/* For browser that don't support gradients, we'll set a blanket background colour */
|
||||
background-color: #abb2bc;
|
||||
|
||||
/* Rounds the ends, we specify an excessive amount to make sure they are completely rounded */
|
||||
/* Adjust to your liking, and don't forget to adjust to the same amount in .ui-progress */
|
||||
/*border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;*/
|
||||
|
||||
/* Webkit background gradient */
|
||||
background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #b6bcc6), color-stop(1, #9da5b0));
|
||||
/* Mozilla background gradient */
|
||||
background: -moz-linear-gradient(#9da5b0 0%, #b6bcc6 100%);
|
||||
|
||||
/* Give it the inset look by adding some shadows and highlights */
|
||||
-webkit-box-shadow: inset 0px 1px 2px 0px rgba(0, 0, 0, 0.5), 0px 1px 0px 0px #FFF;
|
||||
-moz-box-shadow: inset 0px 1px 2px 0px rgba(0, 0, 0, 0.5), 0px 1px 0px 0px #FFF;
|
||||
box-shadow: inset 0px 1px 2px 0px rgba(0, 0, 0, 0.5), 0px 1px 0px 0px #FFF;
|
||||
}
|
||||
|
||||
/* Progress part of the progress bar */
|
||||
.ui-progress {
|
||||
/* Usual setup stuff */
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
|
||||
/* Height should be 2px less than .ui-progress-bar so as to not cover borders and give it a look of being inset */
|
||||
height: 16px;
|
||||
|
||||
/* Rounds the ends, we specify an excessive amount to make sure they are completely rounded */
|
||||
/* Adjust to your liking, and don't forget to adjust to the same amount in .ui-progress-bar */
|
||||
/*-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;*/
|
||||
|
||||
/* Set the background size so the stripes work correctly */
|
||||
-webkit-background-size: 44px 44px; /* Webkit */
|
||||
|
||||
/* For browser that don't support gradients, we'll set a blanket background colour */
|
||||
background-color: #74d04c;
|
||||
|
||||
/* Webkit background stripes and gradient */
|
||||
background: -webkit-gradient(linear, 0 0, 44 44,
|
||||
color-stop(0.00, rgba(255,255,255,0.17)),
|
||||
color-stop(0.25, rgba(255,255,255,0.17)),
|
||||
color-stop(0.26, rgba(255,255,255,0)),
|
||||
color-stop(0.50, rgba(255,255,255,0)),
|
||||
color-stop(0.51, rgba(255,255,255,0.17)),
|
||||
color-stop(0.75, rgba(255,255,255,0.17)),
|
||||
color-stop(0.76, rgba(255,255,255,0)),
|
||||
color-stop(1.00, rgba(255,255,255,0))
|
||||
), -webkit-gradient(linear, left bottom, left top, color-stop(0, #74d04c), color-stop(1, #9bdd62));
|
||||
|
||||
/* Mozilla (Firefox etc) background stripes */
|
||||
/* Note: Mozilla's support for gradients is more true to the original design, allowing gradients at 30 degrees, as apposed to 45 degress in webkit. */
|
||||
background: -moz-repeating-linear-gradient(top left -30deg,
|
||||
rgba(255,255,255,0.17),
|
||||
rgba(255,255,255,0.17) 15px,
|
||||
rgba(255,255,255,0) 15px,
|
||||
rgba(255,255,255,0) 30px
|
||||
), -moz-linear-gradient(#9bdd62 0%, #74d04c 100%);
|
||||
|
||||
/* Webkit embossing */
|
||||
-webkit-box-shadow: inset 0px 1px 0px 0px #dbf383, inset 0px -1px 1px #58c43a;
|
||||
/* Mozilla embossing */
|
||||
-moz-box-shadow: inset 0px 1px 0px 0px #dbf383, inset 0px -1px 1px #58c43a;
|
||||
/* IE9 and Opera embossing */
|
||||
box-shadow: inset 0px 1px 0px 0px #dbf383, inset 0px -1px 1px #58c43a;
|
||||
|
||||
/* Give it a higher contrast outline */
|
||||
border: 1px solid #4c8932;
|
||||
}
|
||||
|
||||
/* Progress indicator text */
|
||||
.ui-progress span.ui-label {
|
||||
display: none;
|
||||
}
|
|
@ -1,146 +0,0 @@
|
|||
/* question numbers are bold */
|
||||
.qnumber {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.required::after {
|
||||
content: " * ";
|
||||
color: red;
|
||||
}
|
||||
.required:hover::after {
|
||||
content: " * required";
|
||||
color: red;
|
||||
}
|
||||
|
||||
.questionset-title {
|
||||
/* margin right is there to make space for the progressbar */
|
||||
margin: 0 220px 25px 0;
|
||||
}
|
||||
|
||||
.questionset-text {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.questionset-text p{
|
||||
font-size: 1.2em;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
|
||||
.question-text {
|
||||
font-size: 1.2em;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
div.input {
|
||||
margin-left: 35px;
|
||||
}
|
||||
|
||||
.answer {
|
||||
margin: 5px 0 25px 0;
|
||||
padding: 0;
|
||||
border-top: 1px solid #dadada;
|
||||
}
|
||||
|
||||
.answer label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ensure that all inputs have the same distance to the question */
|
||||
div.input input[type="text"], div.input textarea, div.input select {
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
|
||||
div.input span.add-on {
|
||||
margin: 8px 0 0 0;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
ul.inputs-list input[type="text"] {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
div.error {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* move the back link left of the submit button */
|
||||
.back-link {
|
||||
margin-top: -66px;
|
||||
margin-left: 25px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.questionset-submit {
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
#languages {
|
||||
float: right;
|
||||
margin: 15px 0px 0px 0px;
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
margin: 0px -20px; /* negative indent the amount of the padding to maintain the grid system */
|
||||
-webkit-border-radius: 0 0 6px 6px;
|
||||
-moz-border-radius: 0 0 6px 6px;
|
||||
border-radius: 0 0 6px 6px;
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
||||
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px 20px 10px;
|
||||
margin: -20px -20px 20px;
|
||||
}
|
||||
|
||||
.rangeinput div.input {
|
||||
display: inline;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.rangeinput label {
|
||||
text-align: left;
|
||||
max-width: 40px;
|
||||
margin-left: 10px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.rangeinput {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.rangeinput span.help-block {
|
||||
clear: left;
|
||||
margin-left: 35px;
|
||||
}
|
||||
|
||||
span.choice-multiple-values-text {
|
||||
display: inline-block;
|
||||
max-width: 40%;
|
||||
}
|
||||
|
||||
input.sending {
|
||||
text-shadow: 0px 0px 5px white !important;
|
||||
-webkit-transition: 0.3s linear all !important;
|
||||
-moz-transition: 0.3s linear all !important;
|
||||
-o-transition: 0.3s linear all !important;
|
||||
transition: 0.3s linear all !important;
|
||||
|
||||
}
|
||||
|
||||
.questionset-submit input:disabled {
|
||||
-webkit-transition: 0.3s linear all !important;
|
||||
-moz-transition: 0.3s linear all !important;
|
||||
-o-transition: 0.3s linear all !important;
|
||||
transition: 0.3s linear all !important;
|
||||
}
|
||||
|
||||
.hoverable {
|
||||
color: #79E2E8;
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
var qvalues = new Array(); // used as dictionary
|
||||
var qtriggers = new Array();
|
||||
|
||||
function dep_check(expr) {
|
||||
var exprs = expr.split(",",2);
|
||||
var qnum = exprs[0];
|
||||
var value = exprs[1];
|
||||
var qvalue = qvalues[qnum];
|
||||
if(value.substring(0,1) == "!") {
|
||||
var multiple_option = qvalues[qnum+'_'+value.substring(1)];
|
||||
if(multiple_option != undefined)
|
||||
return !multiple_option;
|
||||
value = value.substring(1);
|
||||
return qvalue != value;
|
||||
}
|
||||
if(value.substring(0,1) == "<") {
|
||||
qvalue = parseInt(qvalue);
|
||||
if(value.substring(1,2) == "=") {
|
||||
value = parseInt(value.substring(2));
|
||||
return qvalue <= value;
|
||||
}
|
||||
value = parseInt(value.substring(1));
|
||||
return qvalue < value;
|
||||
}
|
||||
if(value.substring(0,1) == ">") {
|
||||
qvalue = parseInt(qvalue);
|
||||
if(value.substring(1,2) == "=") {
|
||||
value = parseInt(value.substring(2));
|
||||
return qvalue >= value;
|
||||
}
|
||||
value = parseInt(value.substring(1));
|
||||
return qvalue > value;
|
||||
}
|
||||
var multiple_option = qvalues[qnum+'_'+value];
|
||||
if(multiple_option != undefined) {
|
||||
return multiple_option;
|
||||
}
|
||||
if(qvalues[qnum] == value) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getChecksAttr(obj) {
|
||||
return obj.getAttribute('checks');
|
||||
}
|
||||
|
||||
function statusChanged(obj, res) {
|
||||
if(obj.tagName == 'DIV') {
|
||||
obj.style.display = !res ? 'none' : 'block';
|
||||
return;
|
||||
}
|
||||
if(obj.tagName == 'SPAN') {
|
||||
obj.style.visibility = !res ? 'hidden': 'visible';
|
||||
}
|
||||
//obj.style.backgroundColor = !res ? "#eee" : "#fff";
|
||||
obj.disabled = !res;
|
||||
}
|
||||
|
||||
function valchanged(qnum, value) {
|
||||
qvalues[qnum] = value;
|
||||
// qnum may be 'X_Y' for option Y of multiple choice question X
|
||||
qnum = qnum.split('_')[0];
|
||||
for (var t in qtriggers) {
|
||||
t = qtriggers[t];
|
||||
checks = getChecksAttr(t);
|
||||
var res = eval(checks);
|
||||
statusChanged(t, res)
|
||||
}
|
||||
}
|
||||
|
||||
function addtrigger(elemid) {
|
||||
var elem = document.getElementById(elemid);
|
||||
if(!elem) {
|
||||
alert("addtrigger: Element with id "+elemid+" not found.");
|
||||
return;
|
||||
}
|
||||
qtriggers[qtriggers.length] = elem;
|
||||
}
|
||||
|
||||
function alignValueBoxes(question) {
|
||||
var question = document.getElementById("qc_" + question);
|
||||
var choice_boxes = question.getElementsByClassName("choice-multiple-values-text");
|
||||
var max_width = 0;
|
||||
for (i = 0 ; i < choice_boxes.length ; i++) {
|
||||
box_width = choice_boxes[i].offsetWidth;
|
||||
if (box_width > max_width)
|
||||
max_width = box_width;
|
||||
}
|
||||
// add padding to right of narrower boxes
|
||||
for (i = 0 ; i < choice_boxes.length ; i++) {
|
||||
box_width = choice_boxes[i].offsetWidth;
|
||||
padding = max_width - box_width;
|
||||
if (padding > 0)
|
||||
choice_boxes[i].style.paddingRight = padding + "px";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
- disable the submit button once it's been clicked
|
||||
- do it for a total of 5 seconds by which time the page should've been sent
|
||||
- oscillate the sending class which does some fancy css transition trickery
|
||||
*/
|
||||
(function($){
|
||||
$(document).ready(function() {
|
||||
// Ensure that the submit button is enabled to prevent a disabled
|
||||
// button when user clicks the back button on their browser
|
||||
$('.questionset-submit input').removeAttr('disabled');
|
||||
|
||||
$('#qform').submit(function() {
|
||||
var input = $('.questionset-submit input');
|
||||
var interval = 400; // ms
|
||||
var duration = 10000; // 10s
|
||||
|
||||
var disable = function(){
|
||||
input.attr('disabled', 'disabled');
|
||||
input.toggleClass('sending', false);
|
||||
};
|
||||
|
||||
var enable = function(){
|
||||
$('body').css({'cursor':'auto'});
|
||||
input.removeAttr('disabled');
|
||||
};
|
||||
|
||||
var step = 0;
|
||||
var animate = function() {
|
||||
// re-enable the button after the duration
|
||||
if (interval * step > duration) {
|
||||
clearInterval(id);
|
||||
enable();
|
||||
}
|
||||
|
||||
step += 1;
|
||||
input.toggleClass('sending');
|
||||
};
|
||||
|
||||
// start animating before disabling as it looks nicer
|
||||
animate();
|
||||
disable();
|
||||
|
||||
// id is availabe in the animate method. js closures ftw!
|
||||
var id = setInterval(animate, interval);
|
||||
});
|
||||
});
|
||||
|
||||
$(window).unload(function() {});
|
||||
})(jQuery);
|
|
@ -1,59 +0,0 @@
|
|||
(function($){$(document).ready(function() {
|
||||
|
||||
// true if the native html5 range input type is supported by the browser
|
||||
var range_support = (function(){
|
||||
var el=document.createElement("input");
|
||||
el.setAttribute("type", "range");
|
||||
return el.type=="range";
|
||||
})();
|
||||
|
||||
// if range is not supported the input will be a simple text field
|
||||
// in which case the following function ensures that the values
|
||||
// in this text field are within the constraints of min, max, step
|
||||
var normalize_value = function(input) {
|
||||
var input = $(input);
|
||||
|
||||
var min = parseFloat(input.attr('min'));
|
||||
var max = parseFloat(input.attr('max'));
|
||||
var step = parseFloat(input.attr('step'));
|
||||
var val = parseFloat(input.attr('value'));
|
||||
|
||||
if (val > max) val = max;
|
||||
if (val < min) val = min;
|
||||
|
||||
// returns the number of digits after the decimal point
|
||||
var digits = function(value){
|
||||
var str = value.toString();
|
||||
|
||||
if (str.indexOf('.') == -1 && str.indexOf(',') == -1)
|
||||
return 0;
|
||||
|
||||
if (str.indexOf('.') > -1)
|
||||
return str.split('.')[1].length;
|
||||
else
|
||||
return str.split(',')[1].length;
|
||||
};
|
||||
|
||||
// rounds the number to the next step
|
||||
var round = function(val, step) {
|
||||
return Math.round(val * (1 / step)) / (1 / step);
|
||||
};
|
||||
|
||||
// round the number and fix the digits
|
||||
return round(val, step).toFixed(digits(val));
|
||||
};
|
||||
|
||||
if (range_support) {
|
||||
$('.rangeinput input').change(function() {
|
||||
var label = $(this).parent().parent().find('label');
|
||||
var unit = label.attr('data-unit');
|
||||
label.html($(this).val() + unit);
|
||||
});
|
||||
$('.rangeinput input').trigger('change');
|
||||
} else {
|
||||
$('.rangeinput input').change(function() {
|
||||
$(this).val(normalize_value(this));
|
||||
});
|
||||
}
|
||||
|
||||
});})(jQuery);
|
|
@ -1,17 +0,0 @@
|
|||
{% extends "admin/change_form.html" %}
|
||||
{% load i18n questionnaire %}
|
||||
{% block object-tools %}
|
||||
{% if original %}
|
||||
<a href="{{ original|qtesturl }}">Show on Site</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block after_field_sets %}
|
||||
<h2>{% trans "Choices" %}</h2>
|
||||
<ul>
|
||||
<li>{% blocktrans %}Sortid determines the choice order.{% endblocktrans %}</li>
|
||||
<li>{% blocktrans %}Short values will appear in exported data and are
|
||||
referenced in 'Additional checks' fields. Numbers, letters
|
||||
or single words are valid.{% endblocktrans %}</li>
|
||||
</ul>
|
||||
{% endblock %}
|
|
@ -1,37 +0,0 @@
|
|||
{% extends "admin/change_list.html" %}
|
||||
{% block result_list %}
|
||||
|
||||
<script language="javascript">
|
||||
function togglehide(id) {
|
||||
obj = document.getElementById("questionnaire-" + id);
|
||||
head = document.getElementById("qhead-" + id);
|
||||
if(obj) {
|
||||
if(obj.style.display == 'none') {
|
||||
obj.style.display = 'block';
|
||||
head.innerHTML = '↑'
|
||||
} else {
|
||||
obj.style.display = 'none';
|
||||
head.innerHTML = '↓'
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
{% for questionnaire in questionnaires %}
|
||||
|
||||
<H2 onClick="togglehide('{{ questionnaire.id }}');">
|
||||
<span id="qhead-{{ questionnaire.id }}">↑</span>{{ questionnaire.name }}
|
||||
</H2>
|
||||
|
||||
<div id="questionnaire-{{ questionnaire.id }}" style="margin-bottom: 2em;">
|
||||
{% for questionset in questionnaire.questionsets %}
|
||||
<H4>QuestionSet: <a href="/admin/questionnaire/questionset/{{ questionset.id }}/"><font color="#111">{{ questionset.heading }} ({{ questionset.sortid }})</font></a></H4>
|
||||
{% for q in questionset.sorted_questions %}
|
||||
<a href="{{ q.id }}/">{{ q.number }}. {{ q.text }}</a> [{{ q.type }}] (id={{ q.id }})<br />
|
||||
{% endfor %}
|
||||
→ <a href="add/?questionset={{ questionset.id }}">Add Question to <tt>{{ questionset.heading }}</tt></a>
|
||||
{% endfor %}
|
||||
<br /><br />→ <a href="/admin/questionnaire/questionset/add/?questionnaire={{ questionnaire.id }}">Add QuestionSet to <tt>{{ questionnaire.name }}</tt></a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
|
@ -1,14 +0,0 @@
|
|||
{% extends "admin/change_form.html" %}
|
||||
{% block after_field_sets %}
|
||||
{% for x in original.pending %}
|
||||
{% if forloop.first %}<h3>Pending:</h3><ul>{% endif %}
|
||||
<li><b>{{ x.run.runid }}: created {{ x.created }}, last email sent {{ x.emailsent }}</b></li>
|
||||
{% if forloop.last %}</ul>{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% for x in original.history %}
|
||||
{% if forloop.first %}<h3>History:</h3><ul>{% endif %}
|
||||
<li><b>{{ x.run.runid }}: completed {{ x.completed }}</b></li>
|
||||
{% if forloop.last %}</ul>{% endif %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
|
@ -1,57 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<title>{% block title %}Questionnaire{% endblock title %}</title>
|
||||
|
||||
<link rel="stylesheet" href="/static/bootstrap/bootstrap.min.css" type="text/css" />
|
||||
<link rel="stylesheet" href="/static/questionnaire.css" />
|
||||
|
||||
<style type="text/css">
|
||||
{% block styleextra %}
|
||||
|
||||
{% endblock %}
|
||||
</style>
|
||||
|
||||
{% block headextra %}
|
||||
{% endblock %}
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
|
||||
<div id="languages">
|
||||
{% block language %}
|
||||
{% for lang in LANGUAGES %}
|
||||
{% if not forloop.first %} | {% endif %}
|
||||
<a href="/setlang/?lang={{ lang.0 }}&next={{ request.path }}">{{ lang.1 }}</a>
|
||||
{% endfor %}
|
||||
{% endblock language %}
|
||||
</div>
|
||||
|
||||
<div class="page-header">
|
||||
<h1>Sample Django Questionnaire</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="span1"> </div>
|
||||
<div class="span14">
|
||||
{% block content %}
|
||||
{% block questionnaire %}
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud execitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class="span1"> </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,12 +0,0 @@
|
|||
{% extends "base-questionnaire.html" %}
|
||||
|
||||
{% block title %}
|
||||
{{ block.super }} - {{ page.title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block questionnaire %}
|
||||
{{ page.body }}
|
||||
{% if user.is_authenticated %}
|
||||
<a href="/admin/page/page/{{ page.slug }}/">(edit)</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,8 +0,0 @@
|
|||
{% extends "base-questionnaire.html" %}
|
||||
{% block questionnaire %}
|
||||
<h2>
|
||||
Thanks for completing the survey!
|
||||
</h2>
|
||||
|
||||
|
||||
{% endblock %}
|
|
@ -1,14 +0,0 @@
|
|||
{% extends 'basedocumentation.html' %}
|
||||
|
||||
|
||||
{% block title %}Tell us Stuff!{% endblock %}
|
||||
|
||||
{% block doccontent %}
|
||||
|
||||
<h2>Tell us stuff!</h2>
|
||||
<p>We are excited that this is Open Access book and really hope that it will be shared around, read by lots of people all over the world, and used in lots of exciting new ways. We would love to hear about you and how you are using it - please would you share your story with us using this link: (it will only take about 3 minutes - and you could just get a free book as well ….)</p>
|
||||
<p>But comeback again, our survey for {{landing.label}} isn't ready yet.</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
{% extends "base-questionnaire.html" %}
|
||||
{% block questionnaire %}
|
||||
<h1>
|
||||
Survey Results Summary
|
||||
</h1>
|
||||
{% for summary in summaries %}
|
||||
|
||||
<h2>Question</h2>
|
||||
<p>
|
||||
{{summary.1|safe}}
|
||||
</p>
|
||||
{% if summary.2 %}
|
||||
<h3>Choices</h3>
|
||||
<ul>
|
||||
{% for option in summary.2 %}
|
||||
<li>
|
||||
{{option.1}}: <b>{{option.2}}</b>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<h3>Free Text Answers</h3>
|
||||
<ul>
|
||||
{% for answer in summary.3 %}
|
||||
{% if answer %}
|
||||
<li>
|
||||
{{answer}}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,25 +0,0 @@
|
|||
{% load i18n %}
|
||||
<div class="clearfix">
|
||||
<div class="input">
|
||||
<ul class="inputs-list list-unstyled">
|
||||
{% for sel, choice in qdict.choices %}
|
||||
<li>
|
||||
<label>
|
||||
<input onClick="valchanged('{{ question.number }}', this.value)" type="radio" id="{{ question.number }}_{{ forloop.counter }}" name="question_{{ question.number }}" value="{{ choice.value }}" {% if sel %} checked {% endif %}>
|
||||
<span>{{ choice.text }}</span>
|
||||
</label>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input onClick="valchanged('{{ question.number }}', '_entry_');" type="radio" id="{{ question.number }}_entry" name="question_{{ question.number }}" value="_entry_" {% if qdict.sel_entry %} checked {% endif %}>
|
||||
{% if question.extra %}
|
||||
<label for="{{ question.number }}_entry"><span class="extra-block">{{ question.extra }}</span></label>
|
||||
{% else %}
|
||||
<label for="{{ question.number }}_entry">{% trans "Other..." %}</label>
|
||||
{% endif %}
|
||||
<input id="{{ question.number }}_comment" checks="dep_check('{{ question.number }},_entry_')" type="text" name="question_{{ question.number }}_comment" id="{{ question.number }}_comment" value="{{ qdict.comment }}">
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
{% load i18n %}
|
||||
|
||||
<div class="clearfix">
|
||||
<div class="input">
|
||||
<ul class="inputs-list list-unstyled">
|
||||
{% for choice, key, checked, prev_value in qdict.choices %}
|
||||
<li>
|
||||
<label>
|
||||
<span class="{{ qdict.type }}-text">
|
||||
<input onClick="valchanged('{{ question.number }}_{{ choice.value }}', this.checked);" type="checkbox" id="{{ key }}" name="{{ key }}" value="{{ choice.value }}" {{ checked }}>
|
||||
{{ choice.text }}
|
||||
</span>
|
||||
<!-- limits text width if we're in a choice-multiple-values control - allows proper alignment of value boxes -->
|
||||
{% if qdict.type == 'choice-multiple-values' %}
|
||||
<span checks="dep_check('{{ question.number }},{{ choice.value }}')" id="q{{ question.number }}_{{ choice.value }}_box">
|
||||
<input type="text" value="{{ prev_value }}" maxlength="4" style="margin-left: 40px; width: 4em"
|
||||
id="question_{{ question.number }}_{{ choice.value }}_value" name="question_{{ question.number }}_{{ choice.value }}_value">
|
||||
% <!-- percentage sign: all choice-multiple-values currently represent percentages and must add up to 100% -->
|
||||
</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if qdict.type == 'choice-multiple-values' %}
|
||||
<script type="text/javascript">
|
||||
alignValueBoxes("{{ question.number }}");
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% if question.extra %}
|
||||
<li>
|
||||
<label for="{{ question.number }}extra"><span class="extra-block">{{ question.extra }}</span></label>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<label for="{{ question.number }}extra">{% trans "Other..." %}</label>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if qdict.extras %}
|
||||
{% for key, value in qdict.extras %}
|
||||
<li>
|
||||
{% if not forloop.last or not forloop.first %}
|
||||
<b>{{ forloop.counter }}.</b>
|
||||
{% endif %}
|
||||
<input type="text" id="{{ question.number }}extra{% if not forloop.first %}{{ forloop.counter }}{% endif %}" name="{{ key }}" size="50" value="{{ value }}">
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
|
@ -1,43 +0,0 @@
|
|||
{% load i18n %}
|
||||
<div class="clearfix">
|
||||
<div class="input">
|
||||
<ul class="inputs-list list-unstyled">
|
||||
|
||||
<!-- yes -->
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" id="{{ question.number }}_yes" name="question_{{ question.number }}" value="yes" onClick="valchanged('{{ question.number }}', this.value);" {% ifequal qdict.value "yes" %} checked{% endifequal %}>
|
||||
<span>{% trans "Yes" %}</span>
|
||||
</label>
|
||||
</li>
|
||||
|
||||
<!-- no -->
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" id="{{ question.number }}_no" name="question_{{ question.number }}" value="no" onClick="valchanged('{{ question.number }}', this.value);" {% ifequal qdict.value "no" %} checked {% endifequal %}>
|
||||
<span>{% trans "No" %}</span>
|
||||
</label>
|
||||
</li>
|
||||
|
||||
<!-- don't know -->
|
||||
{% if qdict.hasdontknow %}
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" id="{{ question.number }}_dontknow" name="question_{{ question.number }}" value="dontknow" onClick="valchanged('{{ question.number }}', this.value);" {% ifequal qdict.value "dontknow" %} checked {% endifequal %}>
|
||||
<span>{% trans "Don't Know" %}</span>
|
||||
</label>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- comment -->
|
||||
{% if qdict.hascomment %}
|
||||
<li><label>
|
||||
{% if question.extra %}
|
||||
<span class="extra-block">{{ question.extra }}</span><br />
|
||||
{% endif %}
|
||||
<input type="text" id="{{ question.number }}_comment" name="question_{{ question.number }}_comment" value="{{ qdict.comment }}" size="50" {{ qdict.checks|safe }} placeholder="{% trans 'comment' %}">
|
||||
</label></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
|
@ -1,20 +0,0 @@
|
|||
{% load i18n %}
|
||||
<div class="clearfix">
|
||||
<div class="input">
|
||||
<ul class="inputs-list list-unstyled">
|
||||
{% for sel, choice in qdict.choices %}
|
||||
<li>
|
||||
<label>
|
||||
<input type="radio" id="{{ question.number }}_{{ forloop.counter }}" name="question_{{ question.number }}" onClick="valchanged('{{ question.number }}', this.value)" value="{{ choice.value }}"{% if sel %} checked {% endif %}>
|
||||
<span>{{ choice.text }}</span>
|
||||
</label>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if question.extra %}
|
||||
<li>
|
||||
<span class="help-block">{{ question.extra }}</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
|
@ -1,10 +0,0 @@
|
|||
{% extends "base-questionnaire.html" %}
|
||||
{% block questionnaire %}
|
||||
<h2>
|
||||
Merci vielmals! Die Umfragung ist fertig! Sie bekommen eine neue Einladung nächstes Jahr.
|
||||
</h2>
|
||||
|
||||
<h4>
|
||||
Wenn Sie Ihre E-Mail Adresse in der Zwischenzeit ändern, teilen Sie dies uns bitte mit.
|
||||
</h4>
|
||||
{% endblock %}
|
|
@ -1,18 +0,0 @@
|
|||
{% extends "base-questionnaire.html" %}
|
||||
{% block questionnaire %}
|
||||
|
||||
<h2>
|
||||
Thanks for completing the survey!
|
||||
</h2>
|
||||
<div class="question-text">
|
||||
{{ landing_object.claim.all.0.rights_holder }}
|
||||
|
||||
|
||||
{% if request.COOKIES.next %}
|
||||
<p>redirecting in 5 seconds...</p>
|
||||
<script type="text/JavaScript">
|
||||
setTimeout(function(){location.replace('/next/');}, 5000);
|
||||
</script>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1 +0,0 @@
|
|||
{% include question.checks %}
|
|
@ -1,11 +0,0 @@
|
|||
{% load i18n %}
|
||||
<div class="clearfix">
|
||||
<div class="input">
|
||||
<select id="{{ question.number }}" name="question_{{ question.number }}" onChange="valchanged('{{ question.number }}', this.value)">
|
||||
<option value="">{% if question.extra %}{{question.extra}}{% else %}--{% endif %}</option>
|
||||
{% for sel, choice in qdict.choices %}
|
||||
<option value="{{ choice.value }}"{% if sel %} selected{% endif %}>{{ choice.text }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
|
@ -1,9 +0,0 @@
|
|||
<div class="clearfix rangeinput">
|
||||
<div class="input">
|
||||
<input name="question_{{ question.number }}" type="{{ question.type }}" min="{{ qdict.rmin }}" max="{{ qdict.rmax }}" step="{{ qdict.rstep }}" value="{{ qdict.current }}">
|
||||
</div>
|
||||
<label data-unit="{{ qdict.runit }}">{{ qdict.runit }}</label>
|
||||
{% if question.extra %}
|
||||
<span class="help-block">{{ question.extra }}</span>
|
||||
{% endif %}
|
||||
</div>
|
|
@ -1,9 +0,0 @@
|
|||
{% load i18n %}
|
||||
<div class="clearfix">
|
||||
<div class="input">
|
||||
<textarea class="span8" name="question_{{ question.number }}" cols="60" rows="10" id={{ question.number }}>{{ qdict.value }}</textarea>
|
||||
{% if question.extra %}
|
||||
<span class="help-block">{{ question.extra }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
|
@ -1,10 +0,0 @@
|
|||
{% load i18n %}
|
||||
<div class="clearfix">
|
||||
|
||||
<div class="input">
|
||||
<input type="text" class="span8" size="60" id="{{ question.number }}" name="question_{{ question.number }}" value="{{ qdict.value }}">
|
||||
{% if question.extra %}
|
||||
<span class="help-block">{{ question.extra }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
|
@ -1,186 +0,0 @@
|
|||
{% extends "base-questionnaire.html" %}
|
||||
{% load questionnaire i18n %}
|
||||
{% load static %}
|
||||
{% load dynamicStyleTags %}
|
||||
{% load landings %}
|
||||
|
||||
{% block title %}
|
||||
Survey: {{ questionset.heading }}
|
||||
{% endblock %}
|
||||
|
||||
{% block headextra %}
|
||||
<script type="text/javascript" src="{% static 'jquery-1.7.1.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'questionset.js' %}"></script>
|
||||
<link rel="stylesheet" href="{% static 'progressbar.css' %}" />
|
||||
{% if questionsetstylesheet|getAssociatedStylesheets %}
|
||||
<style type="text/css">
|
||||
{{ questionsetstylesheet|getAssociatedStylesheets|safe }}
|
||||
</style>
|
||||
{% endif %}
|
||||
{% if progress %}
|
||||
{% if questionset.questionnaire.name|add:"Progress"|getAssociatedStylesheets %}
|
||||
<style type="text/css">
|
||||
{{ questionset.questionnaire.name|add:"Progress"|getAssociatedStylesheets|safe }}
|
||||
</style>
|
||||
{% else %}
|
||||
<style type="text/css">
|
||||
{{ "CommonProgressStyles"|getAssociatedStylesheets|safe }}
|
||||
</style>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% for x in jsinclude %}
|
||||
<script type="text/javascript" src="{{ x }}"></script>
|
||||
{% endfor %}
|
||||
|
||||
{% for x in cssinclude %}
|
||||
<link rel="stylesheet" href="{{ x }}" type="text/css" />
|
||||
{% endfor %}
|
||||
|
||||
{% if async_progress %}
|
||||
<script type="text/javascript">
|
||||
var progress_url = "{{ async_url }}";
|
||||
</script>
|
||||
<script type="text/javascript" src="{% static 'progress.js' %}"></script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block language %}
|
||||
{% for lang in LANGUAGES %}{% if not forloop.first %} |{% endif %}
|
||||
<a href="{{request.path}}?lang={{ lang.0 }}">{{ lang.1 }}</a>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block questionnaire %}
|
||||
|
||||
{% if progress %}
|
||||
<div id="progress_bar" class="ui-progress-bar ui-container">
|
||||
<div class="ui-progress" style="width: {{progress}}%;">
|
||||
<span class="ui-label"><b class="value">{{progress}}%</b></span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<h2 class="questionset-title">
|
||||
{% if questionset.heading %}
|
||||
{% if questionset.parse_html %}
|
||||
{% render_with_landing questionset.heading|safe %}
|
||||
{% else %}
|
||||
{{ questionset.heading }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</h2>
|
||||
|
||||
<div class="questionset-text">
|
||||
{% if questionset.text %}
|
||||
{% if questionset.parse_html %}
|
||||
{% render_with_landing questionset.text|safe %}
|
||||
{% else %}
|
||||
{{ questionset.text }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<form name="qform" id="qform" action="{{ request.path }}" method="POST">
|
||||
|
||||
{% csrf_token %}
|
||||
|
||||
<input type="hidden" name="questionset_id" value="{{ questionset.id }}">
|
||||
|
||||
{% for question, qdict in qlist %}
|
||||
{% if question.id|getAssociatedStylesheets %}
|
||||
<style type="text/css">
|
||||
{{question.id|getAssociatedStylesheets|safe}}
|
||||
</style>
|
||||
{% endif %}
|
||||
{% with errors|dictget:question.number as error %}
|
||||
|
||||
<div class="question type_{{ qdict.qtype }} {% if error %} error prepend-top{% endif %}{{ qdict.qnum_class }}{{ qdict.qalpha_class }}" id="qc_{{ question.number }}" {{qdict.checkstring|safe}} style="{{qdict.css_style}}">
|
||||
|
||||
{% if request.user.is_staff %}
|
||||
<span class="pull-right">
|
||||
<a href="/admin/questionnaire/question/{{ question.id }}/">
|
||||
({% trans "edit" %} {{ question.number }})
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if qdict.custom %}
|
||||
{% if error %}
|
||||
<div class="error">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include qdict.template %}
|
||||
{% else %}
|
||||
<div class="question-text {% if qdict.required %}required{% endif %}">
|
||||
<label for="{{ question.number }}">
|
||||
<span class="qnumber">{{ question.display_number|safe }}.</span>
|
||||
{% if question.parse_html %}
|
||||
{{ question.text|safe }}
|
||||
{% else %}
|
||||
{{ question.text }}
|
||||
{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
<div class="answer">
|
||||
{% if error %}
|
||||
<div class="alert-message block-message error input"><strong>{{ error }}</strong></div>
|
||||
{% endif %}
|
||||
{% include qdict.template %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div> <!-- /question container -->
|
||||
{% if question.footer %}
|
||||
<div class="question-footer" id="qc_{{ question.number }}_footer" {{qdict.footerchecks|safe}}>
|
||||
{% if question.parse_html %}
|
||||
{{ question.footer|safe }}
|
||||
{% else %}
|
||||
{{ question.footer }}
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
|
||||
<div style="text-align: center;" class="well questionset-submit">
|
||||
<input class="btn large primary" name="submit" type="submit" value="{% if questionset.next %}{% trans 'Continue' %}{% else %}{% trans 'Finish' %}{% endif %}" />
|
||||
|
||||
</div>
|
||||
|
||||
{% if questionset.prev %}
|
||||
<a class="back-link pull-left" href="{{ prev_url }}">{% trans "return to previous page" %}</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
{% if debug_questionnaire %}
|
||||
Previous Answers:
|
||||
{% for answer in current_answers %}
|
||||
{% if forloop.first %}
|
||||
<ul>
|
||||
{% endif %}
|
||||
|
||||
<li>{{ answer.question }}: {{ answer.answer }}</li>
|
||||
|
||||
{% if forloop.last %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<script type="text/javascript">
|
||||
{% for trigger in triggers %}
|
||||
addtrigger("{{trigger}}");
|
||||
{% endfor %}
|
||||
|
||||
{% for k,v in qvalues.items %}
|
||||
qvalues['{{ k|escapejs }}'] = '{{ v|escapejs }}';
|
||||
{% endfor %}
|
||||
|
||||
for(key in qvalues) {
|
||||
valchanged(key, qvalues[key]);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -1 +0,0 @@
|
|||
{% extends "questionnaire/number.html" %}
|
|
@ -1,13 +0,0 @@
|
|||
<div class="clearfix">
|
||||
<div class="input">
|
||||
<input type="text" class="span2" name="question_{{ question.number }}" size="10" value="{{ qdict.value }}">
|
||||
<select class="span2" name="question_{{ question.number }}_unit">
|
||||
{% for val, desc, sel in qdict.timeperiods %}
|
||||
<option value="{{ val }}"{% if sel %} selected="selected"{% endif %}>{{ desc }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% if question.extra %}
|
||||
<span class="help-block">{{ question.extra }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
|
@ -1,15 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
from django import template
|
||||
from ..models import DBStylesheet
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter(name="getAssociatedStylesheets")
|
||||
def getAssociatedStylesheets(inclusionTag):
|
||||
if DBStylesheet.objects.filter(inclusion_tag=inclusionTag).exists():
|
||||
return DBStylesheet.objects.get(inclusion_tag=inclusionTag).content
|
||||
else:
|
||||
return None
|
|
@ -1,15 +0,0 @@
|
|||
import django.template
|
||||
from django.template import Template
|
||||
register = django.template.Library()
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def render_with_landing(context, text):
|
||||
if not context.has_key('landing_object') and context.has_key('runinfo'):
|
||||
context['landing_object'] = context['runinfo'].landing.content_object
|
||||
if text:
|
||||
template = Template(text)
|
||||
return template.render(context)
|
||||
|
||||
else:
|
||||
return ''
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
from django import template
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter(name="dictget")
|
||||
def dictget(thedict, key):
|
||||
"{{ dictionary|dictget:variableholdingkey }}"
|
||||
return thedict.get(key, None)
|
||||
|
||||
@register.filter(name="spanclass")
|
||||
def spanclass(string):
|
||||
l = 2 + len(string.strip()) // 6
|
||||
if l <= 4:
|
||||
return "span-4"
|
||||
if l <= 7:
|
||||
return "span-7"
|
||||
if l < 10:
|
||||
return "span-10"
|
||||
return "span-%d" % l
|
||||
|
||||
@register.filter(name="qtesturl")
|
||||
def qtesturl(question):
|
||||
qset = question.questionset
|
||||
return reverse("questionset",
|
||||
args=("test:%s" % qset.questionnaire.id,
|
||||
qset.sortid))
|
|
@ -1,62 +0,0 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from .dependency_checker import check_actual_answers_against_expression, explode_answer_into_list
|
||||
from .models import Question
|
||||
|
||||
|
||||
class QuestionSetTests(TestCase):
|
||||
def test_exploding_answer_into_list(self):
|
||||
answer = ['1A']
|
||||
self.assertEqual(['1A'], explode_answer_into_list(answer))
|
||||
|
||||
answer = ['1A', ['foobar']]
|
||||
self.assertEqual(['1A', 'foobar'], explode_answer_into_list(answer))
|
||||
|
||||
answer = ['1A', ['foobar', 'barfoo']]
|
||||
self.assertEqual(['1A', 'foobar', 'barfoo'], explode_answer_into_list(answer))
|
||||
|
||||
def test_dependencies_for_multiple_choice_question(self):
|
||||
check_question = Question()
|
||||
self.assertTrue(check_actual_answers_against_expression('3B', ['3B', '3E'], check_question))
|
||||
self.assertTrue(check_actual_answers_against_expression('3E', ['3B', '3E'], check_question))
|
||||
|
||||
def test_dependencies_for_multiple_choice_question_false(self):
|
||||
check_question = Question()
|
||||
self.assertFalse(check_actual_answers_against_expression('3C', ['3B', '3E'], check_question))
|
||||
self.assertFalse(check_actual_answers_against_expression('3F', ['3B', '3E'], check_question))
|
||||
|
||||
def test_dependencies_for_multiple_choice_question_negation(self):
|
||||
check_question = Question()
|
||||
self.assertTrue(check_actual_answers_against_expression('!3C', ['3B', '3E'], check_question))
|
||||
self.assertTrue(check_actual_answers_against_expression('!3A', ['3B', '3E'], check_question))
|
||||
|
||||
def test_dependencies_for_multiple_choice_question_negation_false(self):
|
||||
check_question = Question()
|
||||
self.assertFalse(check_actual_answers_against_expression('!3C', ['3C', '3E'], check_question))
|
||||
self.assertFalse(check_actual_answers_against_expression('!3E', ['3C', '3E'], check_question))
|
||||
|
||||
def test_dependencies_for_single_choice_question(self):
|
||||
check_question = Question()
|
||||
self.assertTrue(check_actual_answers_against_expression('3B', '3B', check_question))
|
||||
self.assertFalse(check_actual_answers_against_expression('3C', '3B', check_question))
|
||||
self.assertTrue(check_actual_answers_against_expression('!3C', '3B', check_question))
|
||||
self.assertFalse(check_actual_answers_against_expression('!3C', '', check_question))
|
||||
self.assertFalse(check_actual_answers_against_expression('!3C', [''], check_question))
|
||||
|
||||
def test_dependencies_for_numeric_checks(self):
|
||||
check_question = Question()
|
||||
self.assertTrue(check_actual_answers_against_expression('>5.6', '6', check_question))
|
||||
self.assertFalse(check_actual_answers_against_expression('>5.6', '3.6', check_question))
|
||||
self.assertFalse(check_actual_answers_against_expression('>5.6', '5.6', check_question))
|
||||
|
||||
self.assertTrue(check_actual_answers_against_expression('>=5.6', '6', check_question))
|
||||
self.assertFalse(check_actual_answers_against_expression('>=5.6', '3.6', check_question))
|
||||
self.assertTrue(check_actual_answers_against_expression('>=5.6', '5.6', check_question))
|
||||
|
||||
self.assertTrue(check_actual_answers_against_expression('<5.6', '4.6', check_question))
|
||||
self.assertFalse(check_actual_answers_against_expression('<5.6', '8.6', check_question))
|
||||
self.assertFalse(check_actual_answers_against_expression('<5.6', '5.6', check_question))
|
||||
|
||||
self.assertTrue(check_actual_answers_against_expression('<=5.6', '3.6', check_question))
|
||||
self.assertFalse(check_actual_answers_against_expression('<=5.6', '9.6', check_question))
|
||||
self.assertTrue(check_actual_answers_against_expression('<=5.6', '5.6', check_question))
|
|
@ -1,41 +0,0 @@
|
|||
# vim: set fileencoding=utf-8
|
||||
|
||||
from django.conf.urls import *
|
||||
from .views import *
|
||||
from .page.views import page, langpage
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$',
|
||||
questionnaire, name='questionnaire_noargs'),
|
||||
url(r'^csv/(?P<qid>\d+)/$',
|
||||
export_csv, name='export_csv'),
|
||||
url(r'^summary/(?P<qid>\d+)/$',
|
||||
export_summary, name='export_summary'),
|
||||
url(r'^(?P<runcode>[^/]+)/progress/$',
|
||||
get_async_progress, name='progress'),
|
||||
url(r'^take/(?P<questionnaire_id>[0-9]+)/$', generate_run),
|
||||
url(r'^$', page, {'page_to_render' : 'index'}),
|
||||
url(r'^(?P<page_to_render>.*)\.html$', page),
|
||||
url(r'^(?P<lang>..)/(?P<page_to_trans>.*)\.html$', langpage),
|
||||
url(r'^setlang/$', set_language),
|
||||
url(r'^landing/(?P<nonce>\w+)/$', SurveyView.as_view(), name="landing"),
|
||||
url(r'^(?P<runcode>[^/]+)/(?P<qs>[-]{0,1}\d+)/$',
|
||||
questionnaire, name='questionset'),
|
||||
]
|
||||
|
||||
if not use_session:
|
||||
urlpatterns += [
|
||||
url(r'^(?P<runcode>[^/]+)/$',
|
||||
questionnaire, name='questionnaire'),
|
||||
url(r'^(?P<runcode>[^/]+)/(?P<qs>[-]{0,1}\d+)/prev/$',
|
||||
redirect_to_prev_questionnaire,
|
||||
name='redirect_to_prev_questionnaire'),
|
||||
]
|
||||
else:
|
||||
urlpatterns += [
|
||||
url(r'^$',
|
||||
questionnaire, name='questionnaire'),
|
||||
url(r'^prev/$',
|
||||
redirect_to_prev_questionnaire,
|
||||
name='redirect_to_prev_questionnaire')
|
||||
]
|
|
@ -1,92 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
import codecs
|
||||
import cStringIO
|
||||
import csv
|
||||
|
||||
from django.conf import settings
|
||||
try:
|
||||
use_session = settings.QUESTIONNAIRE_USE_SESSION
|
||||
except AttributeError:
|
||||
use_session = False
|
||||
|
||||
def split_numal(val):
|
||||
"""Split, for example, '1a' into (1, 'a')
|
||||
>>> split_numal("11a")
|
||||
(11, 'a')
|
||||
>>> split_numal("99")
|
||||
(99, '')
|
||||
>>> split_numal("a")
|
||||
(0, 'a')
|
||||
>>> split_numal("")
|
||||
(0, '')
|
||||
"""
|
||||
if not val:
|
||||
return 0, ''
|
||||
for i in range(len(val)):
|
||||
if not val[i].isdigit():
|
||||
return int(val[0:i] or '0'), val[i:]
|
||||
return int(val), ''
|
||||
|
||||
|
||||
|
||||
def numal_sort(a, b):
|
||||
"""Sort a list numeric-alphabetically
|
||||
|
||||
>>> vals = "1a 1 10 10a 10b 11 2 2a z".split(" "); \\
|
||||
... vals.sort(numal_sort); \\
|
||||
... " ".join(vals)
|
||||
'z 1 1a 2 2a 10 10a 10b 11'
|
||||
"""
|
||||
anum, astr = split_numal(a)
|
||||
bnum, bstr = split_numal(b)
|
||||
cmpnum = cmp(anum, bnum)
|
||||
if(cmpnum == 0):
|
||||
return cmp(astr.lower(), bstr.lower())
|
||||
return cmpnum
|
||||
|
||||
def numal0_sort(a, b):
|
||||
"""
|
||||
numal_sort on the first items in the list
|
||||
"""
|
||||
return numal_sort(a[0], b[0])
|
||||
|
||||
def get_runid_from_request(request):
|
||||
if use_session:
|
||||
return request.session.get('runcode', None)
|
||||
else:
|
||||
return request.runinfo.run.runid
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
class UnicodeWriter:
|
||||
"""
|
||||
COPIED from http://docs.python.org/library/csv.html example:
|
||||
|
||||
A CSV writer which will write rows to CSV file "f",
|
||||
which is encoded in the given encoding.
|
||||
"""
|
||||
|
||||
def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
|
||||
# Redirect output to a queue
|
||||
self.queue = cStringIO.StringIO()
|
||||
self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
|
||||
self.stream = f
|
||||
self.encoder = codecs.getincrementalencoder(encoding)()
|
||||
|
||||
def writerow(self, row):
|
||||
self.writer.writerow([unicode(s).encode("utf-8") for s in row])
|
||||
# Fetch UTF-8 output from the queue ...
|
||||
data = self.queue.getvalue()
|
||||
data = data.decode("utf-8")
|
||||
# ... and reencode it into the target encoding
|
||||
data = self.encoder.encode(data)
|
||||
# write to the target stream
|
||||
self.stream.write(data)
|
||||
# empty queue
|
||||
self.queue.truncate(0)
|
||||
|
||||
def writerows(self, rows):
|
||||
for row in rows:
|
||||
self.writerow(row)
|
|
@ -1,20 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
from .models import RunInfoHistory, Answer
|
||||
|
||||
def get_completed_answers_for_questions(questionnaire_id, question_list):
|
||||
completed_questionnaire_runs = RunInfoHistory.objects.filter(questionnaire__id=questionnaire_id)
|
||||
completed_answers = []
|
||||
for run in completed_questionnaire_runs:
|
||||
specific_answers = Answer.objects.filter(run=run.run, question_id__in=question_list)
|
||||
answer_set = []
|
||||
for answer in specific_answers:
|
||||
if answer.answer != '[]':
|
||||
answer_set.append([int(answer.question_id), answer.answer])
|
||||
if len(answer_set) > 0:
|
||||
completed_answers.append(answer_set)
|
||||
return completed_answers
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
File diff suppressed because it is too large
Load Diff
|
@ -43,6 +43,7 @@ django-storages==1.4.1
|
|||
django-tastypie==0.13.3
|
||||
django-transmeta==0.7.3
|
||||
feedparser==5.1.2
|
||||
fef-questionnaire==4.0.0
|
||||
freebase==1.0.8
|
||||
#gitenberg.metadata==0.1.6
|
||||
git+ssh://git@github.com/gitenberg-dev/metadata.git@0.1.11
|
||||
|
|
|
@ -138,7 +138,7 @@ MIDDLEWARE_CLASSES = (
|
|||
'maintenancemode.middleware.MaintenanceModeMiddleware',
|
||||
'regluit.libraryauth.auth.SocialAuthExceptionMiddlewareWithoutMessages',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'regluit.questionnaire.request_cache.RequestCacheMiddleware',
|
||||
'questionnaire.request_cache.RequestCacheMiddleware',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'regluit.urls'
|
||||
|
@ -182,8 +182,8 @@ INSTALLED_APPS = (
|
|||
'regluit.pyepub',
|
||||
'regluit.libraryauth',
|
||||
'transmeta',
|
||||
'regluit.questionnaire',
|
||||
'regluit.questionnaire.page',
|
||||
'questionnaire',
|
||||
'questionnaire.page',
|
||||
)
|
||||
|
||||
# A sample logging configuration. The only tangible logging
|
||||
|
@ -463,6 +463,8 @@ FILE_UPLOAD_MAX_MEMORY_SIZE = 20971520 #20MB
|
|||
|
||||
QUESTIONNAIRE_USE_SESSION = False
|
||||
QUESTIONNAIRE_DEBUG = True
|
||||
QUESTIONNAIRE_ITEM_MODEL = 'core.Work'
|
||||
QUESTIONNAIRE_SHOW_ITEM_RESULTS = False
|
||||
|
||||
# Selenium related -- set if Se tests run
|
||||
FIREFOX_PATH = ''
|
||||
|
|
2
urls.py
2
urls.py
|
@ -22,7 +22,7 @@ urlpatterns = [
|
|||
url(r"^notification/", include('notification.urls')),
|
||||
url(r'^ckeditor/', include('ckeditor.urls')),
|
||||
# questionnaire urls
|
||||
url(r'^survey/', include('regluit.questionnaire.urls')),
|
||||
url(r'^survey/', include('questionnaire.urls')),
|
||||
# sitemaps
|
||||
url(r'^sitemap\.xml$', index, {'sitemaps': sitemaps},
|
||||
name='django.contrib.sitemaps.views.sitemap'),
|
||||
|
|
Loading…
Reference in New Issue