18 | {% else %}
19 |
20 | {% endif %}
21 |
22 | {% trans "Your answer was" %}
23 |
24 | {{ previous.previous_outcome|yesno:"correct,incorrect" }}
25 |
26 |
27 |
28 |
29 |
30 | {% include 'correct_answer.html' %}
31 |
32 |
{% trans "Explanation" %}:
33 |
34 |
{{ previous.previous_question.explanation }}
35 |
36 |
37 |
38 |
39 | {% endif %}
40 |
41 |
42 |
43 | {% if question %}
44 |
45 | {% if progress %}
46 |
47 | {% trans "Question" %} {{ progress.0|add:1 }} {% trans "of" %} {{ progress.1 }}
48 |
49 | {% endif %}
50 |
51 |
52 | {% trans "Question category" %}:
53 | {{ question.category }}
54 |
55 |
56 |
{{ question.content }}
57 |
58 | {% if question.figure %}
59 |

60 | {% endif %}
61 |
62 |
76 |
77 | {% endif %}
78 |
79 |
80 |
81 |
82 | {% endblock %}
83 |
--------------------------------------------------------------------------------
/multichoice/migrations/0001_initial.py:
--------------------------------------------------------------------------------
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 | ]
48 |
--------------------------------------------------------------------------------
/multichoice/locale/de/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR
, YEAR.
5 | #
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: \n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2015-10-31 22:44+0000\n"
11 | "PO-Revision-Date: 2019-03-07 21:13+0100\n"
12 | "Last-Translator: Reiner Mayers\n"
13 | "Language: de\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
18 | "X-Translated-Using: django-rosetta 0.7.6\n"
19 | "Language-Team: \n"
20 | "X-Generator: Poedit 2.0.6\n"
21 |
22 | #: apps/multichoice/models.py:9 apps/multichoice/models.py:65
23 | msgid "Content"
24 | msgstr "Inhalt"
25 |
26 | #: apps/multichoice/models.py:10
27 | msgid "Random"
28 | msgstr "Zufällig"
29 |
30 | #: apps/multichoice/models.py:11
31 | msgid "None"
32 | msgstr "Kein"
33 |
34 | #: apps/multichoice/models.py:20
35 | msgid "The order in which multichoice answer options are displayed to the user"
36 | msgstr "Reihenfolge in der Multichoice Antworten dem Benutzer angezeigt werden"
37 |
38 | #: apps/multichoice/models.py:23
39 | msgid "Answer Order"
40 | msgstr "Antwort Reihenfolge"
41 |
42 | #: apps/multichoice/models.py:53
43 | msgid "Multiple Choice Question"
44 | msgstr "Multiple Choice Frage"
45 |
46 | #: apps/multichoice/models.py:54
47 | msgid "Multiple Choice Questions"
48 | msgstr "Multiple Choice Frage"
49 |
50 | #: apps/multichoice/models.py:59
51 | msgid "Question"
52 | msgstr "Frage"
53 |
54 | #: apps/multichoice/models.py:63
55 | msgid "Enter the answer text that you want displayed"
56 | msgstr "Geben Sie den Antworttext ein der angezeigt werden soll"
57 |
58 | #: apps/multichoice/models.py:69
59 | msgid "Is this a correct answer?"
60 | msgstr "Ist diese Antwort richtig?"
61 |
62 | #: apps/multichoice/models.py:70
63 | msgid "Correct"
64 | msgstr "Korrekt"
65 |
66 | #: apps/multichoice/models.py:76
67 | msgid "Answer"
68 | msgstr "Antwort"
69 |
70 | #: apps/multichoice/models.py:77
71 | msgid "Answers"
72 | msgstr "Antworten"
73 |
--------------------------------------------------------------------------------
/multichoice/locale/it/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2015-10-31 22:44+0000\n"
12 | "PO-Revision-Date: 2015-10-31 22:19+0000\n"
13 | "Last-Translator: b' <>'\n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20 | "X-Translated-Using: django-rosetta 0.7.6\n"
21 |
22 | #: apps/multichoice/models.py:9 apps/multichoice/models.py:65
23 | msgid "Content"
24 | msgstr "Contenuto"
25 |
26 | #: apps/multichoice/models.py:10
27 | msgid "Random"
28 | msgstr "Casuale"
29 |
30 | #: apps/multichoice/models.py:11
31 | msgid "None"
32 | msgstr "Nessuno"
33 |
34 | #: apps/multichoice/models.py:20
35 | msgid "The order in which multichoice answer options are displayed to the user"
36 | msgstr ""
37 | "Ordine in cui le risposte multiple disponibili vengono visualizzate "
38 | "all'utente"
39 |
40 | #: apps/multichoice/models.py:23
41 | msgid "Answer Order"
42 | msgstr "Ordine Risposte"
43 |
44 | #: apps/multichoice/models.py:53
45 | msgid "Multiple Choice Question"
46 | msgstr "Domanda a risposta multipla"
47 |
48 | #: apps/multichoice/models.py:54
49 | msgid "Multiple Choice Questions"
50 | msgstr "Domande a risposta multipla"
51 |
52 | #: apps/multichoice/models.py:59
53 | msgid "Question"
54 | msgstr "Domanda"
55 |
56 | #: apps/multichoice/models.py:63
57 | msgid "Enter the answer text that you want displayed"
58 | msgstr "Inserisci il testo della risposta che vuoi visualizzare"
59 |
60 | #: apps/multichoice/models.py:69
61 | msgid "Is this a correct answer?"
62 | msgstr "Questa è la risposta corretta?"
63 |
64 | #: apps/multichoice/models.py:70
65 | msgid "Correct"
66 | msgstr "Corretto"
67 |
68 | #: apps/multichoice/models.py:76
69 | msgid "Answer"
70 | msgstr "Risposta"
71 |
72 | #: apps/multichoice/models.py:77
73 | msgid "Answers"
74 | msgstr "Risposte"
75 |
--------------------------------------------------------------------------------
/multichoice/tests.py:
--------------------------------------------------------------------------------
1 | from django.core.files.base import ContentFile
2 | from django.db.models.fields.files import ImageFieldFile
3 | from django.test import TestCase
4 | from django.utils.six import StringIO
5 |
6 | from .models import MCQuestion, Answer
7 |
8 |
9 | class TestMCQuestionModel(TestCase):
10 | def setUp(self):
11 | self.q = MCQuestion.objects.create(id=1,
12 | content=("WHAT is the airspeed" +
13 | "velocity of an unladen" +
14 | "swallow?"),
15 | explanation="I, I don't know that!")
16 |
17 | self.answer1 = Answer.objects.create(id=123,
18 | question=self.q,
19 | content="African",
20 | correct=False)
21 |
22 | self.answer2 = Answer.objects.create(id=456,
23 | question=self.q,
24 | content="European",
25 | correct=True)
26 |
27 | def test_answers(self):
28 | answers = Answer.objects.filter(question=self.q)
29 | correct_a = Answer.objects.get(question=self.q,
30 | correct=True)
31 | answers_by_method = self.q.get_answers()
32 |
33 | self.assertEqual(answers.count(), 2)
34 | self.assertEqual(correct_a.content, "European")
35 | self.assertEqual(self.q.check_if_correct(123), False)
36 | self.assertEqual(self.q.check_if_correct(456), True)
37 | self.assertEqual(answers_by_method.count(), 2)
38 | self.assertEqual(self.q.answer_choice_to_string(123),
39 | self.answer1.content)
40 |
41 | def test_figure(self):
42 | # http://stackoverflow.com/a/2473445/1694979
43 | imgfile = StringIO(
44 | 'GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,'
45 | '\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;')
46 | imgfile.name = 'test_img_file.gif'
47 |
48 | self.q.figure.save('image', ContentFile(imgfile.read()))
49 | self.assertIsInstance(self.q.figure, ImageFieldFile)
50 |
51 | def test_answer_to_string(self):
52 | self.assertEqual('African', self.q.answer_choice_to_string(123))
53 |
--------------------------------------------------------------------------------
/quiz/templates/result.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load i18n %}
3 |
4 | {% load quiz_tags %}
5 |
6 | {% block title %} {{ quiz.title}} {% endblock %}
7 | {% block description %} {% trans "Exam Results for" %} {{ quiz.title }} {% endblock %}
8 |
9 | {% block content %}
10 |
11 | {% if previous.answers %}
12 |
13 | {% trans "The previous question" %}:
14 | {{ previous.previous_question }}
15 | Your answer was
16 |
17 | {{ previous.previous_outcome|yesno:"correct,incorrect" }}
18 |
19 |
20 | {% include 'correct_answer.html' %}
21 | {% trans "Explanation" %}:
22 |
23 |
{{ previous.previous_question.explanation }}
24 |
25 |
26 |
27 | {% endif %}
28 |
29 | {% if max_score %}
30 |
31 |
32 |
{% trans "Exam results" %}
33 |
34 | {% trans "Exam title" %}:
35 | {{ quiz.title }}
36 |
37 |
38 | {% trans "You answered" %} {{ score }} {% trans "questions correctly out of" %} {{ max_score }}, {% trans "giving you" %} {{ percent }} {% trans "percent correct" %}
39 |
40 |
41 | {% if quiz.pass_mark %}
42 |
43 |
{{ sitting.result_message }}
44 |
45 |
46 | {% endif %}
47 |
48 |
{% trans "Review the questions below and try the exam again in the future"%}.
49 |
50 | {% if user.is_authenticated %}
51 |
52 |
{% trans "The result of this exam will be stored in your progress section so you can review and monitor your progression" %}.
53 |
54 | {% endif %}
55 |
56 |
57 |
58 | {% endif %}
59 |
60 |
61 |
62 |
63 | {% if possible %}
64 |
65 |
66 | {% trans "Your session score is" %} {{ session }} {% trans "out of a possible" %} {{ possible }}
67 |
68 |
69 |
70 |
71 | {% endif %}
72 |
73 | {% if questions %}
74 |
75 | {% for question in questions %}
76 |
77 |
78 | {{ question.content }}
79 |
80 |
81 | {% correct_answer_for_all question %}
82 |
83 | {% if question.user_answer %}
84 | {% trans "Your answer" %}: {{ question|answer_choice_to_string:question.user_answer }}
85 | {% endif %}
86 |
87 | {% trans "Explanation" %}:
88 |
89 |
{{ question.explanation|safe }}
90 |
91 |
92 |
93 |
94 | {% endfor %}
95 |
96 | {% endif %}
97 |
98 |
99 | {% endblock %}
100 |
--------------------------------------------------------------------------------
/multichoice/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | from django.utils.encoding import python_2_unicode_compatible
3 | from django.utils.translation import ugettext_lazy as _
4 | from django.db import models
5 | from quiz.models import Question
6 |
7 |
8 | ANSWER_ORDER_OPTIONS = (
9 | ('content', _('Content')),
10 | ('random', _('Random')),
11 | ('none', _('None'))
12 | )
13 |
14 |
15 | class MCQuestion(Question):
16 |
17 | answer_order = models.CharField(
18 | max_length=30, null=True, blank=True,
19 | choices=ANSWER_ORDER_OPTIONS,
20 | help_text=_("The order in which multichoice "
21 | "answer options are displayed "
22 | "to the user"),
23 | verbose_name=_("Answer Order"))
24 |
25 | def check_if_correct(self, guess):
26 | answer = Answer.objects.get(id=guess)
27 |
28 | if answer.correct is True:
29 | return True
30 | else:
31 | return False
32 |
33 | def order_answers(self, queryset):
34 | if self.answer_order == 'content':
35 | return queryset.order_by('content')
36 | if self.answer_order == 'random':
37 | return queryset.order_by('?')
38 | if self.answer_order == 'none':
39 | return queryset.order_by()
40 | return queryset
41 |
42 | def get_answers(self):
43 | return self.order_answers(Answer.objects.filter(question=self))
44 |
45 | def get_answers_list(self):
46 | return [(answer.id, answer.content) for answer in
47 | self.order_answers(Answer.objects.filter(question=self))]
48 |
49 | def answer_choice_to_string(self, guess):
50 | return Answer.objects.get(id=guess).content
51 |
52 | class Meta:
53 | verbose_name = _("Multiple Choice Question")
54 | verbose_name_plural = _("Multiple Choice Questions")
55 |
56 |
57 | @python_2_unicode_compatible
58 | class Answer(models.Model):
59 | question = models.ForeignKey(MCQuestion, verbose_name=_("Question"), on_delete=models.CASCADE)
60 |
61 | content = models.CharField(max_length=1000,
62 | blank=False,
63 | help_text=_("Enter the answer text that "
64 | "you want displayed"),
65 | verbose_name=_("Content"))
66 |
67 | correct = models.BooleanField(blank=False,
68 | default=False,
69 | help_text=_("Is this a correct answer?"),
70 | verbose_name=_("Correct"))
71 |
72 | def __str__(self):
73 | return self.content
74 |
75 | class Meta:
76 | verbose_name = _("Answer")
77 | verbose_name_plural = _("Answers")
78 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ===============
2 | Django quiz app
3 | ===============
4 |
5 | This is a configurable quiz app for Django.
6 |
7 | Current features
8 | ----------------
9 | * Question order randomisation
10 | * Storing of quiz results under each user
11 | * Previous quiz scores can be viewed on category page
12 | * Correct answers can be shown after each question or all at once at the end
13 | * Logged in users can return to an incomplete quiz to finish it and non-logged in users can complete a quiz if their session persists
14 | * The quiz can be limited to one attempt per user
15 | * Questions can be given a category
16 | * Success rate for each category can be monitored on a progress page
17 | * Explanation for each question result can be given
18 | * Pass marks can be set
19 | * Multiple choice question type
20 | * True/False question type
21 | * Essay question type
22 | * Custom message displayed for those that pass or fail a quiz
23 | * Custom permission (view_sittings) added, allowing users with that permission to view quiz results from users
24 | * A marking page which lists completed quizzes, can be filtered by quiz or user, and is used to mark essay questions
25 |
26 |
27 | Requirements
28 | ------------
29 |
30 | django-model-utils
31 |
32 |
33 | Installation
34 | ------------
35 |
36 | git clone https://github.com/tomwalker/django_quiz.git
37 |
38 | pip install -r requirements.txt
39 |
40 | Add 'quiz', 'multichoice', 'true_false', and 'essay' to your 'INSTALLED_APPS' setting.
41 |
42 | INSTALLED_APPS = (
43 | ...
44 | 'quiz',
45 | 'multichoice',
46 | 'true_false',
47 | ...
48 | )
49 |
50 | Add the following to your projects 'urls.py' file, substituting 'q'
51 | for whatever you want the quiz base url to be.
52 |
53 | urlpatterns = patterns('',
54 | ...
55 | url(r'^q/', include('quiz.urls')),
56 | ...
57 | )
58 |
59 | MIT License (MIT) Copyright (c) 2012 - 2014 Dr Tom Walker
60 |
61 | Permission is hereby granted, free of charge, to any person obtaining a
62 | copy of this software and associated documentation files (the
63 | “Software”), to deal in the Software without restriction, including
64 | without limitation the rights to use, copy, modify, merge, publish,
65 | distribute, sublicense, and/or sell copies of the Software, and to
66 | permit persons to whom the Software is furnished to do so, subject to
67 | the following conditions:
68 |
69 | The above copyright notice and this permission notice shall be included
70 | in all copies or substantial portions of the Software.
71 |
72 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
73 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
74 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
75 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
76 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
77 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
78 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
79 |
--------------------------------------------------------------------------------
/quiz/admin.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.contrib import admin
3 | from django.contrib.admin.widgets import FilteredSelectMultiple
4 | from django.utils.translation import ugettext_lazy as _
5 |
6 | from .models import Quiz, Category, SubCategory, Progress, Question
7 | from multichoice.models import MCQuestion, Answer
8 | from true_false.models import TF_Question
9 | from essay.models import Essay_Question
10 |
11 |
12 | class AnswerInline(admin.TabularInline):
13 | model = Answer
14 |
15 |
16 | class QuizAdminForm(forms.ModelForm):
17 | """
18 | below is from
19 | http://stackoverflow.com/questions/11657682/
20 | django-admin-interface-using-horizontal-filter-with-
21 | inline-manytomany-field
22 | """
23 |
24 | class Meta:
25 | model = Quiz
26 | exclude = []
27 |
28 | questions = forms.ModelMultipleChoiceField(
29 | queryset=Question.objects.all().select_subclasses(),
30 | required=False,
31 | label=_("Questions"),
32 | widget=FilteredSelectMultiple(
33 | verbose_name=_("Questions"),
34 | is_stacked=False))
35 |
36 | def __init__(self, *args, **kwargs):
37 | super(QuizAdminForm, self).__init__(*args, **kwargs)
38 | if self.instance.pk:
39 | self.fields['questions'].initial =\
40 | self.instance.question_set.all().select_subclasses()
41 |
42 | def save(self, commit=True):
43 | quiz = super(QuizAdminForm, self).save(commit=False)
44 | quiz.save()
45 | quiz.question_set.set(self.cleaned_data['questions'])
46 | self.save_m2m()
47 | return quiz
48 |
49 |
50 | class QuizAdmin(admin.ModelAdmin):
51 | form = QuizAdminForm
52 |
53 | list_display = ('title', 'category', )
54 | list_filter = ('category',)
55 | search_fields = ('description', 'category', )
56 |
57 |
58 | class CategoryAdmin(admin.ModelAdmin):
59 | search_fields = ('category', )
60 |
61 |
62 | class SubCategoryAdmin(admin.ModelAdmin):
63 | search_fields = ('sub_category', )
64 | list_display = ('sub_category', 'category',)
65 | list_filter = ('category',)
66 |
67 |
68 | class MCQuestionAdmin(admin.ModelAdmin):
69 | list_display = ('content', 'category', )
70 | list_filter = ('category',)
71 | fields = ('content', 'category', 'sub_category',
72 | 'figure', 'quiz', 'explanation', 'answer_order')
73 |
74 | search_fields = ('content', 'explanation')
75 | filter_horizontal = ('quiz',)
76 |
77 | inlines = [AnswerInline]
78 |
79 |
80 | class ProgressAdmin(admin.ModelAdmin):
81 | """
82 | to do:
83 | create a user section
84 | """
85 | search_fields = ('user', 'score', )
86 |
87 |
88 | class TFQuestionAdmin(admin.ModelAdmin):
89 | list_display = ('content', 'category', )
90 | list_filter = ('category',)
91 | fields = ('content', 'category', 'sub_category',
92 | 'figure', 'quiz', 'explanation', 'correct',)
93 |
94 | search_fields = ('content', 'explanation')
95 | filter_horizontal = ('quiz',)
96 |
97 |
98 | class EssayQuestionAdmin(admin.ModelAdmin):
99 | list_display = ('content', 'category', )
100 | list_filter = ('category',)
101 | fields = ('content', 'category', 'sub_category', 'quiz', 'explanation', )
102 | search_fields = ('content', 'explanation')
103 | filter_horizontal = ('quiz',)
104 |
105 | admin.site.register(Quiz, QuizAdmin)
106 | admin.site.register(Category, CategoryAdmin)
107 | admin.site.register(SubCategory, SubCategoryAdmin)
108 | admin.site.register(MCQuestion, MCQuestionAdmin)
109 | admin.site.register(Progress, ProgressAdmin)
110 | admin.site.register(TF_Question, TFQuestionAdmin)
111 | admin.site.register(Essay_Question, EssayQuestionAdmin)
112 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Django quiz app
2 | ===============
3 | [](https://travis-ci.org/tomwalker/django_quiz)
4 |
5 | This is a configurable quiz app for Django.
6 |
7 | I use it to run a few medical revision websites. Here is an [example website](http://www.revisemrcp.com/)
8 |
9 | My websites have used twitter bootstrap for the front end and I have tried to strip out anything from
10 | the template files that are dependant on bootstrap.
11 |
12 | 
13 |
14 |
15 | Current features
16 | ----------------
17 | Features of each quiz:
18 | * Question order randomisation
19 | * Storing of quiz results under each user
20 | * Previous quiz scores can be viewed on category page
21 | * Correct answers can be shown after each question or all at once at the end
22 | * Logged in users can return to an incomplete quiz to finish it and non-logged in users can complete a quiz if their session persists
23 | * The quiz can be limited to one attempt per user
24 | * Questions can be given a category and subcategory
25 | * Success rate for each category can be monitored on a progress page
26 | * Explanation for each question result can be given
27 | * Pass marks can be set
28 | * Multiple choice question type
29 | * True/False question type
30 | * Essay question type
31 | * Display an image alongside the question
32 | * Custom message displayed for those that pass or fail a quiz
33 | * Custom permission (view_sittings) added, allowing users with that permission to view quiz results from users
34 | * A marking page which lists completed quizzes, can be filtered by quiz or user, and is used to mark essay questions
35 | * After selecting a larger pool of questions, a quiz can be set to show a random subset rather than all within the pool
36 | * Start and end times for sitting exams are recorded
37 | * i18n support
38 | * Russian and Italian language translation
39 |
40 |
41 |
42 |
43 | 
44 |
45 | Requirements
46 | ------------
47 | django-model-utils
48 |
49 | Pillow
50 |
51 | Tests are included and pass for Django versions 1.5, 1.6, 1.7 and 1.8, running with Python 2.7, 3.3 and 3.4
52 |
53 | Installation
54 | ------------
55 | Clone the repo with `git clone https://github.com/tomwalker/django_quiz.git`.
56 |
57 | Run `pip install -r requirements.txt`.
58 | Run `python setup.py install`
59 |
60 | Add `'quiz', 'multichoice', 'true_false', 'essay'` to your `INSTALLED_APPS` setting.
61 |
62 | INSTALLED_APPS = (
63 | ...
64 | 'quiz',
65 | 'multichoice',
66 | 'true_false',
67 | 'essay',
68 | ...
69 | )
70 |
71 | Add the following to your projects `urls.py` file, substituting `q` for whatever you want the quiz base url to be.
72 |
73 | urlpatterns = patterns('',
74 | ...
75 | url(r'^q/', include('quiz.urls')),
76 | ...
77 | )
78 |
79 |
80 | Contributors
81 | ------------
82 | * [https://github.com/certifiedloud](https://github.com/certifiedloud)
83 | * [https://github.com/crackjack](https://github.com/crackjack)
84 | * [https://github.com/richardmansfield](https://github.com/richardmansfield)
85 | * [https://github.com/rkashapov](https://github.com/rkashapov)
86 | * [https://github.com/zamphatta](https://github.com/zamphatta)
87 | * [https://github.com/d0ugal](https://github.com/d0ugal)
88 | * [https://github.com/swfiua](https://github.com/swfiua)
89 |
90 |
91 |
92 | MIT License (MIT)
93 | Copyright (c) 2012 - 2015 Dr Tom Walker
94 |
95 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
96 |
97 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
98 |
99 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
100 |
--------------------------------------------------------------------------------
/test_settings.py:
--------------------------------------------------------------------------------
1 | import django
2 |
3 | DEBUG = True
4 | DEBUG_PROPAGATE_EXCEPTIONS = True
5 |
6 | ALLOWED_HOSTS = ['*']
7 |
8 | ADMINS = (
9 | # ('Your Name', 'your_email@domain.com'),
10 | )
11 |
12 | MANAGERS = ADMINS
13 |
14 | DATABASES = {
15 | 'default': {
16 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
17 | 'NAME': 'sqlite.db', # Or path to database file if using sqlite3.
18 | 'USER': '', # Not used with sqlite3.
19 | 'PASSWORD': '', # Not used with sqlite3.
20 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
21 | 'PORT': '', # Set to empty string for default. Not used with sqlite3.
22 | }
23 | }
24 |
25 | CACHES = {
26 | 'default': {
27 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
28 | }
29 | }
30 |
31 | # Local time zone for this installation. Choices can be found here:
32 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
33 | # although not all choices may be available on all operating systems.
34 | # On Unix systems, a value of None will cause Django to use the same
35 | # timezone as the operating system.
36 | # If running in a Windows environment this must be set to the same as your
37 | # system time zone.
38 | TIME_ZONE = 'Europe/London'
39 |
40 | # Language code for this installation. All choices can be found here:
41 | # http://www.i18nguy.com/unicode/language-identifiers.html
42 | LANGUAGE_CODE = 'en-uk'
43 |
44 | SITE_ID = 1
45 |
46 | # If you set this to False, Django will make some optimizations so as not
47 | # to load the internationalization machinery.
48 | USE_I18N = True
49 |
50 | # If you set this to False, Django will not format dates, numbers and
51 | # calendars according to the current locale
52 | USE_L10N = True
53 |
54 | # Absolute filesystem path to the directory that will hold user-uploaded files.
55 | # Example: "/home/media/media.lawrence.com/"
56 | MEDIA_ROOT = ''
57 |
58 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a
59 | # trailing slash if there is a path component (optional in other cases).
60 | # Examples: "http://media.lawrence.com", "http://example.com/media/"
61 | MEDIA_URL = ''
62 |
63 | # Make this unique, and don't share it with anybody.
64 | SECRET_KEY = 'boing!'
65 |
66 | TEMPLATES = [
67 | {
68 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
69 | 'DIRS': [],
70 | 'APP_DIRS': True,
71 | 'OPTIONS': {
72 | 'context_processors': [
73 | 'django.contrib.auth.context_processors.auth',
74 | 'django.template.context_processors.debug',
75 | 'django.template.context_processors.i18n',
76 | 'django.template.context_processors.media',
77 | 'django.template.context_processors.static',
78 | 'django.template.context_processors.tz',
79 | 'django.contrib.messages.context_processors.messages',
80 | ],
81 | },
82 | },
83 | ]
84 |
85 | if django.VERSION >= (1, 10):
86 | MIDDLEWARE = (
87 | 'django.middleware.common.CommonMiddleware',
88 | 'django.contrib.sessions.middleware.SessionMiddleware',
89 | 'django.middleware.csrf.CsrfViewMiddleware',
90 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
91 | 'django.contrib.messages.middleware.MessageMiddleware',
92 | )
93 | else:
94 | MIDDLEWARE_CLASSES = (
95 | 'django.middleware.common.CommonMiddleware',
96 | 'django.contrib.sessions.middleware.SessionMiddleware',
97 | 'django.middleware.csrf.CsrfViewMiddleware',
98 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
99 | 'django.contrib.messages.middleware.MessageMiddleware',
100 | )
101 |
102 | ROOT_URLCONF = 'quiz.urls'
103 |
104 | TEMPLATE_DIRS = (
105 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
106 | # Always use forward slashes, even on Windows.
107 | # Don't forget to use absolute paths, not relative paths.
108 | )
109 |
110 | INSTALLED_APPS = (
111 | 'django.contrib.auth',
112 | 'django.contrib.contenttypes',
113 | 'django.contrib.sessions',
114 | 'django.contrib.sites',
115 | 'django.contrib.messages',
116 | # Uncomment the next line to enable the admin:
117 | # 'django.contrib.admin',
118 | # Uncomment the next line to enable admin documentation:
119 | # 'django.contrib.admindocs',
120 | 'quiz',
121 | 'multichoice',
122 | 'true_false',
123 | 'essay',
124 | )
125 |
126 |
127 | STATIC_URL = '/static/'
128 |
129 | PASSWORD_HASHERS = (
130 | 'django.contrib.auth.hashers.SHA1PasswordHasher',
131 | 'django.contrib.auth.hashers.PBKDF2PasswordHasher',
132 | 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
133 | 'django.contrib.auth.hashers.BCryptPasswordHasher',
134 | 'django.contrib.auth.hashers.MD5PasswordHasher',
135 | 'django.contrib.auth.hashers.CryptPasswordHasher',
136 | )
137 |
138 | AUTH_USER_MODEL = 'auth.User'
139 |
--------------------------------------------------------------------------------
/quiz/migrations/0001_initial.py:
--------------------------------------------------------------------------------
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 | ]
124 |
--------------------------------------------------------------------------------
/quiz/locale/zh_CN/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2015-10-31 22:44+0000\n"
12 | "PO-Revision-Date: 2015-10-31 22:43+0000\n"
13 | "Last-Translator: b' <>'\n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20 | "X-Translated-Using: django-rosetta 0.7.6\n"
21 |
22 | #: apps/quiz/models.py:30 apps/quiz/models.py:37 apps/quiz/models.py:53
23 | #: apps/quiz/models.py:83 apps/quiz/models.py:545
24 | #: apps/quiz/templates/progress.html:19
25 | #: apps/quiz/templates/quiz/quiz_detail.html:9
26 | #: apps/quiz/templates/quiz/quiz_list.html:13
27 | #: apps/quiz/templates/quiz/sitting_detail.html:10
28 | msgid "Category"
29 | msgstr "类别"
30 |
31 | #: apps/quiz/models.py:38
32 | msgid "Categories"
33 | msgstr "类别"
34 |
35 | #: apps/quiz/models.py:48 apps/quiz/models.py:58 apps/quiz/models.py:550
36 | msgid "Sub-Category"
37 | msgstr "子类"
38 |
39 | #: apps/quiz/models.py:59
40 | msgid "Sub-Categories"
41 | msgstr "子类"
42 |
43 | #: apps/quiz/models.py:69 apps/quiz/templates/quiz/quiz_list.html:12
44 | msgid "Title"
45 | msgstr "标题"
46 |
47 | #: apps/quiz/models.py:73
48 | msgid "Description"
49 | msgstr "描述"
50 |
51 | #: apps/quiz/models.py:74
52 | msgid "a description of the quiz"
53 | msgstr "测试的描述"
54 |
55 | #: apps/quiz/models.py:78
56 | msgid "a user friendly url"
57 | msgstr "一个用户友好的URL"
58 |
59 | #: apps/quiz/models.py:79
60 | msgid "user friendly url"
61 | msgstr "用户友好的URL"
62 |
63 | #: apps/quiz/models.py:87
64 | msgid "Random Order"
65 | msgstr "随机次序"
66 |
67 | #: apps/quiz/models.py:88
68 | msgid "Display the questions in a random order or as they are set?"
69 | msgstr "按设定的次序还是随机次序显示问题?"
70 |
71 | #: apps/quiz/models.py:93
72 | msgid "Max Questions"
73 | msgstr "最大问题数量"
74 |
75 | #: apps/quiz/models.py:94
76 | msgid "Number of questions to be answered on each attempt."
77 | msgstr "每次尝试可回答的问题数量。"
78 |
79 | #: apps/quiz/models.py:98
80 | msgid ""
81 | "Correct answer is NOT shown after question. Answers displayed at the end."
82 | msgstr ""
83 | "正确答案不会在问题后显示。回答显示在最后。"
84 |
85 | #: apps/quiz/models.py:100
86 | msgid "Answers at end"
87 | msgstr "最后给出答案"
88 |
89 | #: apps/quiz/models.py:104
90 | msgid ""
91 | "If yes, the result of each attempt by a user will be stored. Necessary for "
92 | "marking."
93 | msgstr ""
94 | "如果是,用户每次尝试的结果都将记录。如需打分此项必选。"
95 |
96 | #: apps/quiz/models.py:107
97 | msgid "Exam Paper"
98 | msgstr "试卷"
99 |
100 | #: apps/quiz/models.py:111
101 | msgid ""
102 | "If yes, only one attempt by a user will be permitted. Non users cannot sit "
103 | "this exam."
104 | msgstr ""
105 | "如果是,一个用户只允许尝试一次。非注册用户不能参加本考试。"
106 |
107 | #: apps/quiz/models.py:114
108 | msgid "Single Attempt"
109 | msgstr "单次尝试"
110 |
111 | #: apps/quiz/models.py:116
112 | msgid "Pass Mark"
113 | msgstr "通过分数"
114 |
115 | #: apps/quiz/models.py:118
116 | msgid "Percentage required to pass exam."
117 | msgstr "通过考试需要的百分数。"
118 |
119 | #: apps/quiz/models.py:122
120 | msgid "Displayed if user passes."
121 | msgstr "如果用户通过则显示。"
122 |
123 | #: apps/quiz/models.py:123
124 | msgid "Success Text"
125 | msgstr "成功文本"
126 |
127 | #: apps/quiz/models.py:126
128 | msgid "Fail Text"
129 | msgstr "失败文本"
130 |
131 | #: apps/quiz/models.py:127
132 | msgid "Displayed if user fails."
133 | msgstr "如果用户未通过则显示"
134 |
135 | #: apps/quiz/models.py:131
136 | msgid "Draft"
137 | msgstr "草稿"
138 |
139 | #: apps/quiz/models.py:132
140 | msgid ""
141 | "If yes, the quiz is not displayed in the quiz list and can only be taken by "
142 | "users who can edit quizzes."
143 | msgstr ""
144 | "如果是,测试不会显示在测试列表里,只能被可以编辑测试的用户操作。"
145 |
146 | #: apps/quiz/models.py:152 apps/quiz/models.py:372 apps/quiz/models.py:541
147 | #: apps/quiz/templates/quiz/sitting_list.html:14
148 | msgid "Quiz"
149 | msgstr "测试"
150 |
151 | #: apps/quiz/models.py:153
152 | msgid "Quizzes"
153 | msgstr "测试"
154 |
155 | #: apps/quiz/models.py:192 apps/quiz/models.py:370
156 | #: apps/quiz/templates/quiz/sitting_detail.html:13
157 | #: apps/quiz/templates/quiz/sitting_list.html:13
158 | msgid "User"
159 | msgstr "用户"
160 |
161 | #: apps/quiz/models.py:195 apps/quiz/templates/progress.html:60
162 | #: apps/quiz/templates/quiz/sitting_detail.html:15
163 | #: apps/quiz/templates/quiz/sitting_list.html:16
164 | msgid "Score"
165 | msgstr "得分"
166 |
167 | #: apps/quiz/models.py:200
168 | msgid "User Progress"
169 | msgstr "用户进度"
170 |
171 | #: apps/quiz/models.py:201
172 | msgid "User progress records"
173 | msgstr "用户进度记录"
174 |
175 | #: apps/quiz/models.py:261
176 | msgid "error"
177 | msgstr "错误"
178 |
179 | #: apps/quiz/models.py:261
180 | msgid "category does not exist or invalid score"
181 | msgstr "类别不存在或得分无效"
182 |
183 | #: apps/quiz/models.py:375
184 | msgid "Question Order"
185 | msgstr "问题次序"
186 |
187 | #: apps/quiz/models.py:378
188 | msgid "Question List"
189 | msgstr "问题列表"
190 |
191 | #: apps/quiz/models.py:381
192 | msgid "Incorrect questions"
193 | msgstr "不正确的答案"
194 |
195 | #: apps/quiz/models.py:383
196 | msgid "Current Score"
197 | msgstr "当前得分"
198 |
199 | #: apps/quiz/models.py:386
200 | msgid "Complete"
201 | msgstr "完成"
202 |
203 | #: apps/quiz/models.py:389
204 | msgid "User Answers"
205 | msgstr "用户回答"
206 |
207 | #: apps/quiz/models.py:392
208 | msgid "Start"
209 | msgstr "开始"
210 |
211 | #: apps/quiz/models.py:394
212 | msgid "End"
213 | msgstr "结束"
214 |
215 | #: apps/quiz/models.py:399
216 | msgid "Can see completed exams."
217 | msgstr "可以看到已完成的考试"
218 |
219 | #: apps/quiz/models.py:557
220 | msgid "Figure"
221 | msgstr "图像"
222 |
223 | #: apps/quiz/models.py:561
224 | msgid "Enter the question text that you want displayed"
225 | msgstr "输入你想显示的问题文本"
226 |
227 | #: apps/quiz/models.py:563 apps/quiz/models.py:575
228 | #: apps/quiz/templates/question.html:47
229 | #: apps/quiz/templates/quiz/sitting_detail.html:21
230 | msgid "Question"
231 | msgstr "问题"
232 |
233 | #: apps/quiz/models.py:567
234 | msgid "Explanation to be shown after the question has been answered."
235 | msgstr "回答问题后将展示题解。"
236 |
237 | #: apps/quiz/models.py:570 apps/quiz/templates/question.html:32
238 | #: apps/quiz/templates/result.html:21 apps/quiz/templates/result.html.py:87
239 | msgid "Explanation"
240 | msgstr "题解"
241 |
242 | #: apps/quiz/models.py:576
243 | msgid "Questions"
244 | msgstr "问题"
245 |
246 | #: apps/quiz/templates/base.html:7
247 | msgid "Example Quiz Website"
248 | msgstr "测试样例网站"
249 |
250 | #: apps/quiz/templates/correct_answer.html:6
251 | msgid "You answered the above question incorrectly"
252 | msgstr "你错误回答了以上问题"
253 |
254 | #: apps/quiz/templates/correct_answer.html:16
255 | msgid "This is the correct answer"
256 | msgstr "这是正确答案"
257 |
258 | #: apps/quiz/templates/correct_answer.html:23
259 | msgid "This was your answer."
260 | msgstr "这是你的回答。"
261 |
262 | #: apps/quiz/templates/progress.html:6
263 | msgid "Progress Page"
264 | msgstr "进度页面"
265 |
266 | #: apps/quiz/templates/progress.html:7
267 | msgid "User Progress Page"
268 | msgstr "用户进度页面"
269 |
270 | #: apps/quiz/templates/progress.html:13
271 | msgid "Question Category Scores"
272 | msgstr "问题类别得分"
273 |
274 | #: apps/quiz/templates/progress.html:20
275 | msgid "Correctly answererd"
276 | msgstr "正确回答了"
277 |
278 | #: apps/quiz/templates/progress.html:21
279 | msgid "Incorrect"
280 | msgstr "不正确"
281 |
282 | #: apps/quiz/templates/progress.html:50
283 | msgid "Previous exam papers"
284 | msgstr "前一试卷"
285 |
286 | #: apps/quiz/templates/progress.html:52
287 | msgid "Below are the results of exams that you have sat."
288 | msgstr "以下是你已经参加的考试的结果。"
289 |
290 | #: apps/quiz/templates/progress.html:59
291 | msgid "Quiz Title"
292 | msgstr "测试标题"
293 |
294 | #: apps/quiz/templates/progress.html:61
295 | msgid "Possible Score"
296 | msgstr "可能得分"
297 |
298 | #: apps/quiz/templates/question.html:13 apps/quiz/templates/result.html:13
299 | msgid "The previous question"
300 | msgstr "前一问题"
301 |
302 | #: apps/quiz/templates/question.html:22
303 | msgid "Your answer was"
304 | msgstr "你的回答是"
305 |
306 | #: apps/quiz/templates/question.html:47
307 | msgid "of"
308 | msgstr "属于"
309 |
310 | #: apps/quiz/templates/question.html:52
311 | msgid "Question category"
312 | msgstr "问题类别"
313 |
314 | #: apps/quiz/templates/question.html:74
315 | msgid "Check"
316 | msgstr "检查"
317 |
318 | #: apps/quiz/templates/quiz/category_list.html:3
319 | #: apps/quiz/templates/quiz/quiz_list.html:3
320 | #: apps/quiz/templates/quiz/sitting_list.html:3
321 | msgid "All Quizzes"
322 | msgstr "全部测试"
323 |
324 | #: apps/quiz/templates/quiz/category_list.html:6
325 | msgid "Category list"
326 | msgstr "类别列表"
327 |
328 | #: apps/quiz/templates/quiz/quiz_detail.html:11
329 | msgid "You will only get one attempt at this quiz"
330 | msgstr "本测试你只有一次尝试的机会"
331 |
332 | #: apps/quiz/templates/quiz/quiz_detail.html:16
333 | msgid "Start quiz"
334 | msgstr "开始测试"
335 |
336 | #: apps/quiz/templates/quiz/quiz_list.html:6
337 | msgid "List of quizzes"
338 | msgstr "测试列表"
339 |
340 | #: apps/quiz/templates/quiz/quiz_list.html:14
341 | msgid "Exam"
342 | msgstr "考试"
343 |
344 | #: apps/quiz/templates/quiz/quiz_list.html:15
345 | msgid "Single attempt"
346 | msgstr "单一尝试"
347 |
348 | #: apps/quiz/templates/quiz/quiz_list.html:31
349 | #: apps/quiz/templates/quiz/sitting_list.html:42
350 | msgid "View details"
351 | msgstr "显示细节"
352 |
353 | #: apps/quiz/templates/quiz/quiz_list.html:41
354 | msgid "There are no available quizzes"
355 | msgstr "没有可做的测试"
356 |
357 | #: apps/quiz/templates/quiz/sitting_detail.html:5
358 | msgid "Result of"
359 | msgstr "结果属于"
360 |
361 | #: apps/quiz/templates/quiz/sitting_detail.html:5
362 | msgid "for"
363 | msgstr "为了"
364 |
365 | #: apps/quiz/templates/quiz/sitting_detail.html:9
366 | msgid "Quiz title"
367 | msgstr "测试标题"
368 |
369 | #: apps/quiz/templates/quiz/sitting_detail.html:14
370 | #: apps/quiz/templates/quiz/sitting_list.html:15
371 | msgid "Completed"
372 | msgstr "已完成"
373 |
374 | #: apps/quiz/templates/quiz/sitting_detail.html:22
375 | msgid "User answer"
376 | msgstr "用户的回答"
377 |
378 | #: apps/quiz/templates/quiz/sitting_detail.html:41
379 | msgid "incorrect"
380 | msgstr "不正确"
381 |
382 | #: apps/quiz/templates/quiz/sitting_detail.html:43
383 | msgid "Correct"
384 | msgstr "正确"
385 |
386 | #: apps/quiz/templates/quiz/sitting_detail.html:49
387 | msgid "Toggle whether correct"
388 | msgstr "切换是否正确"
389 |
390 | #: apps/quiz/templates/quiz/sitting_list.html:6
391 | msgid "List of complete exams"
392 | msgstr "已完成考试列表"
393 |
394 | #: apps/quiz/templates/quiz/sitting_list.html:28
395 | msgid "Filter"
396 | msgstr "过滤"
397 |
398 | #: apps/quiz/templates/quiz/sitting_list.html:52
399 | msgid "There are no matching quizzes"
400 | msgstr "没有符合条件的测试"
401 |
402 | #: apps/quiz/templates/result.html:7
403 | msgid "Exam Results for"
404 | msgstr "考试结果"
405 |
406 | #: apps/quiz/templates/result.html:32
407 | msgid "Exam results"
408 | msgstr "考试结果"
409 |
410 | #: apps/quiz/templates/result.html:34
411 | msgid "Exam title"
412 | msgstr "考试标题"
413 |
414 | #: apps/quiz/templates/result.html:38
415 | msgid "You answered"
416 | msgstr "你正确回答了"
417 |
418 | #: apps/quiz/templates/result.html:38
419 | msgid "questions correctly out of"
420 | msgstr "个问题,总共"
421 |
422 | #: apps/quiz/templates/result.html:38
423 | msgid "giving you"
424 | msgstr "个问题"
425 |
426 | #: apps/quiz/templates/result.html:38
427 | msgid "percent correct"
428 | msgstr "%正确率"
429 |
430 | #: apps/quiz/templates/result.html:48
431 | msgid "Review the questions below and try the exam again in the future"
432 | msgstr "回顾以下问题,将来可以再尝试本考试"
433 |
434 | #: apps/quiz/templates/result.html:52
435 | msgid ""
436 | "The result of this exam will be stored in your progress section so you can "
437 | "review and monitor your progression"
438 | msgstr "本考试的结果将储存于你的进度区域,你可以查看和监控你的进度。"
439 |
440 | #: apps/quiz/templates/result.html:66
441 | msgid "Your session score is"
442 | msgstr "你目前得分为"
443 |
444 | #: apps/quiz/templates/result.html:66
445 | msgid "out of a possible"
446 | msgstr "而最多可能得分为"
447 |
448 | #: apps/quiz/templates/result.html:84
449 | msgid "Your answer"
450 | msgstr "你的答案"
451 |
452 | #: apps/quiz/templates/single_complete.html:13
453 | msgid "You have already sat this exam and only one sitting is permitted"
454 | msgstr "你已经参加本考试,只能参加一次"
455 |
456 | #: apps/quiz/templates/single_complete.html:15
457 | msgid "This exam is only accessible to signed in users"
458 | msgstr "用户需登录方可访问本考试"
459 |
460 | #: apps/quiz/templates/view_quiz_category.html:3
461 | msgid "Quizzes related to"
462 | msgstr "测试有关于"
463 |
464 | #: apps/quiz/templates/view_quiz_category.html:6
465 | msgid "Quizzes in the"
466 | msgstr "测试属于"
467 |
468 | #: apps/quiz/templates/view_quiz_category.html:6
469 | msgid "category"
470 | msgstr "类别"
471 |
472 | #: apps/quiz/templates/view_quiz_category.html:20
473 | msgid "There are no quizzes"
474 | msgstr "没有测试"
475 |
--------------------------------------------------------------------------------
/quiz/locale/ru/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: django-quiz\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2016-01-01 11:37+0400\n"
11 | "PO-Revision-Date: 2015-08-21 19:40+0500\n"
12 | "Last-Translator: Eugena Mihailikova \n"
13 | "Language-Team: LANGUAGE \n"
14 | "Language: ru\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
19 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
20 | "X-Generator: Poedit 1.5.4\n"
21 |
22 | #: admin.py:31 admin.py:33 models.py:576
23 | msgid "Questions"
24 | msgstr "Вопросы"
25 |
26 | #: models.py:30 models.py:37 models.py:53 models.py:83 models.py:545
27 | #: templates/progress.html:19 templates/quiz/quiz_detail.html:9
28 | #: templates/quiz/quiz_list.html:13 templates/quiz/sitting_detail.html:10
29 | msgid "Category"
30 | msgstr "Категория"
31 |
32 | #: models.py:38
33 | msgid "Categories"
34 | msgstr "Категории"
35 |
36 | #: models.py:48 models.py:58 models.py:550
37 | msgid "Sub-Category"
38 | msgstr "Подкатегория"
39 |
40 | #: models.py:59
41 | msgid "Sub-Categories"
42 | msgstr "Подкатегории"
43 |
44 | #: models.py:69 templates/quiz/quiz_list.html:12
45 | msgid "Title"
46 | msgstr "Название"
47 |
48 | #: models.py:73
49 | msgid "Description"
50 | msgstr "Описание"
51 |
52 | #: models.py:74
53 | msgid "a description of the quiz"
54 | msgstr "описание теста"
55 |
56 | #: models.py:78
57 | msgid "a user friendly url"
58 | msgstr "url теста"
59 |
60 | #: models.py:79
61 | msgid "user friendly url"
62 | msgstr "url теста"
63 |
64 | #: models.py:87
65 | msgid "Random Order"
66 | msgstr "Случайный порядок"
67 |
68 | #: models.py:88
69 | msgid "Display the questions in a random order or as they are set?"
70 | msgstr "Отображать вопросы в случайном порядке или в порядке добавления?"
71 |
72 | #: models.py:93
73 | msgid "Max Questions"
74 | msgstr "Максимальное количество вопросов"
75 |
76 | #: models.py:94
77 | msgid "Number of questions to be answered on each attempt."
78 | msgstr ""
79 | "Количество вопросов, на которые должны быть даны ответы при каждой попытке"
80 |
81 | #: models.py:98
82 | msgid ""
83 | "Correct answer is NOT shown after question. Answers displayed at the end."
84 | msgstr ""
85 | "Правильный ответ НЕ показан после вопроса. Ответы отображаются после "
86 | "прохождения теста"
87 |
88 | #: models.py:100
89 | msgid "Answers at end"
90 | msgstr "Ответы в конце"
91 |
92 | #: models.py:104
93 | msgid ""
94 | "If yes, the result of each attempt by a user will be stored. Necessary for "
95 | "marking."
96 | msgstr "Если отмечено, результаты каждой попытки пользователя будет сохранен"
97 |
98 | #: models.py:107
99 | msgid "Exam Paper"
100 | msgstr "Экзаменационный лист"
101 |
102 | #: models.py:111
103 | msgid ""
104 | "If yes, only one attempt by a user will be permitted. Non users cannot sit "
105 | "this exam."
106 | msgstr "Если отмечено, пользователю будет разрешена только одна попытка"
107 |
108 | #: models.py:114
109 | msgid "Single Attempt"
110 | msgstr "Единственная попытка"
111 |
112 | #: models.py:118
113 | msgid "Percentage required to pass exam."
114 | msgstr "Процент правильных ответов для прохождения теста"
115 |
116 | #: models.py:122
117 | msgid "Displayed if user passes."
118 | msgstr "Отображается, если пользователь успешно прошел тест"
119 |
120 | #: models.py:123
121 | msgid "Success Text"
122 | msgstr "Текст в случае успеха"
123 |
124 | #: models.py:126
125 | msgid "Fail Text"
126 | msgstr "Текст в случае неудачи"
127 |
128 | #: models.py:127
129 | msgid "Displayed if user fails."
130 | msgstr "Отображается, если пользователь провалил тест"
131 |
132 | #: models.py:131
133 | msgid "Draft"
134 | msgstr "Черновик"
135 |
136 | #: models.py:132
137 | msgid ""
138 | "If yes, the quiz is not displayed in the quiz list and can only be taken by "
139 | "users who can edit quizzes."
140 | msgstr "Если отмечено, то не отображается в публичном списке и может быть "
141 | "взято только пользователями с соответствующим правом"
142 |
143 | #: models.py:152 models.py:372 models.py:541
144 | #: templates/quiz/sitting_list.html:14
145 | msgid "Quiz"
146 | msgstr "Тест"
147 |
148 | #: models.py:153
149 | msgid "Quizzes"
150 | msgstr "Тесты"
151 |
152 | #: models.py:192 models.py:370 templates/quiz/sitting_detail.html:13
153 | #: templates/quiz/sitting_list.html:13
154 | msgid "User"
155 | msgstr "Пользователь"
156 |
157 | #: models.py:195 templates/progress.html:60
158 | #: templates/quiz/sitting_detail.html:15 templates/quiz/sitting_list.html:16
159 | msgid "Score"
160 | msgstr "Баллы"
161 |
162 | #: models.py:200
163 | msgid "User Progress"
164 | msgstr "Прогресс пользователя"
165 |
166 | #: models.py:201
167 | msgid "User progress records"
168 | msgstr "Прогресс пользователя"
169 |
170 | #: models.py:261
171 | msgid "error"
172 | msgstr "ошибка"
173 |
174 | #: models.py:261
175 | msgid "category does not exist or invalid score"
176 | msgstr "категории не существует или недопустимый балл"
177 |
178 | #: models.py:375
179 | msgid "Question Order"
180 | msgstr "Порядок вопросов"
181 |
182 | #: models.py:378
183 | msgid "Question List"
184 | msgstr "Список вопросов"
185 |
186 | #: models.py:381
187 | msgid "Incorrect questions"
188 | msgstr "Вопросы, на которые дан неверный ответ"
189 |
190 | #: models.py:383
191 | msgid "Current Score"
192 | msgstr "Текущий балл"
193 |
194 | #: models.py:386
195 | msgid "Complete"
196 | msgstr "Завершен"
197 |
198 | #: models.py:389
199 | msgid "User Answers"
200 | msgstr "Ответы пользователя"
201 |
202 | #: models.py:392
203 | msgid "Start"
204 | msgstr "Начало"
205 |
206 | #: models.py:394
207 | msgid "End"
208 | msgstr "Окончание"
209 |
210 | #: models.py:399
211 | msgid "Can see completed exams."
212 | msgstr "Может просматривать оконченные тесты"
213 |
214 | #: models.py:557
215 | msgid "Figure"
216 | msgstr "Рисунок"
217 |
218 | #: models.py:561
219 | msgid "Enter the question text that you want displayed"
220 | msgstr "Введите текст вопроса, который должен отобразиться"
221 |
222 | #: models.py:563 models.py:575 templates/question.html:47
223 | #: templates/quiz/sitting_detail.html:21
224 | msgid "Question"
225 | msgstr "Вопрос"
226 |
227 | #: models.py:567
228 | msgid "Explanation to be shown after the question has been answered."
229 | msgstr "Объяснение показывается после того, как дан ответ на вопрос"
230 |
231 | #: models.py:570 templates/question.html:32 templates/result.html:21
232 | #: templates/result.html.py:87
233 | msgid "Explanation"
234 | msgstr "Объяснение"
235 |
236 | #: templates/base.html:7
237 | msgid "Example Quiz Website"
238 | msgstr "Тесты"
239 |
240 | #: templates/correct_answer.html:6
241 | msgid "You answered the above question incorrectly"
242 | msgstr "Вы дали неверный ответ"
243 |
244 | #: templates/correct_answer.html:16
245 | msgid "This is the correct answer"
246 | msgstr "Это правильный ответ"
247 |
248 | #: templates/correct_answer.html:23
249 | msgid "This was your answer."
250 | msgstr "Это был ваш ответ"
251 |
252 | #: templates/progress.html:6
253 | msgid "Progress Page"
254 | msgstr "Страница прогесса"
255 |
256 | #: templates/progress.html:7
257 | msgid "User Progress Page"
258 | msgstr "Страница прогресса пользователя"
259 |
260 | #: templates/progress.html:13
261 | msgid "Question Category Scores"
262 | msgstr "Баллы по категориям вопросов"
263 |
264 | #: templates/progress.html:20
265 | msgid "Correctly answererd"
266 | msgstr "Верных ответов"
267 |
268 | #: templates/progress.html:21
269 | msgid "Incorrect"
270 | msgstr "Неверных ответов"
271 |
272 | #: templates/progress.html:50
273 | msgid "Previous exam papers"
274 | msgstr "Список предыдущих экзаменов"
275 |
276 | #: templates/progress.html:52
277 | msgid "Below are the results of exams that you have sat."
278 | msgstr "Ниже представлены результаты пройденных Вами тестов"
279 |
280 | #: templates/progress.html:59
281 | msgid "Quiz Title"
282 | msgstr "Название теста"
283 |
284 | #: templates/progress.html:61
285 | msgid "Possible Score"
286 | msgstr "Возможный балл"
287 |
288 | #: templates/question.html:13 templates/result.html:13
289 | msgid "The previous question"
290 | msgstr "Предыдущий вопрос"
291 |
292 | #: templates/question.html:22
293 | msgid "Your answer was"
294 | msgstr "Ваш ответ был"
295 |
296 | #: templates/question.html:47
297 | msgid "of"
298 | msgstr "из"
299 |
300 | #: templates/question.html:52
301 | msgid "Question category"
302 | msgstr "Категория вопроса"
303 |
304 | #: templates/question.html:74
305 | msgid "Check"
306 | msgstr "Ответить"
307 |
308 | #: templates/quiz/category_list.html:3 templates/quiz/quiz_list.html:3
309 | #: templates/quiz/sitting_list.html:3
310 | msgid "All Quizzes"
311 | msgstr "Все тесты"
312 |
313 | #: templates/quiz/category_list.html:6
314 | msgid "Category list"
315 | msgstr "Список категорий"
316 |
317 | #: templates/quiz/quiz_detail.html:11
318 | msgid "You will only get one attempt at this quiz"
319 | msgstr "У вас есть одна попытка для прохождения данного теста"
320 |
321 | #: templates/quiz/quiz_detail.html:16
322 | msgid "Start quiz"
323 | msgstr "Начать тест"
324 |
325 | #: templates/quiz/quiz_list.html:6
326 | msgid "List of quizzes"
327 | msgstr "Список тестов"
328 |
329 | #: templates/quiz/quiz_list.html:14
330 | msgid "Exam"
331 | msgstr "Тестирование"
332 |
333 | #: templates/quiz/quiz_list.html:15
334 | msgid "Single attempt"
335 | msgstr "Единственная попытка"
336 |
337 | #: templates/quiz/quiz_list.html:31 templates/quiz/sitting_list.html:42
338 | msgid "View details"
339 | msgstr "Подробнее"
340 |
341 | #: templates/quiz/quiz_list.html:41
342 | msgid "There are no available quizzes"
343 | msgstr "Доступных тестов нет"
344 |
345 | #: templates/quiz/sitting_detail.html:5
346 | msgid "Result of"
347 | msgstr "Результаты"
348 |
349 | #: templates/quiz/sitting_detail.html:5
350 | msgid "for"
351 | msgstr "для"
352 |
353 | #: templates/quiz/sitting_detail.html:9
354 | msgid "Quiz title"
355 | msgstr "Назвние теста"
356 |
357 | #: templates/quiz/sitting_detail.html:14 templates/quiz/sitting_list.html:15
358 | msgid "Completed"
359 | msgstr "Завершено"
360 |
361 | #: templates/quiz/sitting_detail.html:22
362 | msgid "User answer"
363 | msgstr "Ответ пользователя"
364 |
365 | #: templates/quiz/sitting_detail.html:41
366 | msgid "incorrect"
367 | msgstr "Неверно"
368 |
369 | #: templates/quiz/sitting_detail.html:43
370 | msgid "Correct"
371 | msgstr "Верно"
372 |
373 | #: templates/quiz/sitting_detail.html:49
374 | msgid "Toggle whether correct"
375 | msgstr "Изменить результат"
376 |
377 | #: templates/quiz/sitting_list.html:6
378 | msgid "List of complete exams"
379 | msgstr "Список завершенных тестов"
380 |
381 | #: templates/quiz/sitting_list.html:28
382 | msgid "Filter"
383 | msgstr "Фильтр"
384 |
385 | #: templates/quiz/sitting_list.html:52
386 | msgid "There are no matching quizzes"
387 | msgstr "Подходящих тестов нет"
388 |
389 | #: templates/result.html:7
390 | msgid "Exam Results for"
391 | msgstr "Результат теста для"
392 |
393 | #: templates/result.html:32
394 | msgid "Exam results"
395 | msgstr "Результаты тестирования"
396 |
397 | #: templates/result.html:34
398 | msgid "Exam title"
399 | msgstr "Название теста"
400 |
401 | #: templates/result.html:38
402 | msgid "You answered"
403 | msgstr "Ваш результат"
404 |
405 | #: templates/result.html:38
406 | msgid "questions correctly out of"
407 | msgstr "правильных ответов из"
408 |
409 | #: templates/result.html:38
410 | msgid "giving you"
411 | msgstr "вы дали"
412 |
413 | #: templates/result.html:38
414 | msgid "percent correct"
415 | msgstr "процент правильных ответов"
416 |
417 | #: templates/result.html:48
418 | msgid "Review the questions below and try the exam again in the future"
419 | msgstr ""
420 | "Просмотрите вопросы, представленные ниже и попробуйте пройти тест еще раз"
421 |
422 | #: templates/result.html:52
423 | msgid ""
424 | "The result of this exam will be stored in your progress section so you can "
425 | "review and monitor your progression"
426 | msgstr ""
427 | "Результаты данного экзамена будут сохранены. Вы сможете просматривать ваш "
428 | "прогресс"
429 |
430 | #: templates/result.html:66
431 | msgid "Your session score is"
432 | msgstr "Балл вашей сессии"
433 |
434 | #: templates/result.html:66
435 | msgid "out of a possible"
436 | msgstr "из возможных"
437 |
438 | #: templates/result.html:84
439 | msgid "Your answer"
440 | msgstr "Ваш ответ"
441 |
442 | #: templates/single_complete.html:13
443 | msgid "You have already sat this exam and only one sitting is permitted"
444 | msgstr "Вы уже прошли данный тест. Разрешена только одна попытка"
445 |
446 | #: templates/single_complete.html:15
447 | msgid "This exam is only accessible to signed in users"
448 | msgstr "Этот тест доступен только зарегистрированным пользователям"
449 |
450 | #: templates/view_quiz_category.html:3
451 | msgid "Quizzes related to"
452 | msgstr "Тесты относятся к"
453 |
454 | #: templates/view_quiz_category.html:6
455 | msgid "Quizzes in the"
456 | msgstr "Тесты в"
457 |
458 | #: templates/view_quiz_category.html:6
459 | msgid "category"
460 | msgstr "категория"
461 |
462 | #: templates/view_quiz_category.html:20
463 | msgid "There are no quizzes"
464 | msgstr "Тестов нет"
465 |
--------------------------------------------------------------------------------
/quiz/locale/es_CO/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: django-quiz\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2016-01-01 11:37+0400\n"
11 | "PO-Revision-Date: 2016-03-24 16:26-0500\n"
12 | "MIME-Version: 1.0\n"
13 | "Content-Type: text/plain; charset=UTF-8\n"
14 | "Content-Transfer-Encoding: 8bit\n"
15 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
16 | "X-Generator: Poedit 1.8.4\n"
17 | "Language-Team: \n"
18 | "Last-Translator: \n"
19 | "Language: es_CO\n"
20 |
21 | #: admin.py:31 admin.py:33 models.py:576
22 | msgid "Questions"
23 | msgstr "Preguntas"
24 |
25 | #: models.py:30 models.py:37 models.py:53 models.py:83 models.py:545
26 | #: templates/progress.html:19 templates/quiz/quiz_detail.html:9
27 | #: templates/quiz/quiz_list.html:13 templates/quiz/sitting_detail.html:10
28 | msgid "Category"
29 | msgstr "Categoría"
30 |
31 | #: models.py:38
32 | msgid "Categories"
33 | msgstr "Categorías"
34 |
35 | #: models.py:48 models.py:58 models.py:550
36 | msgid "Sub-Category"
37 | msgstr "Sub-Categoría"
38 |
39 | #: models.py:59
40 | msgid "Sub-Categories"
41 | msgstr "Sub-Categorías"
42 |
43 | #: models.py:69 templates/quiz/quiz_list.html:12
44 | msgid "Title"
45 | msgstr "Título"
46 |
47 | #: models.py:73
48 | msgid "Description"
49 | msgstr "Descripción"
50 |
51 | #: models.py:74
52 | msgid "a description of the quiz"
53 | msgstr "una descripción del quiz"
54 |
55 | #: models.py:78
56 | msgid "a user friendly url"
57 | msgstr "una url amigable para el usuario"
58 |
59 | #: models.py:79
60 | msgid "user friendly url"
61 | msgstr "url amigable para el usuario"
62 |
63 | #: models.py:87
64 | msgid "Random Order"
65 | msgstr "Orden Aleatorio"
66 |
67 | #: models.py:88
68 | msgid "Display the questions in a random order or as they are set?"
69 | msgstr "¿Muestra las preguntas en un orden aleatorio o como se crearon?"
70 |
71 | #: models.py:93
72 | msgid "Max Questions"
73 | msgstr "Número Máximo de Preguntas"
74 |
75 | #: models.py:94
76 | msgid "Number of questions to be answered on each attempt."
77 | msgstr "Número de preguntas a ser resueltas por cada intento"
78 |
79 | #: models.py:98
80 | msgid ""
81 | "Correct answer is NOT shown after question. Answers displayed at the end."
82 | msgstr ""
83 | "Respuestas correctas NO serán mostradas después de la pregunta. Las "
84 | "respuestas se mostrarán al final"
85 |
86 | #: models.py:100
87 | msgid "Answers at end"
88 | msgstr "Respuestas al final"
89 |
90 | #: models.py:104
91 | msgid ""
92 | "If yes, the result of each attempt by a user will be stored. Necessary for "
93 | "marking."
94 | msgstr ""
95 | "Si escoge si, el resultado de cada intento por usuario será guardado. "
96 | "Necesario para marcar"
97 |
98 | #: models.py:107
99 | msgid "Exam Paper"
100 | msgstr "Papel del Exámen"
101 |
102 | #: models.py:111
103 | msgid ""
104 | "If yes, only one attempt by a user will be permitted. Non users cannot sit "
105 | "this exam."
106 | msgstr ""
107 | "Si escoge si, solo un intento será permitido por usuario. Los que no sean "
108 | "usuarios no tomarán este exámen"
109 |
110 | #: models.py:114
111 | msgid "Single Attempt"
112 | msgstr "Un Intento"
113 |
114 | #: apps/quiz/models.py:116
115 | msgid "Pass Mark"
116 | msgstr "Mínimo exigido para aprobar"
117 |
118 | #: models.py:118
119 | msgid "Percentage required to pass exam."
120 | msgstr "Porcentaje requerido para aprobar el examen"
121 |
122 | #: models.py:122
123 | msgid "Displayed if user passes."
124 | msgstr "Mostrar si el usuario pasa"
125 |
126 | #: models.py:123
127 | msgid "Success Text"
128 | msgstr "Texto de Éxito"
129 |
130 | #: models.py:126
131 | msgid "Fail Text"
132 | msgstr "Texto de Fracaso"
133 |
134 | #: models.py:127
135 | msgid "Displayed if user fails."
136 | msgstr "Mostrar si el usuario fracasa"
137 |
138 | #: models.py:131
139 | msgid "Draft"
140 | msgstr "Borrador"
141 |
142 | #: models.py:132
143 | msgid ""
144 | "If yes, the quiz is not displayed in the quiz list and can only be taken by "
145 | "users who can edit quizzes."
146 | msgstr ""
147 | "Si escoge si, este quiz no será mostrado en la lista de quizes y solo podrá "
148 | "ser tomado por usuarios que puedan editar quizes"
149 |
150 | #: models.py:152 models.py:372 models.py:541
151 | #: templates/quiz/sitting_list.html:14
152 | msgid "Quiz"
153 | msgstr "Quiz"
154 |
155 | #: models.py:153
156 | msgid "Quizzes"
157 | msgstr "Quizes"
158 |
159 | #: models.py:192 models.py:370 templates/quiz/sitting_detail.html:13
160 | #: templates/quiz/sitting_list.html:13
161 | msgid "User"
162 | msgstr "Usuario"
163 |
164 | #: models.py:195 templates/progress.html:60
165 | #: templates/quiz/sitting_detail.html:15 templates/quiz/sitting_list.html:16
166 | msgid "Score"
167 | msgstr "Puntaje"
168 |
169 | #: models.py:200
170 | msgid "User Progress"
171 | msgstr "Progreso de Usuario"
172 |
173 | #: models.py:201
174 | msgid "User progress records"
175 | msgstr "Registros de progreso del usuario"
176 |
177 | #: models.py:261
178 | msgid "error"
179 | msgstr "error"
180 |
181 | #: models.py:261
182 | msgid "category does not exist or invalid score"
183 | msgstr "categoría no existe o puntaje inválido"
184 |
185 | #: models.py:375
186 | msgid "Question Order"
187 | msgstr "Orden de las Preguntas"
188 |
189 | #: models.py:378
190 | msgid "Question List"
191 | msgstr "Lista de Preguntas"
192 |
193 | #: models.py:381
194 | msgid "Incorrect questions"
195 | msgstr "Preguntas Incorrectas"
196 |
197 | #: models.py:383
198 | msgid "Current Score"
199 | msgstr "Puntaje Actual"
200 |
201 | #: models.py:386
202 | msgid "Complete"
203 | msgstr "Completo"
204 |
205 | #: models.py:389
206 | msgid "User Answers"
207 | msgstr "Respuestas de los Usuarios"
208 |
209 | #: models.py:392
210 | msgid "Start"
211 | msgstr "Inicio"
212 |
213 | #: models.py:394
214 | msgid "End"
215 | msgstr "Fin"
216 |
217 | #: models.py:399
218 | msgid "Can see completed exams."
219 | msgstr "Puede ver exámenes completados"
220 |
221 | #: models.py:557
222 | msgid "Figure"
223 | msgstr "Figura"
224 |
225 | #: models.py:561
226 | msgid "Enter the question text that you want displayed"
227 | msgstr "Ingresa el texto de la pregunta que quiere que aparezca"
228 |
229 | #: models.py:563 models.py:575 templates/question.html:47
230 | #: templates/quiz/sitting_detail.html:21
231 | msgid "Question"
232 | msgstr "Pregunta"
233 |
234 | #: models.py:567
235 | msgid "Explanation to be shown after the question has been answered."
236 | msgstr ""
237 | "Explicación que debe ser mostrada después de que la pregunta fue respondida"
238 |
239 | #: models.py:570 templates/question.html:32 templates/result.html:21
240 | #: templates/result.html.py:87
241 | msgid "Explanation"
242 | msgstr "Explicación"
243 |
244 | #: templates/base.html:7
245 | msgid "Example Quiz Website"
246 | msgstr "Sitio Web Ejemplo Quiz"
247 |
248 | #: templates/correct_answer.html:6
249 | msgid "You answered the above question incorrectly"
250 | msgstr "Ha respondido a la pregunta anterior de forma incorrecta"
251 |
252 | #: templates/correct_answer.html:16
253 | msgid "This is the correct answer"
254 | msgstr "Esta es la respuesta correcta"
255 |
256 | #: templates/correct_answer.html:23
257 | msgid "This was your answer."
258 | msgstr "Esta fue tu respuesta"
259 |
260 | #: templates/progress.html:6
261 | msgid "Progress Page"
262 | msgstr "Página de Progreso"
263 |
264 | #: templates/progress.html:7
265 | msgid "User Progress Page"
266 | msgstr "Página Progreso de Usuario"
267 |
268 | #: templates/progress.html:13
269 | msgid "Question Category Scores"
270 | msgstr "Pregunta Puntajes de las Categorías"
271 |
272 | #: templates/progress.html:20
273 | msgid "Correctly answererd"
274 | msgstr "Correctamente contestada"
275 |
276 | #: templates/progress.html:21
277 | msgid "Incorrect"
278 | msgstr "Incorrecta"
279 |
280 | #: templates/progress.html:50
281 | msgid "Previous exam papers"
282 | msgstr "Exámenes anteriores"
283 |
284 | #: templates/progress.html:52
285 | msgid "Below are the results of exams that you have sat."
286 | msgstr "Abajo están los resultados de los exámenes que se ha realizado."
287 |
288 | #: templates/progress.html:59
289 | msgid "Quiz Title"
290 | msgstr "Título del Quiz"
291 |
292 | #: templates/progress.html:61
293 | msgid "Possible Score"
294 | msgstr "Puntos posibles"
295 |
296 | #: templates/question.html:13 templates/result.html:13
297 | msgid "The previous question"
298 | msgstr "La pregunta anterior"
299 |
300 | #: templates/question.html:22
301 | msgid "Your answer was"
302 | msgstr "Tu respuesta fue"
303 |
304 | #: templates/question.html:47
305 | msgid "of"
306 | msgstr "de"
307 |
308 | #: templates/question.html:52
309 | msgid "Question category"
310 | msgstr "Categoría de la pregunta"
311 |
312 | #: templates/question.html:74
313 | msgid "Check"
314 | msgstr "Verificar"
315 |
316 | #: templates/quiz/category_list.html:3 templates/quiz/quiz_list.html:3
317 | #: templates/quiz/sitting_list.html:3
318 | msgid "All Quizzes"
319 | msgstr "Todos los Quizes"
320 |
321 | #: templates/quiz/category_list.html:6
322 | msgid "Category list"
323 | msgstr "Lista de Categorías"
324 |
325 | #: templates/quiz/quiz_detail.html:11
326 | msgid "You will only get one attempt at this quiz"
327 | msgstr "Solo se tendrá un intento para este quiz"
328 |
329 | #: templates/quiz/quiz_detail.html:16
330 | msgid "Start quiz"
331 | msgstr "Empezar quiz"
332 |
333 | #: templates/quiz/quiz_list.html:6
334 | msgid "List of quizzes"
335 | msgstr "Lista de quizes"
336 |
337 | #: templates/quiz/quiz_list.html:14
338 | msgid "Exam"
339 | msgstr "Examen"
340 |
341 | #: templates/quiz/quiz_list.html:15
342 | msgid "Single attempt"
343 | msgstr "Un solo intento"
344 |
345 | #: templates/quiz/quiz_list.html:31 templates/quiz/sitting_list.html:42
346 | msgid "View details"
347 | msgstr "Ver detalles"
348 |
349 | #: templates/quiz/quiz_list.html:41
350 | msgid "There are no available quizzes"
351 | msgstr "No hay quizes disponibles"
352 |
353 | #: templates/quiz/sitting_detail.html:5
354 | msgid "Result of"
355 | msgstr "Resultado de"
356 |
357 | #: templates/quiz/sitting_detail.html:5
358 | msgid "for"
359 | msgstr "para"
360 |
361 | #: templates/quiz/sitting_detail.html:9
362 | msgid "Quiz title"
363 | msgstr "Título de Quiz"
364 |
365 | #: templates/quiz/sitting_detail.html:14 templates/quiz/sitting_list.html:15
366 | msgid "Completed"
367 | msgstr "Completado"
368 |
369 | #: templates/quiz/sitting_detail.html:22
370 | msgid "User answer"
371 | msgstr "Respuesta de usuario"
372 |
373 | #: templates/quiz/sitting_detail.html:41
374 | msgid "incorrect"
375 | msgstr "Incorrecta"
376 |
377 | #: templates/quiz/sitting_detail.html:43
378 | msgid "Correct"
379 | msgstr "Correcto"
380 |
381 | #: templates/quiz/sitting_detail.html:49
382 | msgid "Toggle whether correct"
383 | msgstr "Alternar si es correcta"
384 |
385 | #: templates/quiz/sitting_list.html:6
386 | msgid "List of complete exams"
387 | msgstr "Lista de examenes completos"
388 |
389 | #: templates/quiz/sitting_list.html:28
390 | msgid "Filter"
391 | msgstr "Filtro"
392 |
393 | #: templates/quiz/sitting_list.html:52
394 | msgid "There are no matching quizzes"
395 | msgstr "No hay quizes que coincidan"
396 |
397 | #: templates/result.html:7
398 | msgid "Exam Results for"
399 | msgstr "Resultados del exámen de"
400 |
401 | #: templates/result.html:32
402 | msgid "Exam results"
403 | msgstr "Resultados del exámen"
404 |
405 | #: templates/result.html:34
406 | msgid "Exam title"
407 | msgstr "Título del exámen"
408 |
409 | #: templates/result.html:38
410 | msgid "You answered"
411 | msgstr "Tu respondiste"
412 |
413 | #: templates/result.html:38
414 | msgid "questions correctly out of"
415 | msgstr "preguntas correctas de cada"
416 |
417 | #: templates/result.html:38
418 | msgid "giving you"
419 | msgstr "darle"
420 |
421 | #: templates/result.html:38
422 | msgid "percent correct"
423 | msgstr "porcentaje de respuestas correctas"
424 |
425 | #: templates/result.html:48
426 | msgid "Review the questions below and try the exam again in the future"
427 | msgstr ""
428 | "Revisa las preguntas a continuación e intente el examen de nuevo en el "
429 | "futuro"
430 |
431 | #: templates/result.html:52
432 | msgid ""
433 | "The result of this exam will be stored in your progress section so you can "
434 | "review and monitor your progression"
435 | msgstr ""
436 | "El resultado de este examen se almacenará en su sección de progreso para "
437 | "que pueda revisar y supervisar su progreso"
438 |
439 | #: templates/result.html:66
440 | msgid "Your session score is"
441 | msgstr "Su puntuación de sesión es"
442 |
443 | #: templates/result.html:66
444 | msgid "out of a possible"
445 | msgstr "de un total posible"
446 |
447 | #: templates/result.html:84
448 | msgid "Your answer"
449 | msgstr "Tu respuesta"
450 |
451 | #: templates/single_complete.html:13
452 | msgid "You have already sat this exam and only one sitting is permitted"
453 | msgstr "Usted ya ha enviado este examen, y sólo se permite una sola sesión"
454 |
455 | #: templates/single_complete.html:15
456 | msgid "This exam is only accessible to signed in users"
457 | msgstr "Este examen es sólo accesible para los usuarios inscritos"
458 |
459 | #: templates/view_quiz_category.html:3
460 | msgid "Quizzes related to"
461 | msgstr "Quizes relacionados con"
462 |
463 | #: templates/view_quiz_category.html:6
464 | msgid "Quizzes in the"
465 | msgstr "Quizes en el"
466 |
467 | #: templates/view_quiz_category.html:6
468 | msgid "category"
469 | msgstr "categoría"
470 |
471 | #: templates/view_quiz_category.html:20
472 | msgid "There are no quizzes"
473 | msgstr "No hay quizes"
474 |
--------------------------------------------------------------------------------
/quiz/locale/de/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: \n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2015-10-31 22:44+0000\n"
11 | "PO-Revision-Date: 2019-03-07 21:14+0100\n"
12 | "Last-Translator: Reiner Mayers\n"
13 | "Language: de\n"
14 | "MIME-Version: 1.0\n"
15 | "Content-Type: text/plain; charset=UTF-8\n"
16 | "Content-Transfer-Encoding: 8bit\n"
17 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
18 | "X-Translated-Using: django-rosetta 0.7.6\n"
19 | "Language-Team: \n"
20 | "X-Generator: Poedit 2.0.6\n"
21 |
22 | #: apps/quiz/models.py:30 apps/quiz/models.py:37 apps/quiz/models.py:53
23 | #: apps/quiz/models.py:83 apps/quiz/models.py:545
24 | #: apps/quiz/templates/progress.html:19
25 | #: apps/quiz/templates/quiz/quiz_detail.html:9
26 | #: apps/quiz/templates/quiz/quiz_list.html:13
27 | #: apps/quiz/templates/quiz/sitting_detail.html:10
28 | msgid "Category"
29 | msgstr "Kategorie"
30 |
31 | #: apps/quiz/models.py:38
32 | msgid "Categories"
33 | msgstr "Kategorien"
34 |
35 | #: apps/quiz/models.py:48 apps/quiz/models.py:58 apps/quiz/models.py:550
36 | msgid "Sub-Category"
37 | msgstr "Unterkategorie"
38 |
39 | #: apps/quiz/models.py:59
40 | msgid "Sub-Categories"
41 | msgstr "Unterkategorien"
42 |
43 | #: apps/quiz/models.py:69 apps/quiz/templates/quiz/quiz_list.html:12
44 | msgid "Title"
45 | msgstr "Titel"
46 |
47 | #: apps/quiz/models.py:73
48 | msgid "Description"
49 | msgstr "Beschreibung"
50 |
51 | #: apps/quiz/models.py:74
52 | msgid "a description of the quiz"
53 | msgstr "Eine Beschreibung des Quiz"
54 |
55 | #: apps/quiz/models.py:78
56 | msgid "a user friendly url"
57 | msgstr "Eine benutzerfreundliche url"
58 |
59 | #: apps/quiz/models.py:79
60 | msgid "user friendly url"
61 | msgstr "benutzerfreundliche url"
62 |
63 | #: apps/quiz/models.py:87
64 | msgid "Random Order"
65 | msgstr "Zufällige Reihenfolge"
66 |
67 | #: apps/quiz/models.py:88
68 | msgid "Display the questions in a random order or as they are set?"
69 | msgstr "Fragen zufällig anzeigen oder in der Eingabereihenfolge?"
70 |
71 | #: apps/quiz/models.py:93
72 | msgid "Max Questions"
73 | msgstr "Maximale Anzahl der Fragen"
74 |
75 | #: apps/quiz/models.py:94
76 | msgid "Number of questions to be answered on each attempt."
77 | msgstr "Anzahl der Fragen die bei jedem Versuch gestellt werden."
78 |
79 | #: apps/quiz/models.py:98
80 | msgid ""
81 | "Correct answer is NOT shown after question. Answers displayed at the end."
82 | msgstr ""
83 | "Die Richtige Antwort wird nicht nach der Frage angezeigt sondern am Ende."
84 |
85 | #: apps/quiz/models.py:100
86 | msgid "Answers at end"
87 | msgstr "Antworten am Ende"
88 |
89 | #: apps/quiz/models.py:104
90 | msgid ""
91 | "If yes, the result of each attempt by a user will be stored. Necessary for "
92 | "marking."
93 | msgstr ""
94 | "Wenn ja, wird das Ergebnis jeden Versuchs gespeichert. Zum markieren ist die "
95 | "notwendig."
96 |
97 | #: apps/quiz/models.py:107
98 | msgid "Exam Paper"
99 | msgstr "Prüfungsarbeit"
100 |
101 | #: apps/quiz/models.py:111
102 | msgid ""
103 | "If yes, only one attempt by a user will be permitted. Non users cannot sit "
104 | "this exam."
105 | msgstr ""
106 | "Wenn ja wird nur ein Versuch pro Anwender zugelassen. Nur angemeldete "
107 | "Anwender können diese Prüfung machen."
108 |
109 | #: apps/quiz/models.py:114
110 | msgid "Single Attempt"
111 | msgstr "Nur ein Versuch"
112 |
113 | #: apps/quiz/models.py:118
114 | msgid "Percentage required to pass exam."
115 | msgstr "Prozent zum bestehen der Prüfung."
116 |
117 | #: apps/quiz/models.py:122
118 | msgid "Displayed if user passes."
119 | msgstr "Angezeigt wenn der Anwender besteht."
120 |
121 | #: apps/quiz/models.py:123
122 | msgid "Success Text"
123 | msgstr "Text für den Erfolg"
124 |
125 | #: apps/quiz/models.py:126
126 | msgid "Fail Text"
127 | msgstr "Text für den Misserfolg"
128 |
129 | #: apps/quiz/models.py:127
130 | msgid "Displayed if user fails."
131 | msgstr "Wird angezeigt wenn der Anwender durchfällt."
132 |
133 | #: apps/quiz/models.py:131
134 | msgid "Draft"
135 | msgstr "Entwurf"
136 |
137 | #: apps/quiz/models.py:132
138 | msgid ""
139 | "If yes, the quiz is not displayed in the quiz list and can only be taken by "
140 | "users who can edit quizzes."
141 | msgstr ""
142 | "Wenn ja wird die Prüfung nicht in der Prüfungsliste angezeigt und kann nur "
143 | "von Anwendern ausgeführt werden welche Prüfungen bearbeiten können."
144 |
145 | #: apps/quiz/models.py:152 apps/quiz/models.py:372 apps/quiz/models.py:541
146 | #: apps/quiz/templates/quiz/sitting_list.html:14
147 | msgid "Quiz"
148 | msgstr "Quiz"
149 |
150 | #: apps/quiz/models.py:153
151 | msgid "Quizzes"
152 | msgstr "Quizze"
153 |
154 | #: apps/quiz/models.py:192 apps/quiz/models.py:370
155 | #: apps/quiz/templates/quiz/sitting_detail.html:13
156 | #: apps/quiz/templates/quiz/sitting_list.html:13
157 | msgid "User"
158 | msgstr "Anwender"
159 |
160 | #: apps/quiz/models.py:195 apps/quiz/templates/progress.html:60
161 | #: apps/quiz/templates/quiz/sitting_detail.html:15
162 | #: apps/quiz/templates/quiz/sitting_list.html:16
163 | msgid "Score"
164 | msgstr "Punkte"
165 |
166 | #: apps/quiz/models.py:200
167 | msgid "User Progress"
168 | msgstr "Anwender Fortschritt"
169 |
170 | #: apps/quiz/models.py:201
171 | msgid "User progress records"
172 | msgstr "Fortschrittsaufzeichnung des Anwenders"
173 |
174 | #: apps/quiz/models.py:261
175 | msgid "error"
176 | msgstr "Fehler"
177 |
178 | #: apps/quiz/models.py:261
179 | msgid "category does not exist or invalid score"
180 | msgstr "Diese Kategorie existiert nicht oder das Ergebnis ist ungültig"
181 |
182 | #: apps/quiz/models.py:375
183 | msgid "Question Order"
184 | msgstr "Reihenfolge der Fragen"
185 |
186 | #: apps/quiz/models.py:378
187 | msgid "Question List"
188 | msgstr "Liste der Fragen"
189 |
190 | #: apps/quiz/models.py:381
191 | msgid "Incorrect questions"
192 | msgstr "Falsche Antworten"
193 |
194 | #: apps/quiz/models.py:383
195 | msgid "Current Score"
196 | msgstr "Aktueller Punktestand"
197 |
198 | #: apps/quiz/models.py:386
199 | msgid "Complete"
200 | msgstr "Vollständig"
201 |
202 | #: apps/quiz/models.py:389
203 | msgid "User Answers"
204 | msgstr "Antworten des Anwenders"
205 |
206 | #: apps/quiz/models.py:392
207 | msgid "Start"
208 | msgstr "Anfang"
209 |
210 | #: apps/quiz/models.py:394
211 | msgid "End"
212 | msgstr "Ende"
213 |
214 | #: apps/quiz/models.py:399
215 | msgid "Can see completed exams."
216 | msgstr "Kann abgelegte Prüfungen sehen."
217 |
218 | #: apps/quiz/models.py:557
219 | msgid "Figure"
220 | msgstr "Abbildung"
221 |
222 | #: apps/quiz/models.py:561
223 | msgid "Enter the question text that you want displayed"
224 | msgstr "Geben Sie die Frage ein welche angezeigt werden soll"
225 |
226 | #: apps/quiz/models.py:563 apps/quiz/models.py:575
227 | #: apps/quiz/templates/question.html:47
228 | #: apps/quiz/templates/quiz/sitting_detail.html:21
229 | msgid "Question"
230 | msgstr "Frage"
231 |
232 | #: apps/quiz/models.py:567
233 | msgid "Explanation to be shown after the question has been answered."
234 | msgstr "Erklärung welche nach der Antwort angezeigt wird."
235 |
236 | #: apps/quiz/models.py:570 apps/quiz/templates/question.html:32
237 | #: apps/quiz/templates/result.html:21 apps/quiz/templates/result.html.py:87
238 | msgid "Explanation"
239 | msgstr "Erklärung"
240 |
241 | #: apps/quiz/models.py:576
242 | msgid "Questions"
243 | msgstr "Fragen"
244 |
245 | #: apps/quiz/templates/base.html:7
246 | msgid "Example Quiz Website"
247 | msgstr "Beispiel Quiz Webseite"
248 |
249 | #: apps/quiz/templates/correct_answer.html:6
250 | msgid "You answered the above question incorrectly"
251 | msgstr "Sie haben die obenstehende Frage falsch beantwortet"
252 |
253 | #: apps/quiz/templates/correct_answer.html:16
254 | msgid "This is the correct answer"
255 | msgstr "Dies ist die richtige Antwort"
256 |
257 | #: apps/quiz/templates/correct_answer.html:23
258 | msgid "This was your answer."
259 | msgstr "Dies war Ihre Antwort."
260 |
261 | #: apps/quiz/templates/progress.html:6
262 | msgid "Progress Page"
263 | msgstr "Fortschrittsseite"
264 |
265 | #: apps/quiz/templates/progress.html:7
266 | msgid "User Progress Page"
267 | msgstr "Fortschrittsseite des Anwenders"
268 |
269 | #: apps/quiz/templates/progress.html:13
270 | msgid "Question Category Scores"
271 | msgstr "Punkte in dieser Fragenkategorie"
272 |
273 | #: apps/quiz/templates/progress.html:20
274 | msgid "Correctly answererd"
275 | msgstr "Die Antwort ist richtig"
276 |
277 | #: apps/quiz/templates/progress.html:21
278 | msgid "Incorrect"
279 | msgstr "Falsch"
280 |
281 | #: apps/quiz/templates/progress.html:50
282 | msgid "Previous exam papers"
283 | msgstr "Vorhergehende Prüfung"
284 |
285 | #: apps/quiz/templates/progress.html:52
286 | msgid "Below are the results of exams that you have sat."
287 | msgstr "Im Folgenden sehen Sie die Ergebnisse Ihrer Prüfungen."
288 |
289 | #: apps/quiz/templates/progress.html:59
290 | msgid "Quiz Title"
291 | msgstr "Titel des Quiz"
292 |
293 | #: apps/quiz/templates/progress.html:61
294 | msgid "Possible Score"
295 | msgstr "Mögliche Punkte"
296 |
297 | #: apps/quiz/templates/question.html:13 apps/quiz/templates/result.html:13
298 | msgid "The previous question"
299 | msgstr "Die vorherige Frage"
300 |
301 | #: apps/quiz/templates/question.html:22
302 | msgid "Your answer was"
303 | msgstr "Ihre Antwort war"
304 |
305 | #: apps/quiz/templates/question.html:47
306 | msgid "of"
307 | msgstr "von"
308 |
309 | #: apps/quiz/templates/question.html:52
310 | msgid "Question category"
311 | msgstr "Fragenkategorie"
312 |
313 | #: apps/quiz/templates/question.html:74
314 | msgid "Check"
315 | msgstr "Überprüfung"
316 |
317 | #: apps/quiz/templates/quiz/category_list.html:3
318 | #: apps/quiz/templates/quiz/quiz_list.html:3
319 | #: apps/quiz/templates/quiz/sitting_list.html:3
320 | msgid "All Quizzes"
321 | msgstr "Alle Quizze"
322 |
323 | #: apps/quiz/templates/quiz/category_list.html:6
324 | msgid "Category list"
325 | msgstr "Liste der Kategorien"
326 |
327 | #: apps/quiz/templates/quiz/quiz_detail.html:11
328 | msgid "You will only get one attempt at this quiz"
329 | msgstr "Sie haben nur einen Versuch bei dieser Prüfung"
330 |
331 | #: apps/quiz/templates/quiz/quiz_detail.html:16
332 | msgid "Start quiz"
333 | msgstr "Quiz beginnen"
334 |
335 | #: apps/quiz/templates/quiz/quiz_list.html:6
336 | msgid "List of quizzes"
337 | msgstr "Liste der Quizze"
338 |
339 | #: apps/quiz/templates/quiz/quiz_list.html:14
340 | msgid "Exam"
341 | msgstr "Prüfung"
342 |
343 | #: apps/quiz/templates/quiz/quiz_list.html:15
344 | msgid "Single attempt"
345 | msgstr "Einzelversuch"
346 |
347 | #: apps/quiz/templates/quiz/quiz_list.html:31
348 | #: apps/quiz/templates/quiz/sitting_list.html:42
349 | msgid "View details"
350 | msgstr "Details ansehen"
351 |
352 | #: apps/quiz/templates/quiz/quiz_list.html:41
353 | msgid "There are no available quizzes"
354 | msgstr "Es sind keine Prüfungen verfügbar"
355 |
356 | #: apps/quiz/templates/quiz/sitting_detail.html:5
357 | msgid "Result of"
358 | msgstr "Resultat von"
359 |
360 | #: apps/quiz/templates/quiz/sitting_detail.html:5
361 | msgid "for"
362 | msgstr "für"
363 |
364 | #: apps/quiz/templates/quiz/sitting_detail.html:9
365 | msgid "Quiz title"
366 | msgstr "Quiztitel"
367 |
368 | #: apps/quiz/templates/quiz/sitting_detail.html:14
369 | #: apps/quiz/templates/quiz/sitting_list.html:15
370 | msgid "Completed"
371 | msgstr "Vollständig"
372 |
373 | #: apps/quiz/templates/quiz/sitting_detail.html:22
374 | msgid "User answer"
375 | msgstr "Anwender Antwort"
376 |
377 | #: apps/quiz/templates/quiz/sitting_detail.html:41
378 | msgid "incorrect"
379 | msgstr "falsch"
380 |
381 | #: apps/quiz/templates/quiz/sitting_detail.html:43
382 | msgid "Correct"
383 | msgstr "Richtig"
384 |
385 | #: apps/quiz/templates/quiz/sitting_detail.html:49
386 | msgid "Toggle whether correct"
387 | msgstr "Markieren wenn richtig"
388 |
389 | #: apps/quiz/templates/quiz/sitting_list.html:6
390 | msgid "List of complete exams"
391 | msgstr "Liste der abgelegten Prüfungen"
392 |
393 | #: apps/quiz/templates/quiz/sitting_list.html:28
394 | msgid "Filter"
395 | msgstr "Filter"
396 |
397 | #: apps/quiz/templates/quiz/sitting_list.html:52
398 | msgid "There are no matching quizzes"
399 | msgstr "Es gibt keine übereinstimmenden Prüfungen"
400 |
401 | #: apps/quiz/templates/result.html:7
402 | msgid "Exam Results for"
403 | msgstr "Prüfungsergebnisse für"
404 |
405 | #: apps/quiz/templates/result.html:32
406 | msgid "Exam results"
407 | msgstr "Prüfungsergebnisse"
408 |
409 | #: apps/quiz/templates/result.html:34
410 | msgid "Exam title"
411 | msgstr "Prüfungstitel"
412 |
413 | #: apps/quiz/templates/result.html:38
414 | msgid "You answered"
415 | msgstr "Ihre Antwort"
416 |
417 | #: apps/quiz/templates/result.html:38
418 | msgid "questions correctly out of"
419 | msgstr "Fragen richtig von "
420 |
421 | #: apps/quiz/templates/result.html:38
422 | msgid "giving you"
423 | msgstr "Sie erhalten"
424 |
425 | #: apps/quiz/templates/result.html:38
426 | msgid "percent correct"
427 | msgstr "Prozent korrekt"
428 |
429 | #: apps/quiz/templates/result.html:48
430 | msgid "Review the questions below and try the exam again in the future"
431 | msgstr "Sehen Sie sich die folgenden Fragen an und wiederholen Sie die Prüfung"
432 |
433 | #: apps/quiz/templates/result.html:52
434 | msgid ""
435 | "The result of this exam will be stored in your progress section so you can "
436 | "review and monitor your progression"
437 | msgstr ""
438 | "Das Resultat dieser Prüfung wird gespeichert und Sie können sich Ihren "
439 | "Fortschritt ansehen"
440 |
441 | #: apps/quiz/templates/result.html:66
442 | msgid "Your session score is"
443 | msgstr "Der Punktestand dieser Sitzung ist"
444 |
445 | #: apps/quiz/templates/result.html:66
446 | msgid "out of a possible"
447 | msgstr "von möglichen"
448 |
449 | #: apps/quiz/templates/result.html:84
450 | msgid "Your answer"
451 | msgstr "Ihre Antwort"
452 |
453 | #: apps/quiz/templates/single_complete.html:13
454 | msgid "You have already sat this exam and only one sitting is permitted"
455 | msgstr ""
456 | "Sie haben diese Prüfung schon abgelegt und dies kann nur einmal erfolgen"
457 |
458 | #: apps/quiz/templates/single_complete.html:15
459 | msgid "This exam is only accessible to signed in users"
460 | msgstr "Diese Prüfung ist angemeldeten Anwendern vorbehalten"
461 |
462 | #: apps/quiz/templates/view_quiz_category.html:3
463 | msgid "Quizzes related to"
464 | msgstr "Quizze bezogen auf"
465 |
466 | #: apps/quiz/templates/view_quiz_category.html:6
467 | msgid "Quizzes in the"
468 | msgstr "Quizze in der"
469 |
470 | #: apps/quiz/templates/view_quiz_category.html:6
471 | msgid "category"
472 | msgstr "Kategorie"
473 |
474 | #: apps/quiz/templates/view_quiz_category.html:20
475 | msgid "There are no quizzes"
476 | msgstr "Es gibt keine Quizze"
477 |
--------------------------------------------------------------------------------
/quiz/locale/it/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2015-10-31 22:44+0000\n"
12 | "PO-Revision-Date: 2015-10-31 22:43+0000\n"
13 | "Last-Translator: b' <>'\n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20 | "X-Translated-Using: django-rosetta 0.7.6\n"
21 |
22 | #: apps/quiz/models.py:30 apps/quiz/models.py:37 apps/quiz/models.py:53
23 | #: apps/quiz/models.py:83 apps/quiz/models.py:545
24 | #: apps/quiz/templates/progress.html:19
25 | #: apps/quiz/templates/quiz/quiz_detail.html:9
26 | #: apps/quiz/templates/quiz/quiz_list.html:13
27 | #: apps/quiz/templates/quiz/sitting_detail.html:10
28 | msgid "Category"
29 | msgstr "Categoria"
30 |
31 | #: apps/quiz/models.py:38
32 | msgid "Categories"
33 | msgstr "Categorie"
34 |
35 | #: apps/quiz/models.py:48 apps/quiz/models.py:58 apps/quiz/models.py:550
36 | msgid "Sub-Category"
37 | msgstr "Sotto-Categoria"
38 |
39 | #: apps/quiz/models.py:59
40 | msgid "Sub-Categories"
41 | msgstr "Sotto-categorie"
42 |
43 | #: apps/quiz/models.py:69 apps/quiz/templates/quiz/quiz_list.html:12
44 | msgid "Title"
45 | msgstr "Titolo"
46 |
47 | #: apps/quiz/models.py:73
48 | msgid "Description"
49 | msgstr "Descrizione"
50 |
51 | #: apps/quiz/models.py:74
52 | msgid "a description of the quiz"
53 | msgstr "una descrizione del quiz"
54 |
55 | #: apps/quiz/models.py:78
56 | msgid "a user friendly url"
57 | msgstr "un url amichevole"
58 |
59 | #: apps/quiz/models.py:79
60 | msgid "user friendly url"
61 | msgstr "url amichevole"
62 |
63 | #: apps/quiz/models.py:87
64 | msgid "Random Order"
65 | msgstr "Ordine Casuale"
66 |
67 | #: apps/quiz/models.py:88
68 | msgid "Display the questions in a random order or as they are set?"
69 | msgstr ""
70 | "Visualizzare le domande in un ordine casuale o nell'ordine in cui sono state "
71 | "inserite?"
72 |
73 | #: apps/quiz/models.py:93
74 | msgid "Max Questions"
75 | msgstr "Numero massimo di domande"
76 |
77 | #: apps/quiz/models.py:94
78 | msgid "Number of questions to be answered on each attempt."
79 | msgstr "Numero di domande cui rispondere in ogni tentativo."
80 |
81 | #: apps/quiz/models.py:98
82 | msgid ""
83 | "Correct answer is NOT shown after question. Answers displayed at the end."
84 | msgstr ""
85 | "La risposta corretta NON è visualizzata dopo la domanda. Le risposte vengono "
86 | "visualizzate alla fine."
87 |
88 | #: apps/quiz/models.py:100
89 | msgid "Answers at end"
90 | msgstr "Risposte alla fine"
91 |
92 | #: apps/quiz/models.py:104
93 | msgid ""
94 | "If yes, the result of each attempt by a user will be stored. Necessary for "
95 | "marking."
96 | msgstr ""
97 | "Se si, il risultato di ogni tentativo da parte di un utente verrà salvato. "
98 | "Necessario per segnare."
99 |
100 | #: apps/quiz/models.py:107
101 | msgid "Exam Paper"
102 | msgstr "Foglio dell'esame"
103 |
104 | #: apps/quiz/models.py:111
105 | msgid ""
106 | "If yes, only one attempt by a user will be permitted. Non users cannot sit "
107 | "this exam."
108 | msgstr ""
109 | "Se si, per ogni utente sarà permesso un solo tentativo. I non tutenti non "
110 | "potranno sostenere l'esame."
111 |
112 | #: apps/quiz/models.py:114
113 | msgid "Single Attempt"
114 | msgstr "Songolo tentativo"
115 |
116 | #: apps/quiz/models.py:118
117 | msgid "Percentage required to pass exam."
118 | msgstr "Percentuale richiesta per superare l'esame."
119 |
120 | #: apps/quiz/models.py:122
121 | msgid "Displayed if user passes."
122 | msgstr "Visualizzato se l'utente passa."
123 |
124 | #: apps/quiz/models.py:123
125 | msgid "Success Text"
126 | msgstr "Testo Risposta Corretta"
127 |
128 | #: apps/quiz/models.py:126
129 | msgid "Fail Text"
130 | msgstr "Test risposta Sbagliata"
131 |
132 | #: apps/quiz/models.py:127
133 | msgid "Displayed if user fails."
134 | msgstr "Visualizzato se l'utente fallisce."
135 |
136 | #: apps/quiz/models.py:131
137 | msgid "Draft"
138 | msgstr "Bozza"
139 |
140 | #: apps/quiz/models.py:132
141 | msgid ""
142 | "If yes, the quiz is not displayed in the quiz list and can only be taken by "
143 | "users who can edit quizzes."
144 | msgstr ""
145 | "Se sì, il quiz non viene visualizzato nell'elenco quiz e può essere "
146 | "selezionato solo dagli utenti che possono modificare i quiz."
147 |
148 | #: apps/quiz/models.py:152 apps/quiz/models.py:372 apps/quiz/models.py:541
149 | #: apps/quiz/templates/quiz/sitting_list.html:14
150 | msgid "Quiz"
151 | msgstr "Quiz"
152 |
153 | #: apps/quiz/models.py:153
154 | msgid "Quizzes"
155 | msgstr "Quiz"
156 |
157 | #: apps/quiz/models.py:192 apps/quiz/models.py:370
158 | #: apps/quiz/templates/quiz/sitting_detail.html:13
159 | #: apps/quiz/templates/quiz/sitting_list.html:13
160 | msgid "User"
161 | msgstr "Utente"
162 |
163 | #: apps/quiz/models.py:195 apps/quiz/templates/progress.html:60
164 | #: apps/quiz/templates/quiz/sitting_detail.html:15
165 | #: apps/quiz/templates/quiz/sitting_list.html:16
166 | msgid "Score"
167 | msgstr "Punteggio"
168 |
169 | #: apps/quiz/models.py:200
170 | msgid "User Progress"
171 | msgstr "Progressi dell'utente"
172 |
173 | #: apps/quiz/models.py:201
174 | msgid "User progress records"
175 | msgstr "registarzione progressi utente"
176 |
177 | #: apps/quiz/models.py:261
178 | msgid "error"
179 | msgstr "errore"
180 |
181 | #: apps/quiz/models.py:261
182 | msgid "category does not exist or invalid score"
183 | msgstr "categoria inesistente o punteggio non valido"
184 |
185 | #: apps/quiz/models.py:375
186 | msgid "Question Order"
187 | msgstr "ordine Domande"
188 |
189 | #: apps/quiz/models.py:378
190 | msgid "Question List"
191 | msgstr "Elenca domande"
192 |
193 | #: apps/quiz/models.py:381
194 | msgid "Incorrect questions"
195 | msgstr "Domande sbagliate"
196 |
197 | #: apps/quiz/models.py:383
198 | msgid "Current Score"
199 | msgstr "Punteggio Attuale"
200 |
201 | #: apps/quiz/models.py:386
202 | msgid "Complete"
203 | msgstr "Completo"
204 |
205 | #: apps/quiz/models.py:389
206 | msgid "User Answers"
207 | msgstr "Risposte utente"
208 |
209 | #: apps/quiz/models.py:392
210 | msgid "Start"
211 | msgstr "Inizio"
212 |
213 | #: apps/quiz/models.py:394
214 | msgid "End"
215 | msgstr "Fine"
216 |
217 | #: apps/quiz/models.py:399
218 | msgid "Can see completed exams."
219 | msgstr "Puoi vedere gli esami completati."
220 |
221 | #: apps/quiz/models.py:557
222 | msgid "Figure"
223 | msgstr "Immagine"
224 |
225 | #: apps/quiz/models.py:561
226 | msgid "Enter the question text that you want displayed"
227 | msgstr "Inserisci il testo della domanda che desideri visualizzare"
228 |
229 | #: apps/quiz/models.py:563 apps/quiz/models.py:575
230 | #: apps/quiz/templates/question.html:47
231 | #: apps/quiz/templates/quiz/sitting_detail.html:21
232 | msgid "Question"
233 | msgstr "Domanda"
234 |
235 | #: apps/quiz/models.py:567
236 | msgid "Explanation to be shown after the question has been answered."
237 | msgstr ""
238 | "Spiegazione da visulizzare dopo che è stata data la risposta alla domanda."
239 |
240 | #: apps/quiz/models.py:570 apps/quiz/templates/question.html:32
241 | #: apps/quiz/templates/result.html:21 apps/quiz/templates/result.html.py:87
242 | msgid "Explanation"
243 | msgstr "Spiegazione"
244 |
245 | #: apps/quiz/models.py:576
246 | msgid "Questions"
247 | msgstr "Domande"
248 |
249 | #: apps/quiz/templates/base.html:7
250 | msgid "Example Quiz Website"
251 | msgstr "Sito Quiz di esempio"
252 |
253 | #: apps/quiz/templates/correct_answer.html:6
254 | msgid "You answered the above question incorrectly"
255 | msgstr "Hai risposto alla domanda di cui sopra in modo non corretto"
256 |
257 | #: apps/quiz/templates/correct_answer.html:16
258 | msgid "This is the correct answer"
259 | msgstr "Questa è la risposta corretta"
260 |
261 | #: apps/quiz/templates/correct_answer.html:23
262 | msgid "This was your answer."
263 | msgstr "Questa è stata la tua risposta "
264 |
265 | #: apps/quiz/templates/progress.html:6
266 | msgid "Progress Page"
267 | msgstr "Pagina dei progressi"
268 |
269 | #: apps/quiz/templates/progress.html:7
270 | msgid "User Progress Page"
271 | msgstr "Pagina dei progressi dell'utente"
272 |
273 | #: apps/quiz/templates/progress.html:13
274 | msgid "Question Category Scores"
275 | msgstr "Punteggi Categoria Domanda "
276 |
277 | #: apps/quiz/templates/progress.html:20
278 | msgid "Correctly answererd"
279 | msgstr "Risposto correttamente"
280 |
281 | #: apps/quiz/templates/progress.html:21
282 | msgid "Incorrect"
283 | msgstr "Sbagliato"
284 |
285 | #: apps/quiz/templates/progress.html:50
286 | msgid "Previous exam papers"
287 | msgstr "Fogli esami precedenti"
288 |
289 | #: apps/quiz/templates/progress.html:52
290 | msgid "Below are the results of exams that you have sat."
291 | msgstr "Di seguito sono riportati i risultati degli esami sostenuti."
292 |
293 | #: apps/quiz/templates/progress.html:59
294 | msgid "Quiz Title"
295 | msgstr "Titolo del Quiz"
296 |
297 | #: apps/quiz/templates/progress.html:61
298 | msgid "Possible Score"
299 | msgstr "Possibile Punteggio"
300 |
301 | #: apps/quiz/templates/question.html:13 apps/quiz/templates/result.html:13
302 | msgid "The previous question"
303 | msgstr "La domanda precedente"
304 |
305 | #: apps/quiz/templates/question.html:22
306 | msgid "Your answer was"
307 | msgstr "La tua risposta è stata"
308 |
309 | #: apps/quiz/templates/question.html:47
310 | msgid "of"
311 | msgstr "di"
312 |
313 | #: apps/quiz/templates/question.html:52
314 | msgid "Question category"
315 | msgstr "Categoria domanda"
316 |
317 | #: apps/quiz/templates/question.html:74
318 | msgid "Check"
319 | msgstr "Verifica"
320 |
321 | #: apps/quiz/templates/quiz/category_list.html:3
322 | #: apps/quiz/templates/quiz/quiz_list.html:3
323 | #: apps/quiz/templates/quiz/sitting_list.html:3
324 | msgid "All Quizzes"
325 | msgstr "Tutti i Quiz"
326 |
327 | #: apps/quiz/templates/quiz/category_list.html:6
328 | msgid "Category list"
329 | msgstr "Lista Categorie"
330 |
331 | #: apps/quiz/templates/quiz/quiz_detail.html:11
332 | msgid "You will only get one attempt at this quiz"
333 | msgstr "Avrai un solo tentativo a disposizione per questo quiz"
334 |
335 | #: apps/quiz/templates/quiz/quiz_detail.html:16
336 | msgid "Start quiz"
337 | msgstr "Comincia il Quiz"
338 |
339 | #: apps/quiz/templates/quiz/quiz_list.html:6
340 | msgid "List of quizzes"
341 | msgstr "Lista dei quiz"
342 |
343 | #: apps/quiz/templates/quiz/quiz_list.html:14
344 | msgid "Exam"
345 | msgstr "Esame"
346 |
347 | #: apps/quiz/templates/quiz/quiz_list.html:15
348 | msgid "Single attempt"
349 | msgstr "Singolo tentivo"
350 |
351 | #: apps/quiz/templates/quiz/quiz_list.html:31
352 | #: apps/quiz/templates/quiz/sitting_list.html:42
353 | msgid "View details"
354 | msgstr "Vedi i dettagli"
355 |
356 | #: apps/quiz/templates/quiz/quiz_list.html:41
357 | msgid "There are no available quizzes"
358 | msgstr "Non ci sono quiz disponibili"
359 |
360 | #: apps/quiz/templates/quiz/sitting_detail.html:5
361 | msgid "Result of"
362 | msgstr "Risultato di"
363 |
364 | #: apps/quiz/templates/quiz/sitting_detail.html:5
365 | msgid "for"
366 | msgstr "per"
367 |
368 | #: apps/quiz/templates/quiz/sitting_detail.html:9
369 | msgid "Quiz title"
370 | msgstr "Titolo quiz"
371 |
372 | #: apps/quiz/templates/quiz/sitting_detail.html:14
373 | #: apps/quiz/templates/quiz/sitting_list.html:15
374 | msgid "Completed"
375 | msgstr "Completato"
376 |
377 | #: apps/quiz/templates/quiz/sitting_detail.html:22
378 | msgid "User answer"
379 | msgstr "Risposta utente"
380 |
381 | #: apps/quiz/templates/quiz/sitting_detail.html:41
382 | msgid "incorrect"
383 | msgstr "errato"
384 |
385 | #: apps/quiz/templates/quiz/sitting_detail.html:43
386 | msgid "Correct"
387 | msgstr "Corretto"
388 |
389 | #: apps/quiz/templates/quiz/sitting_detail.html:49
390 | msgid "Toggle whether correct"
391 | msgstr "Seleziona se corretto"
392 |
393 | #: apps/quiz/templates/quiz/sitting_list.html:6
394 | msgid "List of complete exams"
395 | msgstr "Lista edgli esami completati"
396 |
397 | #: apps/quiz/templates/quiz/sitting_list.html:28
398 | msgid "Filter"
399 | msgstr "Filtro"
400 |
401 | #: apps/quiz/templates/quiz/sitting_list.html:52
402 | msgid "There are no matching quizzes"
403 | msgstr "Non ci sono quiz corrispondenti"
404 |
405 | #: apps/quiz/templates/result.html:7
406 | msgid "Exam Results for"
407 | msgstr "Risultati esame per"
408 |
409 | #: apps/quiz/templates/result.html:32
410 | msgid "Exam results"
411 | msgstr "Risultati esame"
412 |
413 | #: apps/quiz/templates/result.html:34
414 | msgid "Exam title"
415 | msgstr "Titolo esame"
416 |
417 | #: apps/quiz/templates/result.html:38
418 | msgid "You answered"
419 | msgstr "Hai risposto"
420 |
421 | #: apps/quiz/templates/result.html:38
422 | msgid "questions correctly out of"
423 | msgstr "domande cui hai correttamente risposto su"
424 |
425 | #: apps/quiz/templates/result.html:38
426 | msgid "giving you"
427 | msgstr "dandoti"
428 |
429 | #: apps/quiz/templates/result.html:38
430 | msgid "percent correct"
431 | msgstr "percentuale corretta"
432 |
433 | #: apps/quiz/templates/result.html:48
434 | msgid "Review the questions below and try the exam again in the future"
435 | msgstr "Riesamina le domande qui sotto e riprova l'esame in futuro"
436 |
437 | #: apps/quiz/templates/result.html:52
438 | msgid ""
439 | "The result of this exam will be stored in your progress section so you can "
440 | "review and monitor your progression"
441 | msgstr ""
442 | " Il risultato di questo esame verrà memorizzato nella sezione di avanzamento "
443 | "in modo da poter verificare e monitorare i tuoi progressi"
444 |
445 | #: apps/quiz/templates/result.html:66
446 | msgid "Your session score is"
447 | msgstr "Il punteggio di questa sessione è "
448 |
449 | #: apps/quiz/templates/result.html:66
450 | msgid "out of a possible"
451 | msgstr "su un massimo di"
452 |
453 | #: apps/quiz/templates/result.html:84
454 | msgid "Your answer"
455 | msgstr "La tua risposta"
456 |
457 | #: apps/quiz/templates/single_complete.html:13
458 | msgid "You have already sat this exam and only one sitting is permitted"
459 | msgstr "Hai già sostenuto questo esame ed è consentito farlo solo una volta"
460 |
461 | #: apps/quiz/templates/single_complete.html:15
462 | msgid "This exam is only accessible to signed in users"
463 | msgstr "Questo esame è disponibile solo per gli utenti collegati"
464 |
465 | #: apps/quiz/templates/view_quiz_category.html:3
466 | msgid "Quizzes related to"
467 | msgstr "Quiz collegati a"
468 |
469 | #: apps/quiz/templates/view_quiz_category.html:6
470 | msgid "Quizzes in the"
471 | msgstr "Quiz nella"
472 |
473 | #: apps/quiz/templates/view_quiz_category.html:6
474 | msgid "category"
475 | msgstr "categoria"
476 |
477 | #: apps/quiz/templates/view_quiz_category.html:20
478 | msgid "There are no quizzes"
479 | msgstr "Non ci sono quiz"
480 |
--------------------------------------------------------------------------------
/quiz/views.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | from django.contrib.auth.decorators import login_required, permission_required
4 | from django.core.exceptions import PermissionDenied
5 | from django.shortcuts import get_object_or_404, render
6 | from django.utils.decorators import method_decorator
7 | from django.views.generic import DetailView, ListView, TemplateView, FormView
8 |
9 | from .forms import QuestionForm, EssayForm
10 | from .models import Quiz, Category, Progress, Sitting, Question
11 | from essay.models import Essay_Question
12 |
13 |
14 | class QuizMarkerMixin(object):
15 | @method_decorator(login_required)
16 | @method_decorator(permission_required('quiz.view_sittings'))
17 | def dispatch(self, *args, **kwargs):
18 | return super(QuizMarkerMixin, self).dispatch(*args, **kwargs)
19 |
20 |
21 | class SittingFilterTitleMixin(object):
22 | def get_queryset(self):
23 | queryset = super(SittingFilterTitleMixin, self).get_queryset()
24 | quiz_filter = self.request.GET.get('quiz_filter')
25 | if quiz_filter:
26 | queryset = queryset.filter(quiz__title__icontains=quiz_filter)
27 |
28 | return queryset
29 |
30 |
31 | class QuizListView(ListView):
32 | model = Quiz
33 |
34 | def get_queryset(self):
35 | queryset = super(QuizListView, self).get_queryset()
36 | return queryset.filter(draft=False)
37 |
38 |
39 | class QuizDetailView(DetailView):
40 | model = Quiz
41 | slug_field = 'url'
42 |
43 | def get(self, request, *args, **kwargs):
44 | self.object = self.get_object()
45 |
46 | if self.object.draft and not request.user.has_perm('quiz.change_quiz'):
47 | raise PermissionDenied
48 |
49 | context = self.get_context_data(object=self.object)
50 | return self.render_to_response(context)
51 |
52 |
53 | class CategoriesListView(ListView):
54 | model = Category
55 |
56 |
57 | class ViewQuizListByCategory(ListView):
58 | model = Quiz
59 | template_name = 'view_quiz_category.html'
60 |
61 | def dispatch(self, request, *args, **kwargs):
62 | self.category = get_object_or_404(
63 | Category,
64 | category=self.kwargs['category_name']
65 | )
66 |
67 | return super(ViewQuizListByCategory, self).\
68 | dispatch(request, *args, **kwargs)
69 |
70 | def get_context_data(self, **kwargs):
71 | context = super(ViewQuizListByCategory, self)\
72 | .get_context_data(**kwargs)
73 |
74 | context['category'] = self.category
75 | return context
76 |
77 | def get_queryset(self):
78 | queryset = super(ViewQuizListByCategory, self).get_queryset()
79 | return queryset.filter(category=self.category, draft=False)
80 |
81 |
82 | class QuizUserProgressView(TemplateView):
83 | template_name = 'progress.html'
84 |
85 | @method_decorator(login_required)
86 | def dispatch(self, request, *args, **kwargs):
87 | return super(QuizUserProgressView, self)\
88 | .dispatch(request, *args, **kwargs)
89 |
90 | def get_context_data(self, **kwargs):
91 | context = super(QuizUserProgressView, self).get_context_data(**kwargs)
92 | progress, c = Progress.objects.get_or_create(user=self.request.user)
93 | context['cat_scores'] = progress.list_all_cat_scores
94 | context['exams'] = progress.show_exams()
95 | return context
96 |
97 |
98 | class QuizMarkingList(QuizMarkerMixin, SittingFilterTitleMixin, ListView):
99 | model = Sitting
100 |
101 | def get_queryset(self):
102 | queryset = super(QuizMarkingList, self).get_queryset()\
103 | .filter(complete=True)
104 |
105 | user_filter = self.request.GET.get('user_filter')
106 | if user_filter:
107 | queryset = queryset.filter(user__username__icontains=user_filter)
108 |
109 | return queryset
110 |
111 |
112 | class QuizMarkingDetail(QuizMarkerMixin, DetailView):
113 | model = Sitting
114 |
115 | def post(self, request, *args, **kwargs):
116 | sitting = self.get_object()
117 |
118 | q_to_toggle = request.POST.get('qid', None)
119 | if q_to_toggle:
120 | q = Question.objects.get_subclass(id=int(q_to_toggle))
121 | if int(q_to_toggle) in sitting.get_incorrect_questions:
122 | sitting.remove_incorrect_question(q)
123 | else:
124 | sitting.add_incorrect_question(q)
125 |
126 | return self.get(request)
127 |
128 | def get_context_data(self, **kwargs):
129 | context = super(QuizMarkingDetail, self).get_context_data(**kwargs)
130 | context['questions'] =\
131 | context['sitting'].get_questions(with_answers=True)
132 | return context
133 |
134 |
135 | class QuizTake(FormView):
136 | form_class = QuestionForm
137 | template_name = 'question.html'
138 | result_template_name = 'result.html'
139 | single_complete_template_name = 'single_complete.html'
140 |
141 | def dispatch(self, request, *args, **kwargs):
142 | self.quiz = get_object_or_404(Quiz, url=self.kwargs['quiz_name'])
143 | if self.quiz.draft and not request.user.has_perm('quiz.change_quiz'):
144 | raise PermissionDenied
145 |
146 | try:
147 | self.logged_in_user = self.request.user.is_authenticated()
148 | except TypeError:
149 | self.logged_in_user = self.request.user.is_authenticated
150 |
151 | if self.logged_in_user:
152 | self.sitting = Sitting.objects.user_sitting(request.user,
153 | self.quiz)
154 | else:
155 | self.sitting = self.anon_load_sitting()
156 |
157 | if self.sitting is False:
158 | return render(request, self.single_complete_template_name)
159 |
160 | return super(QuizTake, self).dispatch(request, *args, **kwargs)
161 |
162 | def get_form(self, *args, **kwargs):
163 | if self.logged_in_user:
164 | self.question = self.sitting.get_first_question()
165 | self.progress = self.sitting.progress()
166 | else:
167 | self.question = self.anon_next_question()
168 | self.progress = self.anon_sitting_progress()
169 |
170 | if self.question.__class__ is Essay_Question:
171 | form_class = EssayForm
172 | else:
173 | form_class = self.form_class
174 |
175 | return form_class(**self.get_form_kwargs())
176 |
177 | def get_form_kwargs(self):
178 | kwargs = super(QuizTake, self).get_form_kwargs()
179 |
180 | return dict(kwargs, question=self.question)
181 |
182 | def form_valid(self, form):
183 | if self.logged_in_user:
184 | self.form_valid_user(form)
185 | if self.sitting.get_first_question() is False:
186 | return self.final_result_user()
187 | else:
188 | self.form_valid_anon(form)
189 | if not self.request.session[self.quiz.anon_q_list()]:
190 | return self.final_result_anon()
191 |
192 | self.request.POST = {}
193 |
194 | return super(QuizTake, self).get(self, self.request)
195 |
196 | def get_context_data(self, **kwargs):
197 | context = super(QuizTake, self).get_context_data(**kwargs)
198 | context['question'] = self.question
199 | context['quiz'] = self.quiz
200 | if hasattr(self, 'previous'):
201 | context['previous'] = self.previous
202 | if hasattr(self, 'progress'):
203 | context['progress'] = self.progress
204 | return context
205 |
206 | def form_valid_user(self, form):
207 | progress, c = Progress.objects.get_or_create(user=self.request.user)
208 | guess = form.cleaned_data['answers']
209 | is_correct = self.question.check_if_correct(guess)
210 |
211 | if is_correct is True:
212 | self.sitting.add_to_score(1)
213 | progress.update_score(self.question, 1, 1)
214 | else:
215 | self.sitting.add_incorrect_question(self.question)
216 | progress.update_score(self.question, 0, 1)
217 |
218 | if self.quiz.answers_at_end is not True:
219 | self.previous = {'previous_answer': guess,
220 | 'previous_outcome': is_correct,
221 | 'previous_question': self.question,
222 | 'answers': self.question.get_answers(),
223 | 'question_type': {self.question
224 | .__class__.__name__: True}}
225 | else:
226 | self.previous = {}
227 |
228 | self.sitting.add_user_answer(self.question, guess)
229 | self.sitting.remove_first_question()
230 |
231 | def final_result_user(self):
232 | results = {
233 | 'quiz': self.quiz,
234 | 'score': self.sitting.get_current_score,
235 | 'max_score': self.sitting.get_max_score,
236 | 'percent': self.sitting.get_percent_correct,
237 | 'sitting': self.sitting,
238 | 'previous': self.previous,
239 | }
240 |
241 | self.sitting.mark_quiz_complete()
242 |
243 | if self.quiz.answers_at_end:
244 | results['questions'] =\
245 | self.sitting.get_questions(with_answers=True)
246 | results['incorrect_questions'] =\
247 | self.sitting.get_incorrect_questions
248 |
249 | if self.quiz.exam_paper is False:
250 | self.sitting.delete()
251 |
252 | return render(self.request, self.result_template_name, results)
253 |
254 | def anon_load_sitting(self):
255 | if self.quiz.single_attempt is True:
256 | return False
257 |
258 | if self.quiz.anon_q_list() in self.request.session:
259 | return self.request.session[self.quiz.anon_q_list()]
260 | else:
261 | return self.new_anon_quiz_session()
262 |
263 | def new_anon_quiz_session(self):
264 | """
265 | Sets the session variables when starting a quiz for the first time
266 | as a non signed-in user
267 | """
268 | self.request.session.set_expiry(259200) # expires after 3 days
269 | questions = self.quiz.get_questions()
270 | question_list = [question.id for question in questions]
271 |
272 | if self.quiz.random_order is True:
273 | random.shuffle(question_list)
274 |
275 | if self.quiz.max_questions and (self.quiz.max_questions
276 | < len(question_list)):
277 | question_list = question_list[:self.quiz.max_questions]
278 |
279 | # session score for anon users
280 | self.request.session[self.quiz.anon_score_id()] = 0
281 |
282 | # session list of questions
283 | self.request.session[self.quiz.anon_q_list()] = question_list
284 |
285 | # session list of question order and incorrect questions
286 | self.request.session[self.quiz.anon_q_data()] = dict(
287 | incorrect_questions=[],
288 | order=question_list,
289 | )
290 |
291 | return self.request.session[self.quiz.anon_q_list()]
292 |
293 | def anon_next_question(self):
294 | next_question_id = self.request.session[self.quiz.anon_q_list()][0]
295 | return Question.objects.get_subclass(id=next_question_id)
296 |
297 | def anon_sitting_progress(self):
298 | total = len(self.request.session[self.quiz.anon_q_data()]['order'])
299 | answered = total - len(self.request.session[self.quiz.anon_q_list()])
300 | return (answered, total)
301 |
302 | def form_valid_anon(self, form):
303 | guess = form.cleaned_data['answers']
304 | is_correct = self.question.check_if_correct(guess)
305 |
306 | if is_correct:
307 | self.request.session[self.quiz.anon_score_id()] += 1
308 | anon_session_score(self.request.session, 1, 1)
309 | else:
310 | anon_session_score(self.request.session, 0, 1)
311 | self.request\
312 | .session[self.quiz.anon_q_data()]['incorrect_questions']\
313 | .append(self.question.id)
314 |
315 | self.previous = {}
316 | if self.quiz.answers_at_end is not True:
317 | self.previous = {'previous_answer': guess,
318 | 'previous_outcome': is_correct,
319 | 'previous_question': self.question,
320 | 'answers': self.question.get_answers(),
321 | 'question_type': {self.question
322 | .__class__.__name__: True}}
323 |
324 | self.request.session[self.quiz.anon_q_list()] =\
325 | self.request.session[self.quiz.anon_q_list()][1:]
326 |
327 | def final_result_anon(self):
328 | score = self.request.session[self.quiz.anon_score_id()]
329 | q_order = self.request.session[self.quiz.anon_q_data()]['order']
330 | max_score = len(q_order)
331 | percent = int(round((float(score) / max_score) * 100))
332 | session, session_possible = anon_session_score(self.request.session)
333 | if score is 0:
334 | score = "0"
335 |
336 | results = {
337 | 'score': score,
338 | 'max_score': max_score,
339 | 'percent': percent,
340 | 'session': session,
341 | 'possible': session_possible
342 | }
343 |
344 | del self.request.session[self.quiz.anon_q_list()]
345 |
346 | if self.quiz.answers_at_end:
347 | results['questions'] = sorted(
348 | self.quiz.question_set.filter(id__in=q_order)
349 | .select_subclasses(),
350 | key=lambda q: q_order.index(q.id))
351 |
352 | results['incorrect_questions'] = (
353 | self.request
354 | .session[self.quiz.anon_q_data()]['incorrect_questions'])
355 |
356 | else:
357 | results['previous'] = self.previous
358 |
359 | del self.request.session[self.quiz.anon_q_data()]
360 |
361 | return render(self.request, 'result.html', results)
362 |
363 |
364 | def anon_session_score(session, to_add=0, possible=0):
365 | """
366 | Returns the session score for non-signed in users.
367 | If number passed in then add this to the running total and
368 | return session score.
369 |
370 | examples:
371 | anon_session_score(1, 1) will add 1 out of a possible 1
372 | anon_session_score(0, 2) will add 0 out of a possible 2
373 | x, y = anon_session_score() will return the session score
374 | without modification
375 |
376 | Left this as an individual function for unit testing
377 | """
378 | if "session_score" not in session:
379 | session["session_score"], session["session_score_possible"] = 0, 0
380 |
381 | if possible > 0:
382 | session["session_score"] += to_add
383 | session["session_score_possible"] += possible
384 |
385 | return session["session_score"], session["session_score_possible"]
386 |
--------------------------------------------------------------------------------
/quiz/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 | import re
3 | import json
4 |
5 | from django.db import models
6 | from django.core.exceptions import ValidationError, ImproperlyConfigured
7 | from django.core.validators import (
8 | MaxValueValidator, validate_comma_separated_integer_list,
9 | )
10 | from django.utils.translation import ugettext_lazy as _
11 | from django.utils.timezone import now
12 | from django.utils.encoding import python_2_unicode_compatible
13 | from django.conf import settings
14 |
15 | from model_utils.managers import InheritanceManager
16 |
17 |
18 | class CategoryManager(models.Manager):
19 |
20 | def new_category(self, category):
21 | new_category = self.create(category=re.sub('\s+', '-', category)
22 | .lower())
23 |
24 | new_category.save()
25 | return new_category
26 |
27 |
28 | @python_2_unicode_compatible
29 | class Category(models.Model):
30 |
31 | category = models.CharField(
32 | verbose_name=_("Category"),
33 | max_length=250, blank=True,
34 | unique=True, null=True)
35 |
36 | objects = CategoryManager()
37 |
38 | class Meta:
39 | verbose_name = _("Category")
40 | verbose_name_plural = _("Categories")
41 |
42 | def __str__(self):
43 | return self.category
44 |
45 |
46 | @python_2_unicode_compatible
47 | class SubCategory(models.Model):
48 |
49 | sub_category = models.CharField(
50 | verbose_name=_("Sub-Category"),
51 | max_length=250, blank=True, null=True)
52 |
53 | category = models.ForeignKey(
54 | Category, null=True, blank=True,
55 | verbose_name=_("Category"), on_delete=models.CASCADE)
56 |
57 | objects = CategoryManager()
58 |
59 | class Meta:
60 | verbose_name = _("Sub-Category")
61 | verbose_name_plural = _("Sub-Categories")
62 |
63 | def __str__(self):
64 | return self.sub_category + " (" + self.category.category + ")"
65 |
66 |
67 | @python_2_unicode_compatible
68 | class Quiz(models.Model):
69 |
70 | title = models.CharField(
71 | verbose_name=_("Title"),
72 | max_length=60, blank=False)
73 |
74 | description = models.TextField(
75 | verbose_name=_("Description"),
76 | blank=True, help_text=_("a description of the quiz"))
77 |
78 | url = models.SlugField(
79 | max_length=60, blank=False,
80 | help_text=_("a user friendly url"),
81 | verbose_name=_("user friendly url"))
82 |
83 | category = models.ForeignKey(
84 | Category, null=True, blank=True,
85 | verbose_name=_("Category"), on_delete=models.CASCADE)
86 |
87 | random_order = models.BooleanField(
88 | blank=False, default=False,
89 | verbose_name=_("Random Order"),
90 | help_text=_("Display the questions in "
91 | "a random order or as they "
92 | "are set?"))
93 |
94 | max_questions = models.PositiveIntegerField(
95 | blank=True, null=True, verbose_name=_("Max Questions"),
96 | help_text=_("Number of questions to be answered on each attempt."))
97 |
98 | answers_at_end = models.BooleanField(
99 | blank=False, default=False,
100 | help_text=_("Correct answer is NOT shown after question."
101 | " Answers displayed at the end."),
102 | verbose_name=_("Answers at end"))
103 |
104 | exam_paper = models.BooleanField(
105 | blank=False, default=False,
106 | help_text=_("If yes, the result of each"
107 | " attempt by a user will be"
108 | " stored. Necessary for marking."),
109 | verbose_name=_("Exam Paper"))
110 |
111 | single_attempt = models.BooleanField(
112 | blank=False, default=False,
113 | help_text=_("If yes, only one attempt by"
114 | " a user will be permitted."
115 | " Non users cannot sit this exam."),
116 | verbose_name=_("Single Attempt"))
117 |
118 | pass_mark = models.SmallIntegerField(
119 | blank=True, default=0,
120 | verbose_name=_("Pass Mark"),
121 | help_text=_("Percentage required to pass exam."),
122 | validators=[MaxValueValidator(100)])
123 |
124 | success_text = models.TextField(
125 | blank=True, help_text=_("Displayed if user passes."),
126 | verbose_name=_("Success Text"))
127 |
128 | fail_text = models.TextField(
129 | verbose_name=_("Fail Text"),
130 | blank=True, help_text=_("Displayed if user fails."))
131 |
132 | draft = models.BooleanField(
133 | blank=True, default=False,
134 | verbose_name=_("Draft"),
135 | help_text=_("If yes, the quiz is not displayed"
136 | " in the quiz list and can only be"
137 | " taken by users who can edit"
138 | " quizzes."))
139 |
140 | def save(self, force_insert=False, force_update=False, *args, **kwargs):
141 | self.url = re.sub('\s+', '-', self.url).lower()
142 |
143 | self.url = ''.join(letter for letter in self.url if
144 | letter.isalnum() or letter == '-')
145 |
146 | if self.single_attempt is True:
147 | self.exam_paper = True
148 |
149 | if self.pass_mark > 100:
150 | raise ValidationError('%s is above 100' % self.pass_mark)
151 |
152 | super(Quiz, self).save(force_insert, force_update, *args, **kwargs)
153 |
154 | class Meta:
155 | verbose_name = _("Quiz")
156 | verbose_name_plural = _("Quizzes")
157 |
158 | def __str__(self):
159 | return self.title
160 |
161 | def get_questions(self):
162 | return self.question_set.all().select_subclasses()
163 |
164 | @property
165 | def get_max_score(self):
166 | return self.get_questions().count()
167 |
168 | def anon_score_id(self):
169 | return str(self.id) + "_score"
170 |
171 | def anon_q_list(self):
172 | return str(self.id) + "_q_list"
173 |
174 | def anon_q_data(self):
175 | return str(self.id) + "_data"
176 |
177 |
178 | class ProgressManager(models.Manager):
179 |
180 | def new_progress(self, user):
181 | new_progress = self.create(user=user,
182 | score="")
183 | new_progress.save()
184 | return new_progress
185 |
186 |
187 | class Progress(models.Model):
188 | """
189 | Progress is used to track an individual signed in users score on different
190 | quiz's and categories
191 |
192 | Data stored in csv using the format:
193 | category, score, possible, category, score, possible, ...
194 | """
195 | user = models.OneToOneField(settings.AUTH_USER_MODEL, verbose_name=_("User"), on_delete=models.CASCADE)
196 |
197 | score = models.CharField(max_length=1024,
198 | verbose_name=_("Score"),
199 | validators=[validate_comma_separated_integer_list])
200 |
201 | objects = ProgressManager()
202 |
203 | class Meta:
204 | verbose_name = _("User Progress")
205 | verbose_name_plural = _("User progress records")
206 |
207 | @property
208 | def list_all_cat_scores(self):
209 | """
210 | Returns a dict in which the key is the category name and the item is
211 | a list of three integers.
212 |
213 | The first is the number of questions correct,
214 | the second is the possible best score,
215 | the third is the percentage correct.
216 |
217 | The dict will have one key for every category that you have defined
218 | """
219 | score_before = self.score
220 | output = {}
221 |
222 | for cat in Category.objects.all():
223 | to_find = re.escape(cat.category) + r",(\d+),(\d+),"
224 | # group 1 is score, group 2 is highest possible
225 |
226 | match = re.search(to_find, self.score, re.IGNORECASE)
227 |
228 | if match:
229 | score = int(match.group(1))
230 | possible = int(match.group(2))
231 |
232 | try:
233 | percent = int(round((float(score) / float(possible))
234 | * 100))
235 | except:
236 | percent = 0
237 |
238 | output[cat.category] = [score, possible, percent]
239 |
240 | else: # if category has not been added yet, add it.
241 | self.score += cat.category + ",0,0,"
242 | output[cat.category] = [0, 0]
243 |
244 | if len(self.score) > len(score_before):
245 | # If a new category has been added, save changes.
246 | self.save()
247 |
248 | return output
249 |
250 | def update_score(self, question, score_to_add=0, possible_to_add=0):
251 | """
252 | Pass in question object, amount to increase score
253 | and max possible.
254 |
255 | Does not return anything.
256 | """
257 | category_test = Category.objects.filter(category=question.category)\
258 | .exists()
259 |
260 | if any([item is False for item in [category_test,
261 | score_to_add,
262 | possible_to_add,
263 | isinstance(score_to_add, int),
264 | isinstance(possible_to_add, int)]]):
265 | return _("error"), _("category does not exist or invalid score")
266 |
267 | to_find = re.escape(str(question.category)) +\
268 | r",(?P\d+),(?P\d+),"
269 |
270 | match = re.search(to_find, self.score, re.IGNORECASE)
271 |
272 | if match:
273 | updated_score = int(match.group('score')) + abs(score_to_add)
274 | updated_possible = int(match.group('possible')) +\
275 | abs(possible_to_add)
276 |
277 | new_score = ",".join(
278 | [
279 | str(question.category),
280 | str(updated_score),
281 | str(updated_possible), ""
282 | ])
283 |
284 | # swap old score for the new one
285 | self.score = self.score.replace(match.group(), new_score)
286 | self.save()
287 |
288 | else:
289 | # if not present but existing, add with the points passed in
290 | self.score += ",".join(
291 | [
292 | str(question.category),
293 | str(score_to_add),
294 | str(possible_to_add),
295 | ""
296 | ])
297 | self.save()
298 |
299 | def show_exams(self):
300 | """
301 | Finds the previous quizzes marked as 'exam papers'.
302 | Returns a queryset of complete exams.
303 | """
304 | return Sitting.objects.filter(user=self.user, complete=True)
305 |
306 |
307 | class SittingManager(models.Manager):
308 |
309 | def new_sitting(self, user, quiz):
310 | if quiz.random_order is True:
311 | question_set = quiz.question_set.all() \
312 | .select_subclasses() \
313 | .order_by('?')
314 | else:
315 | question_set = quiz.question_set.all() \
316 | .select_subclasses()
317 |
318 | question_set = [item.id for item in question_set]
319 |
320 | if len(question_set) == 0:
321 | raise ImproperlyConfigured('Question set of the quiz is empty. '
322 | 'Please configure questions properly')
323 |
324 | if quiz.max_questions and quiz.max_questions < len(question_set):
325 | question_set = question_set[:quiz.max_questions]
326 |
327 | questions = ",".join(map(str, question_set)) + ","
328 |
329 | new_sitting = self.create(user=user,
330 | quiz=quiz,
331 | question_order=questions,
332 | question_list=questions,
333 | incorrect_questions="",
334 | current_score=0,
335 | complete=False,
336 | user_answers='{}')
337 | return new_sitting
338 |
339 | def user_sitting(self, user, quiz):
340 | if quiz.single_attempt is True and self.filter(user=user,
341 | quiz=quiz,
342 | complete=True)\
343 | .exists():
344 | return False
345 |
346 | try:
347 | sitting = self.get(user=user, quiz=quiz, complete=False)
348 | except Sitting.DoesNotExist:
349 | sitting = self.new_sitting(user, quiz)
350 | except Sitting.MultipleObjectsReturned:
351 | sitting = self.filter(user=user, quiz=quiz, complete=False)[0]
352 | return sitting
353 |
354 |
355 | class Sitting(models.Model):
356 | """
357 | Used to store the progress of logged in users sitting a quiz.
358 | Replaces the session system used by anon users.
359 |
360 | Question_order is a list of integer pks of all the questions in the
361 | quiz, in order.
362 |
363 | Question_list is a list of integers which represent id's of
364 | the unanswered questions in csv format.
365 |
366 | Incorrect_questions is a list in the same format.
367 |
368 | Sitting deleted when quiz finished unless quiz.exam_paper is true.
369 |
370 | User_answers is a json object in which the question PK is stored
371 | with the answer the user gave.
372 | """
373 |
374 | user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_("User"), on_delete=models.CASCADE)
375 |
376 | quiz = models.ForeignKey(Quiz, verbose_name=_("Quiz"), on_delete=models.CASCADE)
377 |
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])
393 |
394 | current_score = models.IntegerField(verbose_name=_("Current Score"))
395 |
396 | complete = models.BooleanField(default=False, blank=False,
397 | verbose_name=_("Complete"))
398 |
399 | user_answers = models.TextField(blank=True, default='{}',
400 | verbose_name=_("User Answers"))
401 |
402 | start = models.DateTimeField(auto_now_add=True,
403 | verbose_name=_("Start"))
404 |
405 | end = models.DateTimeField(null=True, blank=True, verbose_name=_("End"))
406 |
407 | objects = SittingManager()
408 |
409 | class Meta:
410 | permissions = (("view_sittings", _("Can see completed exams.")),)
411 |
412 | def get_first_question(self):
413 | """
414 | Returns the next question.
415 | If no question is found, returns False
416 | Does NOT remove the question from the front of the list.
417 | """
418 | if not self.question_list:
419 | return False
420 |
421 | first, _ = self.question_list.split(',', 1)
422 | question_id = int(first)
423 | return Question.objects.get_subclass(id=question_id)
424 |
425 | def remove_first_question(self):
426 | if not self.question_list:
427 | return
428 |
429 | _, others = self.question_list.split(',', 1)
430 | self.question_list = others
431 | self.save()
432 |
433 | def add_to_score(self, points):
434 | self.current_score += int(points)
435 | self.save()
436 |
437 | @property
438 | def get_current_score(self):
439 | return self.current_score
440 |
441 | def _question_ids(self):
442 | return [int(n) for n in self.question_order.split(',') if n]
443 |
444 | @property
445 | def get_percent_correct(self):
446 | dividend = float(self.current_score)
447 | divisor = len(self._question_ids())
448 | if divisor < 1:
449 | return 0 # prevent divide by zero error
450 |
451 | if dividend > divisor:
452 | return 100
453 |
454 | correct = int(round((dividend / divisor) * 100))
455 |
456 | if correct >= 1:
457 | return correct
458 | else:
459 | return 0
460 |
461 | def mark_quiz_complete(self):
462 | self.complete = True
463 | self.end = now()
464 | self.save()
465 |
466 | def add_incorrect_question(self, question):
467 | """
468 | Adds uid of incorrect question to the list.
469 | The question object must be passed in.
470 | """
471 | if len(self.incorrect_questions) > 0:
472 | self.incorrect_questions += ','
473 | self.incorrect_questions += str(question.id) + ","
474 | if self.complete:
475 | self.add_to_score(-1)
476 | self.save()
477 |
478 | @property
479 | def get_incorrect_questions(self):
480 | """
481 | Returns a list of non empty integers, representing the pk of
482 | questions
483 | """
484 | return [int(q) for q in self.incorrect_questions.split(',') if q]
485 |
486 | def remove_incorrect_question(self, question):
487 | current = self.get_incorrect_questions
488 | current.remove(question.id)
489 | self.incorrect_questions = ','.join(map(str, current))
490 | self.add_to_score(1)
491 | self.save()
492 |
493 | @property
494 | def check_if_passed(self):
495 | return self.get_percent_correct >= self.quiz.pass_mark
496 |
497 | @property
498 | def result_message(self):
499 | if self.check_if_passed:
500 | return self.quiz.success_text
501 | else:
502 | return self.quiz.fail_text
503 |
504 | def add_user_answer(self, question, guess):
505 | current = json.loads(self.user_answers)
506 | current[question.id] = guess
507 | self.user_answers = json.dumps(current)
508 | self.save()
509 |
510 | def get_questions(self, with_answers=False):
511 | question_ids = self._question_ids()
512 | questions = sorted(
513 | self.quiz.question_set.filter(id__in=question_ids)
514 | .select_subclasses(),
515 | key=lambda q: question_ids.index(q.id))
516 |
517 | if with_answers:
518 | user_answers = json.loads(self.user_answers)
519 | for question in questions:
520 | question.user_answer = user_answers[str(question.id)]
521 |
522 | return questions
523 |
524 | @property
525 | def questions_with_user_answers(self):
526 | return {
527 | q: q.user_answer for q in self.get_questions(with_answers=True)
528 | }
529 |
530 | @property
531 | def get_max_score(self):
532 | return len(self._question_ids())
533 |
534 | def progress(self):
535 | """
536 | Returns the number of questions answered so far and the total number of
537 | questions.
538 | """
539 | answered = len(json.loads(self.user_answers))
540 | total = self.get_max_score
541 | return answered, total
542 |
543 |
544 | @python_2_unicode_compatible
545 | class Question(models.Model):
546 | """
547 | Base class for all question types.
548 | Shared properties placed here.
549 | """
550 |
551 | quiz = models.ManyToManyField(Quiz,
552 | verbose_name=_("Quiz"),
553 | blank=True)
554 |
555 | category = models.ForeignKey(Category,
556 | verbose_name=_("Category"),
557 | blank=True,
558 | null=True,
559 | on_delete=models.CASCADE)
560 |
561 | sub_category = models.ForeignKey(SubCategory,
562 | verbose_name=_("Sub-Category"),
563 | blank=True,
564 | null=True,
565 | on_delete=models.CASCADE)
566 |
567 | figure = models.ImageField(upload_to='uploads/%Y/%m/%d',
568 | blank=True,
569 | null=True,
570 | verbose_name=_("Figure"))
571 |
572 | content = models.CharField(max_length=1000,
573 | blank=False,
574 | help_text=_("Enter the question text that "
575 | "you want displayed"),
576 | verbose_name=_('Question'))
577 |
578 | explanation = models.TextField(max_length=2000,
579 | blank=True,
580 | help_text=_("Explanation to be shown "
581 | "after the question has "
582 | "been answered."),
583 | verbose_name=_('Explanation'))
584 |
585 | objects = InheritanceManager()
586 |
587 | class Meta:
588 | verbose_name = _("Question")
589 | verbose_name_plural = _("Questions")
590 | ordering = ['category']
591 |
592 | def __str__(self):
593 | return self.content
594 |
--------------------------------------------------------------------------------