commit
6672fa575b
|
@ -60,3 +60,15 @@ atlassian-ide-plugin.xml
|
|||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
|
||||
# Python packaging
|
||||
.eggs*
|
||||
.env
|
||||
.tox*
|
||||
build*
|
||||
dist*
|
||||
*.egg-info/*
|
||||
log/log.txt
|
||||
*.log
|
||||
db.sqlite3
|
||||
.python-version
|
|
@ -0,0 +1,12 @@
|
|||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[packages]
|
||||
fef-questionnaire = {editable = true,path = "."}
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
|
@ -0,0 +1,81 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "3f3bbecd60d8aa8a4e96361e34be69113185857815516ad894e3d6bf44ee9bea"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.6"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:a3b01cdff845a43830d7ccacff55e0b8ff08305a4cbf894517a686e53ba3ad2d",
|
||||
"sha256:b33ce35f47f745fea6b5aa3cf3f4241069803a3712d423ac748bd673a39741eb"
|
||||
],
|
||||
"version": "==1.11.28"
|
||||
},
|
||||
"django-compat": {
|
||||
"hashes": [
|
||||
"sha256:3ac9a3bedc56b9365d9eb241bc5157d0c193769bf995f9a78dc1bc24e7c2331b"
|
||||
],
|
||||
"version": "==1.0.15"
|
||||
},
|
||||
"django-transmeta-eh": {
|
||||
"hashes": [
|
||||
"sha256:cbe504f73e6c7cfed5c23d883db6c28efe2f2e0cdddf33a68c343d1fd862fa01"
|
||||
],
|
||||
"version": "==0.7.6"
|
||||
},
|
||||
"fef-questionnaire": {
|
||||
"editable": true,
|
||||
"path": "."
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
|
||||
"sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
|
||||
],
|
||||
"version": "==2.4.6"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
|
||||
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
|
||||
],
|
||||
"version": "==2019.3"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
|
||||
"sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
|
||||
"sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
|
||||
"sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
|
||||
"sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
|
||||
"sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
|
||||
"sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
|
||||
"sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
|
||||
"sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
|
||||
"sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
|
||||
"sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
|
||||
],
|
||||
"version": "==5.3"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
|
||||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
|
||||
],
|
||||
"version": "==1.14.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
29
README.md
29
README.md
|
@ -73,11 +73,11 @@ Install Django
|
|||
|
||||
Create your Django site
|
||||
|
||||
django-admin.py startproject mysite
|
||||
django-admin.py startproject example
|
||||
|
||||
Create a place for the questionnare
|
||||
|
||||
cd mysite
|
||||
cd example
|
||||
mkdir apps
|
||||
cd apps
|
||||
|
||||
|
@ -95,7 +95,7 @@ The next step is to install the questionnaire.
|
|||
|
||||
If you are working with ed-questionnaire from your own fork you may want to use `python setup.py develop` instead, which will save you from running `python setup.py install` every time the questionnaire changes.
|
||||
|
||||
Now let's configure your basic questionnaire OR copy the settings.py, urls.py, and models.py files from the "example" folder into `mysite/mysite`, then skip down to [initialize your database](#initialize-the-database).
|
||||
Now let's configure your basic questionnaire OR copy the settings.py, urls.py, and models.py files from the "example" folder into `example/example`, then skip down to [initialize your database](#initialize-the-database).
|
||||
|
||||
|
||||
Also add the locale and request cache middleware to MIDDLEWARE_CLASSES:
|
||||
|
@ -104,7 +104,7 @@ Also add the locale and request cache middleware to MIDDLEWARE_CLASSES:
|
|||
|
||||
Add the questionnaire template directory as well as your own to TEMPLATES:
|
||||
|
||||
'DIRS': [os.path.join(BASE_DIR, 'mysite/templates/')],
|
||||
'DIRS': [os.path.join(BASE_DIR, 'example/templates/')],
|
||||
|
||||
If you want to use multiple languages, add the i18n context processor to TEMPLATES
|
||||
'context_processors': ['django.template.context_processors.i18n',]
|
||||
|
@ -119,12 +119,12 @@ To finish the settings, add the fef-questionaire specific parameters. For our ex
|
|||
|
||||
QUESTIONNAIRE_PROGRESS = 'async'
|
||||
QUESTIONNAIRE_USE_SESSION = False
|
||||
QUESTIONNAIRE_ITEM_MODEL = 'mysite.Book'
|
||||
QUESTIONNAIRE_ITEM_MODEL = 'example.Book'
|
||||
QUESTIONNAIRE_SHOW_ITEM_RESULTS = True
|
||||
|
||||
Next up we want to edit the `urls.py` file of your project to link the questionnaire views to your site's url configuration. The example app shows you how.
|
||||
|
||||
Finally, we want to add a model to the mysite app for us to link our questionnaires to. It needs to have a back-relation named "items"
|
||||
Finally, we want to add a model to the example app for us to link our questionnaires to. It needs to have a back-relation named "items"
|
||||
|
||||
class Book(models.Model):
|
||||
title = models.CharField(max_length=1000, default="")
|
||||
|
@ -135,13 +135,13 @@ Finally, we want to add a model to the mysite app for us to link our questionnai
|
|||
|
||||
### Initialize the database
|
||||
|
||||
Having done that we can initialize our database. (For this to work you must have set up your DATABASES in `settings.py`.). First, in your CLI navigate back to the `mysite` folder:
|
||||
Having done that we can initialize our database. (For this to work you must have set up your DATABASES in `settings.py`.). First, in your CLI navigate back to the `example` folder:
|
||||
|
||||
cd ../..
|
||||
|
||||
The check that you are in the proper folder, type `ls`: if you can see `manage.py` in your list of files, you are good. Otherwise, find your way to the folder that contains that file. Then type:
|
||||
|
||||
python manage.py syncdb
|
||||
python manage.py migrate
|
||||
|
||||
You will be asked to create a superuser.
|
||||
|
||||
|
@ -151,9 +151,9 @@ Congratulations, you have setup the basics of the questionnaire! At this point t
|
|||
|
||||
### Internationalizating the database
|
||||
|
||||
First, you want to setup the languages used in your questionnaire. Open up your `mysite` folder in your favorite text editor.
|
||||
First, you want to setup the languages used in your questionnaire. Open up your `example` folder in your favorite text editor.
|
||||
|
||||
Open `mysite/mysite/settings.py` and add following lines, representing your languages of choice:
|
||||
Open `example/example/settings.py` and add following lines, representing your languages of choice:
|
||||
|
||||
LANGUAGES = (
|
||||
('en', 'English'),
|
||||
|
@ -351,4 +351,13 @@ Version 4.0 has not been tested for compatibility with previous versions.
|
|||
* documentation has been updated to reflect Django 1.8.
|
||||
* email and subject functionality has not been tested
|
||||
|
||||
4.0.1
|
||||
---------------
|
||||
Updated for Django 1.11
|
||||
|
||||
5.0
|
||||
---------------
|
||||
Updated for Python 3.6
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,60 +1,60 @@
|
|||
- fields: {title: "A Christmas Carol"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 4
|
||||
- fields: {title: "A Little Princess"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 5
|
||||
- fields: {title: "A Portrait of the Artist as a Young Man"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 6
|
||||
- fields: {title: "A Room with a View"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 7
|
||||
- fields: {title: "Agnes Grey"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 8
|
||||
- fields: {title: "Anne of Green Gables"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 9
|
||||
- fields: {title: "The Personal History, Adventures, Experience and Observation of David Copperfield the Younger of Blunderstone Rookery"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 10
|
||||
- fields: {title: "Far From the Madding Crowd"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 11
|
||||
- fields: {title: "Howards End"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 12
|
||||
- fields: {title: "Jacob\'s Room"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 13
|
||||
- fields: {title: "The Merry Adventures of Robin Hood"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 14
|
||||
- fields: {title: "Sense and Sensibility"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 15
|
||||
- fields: {title: "The Secret Garden"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 16
|
||||
- fields: {title: "The Adventures of Tom Sawyer"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 17
|
||||
- fields: {title: "The Invisible Man"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 18
|
||||
- fields: {title: "The Last of the Mohicans"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 19
|
||||
- fields: {title: "Oliver Twist, or the Parish Boy\'s Progress"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 20
|
||||
- fields: {title: "Peter Pan in Kensington Gardens"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 21
|
||||
- fields: {title: "Tales of the Jazz Age"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 22
|
||||
- fields: {title: "Tess of the D'Urbervilles: A Pure Woman Faithfully Presented"}
|
||||
model: mysite.book
|
||||
model: example.book
|
||||
pk: 23
|
||||
|
|
|
@ -6,5 +6,8 @@ from questionnaire.models import Landing
|
|||
class Book(models.Model):
|
||||
title = models.CharField(max_length=1000, default="")
|
||||
landings = GenericRelation(Landing, related_query_name='items')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
__str__ = __unicode__
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Django settings for mysite project.
|
||||
Django settings for example project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 1.8.18.
|
||||
|
||||
|
@ -40,7 +40,7 @@ INSTALLED_APPS = (
|
|||
'transmeta',
|
||||
'questionnaire',
|
||||
'questionnaire.page',
|
||||
'mysite',
|
||||
'example',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
|
@ -56,12 +56,12 @@ MIDDLEWARE_CLASSES = (
|
|||
'django.middleware.security.SecurityMiddleware',
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'mysite.urls'
|
||||
ROOT_URLCONF = 'example.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'mysite/templates/')],
|
||||
'DIRS': [os.path.join(BASE_DIR, 'example/templates/')],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
|
@ -75,7 +75,7 @@ TEMPLATES = [
|
|||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'mysite.wsgi.application'
|
||||
WSGI_APPLICATION = 'example.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
|
@ -145,7 +145,7 @@ QUESTIONNAIRE_USE_SESSION = False
|
|||
|
||||
# for item-linked questionnaires, defines the model used for the item-linked questionaires.
|
||||
|
||||
QUESTIONNAIRE_ITEM_MODEL = 'mysite.Book'
|
||||
QUESTIONNAIRE_ITEM_MODEL = 'example.Book'
|
||||
|
||||
# for item-linked questionnaires, show the results to any logged in user. If the results are meant to be private, this should be false, and you should wrap the corresponding views with access control appropriate to your application.
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
from django.conf.urls import patterns, include, url
|
||||
from django.conf.urls import include, url
|
||||
from django.contrib import admin
|
||||
|
||||
import questionnaire
|
||||
from questionnaire.page import views
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
|
||||
url(r'^$', 'questionnaire.page.views.page', {'page_to_render' : 'index'}),
|
||||
urlpatterns = [
|
||||
url(r'^$', views.page, {'page_to_render' : 'index'}),
|
||||
url(r'q/', include('questionnaire.urls')),
|
||||
|
||||
# admin
|
||||
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
)
|
||||
]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.utils.translation import ugettext as _
|
||||
from django.contrib import admin
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
from .models import (Choice, Questionnaire, Question, QuestionSet, Subject,
|
||||
RunInfo, RunInfoHistory, Answer, DBStylesheet, Landing)
|
||||
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
Functions to send email reminders to users.
|
||||
"""
|
||||
|
||||
import random, time, smtplib, rfc822
|
||||
import random, time, smtplib
|
||||
from datetime import datetime
|
||||
from email.Header import Header
|
||||
from email.Utils import formataddr, parseaddr
|
||||
from email.header import Header
|
||||
from email.utils import formataddr, parseaddr
|
||||
from django.core.mail import get_connection, EmailMessage
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.template import loader
|
||||
from django.utils import translation
|
||||
from django.conf import settings
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import render_to_response, get_object_or_404
|
||||
from django.shortcuts import get_object_or_404
|
||||
from .models import Subject, QuestionSet, RunInfo, Questionnaire
|
||||
|
||||
try: from hashlib import md5
|
||||
|
@ -37,7 +37,7 @@ def _new_random(subject):
|
|||
Returns: subject_id + 'z' +
|
||||
md5 hexdigest of subject's surname, nextrun date, and a random number
|
||||
"""
|
||||
return "%dz%s" % (subject.id, md5(subject.surname + str(subject.nextrun) + hex(random.randint(1,999999))).hexdigest()[:6])
|
||||
return "%dz%s" % (subject.id, md5(bytes(subject.surname + str(subject.nextrun) + hex(random.randint(1,999999)), 'utf-8')).hexdigest()[:6])
|
||||
|
||||
|
||||
def _new_runinfo(subject, questionset):
|
||||
|
@ -148,7 +148,7 @@ def send_emails(request=None, qname=None):
|
|||
outlog.append(u"[%s] %s, %s: OK" % (r.run.runid, r.subject.surname, r.subject.givenname))
|
||||
else:
|
||||
outlog.append(u"[%s] %s, %s: %s" % (r.run.runid, r.subject.surname, r.subject.givenname, r.lastemailerror))
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
outlog.append("Exception: [%s] %s: %s" % (r.run.runid, r.subject.surname, str(e)))
|
||||
if request:
|
||||
return HttpResponse("Sent Questionnaire Emails:\n "
|
||||
|
|
|
@ -38,7 +38,7 @@ def _load_template_source(template_name, template_dirs=None):
|
|||
error_msg = "Tried %s" % tried
|
||||
else:
|
||||
error_msg = "Your TEMPLATE_DIRS setting is empty. Change it to point to at least one template directory."
|
||||
raise TemplateDoesNotExist, error_msg
|
||||
raise TemplateDoesNotExist(error_msg)
|
||||
|
||||
def load_template_source(template_name, template_dirs=None):
|
||||
"""Assuming the current language is German.
|
||||
|
|
|
@ -86,7 +86,7 @@ class TypeTest(TestCase):
|
|||
response = c.post('/q/test:test/1/', ansdict)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
errors = response.context[-1]['errors']
|
||||
self.assertEqual(len(errors), 1) and errors.has_key('3')
|
||||
self.assertEqual(len(errors), 1) and '3' in errors
|
||||
|
||||
|
||||
def test050_missing_question(self):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from __future__ import print_function
|
||||
from django.core.management.base import BaseCommand
|
||||
from ...models import Landing
|
||||
|
||||
|
@ -10,5 +11,5 @@ class Command(BaseCommand):
|
|||
how_many=int(how_many)
|
||||
while how_many > 0:
|
||||
landing = Landing.objects.create(label = label)
|
||||
print landing.nonce
|
||||
print(landing.nonce)
|
||||
how_many -= 1
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from __future__ import print_function
|
||||
from django.core.management.base import NoArgsCommand
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
|
@ -5,4 +6,4 @@ class Command(NoArgsCommand):
|
|||
from ...emails import send_emails
|
||||
res = send_emails()
|
||||
if res:
|
||||
print res
|
||||
print(res)
|
||||
|
|
|
@ -51,7 +51,7 @@ class Migration(migrations.Migration):
|
|||
('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)),
|
||||
('content_type', models.ForeignKey(on_delete=models.CASCADE, related_name='landings', blank=True, to='contenttypes.ContentType', null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
|
@ -91,7 +91,7 @@ class Migration(migrations.Migration):
|
|||
('checks', models.CharField(help_text=b'Current options are \'femaleonly\' or \'maleonly\' and shownif="QuestionNumber,Answer" which takes the same format as <tt>requiredif</tt> for questions.', max_length=256, blank=True)),
|
||||
('text_en', models.TextField(help_text=b'HTML or Text', null=True, verbose_name='Text', blank=True)),
|
||||
('parse_html', models.BooleanField(default=False, verbose_name=b'Render html in heading?')),
|
||||
('questionnaire', models.ForeignKey(to='questionnaire.Questionnaire')),
|
||||
('questionnaire', models.ForeignKey(on_delete=models.CASCADE, to='questionnaire.Questionnaire')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
|
@ -108,8 +108,8 @@ 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)),
|
||||
('landing', models.ForeignKey(on_delete=models.CASCADE, blank=True, to='questionnaire.Landing', null=True)),
|
||||
('questionset', models.ForeignKey(on_delete=models.CASCADE, blank=True, to='questionnaire.QuestionSet', null=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Run Info',
|
||||
|
@ -123,8 +123,8 @@ class Migration(migrations.Migration):
|
|||
('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')),
|
||||
('landing', models.ForeignKey(on_delete=models.CASCADE, blank=True, to='questionnaire.Landing', null=True)),
|
||||
('questionnaire', models.ForeignKey(on_delete=models.CASCADE, to='questionnaire.Questionnaire')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Run Info History',
|
||||
|
@ -153,37 +153,37 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='runinfohistory',
|
||||
name='subject',
|
||||
field=models.ForeignKey(to='questionnaire.Subject'),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, to='questionnaire.Subject'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='runinfo',
|
||||
name='subject',
|
||||
field=models.ForeignKey(to='questionnaire.Subject'),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, to='questionnaire.Subject'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='questionset',
|
||||
field=models.ForeignKey(to='questionnaire.QuestionSet'),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, to='questionnaire.QuestionSet'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='landing',
|
||||
name='questionnaire',
|
||||
field=models.ForeignKey(related_name='landings', blank=True, to='questionnaire.Questionnaire', null=True),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, related_name='landings', blank=True, to='questionnaire.Questionnaire', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='choice',
|
||||
name='question',
|
||||
field=models.ForeignKey(to='questionnaire.Question'),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, to='questionnaire.Question'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='answer',
|
||||
name='question',
|
||||
field=models.ForeignKey(help_text='The question that this is an answer to', to='questionnaire.Question'),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, help_text='The question that this is an answer to', to='questionnaire.Question'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='answer',
|
||||
name='subject',
|
||||
field=models.ForeignKey(help_text='The user who supplied this answer', to='questionnaire.Subject'),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, help_text='The user who supplied this answer', to='questionnaire.Subject'),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='runinfo',
|
||||
|
|
|
@ -21,16 +21,16 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='answer',
|
||||
name='run',
|
||||
field=models.ForeignKey(related_name='answers', to='questionnaire.Run', null=True),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, related_name='answers', to='questionnaire.Run', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='runinfo',
|
||||
name='run',
|
||||
field=models.ForeignKey(related_name='run_infos', to='questionnaire.Run', null=True),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, related_name='run_infos', to='questionnaire.Run', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='runinfohistory',
|
||||
name='run',
|
||||
field=models.ForeignKey(related_name='run_info_histories', to='questionnaire.Run', null=True),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, related_name='run_info_histories', to='questionnaire.Run', null=True),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -22,19 +22,19 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='answer',
|
||||
name='run',
|
||||
field=models.ForeignKey(related_name='answers', default=1, to='questionnaire.Run'),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, related_name='answers', default=1, to='questionnaire.Run'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='runinfo',
|
||||
name='run',
|
||||
field=models.ForeignKey(related_name='run_infos', default=1, to='questionnaire.Run'),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, related_name='run_infos', default=1, to='questionnaire.Run'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='runinfohistory',
|
||||
name='run',
|
||||
field=models.ForeignKey(related_name='run_info_histories', to='questionnaire.Run'),
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, related_name='run_info_histories', to='questionnaire.Run'),
|
||||
),
|
||||
migrations.AlterIndexTogether(
|
||||
name='answer',
|
||||
|
|
|
@ -3,6 +3,7 @@ import json
|
|||
import re
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from six import text_type as unicodestr
|
||||
from transmeta import TransMeta
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -11,7 +12,7 @@ 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 django.urls import reverse
|
||||
|
||||
from . import QuestionChoices
|
||||
from .utils import split_numal
|
||||
|
@ -58,6 +59,8 @@ class Subject(models.Model):
|
|||
else:
|
||||
return u'%s, %s (%s)' % (self.surname, self.givenname, self.email)
|
||||
|
||||
__str__ = __unicode__
|
||||
|
||||
def next_runid(self):
|
||||
"Return the string form of the runid for the upcoming run"
|
||||
return str(self.nextrun.year)
|
||||
|
@ -94,6 +97,8 @@ class Questionnaire(models.Model):
|
|||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
__str__ = __unicode__
|
||||
|
||||
def questionsets(self):
|
||||
if not hasattr(self, "__qscache"):
|
||||
self.__qscache = \
|
||||
|
@ -115,11 +120,11 @@ class Questionnaire(models.Model):
|
|||
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')
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, 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')
|
||||
questionnaire = models.ForeignKey(Questionnaire, on_delete=models.CASCADE, null=True, blank=True, related_name='landings')
|
||||
def _hash(self):
|
||||
return uuid.uuid4().hex
|
||||
|
||||
|
@ -151,17 +156,18 @@ class DBStylesheet(models.Model):
|
|||
def __unicode__(self):
|
||||
return self.inclusion_tag
|
||||
|
||||
__str__ = __unicode__
|
||||
|
||||
class QuestionSet(models.Model):
|
||||
__metaclass__ = TransMeta
|
||||
|
||||
class QuestionSet(models.Model, metaclass=TransMeta):
|
||||
|
||||
"Which questions to display on a question page"
|
||||
questionnaire = models.ForeignKey(Questionnaire)
|
||||
questionnaire = models.ForeignKey(Questionnaire, on_delete=models.CASCADE)
|
||||
sortid = models.IntegerField() # used to decide which order to display in
|
||||
heading = models.CharField(max_length=64)
|
||||
checks = models.CharField(max_length=256, blank=True,
|
||||
help_text = """Current options are 'femaleonly' or 'maleonly' and shownif="QuestionNumber,Answer" which takes the same format as <tt>requiredif</tt> for questions.""")
|
||||
text = models.TextField(u'Text', help_text="HTML or Text")
|
||||
text = models.TextField(u'Text', help_text="HTML or Text", default="",)
|
||||
|
||||
parse_html = models.BooleanField("Render html in heading?", null=False, default=False)
|
||||
|
||||
|
@ -191,6 +197,8 @@ class QuestionSet(models.Model):
|
|||
retnext = True
|
||||
return None
|
||||
|
||||
__next__ = next
|
||||
|
||||
def prev(self):
|
||||
qs = self.questionnaire.questionsets()
|
||||
last = None
|
||||
|
@ -216,6 +224,8 @@ class QuestionSet(models.Model):
|
|||
def __unicode__(self):
|
||||
return u'%s: %s' % (self.questionnaire.name, self.heading)
|
||||
|
||||
__str__ = __unicode__
|
||||
|
||||
class Meta:
|
||||
translate = ('text',)
|
||||
index_together = [
|
||||
|
@ -227,13 +237,13 @@ class Run(models.Model):
|
|||
|
||||
class RunInfo(models.Model):
|
||||
"Store the active/waiting questionnaire runs here"
|
||||
subject = models.ForeignKey(Subject)
|
||||
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
|
||||
random = models.CharField(max_length=32) # probably a randomized md5sum
|
||||
run = models.ForeignKey(Run, related_name='run_infos')
|
||||
landing = models.ForeignKey(Landing, null=True, blank=True)
|
||||
run = models.ForeignKey(Run, on_delete=models.CASCADE, related_name='run_infos')
|
||||
landing = models.ForeignKey(Landing, on_delete=models.CASCADE, 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?
|
||||
questionset = models.ForeignKey(QuestionSet, on_delete=models.CASCADE, blank=True, null=True) # or straight int?
|
||||
emailcount = models.IntegerField(default=0)
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
@ -281,7 +291,7 @@ class RunInfo(models.Model):
|
|||
"runinfo.set_cookie(key, value). If value is None, delete cookie"
|
||||
key = key.lower().strip()
|
||||
cookies = self.get_cookiedict()
|
||||
if type(value) not in (int, float, str, unicode, type(None)):
|
||||
if type(value) not in (int, float, unicodestr, type(None)):
|
||||
raise Exception("Can only store cookies of type integer or string")
|
||||
if value is None:
|
||||
if key in cookies:
|
||||
|
@ -311,6 +321,8 @@ class RunInfo(models.Model):
|
|||
def __unicode__(self):
|
||||
return "%s: %s, %s" % (self.run.runid, self.subject.surname, self.subject.givenname)
|
||||
|
||||
__str__ = __unicode__
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = 'Run Info'
|
||||
index_together = [
|
||||
|
@ -318,10 +330,10 @@ class RunInfo(models.Model):
|
|||
]
|
||||
|
||||
class RunInfoHistory(models.Model):
|
||||
subject = models.ForeignKey(Subject)
|
||||
run = models.ForeignKey(Run, related_name='run_info_histories')
|
||||
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
|
||||
run = models.ForeignKey(Run, on_delete=models.CASCADE, related_name='run_info_histories')
|
||||
completed = models.DateTimeField()
|
||||
landing = models.ForeignKey(Landing, null=True, blank=True)
|
||||
landing = models.ForeignKey(Landing, on_delete=models.CASCADE, null=True, blank=True)
|
||||
tags = models.TextField(
|
||||
blank=True,
|
||||
help_text=u"Tags used on this run, separated by commas"
|
||||
|
@ -330,10 +342,12 @@ class RunInfoHistory(models.Model):
|
|||
blank=True,
|
||||
help_text=u"A comma sepearted list of questions skipped by this run"
|
||||
)
|
||||
questionnaire = models.ForeignKey(Questionnaire)
|
||||
questionnaire = models.ForeignKey(Questionnaire, on_delete=models.CASCADE)
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s: %s on %s" % (self.run.runid, self.subject, self.completed)
|
||||
return "%s: %s on %s" % (self.run.runid, self.subject, self.completed)
|
||||
|
||||
__str__ = __unicode__
|
||||
|
||||
def answers(self):
|
||||
"Returns the query for the answers."
|
||||
|
@ -343,15 +357,14 @@ class RunInfoHistory(models.Model):
|
|||
verbose_name_plural = 'Run Info History'
|
||||
|
||||
|
||||
class Question(models.Model):
|
||||
__metaclass__ = TransMeta
|
||||
class Question(models.Model, metaclass=TransMeta):
|
||||
|
||||
questionset = models.ForeignKey(QuestionSet)
|
||||
questionset = models.ForeignKey(QuestionSet, on_delete=models.CASCADE)
|
||||
number = models.CharField(max_length=8, help_text=
|
||||
"eg. <tt>1</tt>, <tt>2a</tt>, <tt>2b</tt>, <tt>3c</tt><br /> "
|
||||
"Number is also used for ordering questions.")
|
||||
sort_id = models.IntegerField(null=True, blank=True, help_text="Questions within a questionset are sorted by sort order first, question number second")
|
||||
text = models.TextField(blank=True, verbose_name=_("Text"))
|
||||
text = models.TextField(blank=True, default="", verbose_name=_("Text"))
|
||||
type = models.CharField(u"Type of question", max_length=32,
|
||||
choices = QuestionChoices,
|
||||
help_text = u"Determines the means of answering the question. " \
|
||||
|
@ -373,7 +386,7 @@ 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", blank=True)
|
||||
footer = models.TextField(u"Footer", help_text="Footer rendered below the question", default="", blank=True)
|
||||
|
||||
parse_html = models.BooleanField("Render html in Footer?", null=False, default=False)
|
||||
|
||||
|
@ -392,7 +405,10 @@ class Question(models.Model):
|
|||
return d
|
||||
|
||||
def __unicode__(self):
|
||||
return u'{%s} (%s) %s' % (unicode(self.questionset), self.number, self.text)
|
||||
return u'{%s} (%s) %s' % (unicodestr(self.questionset), self.number, self.text)
|
||||
|
||||
__str__ = __unicode__
|
||||
|
||||
|
||||
def sameas(self):
|
||||
if self.type == 'sameas':
|
||||
|
@ -468,18 +484,20 @@ class Question(models.Model):
|
|||
["number", "questionset"],
|
||||
]
|
||||
|
||||
class Choice(models.Model):
|
||||
__metaclass__ = TransMeta
|
||||
class Choice(models.Model, metaclass=TransMeta):
|
||||
|
||||
question = models.ForeignKey(Question)
|
||||
question = models.ForeignKey(Question, on_delete=models.CASCADE)
|
||||
sortid = models.IntegerField()
|
||||
value = models.CharField(u"Short Value", max_length=64)
|
||||
text = models.CharField(u"Choice Text", max_length=200)
|
||||
value = models.CharField(u"Short Value", max_length=64, default="")
|
||||
text = models.CharField(u"Choice Text", max_length=200, default="")
|
||||
tags = models.CharField(u"Tags", max_length=64, blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'(%s) %d. %s' % (self.question.number, self.sortid, self.text)
|
||||
|
||||
__str__ = __unicode__
|
||||
|
||||
|
||||
class Meta:
|
||||
translate = ('text',)
|
||||
index_together = [
|
||||
|
@ -487,14 +505,16 @@ class Choice(models.Model):
|
|||
]
|
||||
|
||||
class Answer(models.Model):
|
||||
subject = models.ForeignKey(Subject, help_text = u'The user who supplied this answer')
|
||||
question = models.ForeignKey(Question, help_text = u"The question that this is an answer to")
|
||||
run = models.ForeignKey(Run, related_name='answers')
|
||||
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, help_text = u'The user who supplied this answer')
|
||||
question = models.ForeignKey(Question, on_delete=models.CASCADE, help_text = u"The question that this is an answer to")
|
||||
run = models.ForeignKey(Run, on_delete=models.CASCADE, related_name='answers')
|
||||
answer = models.TextField()
|
||||
|
||||
def __unicode__(self):
|
||||
return "Answer(%s: %s, %s)" % (self.question.number, self.subject.surname, self.subject.givenname)
|
||||
|
||||
__str__ = __unicode__
|
||||
|
||||
def split_answer(self):
|
||||
"""
|
||||
Decode stored answer value and return as a list of choices.
|
||||
|
@ -535,7 +555,7 @@ class Answer(models.Model):
|
|||
runinfo.remove_tags(tags)
|
||||
|
||||
for split_answer in self.split_answer():
|
||||
if unicode(split_answer) == choice.value:
|
||||
if unicodestr(split_answer) == choice.value:
|
||||
tags_to_add.extend(tags)
|
||||
|
||||
runinfo.add_tags(tags_to_add)
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
from django.db import models
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
from transmeta import TransMeta
|
||||
|
||||
class Page(models.Model):
|
||||
__metaclass__ = TransMeta
|
||||
class Page(models.Model, metaclass=TransMeta):
|
||||
|
||||
slug = models.SlugField(unique=True, primary_key=True)
|
||||
title = models.CharField(u"Title", max_length=256)
|
||||
|
@ -13,6 +12,8 @@ class Page(models.Model):
|
|||
def __unicode__(self):
|
||||
return u"Page[%s]" % self.slug
|
||||
|
||||
__str__ = __unicode__
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('questionnaire.page.views.page', kwargs={'page_to_render':self.slug})
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from __future__ import print_function
|
||||
import hotshot
|
||||
import os
|
||||
import time
|
||||
|
@ -51,7 +52,7 @@ def timethis(fn):
|
|||
def wrapper(*args, **kwargs):
|
||||
start = time.time()
|
||||
result = fn(*args, **kwargs)
|
||||
print fn.__name__, 'took', time.time() - start
|
||||
print(fn.__name__, 'took', time.time() - start)
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
@ -62,7 +63,7 @@ def sqlprint(fn):
|
|||
def wrapper(*args, **kwargs):
|
||||
connection.queries = list()
|
||||
result = fn(*args, **kwargs)
|
||||
print fn.__name__, 'issued'
|
||||
print(fn.__name__, 'issued')
|
||||
pprint(connection.queries)
|
||||
return result
|
||||
return wrapper
|
|
@ -1,3 +1,4 @@
|
|||
from __future__ import print_function
|
||||
from json import dumps
|
||||
import ast
|
||||
from django.utils.translation import ugettext as _, ungettext
|
||||
|
@ -84,8 +85,6 @@ def question_multiple(request, question):
|
|||
prev_vals[choice_value] = str(prev_value)
|
||||
possiblelist = pl
|
||||
|
||||
# print 'possible value is ', possibledbvalue, ', possiblelist is ', possiblelist
|
||||
|
||||
for choice in question.choices():
|
||||
counter += 1
|
||||
key = "question_%s_multiple_%d" % (question.number, choice.sortid)
|
||||
|
@ -94,7 +93,7 @@ def question_multiple(request, question):
|
|||
# so that the number box will be activated when item is checked
|
||||
|
||||
#try database first and only after that fall back to post choices
|
||||
# print 'choice multiple checking for match for choice ', choice
|
||||
# print('choice multiple checking for match for choice ', choice)
|
||||
checked = ' checked'
|
||||
prev_value = ''
|
||||
qvalue = "%s_%s" % (question.number, choice.value)
|
||||
|
|
|
@ -84,7 +84,6 @@ def question_open(request, question):
|
|||
|
||||
@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()
|
||||
|
@ -109,7 +108,7 @@ def process_simple(question, ansdict):
|
|||
answords = len(ans.split())
|
||||
if answords > maxwords:
|
||||
raise AnswerException(_(u'Answer is ' + str(answords) + ' words. Please shorten answer to ' + str(maxwords) + ' words or less'))
|
||||
if ansdict.has_key('comment') and len(ansdict['comment']) > 0:
|
||||
if 'comment' in ansdict and len(ansdict['comment']) > 0:
|
||||
return dumps([ans, [ansdict['comment']]])
|
||||
if ans:
|
||||
return dumps([ans])
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from six import text_type as unicodestr
|
||||
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from .. import add_type, question_proc, answer_proc, AnswerException
|
||||
|
||||
|
@ -29,7 +31,7 @@ def question_timeperiod(request, question):
|
|||
|
||||
for x in units:
|
||||
if x in perioddict:
|
||||
timeperiods.append( (x, unicode(perioddict[x]), unitselected==x) )
|
||||
timeperiods.append( (x, unicodestr(perioddict[x]), unitselected==x) )
|
||||
return {
|
||||
"required" : "required" in cd,
|
||||
"timeperiods" : timeperiods,
|
||||
|
@ -38,7 +40,7 @@ def question_timeperiod(request, question):
|
|||
|
||||
@answer_proc('timeperiod')
|
||||
def process_timeperiod(question, answer):
|
||||
if not answer['ANSWER'] or not answer.has_key('unit'):
|
||||
if not answer['ANSWER'] or not 'unit' in answer:
|
||||
raise AnswerException(_(u"Invalid time period"))
|
||||
period = answer['ANSWER'].strip()
|
||||
if period:
|
||||
|
|
|
@ -9,6 +9,13 @@ from functools import wraps
|
|||
from threading import currentThread
|
||||
from django.core.cache.backends.locmem import LocMemCache
|
||||
|
||||
try:
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
except ImportError:
|
||||
# djang0 < 1.10
|
||||
class MiddlewareMixin(object):
|
||||
pass
|
||||
|
||||
_request_cache = {}
|
||||
_installed_middleware = False
|
||||
|
||||
|
@ -25,9 +32,10 @@ class RequestCache(LocMemCache):
|
|||
params = dict()
|
||||
super(RequestCache, self).__init__(name, params)
|
||||
|
||||
class RequestCacheMiddleware(object):
|
||||
def __init__(self):
|
||||
class RequestCacheMiddleware(MiddlewareMixin):
|
||||
def __init__(self, get_response=None):
|
||||
global _installed_middleware
|
||||
self.get_response = get_response
|
||||
_installed_middleware = True
|
||||
|
||||
def process_request(self, request):
|
||||
|
@ -42,11 +50,11 @@ class request_cache(object):
|
|||
|
||||
@request_cache()
|
||||
def cached(name):
|
||||
print "My name is %s and I'm cached" % name
|
||||
print("My name is %s and I'm cached" % name)
|
||||
|
||||
@request_cache(keyfn=lambda p: p['id'])
|
||||
def cached(param):
|
||||
print "My id is %s" % p['id']
|
||||
print("My id is %s" % p['id'])
|
||||
|
||||
If no keyfn is provided the decorator expects the args to be hashable.
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ 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'):
|
||||
if not 'landing_object' in context and 'runinfo' in context:
|
||||
landing = context['runinfo'].landing
|
||||
context['landing_object'] = landing.content_object if landing else ''
|
||||
if text:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
from django import template
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#!/usr/bin/python
|
||||
import codecs
|
||||
import cStringIO
|
||||
from six import StringIO
|
||||
import csv
|
||||
from six import text_type as unicodestr
|
||||
|
||||
from django.conf import settings
|
||||
try:
|
||||
|
@ -9,6 +10,17 @@ try:
|
|||
except AttributeError:
|
||||
use_session = False
|
||||
|
||||
def cmp(x, y):
|
||||
"""
|
||||
Replacement for built-in function cmp that was removed in Python 3
|
||||
|
||||
Compare the two objects x and y and return an integer according to
|
||||
the outcome. The return value is negative if x < y, zero if x == y
|
||||
and strictly positive if x > y.
|
||||
"""
|
||||
|
||||
return (x > y) - (x < y)
|
||||
|
||||
def split_numal(val):
|
||||
"""Split, for example, '1a' into (1, 'a')
|
||||
>>> split_numal("11a")
|
||||
|
@ -60,33 +72,3 @@ if __name__ == "__main__":
|
|||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
class UnicodeWriter:
|
||||
"""
|
||||
COPIED from http://docs.python.org/library/csv.html example:
|
||||
|
||||
A CSV writer which will write rows to CSV file "f",
|
||||
which is encoded in the given encoding.
|
||||
"""
|
||||
|
||||
def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
|
||||
# Redirect output to a queue
|
||||
self.queue = cStringIO.StringIO()
|
||||
self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
|
||||
self.stream = f
|
||||
self.encoder = codecs.getincrementalencoder(encoding)()
|
||||
|
||||
def writerow(self, row):
|
||||
self.writer.writerow([unicode(s).encode("utf-8") for s in row])
|
||||
# Fetch UTF-8 output from the queue ...
|
||||
data = self.queue.getvalue()
|
||||
data = data.decode("utf-8")
|
||||
# ... and reencode it into the target encoding
|
||||
data = self.encoder.encode(data)
|
||||
# write to the target stream
|
||||
self.stream.write(data)
|
||||
# empty queue
|
||||
self.queue.truncate(0)
|
||||
|
||||
def writerows(self, rows):
|
||||
for row in rows:
|
||||
self.writerow(row)
|
||||
|
|
|
@ -2,15 +2,18 @@
|
|||
# vim: set fileencoding=utf-8
|
||||
import json
|
||||
import logging
|
||||
from six import text_type as unicodestr
|
||||
import tempfile
|
||||
import csv
|
||||
|
||||
from compat import commit_on_success, commit, rollback
|
||||
from functools import cmp_to_key
|
||||
from hashlib import md5
|
||||
from uuid import uuid4
|
||||
|
||||
from django.apps import apps
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
from django.core.cache import cache
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
|
@ -29,7 +32,7 @@ from .models import (
|
|||
)
|
||||
from .forms import NewLandingForm
|
||||
from .parsers import BooleanParser
|
||||
from .utils import numal_sort, split_numal, UnicodeWriter
|
||||
from .utils import numal_sort, split_numal
|
||||
from .run import (
|
||||
add_answer, delete_answer, get_runinfo, get_question,
|
||||
question_satisfies_checks, questionset_satisfies_checks,
|
||||
|
@ -89,15 +92,15 @@ def redirect_to_qs(runinfo, request=None):
|
|||
# skip questionsets that don't pass
|
||||
if not questionset_satisfies_checks(runinfo.questionset, runinfo):
|
||||
|
||||
next = runinfo.questionset.next()
|
||||
nxt = next(runinfo.questionset)
|
||||
|
||||
while next and not questionset_satisfies_checks(next, runinfo):
|
||||
next = next.next()
|
||||
while nxt and not questionset_satisfies_checks(nxt, runinfo):
|
||||
nxt = next(nxt)
|
||||
|
||||
runinfo.questionset = next
|
||||
runinfo.questionset = nxt
|
||||
runinfo.save()
|
||||
|
||||
hasquestionset = bool(next)
|
||||
hasquestionset = bool(nxt)
|
||||
else:
|
||||
hasquestionset = True
|
||||
|
||||
|
@ -166,7 +169,6 @@ def questionnaire(request, runcode=None, qs=None):
|
|||
We only commit on success, to maintain consistency. We also specifically
|
||||
rollback if there were errors processing the answers for this questionset.
|
||||
"""
|
||||
print translation.get_language()
|
||||
|
||||
if use_session:
|
||||
session_runcode = request.session.get('runcode', None)
|
||||
|
@ -261,7 +263,7 @@ def questionnaire(request, runcode=None, qs=None):
|
|||
# to confirm that we have the correct answers
|
||||
expected = questionset.questions()
|
||||
|
||||
items = request.POST.items()
|
||||
items = list(request.POST.items())
|
||||
extra = {} # question_object => { "ANSWER" : "123", ... }
|
||||
|
||||
# this will ensure that each question will be processed, even if we did not receive
|
||||
|
@ -311,7 +313,7 @@ def questionnaire(request, runcode=None, qs=None):
|
|||
add_answer(runinfo, question, ans)
|
||||
if cd.get('store', False):
|
||||
runinfo.set_cookie(question.number, ans['ANSWER'])
|
||||
except AnswerException, e:
|
||||
except AnswerException as e:
|
||||
errors[question.number] = e
|
||||
except Exception:
|
||||
logging.exception("Unexpected Exception")
|
||||
|
@ -325,15 +327,15 @@ def questionnaire(request, runcode=None, qs=None):
|
|||
|
||||
questionset_done.send(sender=None, runinfo=runinfo, questionset=questionset)
|
||||
|
||||
next = questionset.next()
|
||||
while next and not questionset_satisfies_checks(next, runinfo):
|
||||
next = next.next()
|
||||
runinfo.questionset = next
|
||||
nxt = next(questionset)
|
||||
while nxt and not questionset_satisfies_checks(nxt, runinfo):
|
||||
nxt = next(nxt)
|
||||
runinfo.questionset = nxt
|
||||
runinfo.save()
|
||||
if use_session:
|
||||
request.session['prev_runcode'] = runinfo.random
|
||||
|
||||
if next is None: # we are finished
|
||||
if nxt is None: # we are finished
|
||||
return finish_questionnaire(request, runinfo, questionnaire)
|
||||
|
||||
commit()
|
||||
|
@ -543,19 +545,19 @@ def show_questionnaire(request, runinfo, errors={}):
|
|||
return r
|
||||
|
||||
|
||||
def set_language(request, runinfo=None, next=None):
|
||||
def set_language(request, runinfo=None, nxt=None):
|
||||
"""
|
||||
Change the language, save it to runinfo if provided, and
|
||||
redirect to the provided URL (or the last URL).
|
||||
Can also be used by a url handler, w/o runinfo & next.
|
||||
"""
|
||||
if not next:
|
||||
next = request.GET.get('next', request.POST.get('next', None))
|
||||
if not next:
|
||||
next = request.META.get('HTTP_REFERER', None)
|
||||
if not next:
|
||||
next = '/'
|
||||
response = HttpResponseRedirect(next)
|
||||
if not nxt:
|
||||
nxt = request.GET.get('next', request.POST.get('next', None))
|
||||
if not nxt:
|
||||
nxt = request.META.get('HTTP_REFERER', None)
|
||||
if not nxt:
|
||||
nxt = '/'
|
||||
response = HttpResponseRedirect(nxt)
|
||||
response['Expires'] = "Thu, 24 Jan 1980 00:00:00 GMT"
|
||||
if request.method == 'GET':
|
||||
lang_code = request.GET.get('lang', None)
|
||||
|
@ -604,7 +606,7 @@ def generate_run(request, questionnaire_id, subject_id=None, context={}):
|
|||
# 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()
|
||||
key = md5(bytes(str_to_hash, 'utf-8')).hexdigest()
|
||||
landing = context.get('landing', None)
|
||||
r = Run.objects.create(runid=key)
|
||||
run = RunInfo.objects.create(subject=su, random=key, run=r, questionset=qs, landing=landing)
|
||||
|
@ -662,7 +664,6 @@ def new_questionnaire(request, item_id):
|
|||
if form.is_valid():
|
||||
if not item and form.item:
|
||||
item = form.item
|
||||
print "create landing"
|
||||
landing = Landing.objects.create(label=form.cleaned_data['label'], questionnaire=form.cleaned_data['questionnaire'], content_object=item)
|
||||
return HttpResponseRedirect(reverse('questionnaires'))
|
||||
return render(request, "manage_questionnaire.html", {"item":item, "form":form})
|
||||
|
@ -670,7 +671,6 @@ def new_questionnaire(request, item_id):
|
|||
|
||||
|
||||
def questionnaires(request):
|
||||
print "here"
|
||||
if not request.user.is_authenticated() :
|
||||
return render(request, "questionnaires.html")
|
||||
items = item_model.objects.all()
|
||||
|
@ -698,7 +698,7 @@ def _table_headers(questions):
|
|||
columns.extend([qnum, qnum + "-freeform"])
|
||||
elif q.type.startswith('choice-multiple'):
|
||||
cl = [c.value for c in q.choice_set.all()]
|
||||
cl.sort(numal_sort)
|
||||
cl.sort(key=cmp_to_key(numal_sort))
|
||||
columns.extend([qnum + '-' + value for value in cl])
|
||||
if q.type == 'choice-multiple-freeform':
|
||||
columns.append(qnum + '-freeform')
|
||||
|
@ -733,22 +733,19 @@ def export_csv(request, qid,
|
|||
if answer_filter is None and not request.user.has_perm("questionnaire.export"):
|
||||
return HttpResponse('Sorry, you do not have export permissions', content_type="text/plain")
|
||||
|
||||
fd = tempfile.TemporaryFile()
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename="answers-%s-%s.csv"' % (qid, filecode)
|
||||
|
||||
questionnaire = get_object_or_404(Questionnaire, pk=int(qid))
|
||||
headings, answers = answer_export(questionnaire, answer_filter=answer_filter)
|
||||
|
||||
writer = UnicodeWriter(fd)
|
||||
writer = csv.writer(response, dialect='excel')
|
||||
writer.writerow(extra_headings + headings)
|
||||
for subject, run, answer_row in answers:
|
||||
row = extra_entries(subject, run) + [
|
||||
a if a else '--' for a in answer_row]
|
||||
writer.writerow(row)
|
||||
fd.seek(0)
|
||||
|
||||
response = HttpResponse(fd, content_type="text/csv")
|
||||
response['Content-Length'] = fd.tell()
|
||||
response['Content-Disposition'] = 'attachment; filename="answers-%s-%s.csv"' % (qid, filecode)
|
||||
return response
|
||||
|
||||
|
||||
|
@ -855,7 +852,7 @@ def answer_export(questionnaire, answers=None, answer_filter=None):
|
|||
choice = choice[0]
|
||||
col = coldict.get(qnum + '-freeform', None)
|
||||
if col is None: # look for enumerated choice column (multiple-choice)
|
||||
col = coldict.get(qnum + '-' + unicode(choice), None)
|
||||
col = coldict.get(qnum + '-' + unicodestr(choice), None)
|
||||
if col is None: # single-choice items
|
||||
if ((not qchoicedict[answer.question.id]) or
|
||||
choice in qchoicedict[answer.question.id]):
|
||||
|
@ -917,7 +914,7 @@ def answer_summary(questionnaire, answers=None, answer_filter=None):
|
|||
else:
|
||||
# be tolerant of improperly marked data
|
||||
freeforms.append(choice)
|
||||
freeforms.sort(numal_sort)
|
||||
freeforms.sort(key=cmp_to_key(numal_sort))
|
||||
summary.append((question.number, question.text, [
|
||||
(n, t, choice_totals[n]) for (n, t) in choices], freeforms))
|
||||
return summary
|
||||
|
|
11
setup.py
11
setup.py
|
@ -7,9 +7,10 @@ def read(fname):
|
|||
|
||||
setup(
|
||||
name="fef-questionnaire",
|
||||
version="4.0.1",
|
||||
version="5.0",
|
||||
description="A Django application for creating online questionnaires/surveys.",
|
||||
long_description=read("README.md"),
|
||||
long_description_content_type="text/markdown",
|
||||
author="Eldest Daughter, LLC., Free Ebook Foundation",
|
||||
author_email="gcaprio@eldestdaughter.com, eric@hellman.net",
|
||||
license="BSD",
|
||||
|
@ -24,15 +25,17 @@ setup(
|
|||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
"Framework :: Django",
|
||||
],
|
||||
zip_safe=False,
|
||||
install_requires=[
|
||||
'django',
|
||||
'django-transmeta',
|
||||
'django<2',
|
||||
'django-transmeta-eh',
|
||||
'django-compat',
|
||||
'pyyaml',
|
||||
'pyparsing'
|
||||
'pyparsing',
|
||||
'six'
|
||||
],
|
||||
setup_requires=[
|
||||
'versiontools >= 1.6',
|
||||
|
|
Loading…
Reference in New Issue