Skip to content

Commit 90ee7ca

Browse files
authored
Merge pull request tomwalker#1 from ZeroDeposit/feat/django-1.11
support django-1.11
2 parents 560145f + 0cf4898 commit 90ee7ca

File tree

18 files changed

+296
-38
lines changed

18 files changed

+296
-38
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*.pyc
44
#*#
55
/db.sqlite3
6+
sqlite.db
67
/manage.py
78
/mysite*
89
/plan.txt

essay/migrations/0001_initial.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.11.3 on 2017-06-22 11:20
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations, models
6+
import django.db.models.deletion
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
initial = True
12+
13+
dependencies = [
14+
('quiz', '__first__'),
15+
]
16+
17+
operations = [
18+
migrations.CreateModel(
19+
name='Essay_Question',
20+
fields=[
21+
('question_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='quiz.Question')),
22+
],
23+
options={
24+
'verbose_name': 'Essay style question',
25+
'verbose_name_plural': 'Essay style questions',
26+
},
27+
bases=('quiz.question',),
28+
),
29+
]

essay/migrations/__init__.py

Whitespace-only changes.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.11.3 on 2017-06-22 11:20
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations, models
6+
import django.db.models.deletion
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
initial = True
12+
13+
dependencies = [
14+
('quiz', '__first__'),
15+
]
16+
17+
operations = [
18+
migrations.CreateModel(
19+
name='Answer',
20+
fields=[
21+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22+
('content', models.CharField(help_text='Enter the answer text that you want displayed', max_length=1000, verbose_name='Content')),
23+
('correct', models.BooleanField(default=False, help_text='Is this a correct answer?', verbose_name='Correct')),
24+
],
25+
options={
26+
'verbose_name': 'Answer',
27+
'verbose_name_plural': 'Answers',
28+
},
29+
),
30+
migrations.CreateModel(
31+
name='MCQuestion',
32+
fields=[
33+
('question_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='quiz.Question')),
34+
('answer_order', models.CharField(blank=True, choices=[('content', 'Content'), ('random', 'Random'), ('none', 'None')], help_text='The order in which multichoice answer options are displayed to the user', max_length=30, null=True, verbose_name='Answer Order')),
35+
],
36+
options={
37+
'verbose_name': 'Multiple Choice Question',
38+
'verbose_name_plural': 'Multiple Choice Questions',
39+
},
40+
bases=('quiz.question',),
41+
),
42+
migrations.AddField(
43+
model_name='answer',
44+
name='question',
45+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='multichoice.MCQuestion', verbose_name='Question'),
46+
),
47+
]

multichoice/migrations/__init__.py

Whitespace-only changes.

quiz/migrations/0001_initial.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.11.3 on 2017-06-22 11:20
3+
from __future__ import unicode_literals
4+
5+
from django.conf import settings
6+
import django.core.validators
7+
from django.db import migrations, models
8+
import django.db.models.deletion
9+
import re
10+
11+
12+
class Migration(migrations.Migration):
13+
14+
initial = True
15+
16+
dependencies = [
17+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
18+
]
19+
20+
operations = [
21+
migrations.CreateModel(
22+
name='Category',
23+
fields=[
24+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
25+
('category', models.CharField(blank=True, max_length=250, null=True, unique=True, verbose_name='Category')),
26+
],
27+
options={
28+
'verbose_name': 'Category',
29+
'verbose_name_plural': 'Categories',
30+
},
31+
),
32+
migrations.CreateModel(
33+
name='Progress',
34+
fields=[
35+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
36+
('score', models.CharField(max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:\\,\\d+)*\\Z', 32), code='invalid', message='Enter only digits separated by commas.')], verbose_name='Score')),
37+
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
38+
],
39+
options={
40+
'verbose_name': 'User Progress',
41+
'verbose_name_plural': 'User progress records',
42+
},
43+
),
44+
migrations.CreateModel(
45+
name='Question',
46+
fields=[
47+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
48+
('figure', models.ImageField(blank=True, null=True, upload_to='uploads/%Y/%m/%d', verbose_name='Figure')),
49+
('content', models.CharField(help_text='Enter the question text that you want displayed', max_length=1000, verbose_name='Question')),
50+
('explanation', models.TextField(blank=True, help_text='Explanation to be shown after the question has been answered.', max_length=2000, verbose_name='Explanation')),
51+
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='quiz.Category', verbose_name='Category')),
52+
],
53+
options={
54+
'verbose_name': 'Question',
55+
'verbose_name_plural': 'Questions',
56+
'ordering': ['category'],
57+
},
58+
),
59+
migrations.CreateModel(
60+
name='Quiz',
61+
fields=[
62+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
63+
('title', models.CharField(max_length=60, verbose_name='Title')),
64+
('description', models.TextField(blank=True, help_text='a description of the quiz', verbose_name='Description')),
65+
('url', models.SlugField(help_text='a user friendly url', max_length=60, verbose_name='user friendly url')),
66+
('random_order', models.BooleanField(default=False, help_text='Display the questions in a random order or as they are set?', verbose_name='Random Order')),
67+
('max_questions', models.PositiveIntegerField(blank=True, help_text='Number of questions to be answered on each attempt.', null=True, verbose_name='Max Questions')),
68+
('answers_at_end', models.BooleanField(default=False, help_text='Correct answer is NOT shown after question. Answers displayed at the end.', verbose_name='Answers at end')),
69+
('exam_paper', models.BooleanField(default=False, help_text='If yes, the result of each attempt by a user will be stored. Necessary for marking.', verbose_name='Exam Paper')),
70+
('single_attempt', models.BooleanField(default=False, help_text='If yes, only one attempt by a user will be permitted. Non users cannot sit this exam.', verbose_name='Single Attempt')),
71+
('pass_mark', models.SmallIntegerField(blank=True, default=0, help_text='Percentage required to pass exam.', validators=[django.core.validators.MaxValueValidator(100)], verbose_name='Pass Mark')),
72+
('success_text', models.TextField(blank=True, help_text='Displayed if user passes.', verbose_name='Success Text')),
73+
('fail_text', models.TextField(blank=True, help_text='Displayed if user fails.', verbose_name='Fail Text')),
74+
('draft', models.BooleanField(default=False, help_text='If yes, the quiz is not displayed in the quiz list and can only be taken by users who can edit quizzes.', verbose_name='Draft')),
75+
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='quiz.Category', verbose_name='Category')),
76+
],
77+
options={
78+
'verbose_name': 'Quiz',
79+
'verbose_name_plural': 'Quizzes',
80+
},
81+
),
82+
migrations.CreateModel(
83+
name='Sitting',
84+
fields=[
85+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
86+
('question_order', models.CharField(max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:\\,\\d+)*\\Z', 32), code='invalid', message='Enter only digits separated by commas.')], verbose_name='Question Order')),
87+
('question_list', models.CharField(max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:\\,\\d+)*\\Z', 32), code='invalid', message='Enter only digits separated by commas.')], verbose_name='Question List')),
88+
('incorrect_questions', models.CharField(blank=True, max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:\\,\\d+)*\\Z', 32), code='invalid', message='Enter only digits separated by commas.')], verbose_name='Incorrect questions')),
89+
('current_score', models.IntegerField(verbose_name='Current Score')),
90+
('complete', models.BooleanField(default=False, verbose_name='Complete')),
91+
('user_answers', models.TextField(blank=True, default='{}', verbose_name='User Answers')),
92+
('start', models.DateTimeField(auto_now_add=True, verbose_name='Start')),
93+
('end', models.DateTimeField(blank=True, null=True, verbose_name='End')),
94+
('quiz', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='quiz.Quiz', verbose_name='Quiz')),
95+
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
96+
],
97+
options={
98+
'permissions': (('view_sittings', 'Can see completed exams.'),),
99+
},
100+
),
101+
migrations.CreateModel(
102+
name='SubCategory',
103+
fields=[
104+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
105+
('sub_category', models.CharField(blank=True, max_length=250, null=True, verbose_name='Sub-Category')),
106+
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='quiz.Category', verbose_name='Category')),
107+
],
108+
options={
109+
'verbose_name': 'Sub-Category',
110+
'verbose_name_plural': 'Sub-Categories',
111+
},
112+
),
113+
migrations.AddField(
114+
model_name='question',
115+
name='quiz',
116+
field=models.ManyToManyField(blank=True, to='quiz.Quiz', verbose_name='Quiz'),
117+
),
118+
migrations.AddField(
119+
model_name='question',
120+
name='sub_category',
121+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='quiz.SubCategory', verbose_name='Sub-Category'),
122+
),
123+
]

quiz/migrations/__init__.py

Whitespace-only changes.

quiz/models.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
from django.db import models
66
from django.core.exceptions import ValidationError, ImproperlyConfigured
7-
from django.core.validators import MaxValueValidator
7+
from django.core.validators import (
8+
MaxValueValidator, validate_comma_separated_integer_list,
9+
)
810
from django.utils.translation import ugettext as _
911
from django.utils.timezone import now
1012
from django.utils.encoding import python_2_unicode_compatible
@@ -192,8 +194,9 @@ class Progress(models.Model):
192194
"""
193195
user = models.OneToOneField(settings.AUTH_USER_MODEL, verbose_name=_("User"))
194196

195-
score = models.CommaSeparatedIntegerField(max_length=1024,
196-
verbose_name=_("Score"))
197+
score = models.CharField(max_length=1024,
198+
verbose_name=_("Score"),
199+
validators=[validate_comma_separated_integer_list])
197200

198201
objects = ProgressManager()
199202

@@ -372,14 +375,21 @@ class Sitting(models.Model):
372375

373376
quiz = models.ForeignKey(Quiz, verbose_name=_("Quiz"))
374377

375-
question_order = models.CommaSeparatedIntegerField(
376-
max_length=1024, verbose_name=_("Question Order"))
377-
378-
question_list = models.CommaSeparatedIntegerField(
379-
max_length=1024, verbose_name=_("Question List"))
380-
381-
incorrect_questions = models.CommaSeparatedIntegerField(
382-
max_length=1024, blank=True, verbose_name=_("Incorrect questions"))
378+
question_order = models.CharField(
379+
max_length=1024,
380+
verbose_name=_("Question Order"),
381+
validators=[validate_comma_separated_integer_list])
382+
383+
question_list = models.CharField(
384+
max_length=1024,
385+
verbose_name=_("Question List"),
386+
validators=[validate_comma_separated_integer_list])
387+
388+
incorrect_questions = models.CharField(
389+
max_length=1024,
390+
blank=True,
391+
verbose_name=_("Incorrect questions"),
392+
validators=[validate_comma_separated_integer_list])
383393

384394
current_score = models.IntegerField(verbose_name=_("Current Score"))
385395

quiz/templates/base.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<meta name="description" content="{% block description %}{% endblock %}">
99

1010
<!-- styles -->
11-
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
11+
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
1212

1313
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
1414
<!--[if lt IE 9]>

quiz/tests.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -*- coding: iso-8859-15 -*-
2+
from importlib import import_module
23

34
from django.conf import settings
45
from django.contrib.auth.models import User, Permission
@@ -8,7 +9,6 @@
89
from django.http import HttpRequest
910
from django.template import Template, Context
1011
from django.test import TestCase
11-
from django.utils.importlib import import_module
1212
from django.utils.six import StringIO
1313

1414
from .models import Category, Quiz, Progress, Sitting, SubCategory
@@ -478,14 +478,14 @@ def setUp(self):
478478

479479
def test_paper_marking_list_view(self):
480480
response = self.client.get('/marking/')
481-
self.assertRedirects(response, 'accounts/login/?next=/marking/',
481+
self.assertRedirects(response, '/accounts/login/?next=/marking/',
482482
status_code=302, target_status_code=404 or 200)
483483

484484
self.assertFalse(self.teacher.has_perm('view_sittings', self.student))
485485

486486
self.client.login(username='luke', password='top_secret')
487487
response = self.client.get('/marking/')
488-
self.assertRedirects(response, 'accounts/login/?next=/marking/',
488+
self.assertRedirects(response, '/accounts/login/?next=/marking/',
489489
status_code=302, target_status_code=404 or 200)
490490

491491
self.client.login(username='yoda', password='use_d@_force')

quiz/urls.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
from django.conf.urls import patterns, url
1+
from django.conf.urls import url
22

33
from .views import QuizListView, CategoriesListView,\
44
ViewQuizListByCategory, QuizUserProgressView, QuizMarkingList,\
55
QuizMarkingDetail, QuizDetailView, QuizTake
66

77

8-
urlpatterns = patterns('',
8+
urlpatterns = [
99

1010
url(regex=r'^$',
1111
view=QuizListView.as_view(),
@@ -39,4 +39,4 @@
3939
url(regex=r'^(?P<quiz_name>[\w-]+)/take/$',
4040
view=QuizTake.as_view(),
4141
name='quiz_question'),
42-
)
42+
]

quiz/views.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ def get_context_data(self, **kwargs):
135135
class QuizTake(FormView):
136136
form_class = QuestionForm
137137
template_name = 'question.html'
138+
result_template_name = 'result.html'
139+
single_complete_template_name = 'single_complete.html'
138140

139141
def dispatch(self, request, *args, **kwargs):
140142
self.quiz = get_object_or_404(Quiz, url=self.kwargs['quiz_name'])
@@ -150,11 +152,11 @@ def dispatch(self, request, *args, **kwargs):
150152
self.sitting = self.anon_load_sitting()
151153

152154
if self.sitting is False:
153-
return render(request, 'single_complete.html')
155+
return render(request, self.single_complete_template_name)
154156

155157
return super(QuizTake, self).dispatch(request, *args, **kwargs)
156158

157-
def get_form(self, form_class):
159+
def get_form(self, *args, **kwargs):
158160
if self.logged_in_user:
159161
self.question = self.sitting.get_first_question()
160162
self.progress = self.sitting.progress()
@@ -164,6 +166,8 @@ def get_form(self, form_class):
164166

165167
if self.question.__class__ is Essay_Question:
166168
form_class = EssayForm
169+
else:
170+
form_class = self.form_class
167171

168172
return form_class(**self.get_form_kwargs())
169173

@@ -242,7 +246,7 @@ def final_result_user(self):
242246
if self.quiz.exam_paper is False:
243247
self.sitting.delete()
244248

245-
return render(self.request, 'result.html', results)
249+
return render(self.request, self.result_template_name, results)
246250

247251
def anon_load_sitting(self):
248252
if self.quiz.single_attempt is True:

requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
django-model-utils==2.0.3
2-
Django==1.6.5
3-
Pillow==2.5.0
1+
django==1.11.3
2+
django-model-utils==3.0.0
3+
Pillow==4.1.1
44

55
# Only needed for running tests.
66
tox

runtests.py

100644100755
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
#!/usr/bin/env python3
2+
13
import os
24
import sys
3-
os.environ['DJANGO_SETTINGS_MODULE'] = 'test-settings'
5+
os.environ['DJANGO_SETTINGS_MODULE'] = 'test_settings'
46
test_dir = os.path.dirname(__file__)
57
sys.path.insert(0, test_dir)
68

0 commit comments

Comments
 (0)