general renovations
parent
bad5c9d566
commit
72790b421d
51
README.md
51
README.md
|
@ -1,19 +1,23 @@
|
|||
ED Questionnaire
|
||||
FEF Questionnaire
|
||||
=====================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
ED Questionnaire is a Django questionnaire app which is easily customizable
|
||||
FEF Questionnaire is a Django questionnaire app which is easily customizable
|
||||
and includes advanced dependency support using boolean expressions.
|
||||
|
||||
It allows an administrator to create and edit questionnaires in the Django
|
||||
admin interface, with support for multiple languages.
|
||||
|
||||
It can be run either as a survey where subjects are solicited by email, or as a web-based poll.
|
||||
|
||||
In either mode, an instance can be linked to an arbitrary object via the django content-types module.
|
||||
|
||||
History
|
||||
-------
|
||||
|
||||
The questionnaire app was originally developed by [Seantis](https://github.com/seantis), itself derived from [rmt](https://github.com/rmt). We picked up the project because we had been using it and the Seantis version had entered a steady state of development. There are several feature changes we wanted and decided to head up the maintenance ourselves.
|
||||
The questionnaire app was originally developed by [Seantis](https://github.com/seantis), itself derived from [rmt](https://github.com/rmt). Eldest Daughter picked up the project and named it [ED-questionnaire](git://github.com/eldest-daughter/ed-questionnaire) because they had been using it and the Seantis version had entered a steady state of development. There are several feature changes they wanted and decided to head up the maintenance themselves.
|
||||
|
||||
The old versions are tagged as follows:
|
||||
|
||||
|
@ -22,12 +26,14 @@ The old versions are tagged as follows:
|
|||
* tag 2.0 - original updated trunk from Seantis version
|
||||
* tag 2.5 - contains the original Seantis version and all PRs merged in as of 12/09/15. It's considered to be the backwards compatible version of the repository.
|
||||
|
||||
The new version is the current trunk and is dubbed v3.0. It should be considered a new project and thus will contain backwards incompatible changes. When possible, we'll try and backport fixes to the v2.x branches, but it will not be a priority.
|
||||
The "ED-questionnaire" version was dubbed v3.0. It is not compatible with the v2.x branches.
|
||||
|
||||
The "FEF-questionnaire" was created to add the ability to link the questionnaire to individual books in a book database. We'll call this v4.0
|
||||
|
||||
About this Manual
|
||||
-----------------
|
||||
|
||||
ED Questionnaire is not a very well documented app so far to say the least. This manual should give you a general idea of the layout and concepts of it, but it is not as comprehensive as it should be.
|
||||
FEF Questionnaire is not a very well documented app so far to say the least. This manual should give you a general idea of the layout and concepts of it, but it is not as comprehensive as it should be.
|
||||
|
||||
What it does cover is the following:
|
||||
|
||||
|
@ -70,11 +76,11 @@ Create a place for the questionnare
|
|||
|
||||
Clone the questionnaire source
|
||||
|
||||
git clone git://github.com/eldest-daughter/ed-questionnaire.git
|
||||
git clone git://github.com/EbookFoundation/fef-questionnaire.git
|
||||
|
||||
You should now have a ed-questionnaire folder in your apps folder
|
||||
|
||||
cd ed-questionnaire
|
||||
cd fef-questionnaire
|
||||
|
||||
The next step is to install the questionnaire.
|
||||
|
||||
|
@ -102,7 +108,7 @@ We will use that below for the setup of the folders.
|
|||
In the same file add the questionnaire static directory to your STATICFILES_DIRS:
|
||||
|
||||
STATICFILES_DIRS = (
|
||||
os.path.abspath('./apps/ed-questionnaire/questionnaire/static/'),
|
||||
os.path.abspath('./apps/fef-questionnaire/questionnaire/static/'),
|
||||
)
|
||||
|
||||
Also add the locale and request cache middleware to MIDDLEWARE_CLASSES:
|
||||
|
@ -116,7 +122,7 @@ otherwise you will get an error when trying to start the server.
|
|||
|
||||
Add the questionnaire template directory as well as your own to TEMPLATE_DIRS:
|
||||
|
||||
os.path.abspath('./apps/ed-questionnaire/questionnaire/templates'),
|
||||
os.path.abspath('./apps/fef-questionnaire/questionnaire/templates'),
|
||||
os.path.abspath('./templates'),
|
||||
|
||||
And finally, add `transmeta`, `questionnaire` to your INSTALLED_APPS:
|
||||
|
@ -144,12 +150,6 @@ For an empty site with enabled admin interface you add:
|
|||
|
||||
# questionnaire urls
|
||||
url(r'q/', include('questionnaire.urls')),
|
||||
|
||||
url(r'^take/(?P<questionnaire_id>[0-9]+)/$', 'questionnaire.views.generate_run'),
|
||||
url(r'^$', 'questionnaire.page.views.page', {'page_to_render' : 'index'}),
|
||||
url(r'^(?P<lang>..)/(?P<page_to_trans>.*)\.html$', 'questionnaire.page.views.langpage'),
|
||||
url(r'^(?P<page_to_render>.*)\.html$', 'questionnaire.page.views.page'),
|
||||
url(r'^setlang/$', 'questionnaire.views.set_language'),
|
||||
)
|
||||
|
||||
Having done that we can initialize our database. (For this to work you must have setup your DATABASES in `settings.py`.). First, in your CLI navigate back to the `mysite` folder:
|
||||
|
@ -161,19 +161,19 @@ The check that you are in the proper folder, type `ls`: if you can see `manage.p
|
|||
python manage.py syncdb
|
||||
python manage.py migrate
|
||||
|
||||
The questionnaire expects a `base.html` template to be there, with certain stylesheets and blocks inside. Have a look at `./apps/ed-questionnaire/example/templates/base.html`.
|
||||
The questionnaire expects a `base.html` template to be there, with certain stylesheets and blocks inside. Have a look at `./apps/fef-questionnaire/example/templates/base.html`.
|
||||
|
||||
For now you might want to just copy the `base.html` to your own template folder.
|
||||
|
||||
mkdir templates
|
||||
cd templates
|
||||
cp ../apps/ed-questionnaire/example/templates/base.html .
|
||||
cp ../apps/fef-questionnaire/example/templates/base.html .
|
||||
|
||||
Congratulations, you have setup the basics of the questionnaire! At this point this site doesn't really do anything, as there are no questionnaires defined.
|
||||
|
||||
To see an example questionnaire you can do the following (Note: this will only work if you have both English and German defined as Languages in `settings.py`):
|
||||
|
||||
python manage.py loaddata ./apps/ed-questionnaire/example/fixtures/initial_data.yaml
|
||||
python manage.py loaddata ./apps/fef-questionnaire/example/fixtures/initial_data.yaml
|
||||
|
||||
You may then start your development server:
|
||||
|
||||
|
@ -194,6 +194,7 @@ The ED Questionnaire has the following tables, described in detail below.
|
|||
* QuestionSet
|
||||
* Questionnaire
|
||||
* Answer
|
||||
* Landing
|
||||
|
||||
### Subject
|
||||
|
||||
|
@ -289,6 +290,9 @@ Contains the answer to a question. The value of the answer is stored as JSON.
|
|||
|
||||
A questionnaire is a group of questionsets together.
|
||||
|
||||
### Landing
|
||||
|
||||
In Poll mode, the landing url links a Questionnaire to an Object and a User to a Subject.
|
||||
|
||||
Migration of 1.x to 2.0
|
||||
-----------------------
|
||||
|
@ -340,4 +344,15 @@ There are a few that do some simple testing, but more are needed. More tests wou
|
|||
|
||||
Django admin is a nice feature to have, but we either don't leverage it well enough, or it is not the right tool for the questionnaire. In any case, if you are expecting your customer to work with the questionnaire's structure you might have to write your own admin interface. The current one is not good enough.
|
||||
|
||||
4.0 Changes
|
||||
--------------
|
||||
Version 4.0 has not been tested for compatibility with previous versions.
|
||||
|
||||
* Broken back links have been fixed. The application works in session mode and non-session mode.
|
||||
* We've updated to Bootstrap 3.3.6 and implemented label tags for accessibility
|
||||
* "landings" have been added so that survey responses can be linked to arbitrary models in an application. template tags have been added that allow questions and answers to refer to those models.
|
||||
* question types have been added so that choices can be offered without making the question required.
|
||||
* styling of required questions has been spiffed up
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -7,13 +7,13 @@ Create flexible questionnaires.
|
|||
Author: Robert Thomson <git AT corporatism.org>
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.dispatch import Signal
|
||||
import imp
|
||||
|
||||
__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
|
||||
|
@ -28,7 +28,6 @@ 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
|
||||
|
@ -82,19 +81,3 @@ def add_type(id, name):
|
|||
QuestionChoices.append((id, name))
|
||||
|
||||
|
||||
import questionnaire.qprocessors # make sure ours are imported first
|
||||
|
||||
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,6 +1,8 @@
|
|||
from django.utils.translation import ugettext as _
|
||||
from django.contrib import admin
|
||||
from models import *
|
||||
from django.core.urlresolvers import reverse
|
||||
from .models import (Choice, Questionnaire, Question, QuestionSet, Subject,
|
||||
RunInfo, RunInfoHistory, Answer, DBStylesheet, Landing)
|
||||
|
||||
adminsite = admin.site
|
||||
|
||||
|
@ -51,7 +53,8 @@ class QuestionnaireAdmin(admin.ModelAdmin):
|
|||
readonly_fields = ('export',)
|
||||
|
||||
def export(self, obj):
|
||||
return '<a href="/q/csv/%s">%s</a>' % (obj.id, _("Download data"))
|
||||
csv_url= reverse("export_csv", args=[obj.id,])
|
||||
return '<a href="%s">%s</a>' % (csv_url, _("Download data"))
|
||||
|
||||
export.allow_tags = True
|
||||
export.short_description = _('Export to CSV')
|
||||
|
@ -72,6 +75,13 @@ class AnswerAdmin(admin.ModelAdmin):
|
|||
list_filter = ['subject', 'runid']
|
||||
ordering = [ 'id', 'subject', 'runid', 'question', ]
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
# new in dj1.7
|
||||
# @admin.register(Landing)
|
||||
class LandingAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
adminsite.register(Questionnaire, QuestionnaireAdmin)
|
||||
adminsite.register(Question, QuestionAdmin)
|
||||
adminsite.register(QuestionSet, QuestionSetAdmin)
|
||||
|
@ -79,4 +89,5 @@ adminsite.register(Subject, SubjectAdmin)
|
|||
adminsite.register(RunInfo, RunInfoAdmin)
|
||||
adminsite.register(RunInfoHistory, RunInfoHistoryAdmin)
|
||||
adminsite.register(Answer, AnswerAdmin)
|
||||
adminsite.register(Landing, LandingAdmin)
|
||||
adminsite.register(DBStylesheet)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# 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,4 +1,4 @@
|
|||
from questionnaire.models import Question, Answer
|
||||
from .models import Question, Answer
|
||||
import logging
|
||||
|
||||
|
||||
|
|
|
@ -3,18 +3,19 @@
|
|||
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 Context, loader
|
||||
from django.utils import translation
|
||||
from django.conf import settings
|
||||
from django.http import Http404, HttpResponse
|
||||
from models import Subject, QuestionSet, RunInfo, Questionnaire
|
||||
from datetime import datetime
|
||||
from django.shortcuts import render_to_response, get_object_or_404
|
||||
import random, time, smtplib, rfc822
|
||||
from email.Header import Header
|
||||
from email.Utils import formataddr, parseaddr
|
||||
from .models import Subject, QuestionSet, RunInfo, Questionnaire
|
||||
|
||||
try: from hashlib import md5
|
||||
except: from md5 import md5
|
||||
|
||||
|
|
|
@ -0,0 +1,512 @@
|
|||
[
|
||||
{
|
||||
"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
|
||||
}
|
||||
]
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import questionnaire
|
||||
from django.conf.urls.defaults import *
|
||||
from views import *
|
||||
from .views import *
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^q/(?P<runcode>[^/]+)/(?P<qs>\d+)/$',
|
||||
|
|
|
@ -7,7 +7,7 @@ answers submitted to the DB.
|
|||
"""
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from questionnaire.models import *
|
||||
from .models import *
|
||||
from datetime import datetime
|
||||
import os
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
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
|
|
@ -2,7 +2,7 @@ from django.core.management.base import NoArgsCommand
|
|||
|
||||
class Command(NoArgsCommand):
|
||||
def handle_noargs(self, **options):
|
||||
from questionnaire.emails import send_emails
|
||||
from ...emails import send_emails
|
||||
res = send_emails()
|
||||
if res:
|
||||
print res
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
@ -17,9 +18,6 @@ class Migration(migrations.Migration):
|
|||
('runid', models.CharField(help_text='The RunID (ie. year)', max_length=32, verbose_name='RunID')),
|
||||
('answer', models.TextField()),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Choice',
|
||||
|
@ -27,39 +25,62 @@ class Migration(migrations.Migration):
|
|||
('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, verbose_name='Choice Text')),
|
||||
('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)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
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)),
|
||||
('text_en', models.TextField(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'comment', b'Comment Only'), (b'choice', b'Choice [radio]'), (b'choice-freeform', b'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'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)')])),
|
||||
('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 interpreted as textile', verbose_name='Footer', 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?')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
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'/static/complete.html', help_text=b'URL to redirect to when Questionnaire is complete. Macros: $SUBJECTID, $RUNID, $LANG', 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')),
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='QuestionSet',
|
||||
|
@ -68,12 +89,10 @@ class Migration(migrations.Migration):
|
|||
('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"This is interpreted as Textile: <a href='http://en.wikipedia.org/wiki/Textile_%28markup_language%29' target='_blank'>http://en.wikipedia.org/wiki/Textile_(markup_language)</a>", verbose_name='Text')),
|
||||
('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')),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RunInfo',
|
||||
|
@ -89,79 +108,101 @@ class Migration(migrations.Migration):
|
|||
('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',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
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.DateField()),
|
||||
('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',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
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=75, null=True, verbose_name='Email', 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', max_length=2, verbose_name='Language', choices=[(b'en', b'English')])),
|
||||
('language', models.CharField(default=b'en-us', max_length=5, verbose_name='Language', choices=[(b'en', b'English')])),
|
||||
],
|
||||
options={
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='subject',
|
||||
index_together=set([('givenname', 'surname')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='runinfohistory',
|
||||
name='subject',
|
||||
field=models.ForeignKey(to='questionnaire.Subject'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='runinfo',
|
||||
name='subject',
|
||||
field=models.ForeignKey(to='questionnaire.Subject'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='questionset',
|
||||
field=models.ForeignKey(to='questionnaire.QuestionSet'),
|
||||
preserve_default=True,
|
||||
),
|
||||
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'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='answer',
|
||||
name='question',
|
||||
field=models.ForeignKey(help_text='The question that this is an answer to', to='questionnaire.Question'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='answer',
|
||||
name='subject',
|
||||
field=models.ForeignKey(help_text='The user who supplied this answer', to='questionnaire.Subject'),
|
||||
preserve_default=True,
|
||||
),
|
||||
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,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='sort_id',
|
||||
field=models.IntegerField(help_text=b'Questions within a questionset are sorted by sort order first, question number second', null=True, blank=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
|
@ -1,44 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0002_question_sort_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='choice',
|
||||
name='text_en',
|
||||
field=models.CharField(max_length=200, null=True, verbose_name='Choice Text', blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='question',
|
||||
name='footer_en',
|
||||
field=models.TextField(help_text=b'Footer rendered below the question interpreted as textile', null=True, verbose_name='Footer', blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='question',
|
||||
name='text_en',
|
||||
field=models.TextField(null=True, verbose_name='Text', blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='questionset',
|
||||
name='text_en',
|
||||
field=models.TextField(help_text=b"This is interpreted as Textile: <a href='http://en.wikipedia.org/wiki/Textile_%28markup_language%29' target='_blank'>http://en.wikipedia.org/wiki/Textile_(markup_language)</a>", null=True, verbose_name='Text', blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='subject',
|
||||
name='email',
|
||||
field=models.EmailField(max_length=254, null=True, verbose_name='Email', blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='subject',
|
||||
name='language',
|
||||
field=models.CharField(default=b'en', max_length=2, verbose_name='Language', choices=[(b'en', b'English')]),
|
||||
),
|
||||
]
|
|
@ -1,24 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0003_auto_20151129_0727'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='parse_html',
|
||||
field=models.BooleanField(default=False, verbose_name=b'parse question text and footer as html?'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='question',
|
||||
name='type',
|
||||
field=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'),
|
||||
),
|
||||
]
|
|
@ -1,19 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0004_auto_20151202_0230'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='questionset',
|
||||
name='parse_html',
|
||||
field=models.BooleanField(default=False, verbose_name=b'parse questionset heading and text as html?'),
|
||||
),
|
||||
]
|
|
@ -1,19 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0005_questionset_parse_html'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='questionnaire',
|
||||
name='parse_html',
|
||||
field=models.BooleanField(default=False, verbose_name=b'parse questionnaire name as html?'),
|
||||
),
|
||||
]
|
|
@ -1,24 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0006_questionnaire_parse_html'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='questionnaire',
|
||||
name='html',
|
||||
field=models.TextField(verbose_name='Html', blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='questionnaire',
|
||||
name='parse_html',
|
||||
field=models.BooleanField(default=False, verbose_name=b'Render html instead of name for survey?'),
|
||||
),
|
||||
]
|
|
@ -1,21 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0007_auto_20151207_1045'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DBStylesheet',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('content', models.TextField()),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0008_dbstylesheet'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='dbstylesheet',
|
||||
name='inclusion_tag',
|
||||
field=models.CharField(default='', max_length=128),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0009_dbstylesheet_inclusion_tag'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterIndexTogether(
|
||||
name='choice',
|
||||
index_together=set([('value',)]),
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0010_index_choice_value'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterIndexTogether(
|
||||
name='question',
|
||||
index_together=set([('number', 'questionset')]),
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0011_index_on_question_mod_number_questionset'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterIndexTogether(
|
||||
name='questionset',
|
||||
index_together=set([('questionnaire', 'sortid')]),
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0012_questionset_questionnaire_and_sortid_index'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterIndexTogether(
|
||||
name='answer',
|
||||
index_together=set([('subject', 'runid', 'id'), ('subject', 'runid')]),
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0013__answer_index_subject_run_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterIndexTogether(
|
||||
name='runinfo',
|
||||
index_together=set([('random',)]),
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0014__runinfo_index_random'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterIndexTogether(
|
||||
name='questionset',
|
||||
index_together=set([('questionnaire', 'sortid'), ('sortid',)]),
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0015_questionnaire_index_sortid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterIndexTogether(
|
||||
name='subject',
|
||||
index_together=set([('givenname', 'surname')]),
|
||||
),
|
||||
]
|
|
@ -1,31 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0016_subject_given_sur_name_index'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='GlobalStyles',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('content', models.TextField()),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='questionnaire',
|
||||
name='admin_access_only',
|
||||
field=models.BooleanField(default=False, verbose_name=b'Only allow access to logged in users? (This allows entering paper surveys without allowing new external submissions)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='subject',
|
||||
name='language',
|
||||
field=models.CharField(default=b'en-us', max_length=2, verbose_name='Language', choices=[(b'en', b'English')]),
|
||||
),
|
||||
]
|
|
@ -1,19 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('questionnaire', '0017_auto_20160209_0228'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='runinfohistory',
|
||||
name='completed',
|
||||
field=models.DateTimeField(),
|
||||
),
|
||||
]
|
|
@ -1,16 +1,23 @@
|
|||
from django.db import models
|
||||
from transmeta import TransMeta
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from questionnaire import QuestionChoices
|
||||
import re
|
||||
from utils import split_numal
|
||||
import hashlib
|
||||
import json
|
||||
from parsers import parse_checks, ParseException
|
||||
import re
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from transmeta import TransMeta
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.generic 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")),
|
||||
|
@ -20,6 +27,8 @@ class Subject(models.Model):
|
|||
]
|
||||
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,
|
||||
|
@ -39,10 +48,13 @@ class Subject(models.Model):
|
|||
("email", _("Subject receives emails")),
|
||||
("paperform", _("Subject is sent paper form"),))
|
||||
)
|
||||
language = models.CharField(max_length=2, default=settings.LANGUAGE_CODE,
|
||||
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):
|
||||
|
@ -73,7 +85,7 @@ class GlobalStyles(models.Model):
|
|||
|
||||
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", default="/static/complete.html")
|
||||
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)
|
||||
|
@ -99,6 +111,29 @@ class Questionnaire(models.Model):
|
|||
("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
|
||||
|
@ -120,9 +155,9 @@ class QuestionSet(models.Model):
|
|||
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="This is interpreted as Textile: <a href='http://en.wikipedia.org/wiki/Textile_%28markup_language%29' target='_blank'>http://en.wikipedia.org/wiki/Textile_(markup_language)</a>")
|
||||
text = models.TextField(u'Text', help_text="HTML or Text")
|
||||
|
||||
parse_html = models.BooleanField("parse questionset heading and text as html?", null=False, default=False)
|
||||
parse_html = models.BooleanField("Render html in heading?", null=False, default=False)
|
||||
|
||||
def questions(self):
|
||||
if not hasattr(self, "__qcache"):
|
||||
|
@ -182,12 +217,12 @@ class QuestionSet(models.Model):
|
|||
["sortid",]
|
||||
]
|
||||
|
||||
|
||||
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
|
||||
runid = models.CharField(max_length=32)
|
||||
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?
|
||||
|
@ -274,11 +309,11 @@ class RunInfo(models.Model):
|
|||
["random"],
|
||||
]
|
||||
|
||||
|
||||
class RunInfoHistory(models.Model):
|
||||
subject = models.ForeignKey(Subject)
|
||||
runid = models.CharField(max_length=32)
|
||||
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"
|
||||
|
@ -330,9 +365,9 @@ class Question(models.Model):
|
|||
"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 interpreted as textile", blank=True)
|
||||
footer = models.TextField(u"Footer", help_text="Footer rendered below the question", blank=True)
|
||||
|
||||
parse_html = models.BooleanField("parse question text and footer as html?", null=False, default=False)
|
||||
parse_html = models.BooleanField("Render html in Footer?", null=False, default=False)
|
||||
|
||||
def questionnaire(self):
|
||||
return self.questionset.questionnaire
|
||||
|
@ -412,11 +447,12 @@ class Question(models.Model):
|
|||
def is_comment(self):
|
||||
return self.type == 'comment'
|
||||
|
||||
# def __cmp__(a, b):
|
||||
# anum, astr = split_numal(a.number)
|
||||
# bnum, bstr = split_numal(b.number)
|
||||
# cmpnum = cmp(anum, bnum)
|
||||
# return cmpnum or cmp(astr, bstr)
|
||||
def get_value_for_run_question(self, runid):
|
||||
runanswer = Answer.objects.filter(runid=runid,question=self)
|
||||
if len(runanswer) > 0:
|
||||
return runanswer[0].answer
|
||||
else:
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
translate = ('text', 'extra', 'footer')
|
||||
|
@ -424,7 +460,6 @@ class Question(models.Model):
|
|||
["number", "questionset"],
|
||||
]
|
||||
|
||||
|
||||
class Choice(models.Model):
|
||||
__metaclass__ = TransMeta
|
||||
|
||||
|
@ -443,7 +478,6 @@ class Choice(models.Model):
|
|||
['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")
|
||||
|
|
|
@ -1,14 +1,5 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
from questionnaire.models import Answer
|
||||
|
||||
def get_value_for_run_question(runid, questionid):
|
||||
runanswer = Answer.objects.filter(runid=runid,question__id=questionid)
|
||||
if len(runanswer) > 0:
|
||||
return runanswer[0].answer
|
||||
else:
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
from django.contrib import admin
|
||||
from models import Page
|
||||
from .models import Page
|
||||
|
||||
class PageAdmin(admin.ModelAdmin):
|
||||
list_display = ('slug', 'title',)
|
||||
|
|
|
@ -4,13 +4,16 @@ from django.conf import settings
|
|||
from django.template import RequestContext
|
||||
from django import http
|
||||
from django.utils import translation
|
||||
from models import Page
|
||||
from .models import Page
|
||||
|
||||
def page(request, page_to_render):
|
||||
try:
|
||||
p = Page.objects.get(slug=page_to_render, public=True)
|
||||
except Page.DoesNotExist:
|
||||
raise http.Http404('%s page requested but not found' % page_to_render)
|
||||
return render_to_response("pages/{}.html".format(page_to_render),
|
||||
{ "request" : request,},
|
||||
context_instance = RequestContext(request)
|
||||
)
|
||||
|
||||
return render_to_response("page.html",
|
||||
{ "request" : request, "page" : p, },
|
||||
|
@ -22,7 +25,7 @@ def langpage(request, lang, page_to_trans):
|
|||
return page(request, page_to_trans)
|
||||
|
||||
def set_language(request):
|
||||
next = request.REQUEST.get('next', None)
|
||||
next = request.POST.get('next', request.GET.get('next', None))
|
||||
if not next:
|
||||
next = request.META.get('HTTP_REFERER', None)
|
||||
if not next:
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
from django.conf import settings
|
||||
from questionnaire import *
|
||||
from django.utils.translation import ugettext as _
|
||||
# add all the question types to QuestionChoices before anything else
|
||||
from .. import add_type
|
||||
|
||||
import simple # store value as returned
|
||||
import choice # multiple choice, do checks
|
||||
import range_or_number # range of numbers
|
||||
import timeperiod # time periods
|
||||
import custom # backwards compatibility support
|
||||
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,12 +1,12 @@
|
|||
from questionnaire import *
|
||||
from django.utils.translation import ugettext as _, ungettext
|
||||
from json import dumps
|
||||
import ast
|
||||
from questionnaire.utils import get_runid_from_request
|
||||
from questionnaire.modelutils import get_value_for_run_question
|
||||
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')
|
||||
@question_proc('choice', 'choice-freeform', 'dropdown', 'choice-optional', 'choice-freeform-optional')
|
||||
def question_choice(request, question):
|
||||
choices = []
|
||||
jstriggers = []
|
||||
|
@ -15,7 +15,7 @@ def question_choice(request, question):
|
|||
key = "question_%s" % question.number
|
||||
key2 = "question_%s_comment" % question.number
|
||||
val = None
|
||||
possibledbvalue = get_value_for_run_question(get_runid_from_request(request), question.id)
|
||||
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:
|
||||
|
@ -27,22 +27,25 @@ def question_choice(request, question):
|
|||
for choice in question.choices():
|
||||
choices.append( ( choice.value == val, choice, ) )
|
||||
|
||||
if question.type == 'choice-freeform':
|
||||
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 '',
|
||||
'required' : True,
|
||||
"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')
|
||||
@answer_proc('choice', 'choice-freeform', 'dropdown', 'choice-optional', 'choice-freeform-optional')
|
||||
def process_choice(question, answer):
|
||||
opt = answer['ANSWER'] or ''
|
||||
if not opt:
|
||||
if not opt and not question.type.endswith( '-optional'):
|
||||
raise AnswerException(_(u'You must select an option'))
|
||||
if opt == '_entry_' and question.type == 'choice-freeform':
|
||||
opt = answer.get('comment','')
|
||||
|
@ -51,11 +54,13 @@ def process_choice(question, answer):
|
|||
return dumps([[opt]])
|
||||
else:
|
||||
valid = [c.value for c in question.choices()]
|
||||
if opt not in valid:
|
||||
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')
|
||||
|
@ -67,7 +72,7 @@ def question_multiple(request, question):
|
|||
qvalues = []
|
||||
cd = question.getcheckdict()
|
||||
defaults = cd.get('default','').split(',')
|
||||
possibledbvalue = get_value_for_run_question(get_runid_from_request(request), question.id)
|
||||
possibledbvalue = question.get_value_for_run_question(get_runid_from_request(request))
|
||||
possiblelist = []
|
||||
if not possibledbvalue == None:
|
||||
possiblelist = ast.literal_eval(possibledbvalue)
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
# exist in the drop down list of the management interface.
|
||||
#
|
||||
|
||||
from questionnaire import *
|
||||
from questionnaire import Processors, QuestionProcessors
|
||||
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):
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
from questionnaire import *
|
||||
import ast
|
||||
from json import dumps
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from json import dumps
|
||||
import ast
|
||||
from questionnaire.utils import get_runid_from_request
|
||||
from questionnaire.modelutils import get_value_for_run_question
|
||||
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):
|
||||
|
@ -15,7 +14,7 @@ def question_range_or_number(request, question):
|
|||
runit = cd.get('unit', '')
|
||||
|
||||
#try loading current from database before just setting to min
|
||||
possibledbvalue = get_value_for_run_question(get_runid_from_request(request), question.id)
|
||||
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:
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
from questionnaire import *
|
||||
from questionnaire.utils import get_runid_from_request
|
||||
from questionnaire.modelutils import get_value_for_run_question
|
||||
from django.utils.translation import ugettext as _
|
||||
from json import dumps
|
||||
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')
|
||||
@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
|
||||
|
@ -19,17 +18,17 @@ def question_yesno(request, question):
|
|||
cd = question.getcheckdict()
|
||||
jstriggers = []
|
||||
|
||||
if qtype == 'choice-yesnocomment':
|
||||
if qtype.startswith('choice-yesnocomment'):
|
||||
hascomment = True
|
||||
else:
|
||||
hascomment = False
|
||||
if qtype == 'choice-yesnodontknow' or 'dontknow' in cd:
|
||||
if qtype.startswith( 'choice-yesnodontknow') or 'dontknow' in cd:
|
||||
hasdontknow = True
|
||||
else:
|
||||
hasdontknow = False
|
||||
|
||||
#try the database before reverting to default
|
||||
possiblevalue = get_value_for_run_question(get_runid_from_request(request), question.id)
|
||||
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)
|
||||
|
@ -51,7 +50,7 @@ def question_yesno(request, question):
|
|||
checks = ' checks="dep_check(\'%s,dontknow\')"' % question.number
|
||||
|
||||
return {
|
||||
'required': True,
|
||||
'required': not qtype.endswith("-optional"),
|
||||
'checks': checks,
|
||||
'value': val,
|
||||
'qvalue': val,
|
||||
|
@ -71,7 +70,7 @@ def question_open(request, question):
|
|||
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 = get_value_for_run_question(get_runid_from_request(request), question.id)
|
||||
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)
|
||||
|
@ -83,14 +82,14 @@ def question_open(request, question):
|
|||
}
|
||||
|
||||
|
||||
@answer_proc('open', 'open-textfield', 'choice-yesno', 'choice-yesnocomment', 'choice-yesnodontknow')
|
||||
@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'):
|
||||
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:
|
||||
|
@ -122,6 +121,9 @@ 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')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from questionnaire import *
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from .. import add_type, question_proc, answer_proc, AnswerException
|
||||
|
||||
perioddict = {
|
||||
"second" : ugettext_lazy("second(s)"),
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,8 +1,17 @@
|
|||
/* question numbers are always bold, the whole question if it is required */
|
||||
.required, .qnumber {
|
||||
/* 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;
|
||||
|
@ -12,9 +21,15 @@
|
|||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.questionset-text p{
|
||||
font-size: 1.2em;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
|
||||
.question-text {
|
||||
font-size: 1.3em;
|
||||
line-height: 1.3em;
|
||||
font-size: 1.2em;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
div.input {
|
||||
|
@ -65,9 +80,6 @@ div.error {
|
|||
margin: 15px 0px 0px 0px;
|
||||
}
|
||||
|
||||
html, body {
|
||||
background-color: #cdcdcd;
|
||||
}
|
||||
|
||||
.content {
|
||||
background-color: #fff;
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<!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,10 +1,10 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "base-questionnaire.html" %}
|
||||
|
||||
{% block title %}
|
||||
{{ block.super }} - {{ page.title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block questionnaire %}
|
||||
{{ page.body }}
|
||||
{% if user.is_authenticated %}
|
||||
<a href="/admin/page/page/{{ page.slug }}/">(edit)</a>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{% extends "base-questionnaire.html" %}
|
||||
{% block questionnaire %}
|
||||
<h2>
|
||||
Thanks for completing the survey!
|
||||
</h2>
|
||||
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,14 @@
|
|||
{% 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,7 +1,7 @@
|
|||
{% load i18n %}
|
||||
<div class="clearfix">
|
||||
<div class="input">
|
||||
<ul class="inputs-list">
|
||||
<ul class="inputs-list list-unstyled">
|
||||
{% for sel, choice in qdict.choices %}
|
||||
<li>
|
||||
<label>
|
||||
|
@ -14,10 +14,12 @@
|
|||
</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 %}>
|
||||
<input id="{{ question.number }}_comment" checks="dep_check('{{ question.number }},_entry_')" type="input" name="question_{{ question.number }}_comment" value="{{ qdict.comment }}">
|
||||
{% if question.extra %}
|
||||
<span class="help-block">{{ question.extra }}</span>
|
||||
<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>
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
<div class="clearfix">
|
||||
<div class="input">
|
||||
<ul class="inputs-list">
|
||||
<ul class="inputs-list list-unstyled">
|
||||
{% for choice, key, checked, prev_value in qdict.choices %}
|
||||
<li>
|
||||
<!-- <label> -->
|
||||
<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 }}
|
||||
|
@ -18,7 +18,7 @@
|
|||
% <!-- percentage sign: all choice-multiple-values currently represent percentages and must add up to 100% -->
|
||||
</span>
|
||||
{% endif %}
|
||||
<!-- </label> -->
|
||||
</label>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if qdict.type == 'choice-multiple-values' %}
|
||||
|
@ -27,22 +27,26 @@
|
|||
</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" name="{{ key }}" size="50" value="{{ value }}">
|
||||
<input type="text" id="{{ question.number }}extra{% if not forloop.first %}{{ forloop.counter }}{% endif %}" name="{{ key }}" size="50" value="{{ value }}">
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if question.extra %}
|
||||
<li>
|
||||
<span class="help-block">{{ question.extra }}</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% load i18n %}
|
||||
<div class="clearfix">
|
||||
<div class="input">
|
||||
<ul class="inputs-list">
|
||||
<ul class="inputs-list list-unstyled">
|
||||
|
||||
<!-- yes -->
|
||||
<li>
|
||||
|
@ -31,12 +31,12 @@
|
|||
|
||||
<!-- comment -->
|
||||
{% if qdict.hascomment %}
|
||||
<li>
|
||||
<input type="text" id="{{ question.number }}_comment" name="question_{{ question.number }}_comment" value="{{ qdict.comment }}" size="50" {{ qdict.checks|safe }} placeholder="{% trans 'comment' %}">
|
||||
<li><label>
|
||||
{% if question.extra %}
|
||||
<span class="help-block">{{ question.extra }}</span>
|
||||
<span class="extra-block">{{ question.extra }}</span><br />
|
||||
{% endif %}
|
||||
</li>
|
||||
<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>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% load i18n %}
|
||||
<div class="clearfix">
|
||||
<div class="input">
|
||||
<ul class="inputs-list">
|
||||
<ul class="inputs-list list-unstyled">
|
||||
{% for sel, choice in qdict.choices %}
|
||||
<li>
|
||||
<label>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
{% extends "base-questionnaire.html" %}
|
||||
{% block questionnaire %}
|
||||
<h2>
|
||||
Merci vielmals! Die Umfragung ist fertig! Sie bekommen eine neue Einladung nächstes Jahr.
|
||||
</h2>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{% 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,10 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<h2>
|
||||
Thanks for completing the survey! You will be contacted again next year.
|
||||
</h2>
|
||||
|
||||
<h4>
|
||||
If you change your email address in the meantime, contact us please.
|
||||
</h4>
|
||||
{% endblock %}
|
|
@ -1,7 +1,7 @@
|
|||
{% load i18n %}
|
||||
<div class="clearfix">
|
||||
<div class="input">
|
||||
<textarea class="span8" name="question_{{ question.number }}" cols="60" rows="10">{{ qdict.value }}</textarea>
|
||||
<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 %}
|
||||
|
|
|
@ -1,17 +1,33 @@
|
|||
{% extends "base.html" %}
|
||||
{% 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' %}"/>-->
|
||||
<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 %}
|
||||
|
@ -34,18 +50,9 @@
|
|||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block questionnaire %}
|
||||
|
||||
{% 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 %}
|
||||
<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>
|
||||
|
@ -57,7 +64,7 @@
|
|||
<h2 class="questionset-title">
|
||||
{% if questionset.heading %}
|
||||
{% if questionset.parse_html %}
|
||||
{{ questionset.heading|safe }}
|
||||
{% render_with_landing questionset.heading|safe %}
|
||||
{% else %}
|
||||
{{ questionset.heading }}
|
||||
{% endif %}
|
||||
|
@ -67,7 +74,7 @@
|
|||
<div class="questionset-text">
|
||||
{% if questionset.text %}
|
||||
{% if questionset.parse_html %}
|
||||
{{ questionset.text|safe }}
|
||||
{% render_with_landing questionset.text|safe %}
|
||||
{% else %}
|
||||
{{ questionset.text }}
|
||||
{% endif %}
|
||||
|
@ -107,12 +114,14 @@
|
|||
{% include qdict.template %}
|
||||
{% else %}
|
||||
<div class="question-text {% if qdict.required %}required{% endif %}">
|
||||
<span class="qnumber">{% if not question.parse_html %}{{ question.display_number|safe }}.{% endif %}</span>
|
||||
<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 %}
|
||||
|
@ -138,7 +147,7 @@
|
|||
|
||||
|
||||
<div style="text-align: center;" class="well questionset-submit">
|
||||
<input class="btn large primary" name="submit" type="submit" value="{% trans "Continue" %}">
|
||||
<input class="btn large primary" name="submit" type="submit" value="{% if questionset.next %}{% trans 'Continue' %}{% else %}{% trans 'Finish' %}{% endif %}" />
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import django.template
|
||||
from django.template import Template, Context
|
||||
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,19 +1,15 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
from django import template
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext as _
|
||||
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
|
||||
|
@ -31,4 +27,3 @@ def qtesturl(question):
|
|||
return reverse("questionset",
|
||||
args=("test:%s" % qset.questionnaire.id,
|
||||
qset.sortid))
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from dependency_checker import check_actual_answers_against_expression, explode_answer_into_list
|
||||
from .dependency_checker import check_actual_answers_against_expression, explode_answer_into_list
|
||||
from .models import Question
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# vim: set fileencoding=utf-8
|
||||
|
||||
from django.conf.urls import *
|
||||
from views import *
|
||||
|
||||
from .views import *
|
||||
from .page.views import page, langpage
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
|
@ -12,13 +12,21 @@ urlpatterns = patterns(
|
|||
export_csv, name='export_csv'),
|
||||
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'),
|
||||
url(r'^q/manage/csv/(\d+)/',
|
||||
export_csv, name="export_csv"),
|
||||
)
|
||||
|
||||
if not use_session:
|
||||
urlpatterns += patterns(
|
||||
'',
|
||||
url(r'^(?P<runcode>[^/]+)/(?P<qs>[-]{0,1}\d+)/$',
|
||||
questionnaire, name='questionset'),
|
||||
url(r'^(?P<runcode>[^/]+)/$',
|
||||
questionnaire, name='questionnaire'),
|
||||
url(r'^(?P<runcode>[^/]+)/(?P<qs>[-]{0,1}\d+)/prev/$',
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
#!/usr/bin/python
|
||||
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')
|
||||
|
@ -42,18 +47,10 @@ def numal0_sort(a, b):
|
|||
return numal_sort(a[0], b[0])
|
||||
|
||||
def get_runid_from_request(request):
|
||||
request_string = str(request)
|
||||
string_chunks = request_string.split('/')
|
||||
return string_chunks[2]
|
||||
|
||||
def get_sortid_from_request(request):
|
||||
request_string = str(request)
|
||||
string_chunks = request_string.split('/')
|
||||
if len(string_chunks) > 3:
|
||||
return string_chunks[3]
|
||||
|
||||
#not enough string chunks to get a sortid
|
||||
return None
|
||||
if use_session:
|
||||
return request.session.get('runcode', None)
|
||||
else:
|
||||
return request.runinfo.runid
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
from questionnaire.models import RunInfoHistory, Answer
|
||||
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)
|
||||
|
|
|
@ -6,23 +6,24 @@ from django.core.urlresolvers import reverse
|
|||
from django.core.cache import cache
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.shortcuts import render_to_response, get_object_or_404
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.db import transaction
|
||||
from django.conf import settings
|
||||
from datetime import datetime
|
||||
from django.utils import translation
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from questionnaire import QuestionProcessors
|
||||
from questionnaire import questionnaire_start, questionset_start, questionset_done, questionnaire_done
|
||||
from questionnaire import AnswerException
|
||||
from questionnaire import Processors
|
||||
from questionnaire.models import *
|
||||
from questionnaire.parsers import *
|
||||
from questionnaire.parsers import BoolNot, BoolAnd, BoolOr, Checker
|
||||
from questionnaire.emails import _send_email, send_emails
|
||||
from questionnaire.utils import numal_sort, split_numal, get_sortid_from_request
|
||||
from questionnaire.request_cache import request_cache
|
||||
from questionnaire.dependency_checker import dep_check
|
||||
from questionnaire import profiler
|
||||
from . import QuestionProcessors
|
||||
from . import questionnaire_start, questionset_start, questionset_done, questionnaire_done
|
||||
from . import AnswerException
|
||||
from . import Processors
|
||||
from . import profiler
|
||||
from .models import *
|
||||
from .parsers import *
|
||||
from .parsers import BoolNot, BoolAnd, BoolOr, Checker
|
||||
from .emails import _send_email, send_emails
|
||||
from .utils import numal_sort, split_numal
|
||||
from .request_cache import request_cache
|
||||
from .dependency_checker import dep_check
|
||||
from compat import commit_on_success, commit, rollback
|
||||
import logging
|
||||
import random
|
||||
|
@ -54,9 +55,9 @@ def get_runinfo(random):
|
|||
return res and res[0] or None
|
||||
|
||||
|
||||
def get_question(number, questionnaire):
|
||||
"Return the specified Question (by number) from the specified Questionnaire"
|
||||
res = Question.objects.filter(number=number, questionset__questionnaire=questionnaire)
|
||||
def get_question(number, questionset):
|
||||
"Return the specified Question (by number) from the specified Questionset"
|
||||
res = Question.objects.filter(number=number, questionset=questionset)
|
||||
return res and res[0] or None
|
||||
|
||||
|
||||
|
@ -299,7 +300,7 @@ def redirect_to_prev_questionnaire(request, runcode=None, qs=None):
|
|||
"""
|
||||
Takes the questionnaire set in the session and redirects to the
|
||||
previous questionnaire if any. Used for linking to previous pages
|
||||
both when using sessions or not. (Has not been tested with sessions.)
|
||||
both when using sessions or not.
|
||||
"""
|
||||
if use_session:
|
||||
runcode = request.session.get('runcode', None)
|
||||
|
@ -448,7 +449,7 @@ def questionnaire(request, runcode=None, qs=None):
|
|||
key, value = item[0], item[1]
|
||||
if key.startswith('question_'):
|
||||
answer = key.split("_", 2)
|
||||
question = get_question(answer[1], questionnaire)
|
||||
question = get_question(answer[1], questionset)
|
||||
if not question:
|
||||
logging.warn("Unknown question when processing: %s" % answer[1])
|
||||
continue
|
||||
|
@ -522,13 +523,14 @@ def finish_questionnaire(request, runinfo, questionnaire):
|
|||
hist.questionnaire = questionnaire
|
||||
hist.tags = runinfo.tags
|
||||
hist.skipped = runinfo.skipped
|
||||
hist.landing = runinfo.landing
|
||||
hist.save()
|
||||
|
||||
questionnaire_done.send(sender=None, runinfo=runinfo,
|
||||
questionnaire=questionnaire)
|
||||
|
||||
lang=translation.get_language()
|
||||
redirect_url = questionnaire.redirect_url
|
||||
for x, y in (('$LANG', translation.get_language()),
|
||||
for x, y in (('$LANG', lang),
|
||||
('$SUBJECTID', runinfo.subject.id),
|
||||
('$RUNID', runinfo.runid),):
|
||||
redirect_url = redirect_url.replace(x, str(y))
|
||||
|
@ -542,7 +544,7 @@ def finish_questionnaire(request, runinfo, questionnaire):
|
|||
commit()
|
||||
if redirect_url:
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
return r2r("questionnaire/complete.$LANG.html", request)
|
||||
return r2r("questionnaire/complete.{}.html".format(lang), request, landing_object=hist.landing.content_object)
|
||||
|
||||
|
||||
def recursivly_build_partially_evaluated_javascript_expression_for_shownif_check(treenode, runinfo, question):
|
||||
|
@ -711,7 +713,6 @@ def show_questionnaire(request, runinfo, errors={}):
|
|||
else:
|
||||
qvalues[s[1]] = v
|
||||
|
||||
|
||||
if use_session:
|
||||
prev_url = reverse('redirect_to_prev_questionnaire')
|
||||
else:
|
||||
|
@ -780,7 +781,7 @@ def set_language(request, runinfo=None, next=None):
|
|||
Can also be used by a url handler, w/o runinfo & next.
|
||||
"""
|
||||
if not next:
|
||||
next = request.REQUEST.get('next', None)
|
||||
next = request.GET.get('next', request.POST.get('next', None))
|
||||
if not next:
|
||||
next = request.META.get('HTTP_REFERER', None)
|
||||
if not next:
|
||||
|
@ -811,9 +812,9 @@ def _table_headers(questions):
|
|||
ql.sort(lambda x, y: numal_sort(x.number, y.number))
|
||||
columns = []
|
||||
for q in ql:
|
||||
if q.type == 'choice-yesnocomment':
|
||||
if q.type.startswith('choice-yesnocomment'):
|
||||
columns.extend([q.number, q.number + "-freeform"])
|
||||
elif q.type == 'choice-freeform':
|
||||
elif q.type.startswith('choice-freeform'):
|
||||
columns.extend([q.number, q.number + "-freeform"])
|
||||
elif q.type.startswith('choice-multiple'):
|
||||
cl = [c.value for c in q.choice_set.all()]
|
||||
|
@ -1026,7 +1027,7 @@ def send_email(request, runinfo_id):
|
|||
return r2r("emailsent.html", request, runinfo=runinfo, successful=successful)
|
||||
|
||||
|
||||
def generate_run(request, questionnaire_id, subject_id=None):
|
||||
def generate_run(request, questionnaire_id, subject_id=None, context={}):
|
||||
"""
|
||||
A view that can generate a RunID instance anonymously,
|
||||
and then redirect to the questionnaire itself.
|
||||
|
@ -1044,19 +1045,16 @@ def generate_run(request, questionnaire_id, subject_id=None):
|
|||
if subject_id is not None:
|
||||
su = get_object_or_404(Subject, pk=subject_id)
|
||||
else:
|
||||
su = Subject.objects.filter(givenname='Anonymous', surname='User')[0:1]
|
||||
if su:
|
||||
su = su[0]
|
||||
else:
|
||||
su = Subject(givenname='Anonymous', surname='User')
|
||||
su = Subject(anonymous=True, ip_address=request.META['REMOTE_ADDR'])
|
||||
su.save()
|
||||
|
||||
# str_to_hash = "".join(map(lambda i: chr(random.randint(0, 255)), range(16)))
|
||||
str_to_hash = str(uuid4())
|
||||
str_to_hash += settings.SECRET_KEY
|
||||
key = md5(str_to_hash).hexdigest()
|
||||
landing = context.get('landing', None)
|
||||
|
||||
run = RunInfo(subject=su, random=key, runid=key, questionset=qs)
|
||||
run = RunInfo(subject=su, random=key, runid=key, questionset=qs, landing=landing)
|
||||
run.save()
|
||||
if not use_session:
|
||||
kwargs = {'runcode': key}
|
||||
|
@ -1065,7 +1063,29 @@ def generate_run(request, questionnaire_id, subject_id=None):
|
|||
request.session['runcode'] = key
|
||||
|
||||
questionnaire_start.send(sender=None, runinfo=run, questionnaire=qu)
|
||||
return HttpResponseRedirect(reverse('questionnaire', kwargs=kwargs))
|
||||
response = HttpResponseRedirect(reverse('questionnaire', kwargs=kwargs))
|
||||
response.set_cookie('next', context.get('next',''))
|
||||
return response
|
||||
|
||||
def generate_error(request):
|
||||
return 400/0
|
||||
|
||||
class SurveyView(TemplateView):
|
||||
template_name = "pages/generic.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SurveyView, self).get_context_data(**kwargs)
|
||||
|
||||
nonce = self.kwargs['nonce']
|
||||
landing = get_object_or_404(Landing, nonce=nonce)
|
||||
context["landing"] = landing
|
||||
context["next"] = self.request.GET.get('next', '')
|
||||
|
||||
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
context = self.get_context_data(**kwargs)
|
||||
if context['landing'].questionnaire:
|
||||
return generate_run(request, context['landing'].questionnaire.id, context=context)
|
||||
return self.render_to_response(context)
|
||||
|
|
12
setup.py
12
setup.py
|
@ -6,14 +6,14 @@ def read(fname):
|
|||
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
||||
|
||||
setup(
|
||||
name="ed-questionnaire",
|
||||
version="2.0.1",
|
||||
name="fef-questionnaire",
|
||||
version="4.0.0",
|
||||
description="A Django application for creating online questionnaires/surveys.",
|
||||
long_description=read("README.md"),
|
||||
author="Eldest Daughter, LLC.",
|
||||
author_email="gcaprio@eldestdaughter.com",
|
||||
author="Eldest Daughter, LLC.","Free Ebook Foundation"
|
||||
author_email="gcaprio@eldestdaughter.com", "eric@hellman.net"
|
||||
license="BSD",
|
||||
url="https://github.com/eldest-daughter/ed-questionnaire",
|
||||
url="https://github.com/EbookFoundation/fef-questionnaire",
|
||||
packages=find_packages(exclude=["example"]),
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
|
@ -23,8 +23,6 @@ setup(
|
|||
"License :: OSI Approved :: BSD License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
'Programming Language :: Python :: 2.5',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
"Framework :: Django",
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue