commit 2f61efd30f8f84bd33365af281201f71d845652f Author: greg Date: Wed Apr 3 22:04:11 2024 +0200 push clean refacto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d3cff13 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +db.sqlite3 +Pipfile +Pipfile.lock +essay/__pycache__ +essay/__init__.py +testenv_django_quiz/__pycache__ +testenv_django_quiz/__init__.py +true_false/__pycache__ +true_false/__init__.py +multichoice/__pycache__ +multichoice/__init__.py +venv \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e59ac01 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.11-slim-bookworm + +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +RUN apt-get update && apt-get install -y \ + gcc \ + && rm -rf /var/lib/apt/lists/* + +RUN mkdir /code +WORKDIR /code + +COPY quiz-app/requirements.txt /code/ +RUN pip install -r requirements.txt +COPY quiz-app/. /code/ +CMD ["python3", "manage.py", "runserver", "0.0.0.0:8000"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..582ed1c --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# DJANGO QUIZZ + +### Ce projet est une version fonctionnelle actualisée de Tom Walker (https://github.com/tomwalker/django_quiz). + +les prérequis sont : +- python 3.6 +- pip3 +- les librairies contenus dans le requirements.txt + +Les modifications apportées concernent la librairy pour la lectures des classes codées en python2 : +- Utilisation de la librairy six et utilisation de Django 2.2.9 (la 3.0 ne prends plus en charge python 2). +- Modification du requirements.txt en conséquence. + + +## INSTALLATION + + git clone + + cd djangoquizz + + python3 manage.py runserver + +## VISITER localhost:8000/admin et ce connecter avec les identifiants administrateur par défaut : + + user : Greg + + mot de passe : Juliette21 + +## Changer l'administrateur en créant un nouveaux utilisateur avec tous les droits, puis supprimer l'utilisateur "Greg" + +## CREER SON QUIZZ ! + +### Creer son quizz en choisissant le type (multiplechoice) : + +### Créer des questions : + +### Créer des utilisateurs : + +### Customiser le logo : + +## Avec Docker : + +### INSTALLATION + + git clone + + cd djangoquizz + + docker build -t djangoquizz . + + docker run -d -p 8000:8000 djangoquizz + +### VISITER 0.0.0.0:8000/admin + + admin : Greg / mdp: Juliette21 + + --> Pour modifier les acréditations de l'admin, éffectuer : + + python manage.py makemigrations + + python manage.py migrate + + python manage.py createsuperuser + +### Personnaliser : + +- Modifier la dernière ligne pour spécifier une IP (ex: 192.168.1.1:8000) +- --> répercuter l'adresse dans /testenv_django_quiz/settings.py dans ALLOWED_HOSTS = ['192.168.1.1'] + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..44b87f2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3' + +services: + djangoquiz: + build: . + container_name: djangoquiz + restart: unless-stopped + ports: + - "8000:8000" + diff --git a/quiz-app/app/__pycache__/settings.cpython-311.pyc b/quiz-app/app/__pycache__/settings.cpython-311.pyc new file mode 100644 index 0000000..dc7482b Binary files /dev/null and b/quiz-app/app/__pycache__/settings.cpython-311.pyc differ diff --git a/quiz-app/app/__pycache__/urls.cpython-311.pyc b/quiz-app/app/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000..c7ef74d Binary files /dev/null and b/quiz-app/app/__pycache__/urls.cpython-311.pyc differ diff --git a/quiz-app/app/__pycache__/wsgi.cpython-311.pyc b/quiz-app/app/__pycache__/wsgi.cpython-311.pyc new file mode 100644 index 0000000..3af82de Binary files /dev/null and b/quiz-app/app/__pycache__/wsgi.cpython-311.pyc differ diff --git a/quiz-app/app/settings.py b/quiz-app/app/settings.py new file mode 100644 index 0000000..a81eea4 --- /dev/null +++ b/quiz-app/app/settings.py @@ -0,0 +1,145 @@ +from environs import Env +import os + +# pour utilisation des variables d'environnement pour les données sensibles +envi = Env() +Env.read_env() + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'cgx%r24k2zk(+1*1g)y=+60^2x$)_qj4tdkn(yk19z$v!^yo=_' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +# valide en local et pour docker +ALLOWED_HOSTS = ['*'] + +LOGIN_REDIRECT_URL = '/' +LOGOUT_REDIRECT_URL = '/' + + +# Application definition +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + # django_quiz apps + 'quiz', + 'multichoice', + 'true_false', + 'essay', + 'registration' +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'app.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'templates')], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'app.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/2.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/2.2/topics/i18n/ + +LANGUAGE_CODE = 'fr' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.2/howto/static-files/ + +STATIC_URL = '/static/' + + +# pour envoyer des mail dans un dossier local +# EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +# pour envoyer via GMAIL +#EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +#EMAIL_HOST = 'smtp.gmail.com' +#EMAIL_HOST_USER = envi('EMAIL_HOST_USER') +#EMAIL_HOST_PASSWORD = envi('EMAIL_HOST_PASSWORD') +#EMAIL_PORT = 587 +#EMAIL_USE_TLS = True +#LOGIN_REDIRECT_URL = '/' +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' +#DEFAULT_FROM_EMAIL = 'quizz du garage numerique' + + +## PRODUCTION CONFIG + +#SENDGRID_API_KEY = os.getenv('SENDGRID_API_KEY') +#EMAIL_HOST = 'smtp.sendgrid.net' +#EMAIL_HOST_USER = 'apikey' # this is exactly the value 'apikey' +#EMAIL_HOST_PASSWORD = SENDGRID_API_KEY +#EMAIL_PORT = 587 +#EMAIL_USE_TLS = True + diff --git a/quiz-app/app/static/css/style.css b/quiz-app/app/static/css/style.css new file mode 100644 index 0000000..3df87fc --- /dev/null +++ b/quiz-app/app/static/css/style.css @@ -0,0 +1,17 @@ +@import url('https://fonts.googleapis.com/css2?family=Play&display=swap'); + +/* Appliquer les polices aux éléments du document */ +body { + font-family: 'Play'; /* Aladin pour le corps du document */ + /* font-size: 1.5em; */ +} + +.button-link { + background: none; + border: none; + color: #497AA1; + text-decoration: underline; + cursor: pointer; + padding: 0; + font: inherit; +} \ No newline at end of file diff --git a/quiz-app/app/static/favicon.ico b/quiz-app/app/static/favicon.ico new file mode 100644 index 0000000..02fb53a Binary files /dev/null and b/quiz-app/app/static/favicon.ico differ diff --git a/quiz-app/app/static/images/wp.jpg b/quiz-app/app/static/images/wp.jpg new file mode 100644 index 0000000..bfb2c9f Binary files /dev/null and b/quiz-app/app/static/images/wp.jpg differ diff --git a/quiz-app/app/static/logo.png b/quiz-app/app/static/logo.png new file mode 100644 index 0000000..f9bba13 Binary files /dev/null and b/quiz-app/app/static/logo.png differ diff --git a/quiz-app/app/urls.py b/quiz-app/app/urls.py new file mode 100644 index 0000000..6fbac82 --- /dev/null +++ b/quiz-app/app/urls.py @@ -0,0 +1,8 @@ +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('accounts/', include('django.contrib.auth.urls')), + path('', include('quiz.urls')), +] diff --git a/quiz-app/app/wsgi.py b/quiz-app/app/wsgi.py new file mode 100644 index 0000000..319fd43 --- /dev/null +++ b/quiz-app/app/wsgi.py @@ -0,0 +1,7 @@ +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + +application = get_wsgi_application() diff --git a/quiz-app/essay/__pycache__/models.cpython-311.pyc b/quiz-app/essay/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..860afa6 Binary files /dev/null and b/quiz-app/essay/__pycache__/models.cpython-311.pyc differ diff --git a/quiz-app/essay/locale/de/LC_MESSAGES/django.mo b/quiz-app/essay/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000..abb4756 Binary files /dev/null and b/quiz-app/essay/locale/de/LC_MESSAGES/django.mo differ diff --git a/quiz-app/essay/locale/de/LC_MESSAGES/django.po b/quiz-app/essay/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000..48630bf --- /dev/null +++ b/quiz-app/essay/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,28 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-10-31 22:44+0000\n" +"PO-Revision-Date: 2019-03-07 21:13+0100\n" +"Last-Translator: Reiner Mayers\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Translated-Using: django-rosetta 0.7.6\n" +"Language-Team: \n" +"X-Generator: Poedit 2.0.6\n" + +#: apps/essay/models.py:26 +msgid "Essay style question" +msgstr "Aufsatzartige Frage" + +#: apps/essay/models.py:27 +msgid "Essay style questions" +msgstr "Aufsatzartige Frage" diff --git a/quiz-app/essay/locale/es_CO/LC_MESSAGES/django.mo b/quiz-app/essay/locale/es_CO/LC_MESSAGES/django.mo new file mode 100644 index 0000000..007ebc3 Binary files /dev/null and b/quiz-app/essay/locale/es_CO/LC_MESSAGES/django.mo differ diff --git a/quiz-app/essay/locale/es_CO/LC_MESSAGES/django.po b/quiz-app/essay/locale/es_CO/LC_MESSAGES/django.po new file mode 100644 index 0000000..85ee213 --- /dev/null +++ b/quiz-app/essay/locale/es_CO/LC_MESSAGES/django.po @@ -0,0 +1,26 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# , 2014. +msgid "" +msgstr "" +"Project-Id-Version: django-quiz-essay\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-10-02 16:32+0000\n" +"PO-Revision-Date: 2016-03-24 15:57-0500\n" +"Language-Team: American English \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 1.8.4\n" +"Last-Translator: \n" +"Language: es_CO\n" + +#: models.py:23 +msgid "Essay style question" +msgstr "Pregunta estilo ensayo" + +#: models.py:24 +msgid "Essay style questions" +msgstr "Preguntas estilo ensayo" diff --git a/quiz-app/essay/locale/it/LC_MESSAGES/django.mo b/quiz-app/essay/locale/it/LC_MESSAGES/django.mo new file mode 100644 index 0000000..4f11157 Binary files /dev/null and b/quiz-app/essay/locale/it/LC_MESSAGES/django.mo differ diff --git a/quiz-app/essay/locale/it/LC_MESSAGES/django.po b/quiz-app/essay/locale/it/LC_MESSAGES/django.po new file mode 100644 index 0000000..c9843ee --- /dev/null +++ b/quiz-app/essay/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,28 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-10-31 22:44+0000\n" +"PO-Revision-Date: 2015-10-31 22:15+0000\n" +"Last-Translator: b' <>'\n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Translated-Using: django-rosetta 0.7.6\n" + +#: apps/essay/models.py:26 +msgid "Essay style question" +msgstr "Tipo di domanda per la prova " + +#: apps/essay/models.py:27 +msgid "Essay style questions" +msgstr "Tipo di domande per la prova " diff --git a/quiz-app/essay/locale/ru/LC_MESSAGES/django.mo b/quiz-app/essay/locale/ru/LC_MESSAGES/django.mo new file mode 100644 index 0000000..2c649b0 Binary files /dev/null and b/quiz-app/essay/locale/ru/LC_MESSAGES/django.mo differ diff --git a/quiz-app/essay/locale/ru/LC_MESSAGES/django.po b/quiz-app/essay/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 0000000..c48d4ee --- /dev/null +++ b/quiz-app/essay/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,27 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# , 2014. +msgid "" +msgstr "" +"Project-Id-Version: django-quiz-essay\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-10-02 16:32+0000\n" +"PO-Revision-Date: 2015-08-21 19:40+0500\n" +"Last-Translator: \n" +"Language-Team: American English \n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Poedit 1.5.4\n" + +#: models.py:23 +msgid "Essay style question" +msgstr "Эссе" + +#: models.py:24 +msgid "Essay style questions" +msgstr "Эссе" diff --git a/quiz-app/essay/locale/zh_CN/LC_MESSAGES/django.mo b/quiz-app/essay/locale/zh_CN/LC_MESSAGES/django.mo new file mode 100644 index 0000000..3ec8b57 Binary files /dev/null and b/quiz-app/essay/locale/zh_CN/LC_MESSAGES/django.mo differ diff --git a/quiz-app/essay/locale/zh_CN/LC_MESSAGES/django.po b/quiz-app/essay/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 0000000..805ad2a --- /dev/null +++ b/quiz-app/essay/locale/zh_CN/LC_MESSAGES/django.po @@ -0,0 +1,28 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-10-31 22:44+0000\n" +"PO-Revision-Date: 2015-10-31 22:15+0000\n" +"Last-Translator: b' <>'\n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Translated-Using: django-rosetta 0.7.6\n" + +#: apps/essay/models.py:26 +msgid "Essay style question" +msgstr "论述题" + +#: apps/essay/models.py:27 +msgid "Essay style questions" +msgstr "论述题" diff --git a/quiz-app/essay/migrations/0001_initial.py b/quiz-app/essay/migrations/0001_initial.py new file mode 100644 index 0000000..b0dd781 --- /dev/null +++ b/quiz-app/essay/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-06-22 11:20 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('quiz', '__first__'), + ] + + operations = [ + migrations.CreateModel( + name='Essay_Question', + fields=[ + ('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')), + ], + options={ + 'verbose_name': 'Essay style question', + 'verbose_name_plural': 'Essay style questions', + }, + bases=('quiz.question',), + ), + ] diff --git a/quiz-app/essay/migrations/__init__.py b/quiz-app/essay/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/quiz-app/essay/migrations/__pycache__/0001_initial.cpython-311.pyc b/quiz-app/essay/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..60e8550 Binary files /dev/null and b/quiz-app/essay/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/quiz-app/essay/migrations/__pycache__/0001_initial.cpython-37.pyc b/quiz-app/essay/migrations/__pycache__/0001_initial.cpython-37.pyc new file mode 100644 index 0000000..82c455c Binary files /dev/null and b/quiz-app/essay/migrations/__pycache__/0001_initial.cpython-37.pyc differ diff --git a/quiz-app/essay/migrations/__pycache__/0001_initial.cpython-38.pyc b/quiz-app/essay/migrations/__pycache__/0001_initial.cpython-38.pyc new file mode 100644 index 0000000..5323c6e Binary files /dev/null and b/quiz-app/essay/migrations/__pycache__/0001_initial.cpython-38.pyc differ diff --git a/quiz-app/essay/migrations/__pycache__/0001_initial.cpython-39.pyc b/quiz-app/essay/migrations/__pycache__/0001_initial.cpython-39.pyc new file mode 100644 index 0000000..176fba5 Binary files /dev/null and b/quiz-app/essay/migrations/__pycache__/0001_initial.cpython-39.pyc differ diff --git a/quiz-app/essay/migrations/__pycache__/__init__.cpython-311.pyc b/quiz-app/essay/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..badaed7 Binary files /dev/null and b/quiz-app/essay/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/quiz-app/essay/migrations/__pycache__/__init__.cpython-37.pyc b/quiz-app/essay/migrations/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..5c35159 Binary files /dev/null and b/quiz-app/essay/migrations/__pycache__/__init__.cpython-37.pyc differ diff --git a/quiz-app/essay/migrations/__pycache__/__init__.cpython-38.pyc b/quiz-app/essay/migrations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..0bc9e84 Binary files /dev/null and b/quiz-app/essay/migrations/__pycache__/__init__.cpython-38.pyc differ diff --git a/quiz-app/essay/migrations/__pycache__/__init__.cpython-39.pyc b/quiz-app/essay/migrations/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..0af74dc Binary files /dev/null and b/quiz-app/essay/migrations/__pycache__/__init__.cpython-39.pyc differ diff --git a/quiz-app/essay/models.py b/quiz-app/essay/models.py new file mode 100644 index 0000000..7a8905a --- /dev/null +++ b/quiz-app/essay/models.py @@ -0,0 +1,29 @@ +from __future__ import unicode_literals +# from django.utils.encoding import python_2_unicode_compatible +from six import python_2_unicode_compatible +from django.utils.translation import gettext_lazy as _ + +from quiz.models import Question + + +@python_2_unicode_compatible +class Essay_Question(Question): + + def check_if_correct(self, guess): + return False + + def get_answers(self): + return False + + def get_answers_list(self): + return False + + def answer_choice_to_string(self, guess): + return str(guess) + + def __str__(self): + return self.content + + class Meta: + verbose_name = _("Essay style question") + verbose_name_plural = _("Essay style questions") diff --git a/quiz-app/essay/tests.py b/quiz-app/essay/tests.py new file mode 100644 index 0000000..28d8aef --- /dev/null +++ b/quiz-app/essay/tests.py @@ -0,0 +1,22 @@ +from django.test import TestCase + +from .models import Essay_Question + + +class TestEssayQuestionModel(TestCase): + def setUp(self): + self.essay = Essay_Question.objects.create(content="Tell me stuff", + explanation="Wow!") + + def test_always_false(self): + self.assertEqual(self.essay.check_if_correct('spam'), False) + self.assertEqual(self.essay.get_answers(), False) + self.assertEqual(self.essay.get_answers_list(), False) + + def test_returns_guess(self): + guess = "To be or not to be" + self.assertEqual(self.essay.answer_choice_to_string(guess), guess) + + def test_answer_to_string(self): + self.assertEqual('To be...', + self.essay.answer_choice_to_string('To be...')) diff --git a/quiz-app/manage.py b/quiz-app/manage.py new file mode 100755 index 0000000..5522795 --- /dev/null +++ b/quiz-app/manage.py @@ -0,0 +1,14 @@ +import os +import sys + +if __name__ == '__main__': + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) diff --git a/quiz-app/multichoice/__pycache__/models.cpython-311.pyc b/quiz-app/multichoice/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..1cde474 Binary files /dev/null and b/quiz-app/multichoice/__pycache__/models.cpython-311.pyc differ diff --git a/quiz-app/multichoice/locale/de/LC_MESSAGES/django.mo b/quiz-app/multichoice/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000..dac9710 Binary files /dev/null and b/quiz-app/multichoice/locale/de/LC_MESSAGES/django.mo differ diff --git a/quiz-app/multichoice/locale/de/LC_MESSAGES/django.po b/quiz-app/multichoice/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000..0fd2fcd --- /dev/null +++ b/quiz-app/multichoice/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,72 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-10-31 22:44+0000\n" +"PO-Revision-Date: 2019-03-07 21:13+0100\n" +"Last-Translator: Reiner Mayers\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Translated-Using: django-rosetta 0.7.6\n" +"Language-Team: \n" +"X-Generator: Poedit 2.0.6\n" + +#: apps/multichoice/models.py:9 apps/multichoice/models.py:65 +msgid "Content" +msgstr "Inhalt" + +#: apps/multichoice/models.py:10 +msgid "Random" +msgstr "Zufällig" + +#: apps/multichoice/models.py:11 +msgid "None" +msgstr "Kein" + +#: apps/multichoice/models.py:20 +msgid "The order in which multichoice answer options are displayed to the user" +msgstr "Reihenfolge in der Multichoice Antworten dem Benutzer angezeigt werden" + +#: apps/multichoice/models.py:23 +msgid "Answer Order" +msgstr "Antwort Reihenfolge" + +#: apps/multichoice/models.py:53 +msgid "Multiple Choice Question" +msgstr "Multiple Choice Frage" + +#: apps/multichoice/models.py:54 +msgid "Multiple Choice Questions" +msgstr "Multiple Choice Frage" + +#: apps/multichoice/models.py:59 +msgid "Question" +msgstr "Frage" + +#: apps/multichoice/models.py:63 +msgid "Enter the answer text that you want displayed" +msgstr "Geben Sie den Antworttext ein der angezeigt werden soll" + +#: apps/multichoice/models.py:69 +msgid "Is this a correct answer?" +msgstr "Ist diese Antwort richtig?" + +#: apps/multichoice/models.py:70 +msgid "Correct" +msgstr "Korrekt" + +#: apps/multichoice/models.py:76 +msgid "Answer" +msgstr "Antwort" + +#: apps/multichoice/models.py:77 +msgid "Answers" +msgstr "Antworten" diff --git a/quiz-app/multichoice/locale/es_CO/LC_MESSAGES/django.mo b/quiz-app/multichoice/locale/es_CO/LC_MESSAGES/django.mo new file mode 100644 index 0000000..17ef20f Binary files /dev/null and b/quiz-app/multichoice/locale/es_CO/LC_MESSAGES/django.mo differ diff --git a/quiz-app/multichoice/locale/es_CO/LC_MESSAGES/django.po b/quiz-app/multichoice/locale/es_CO/LC_MESSAGES/django.po new file mode 100644 index 0000000..ee23a7f --- /dev/null +++ b/quiz-app/multichoice/locale/es_CO/LC_MESSAGES/django.po @@ -0,0 +1,70 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# , 2014. +msgid "" +msgstr "" +"Project-Id-Version: django-quiz-multichoice\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-10-02 16:35+0000\n" +"PO-Revision-Date: 2016-03-24 16:00-0500\n" +"Language-Team: American English \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 1.8.4\n" +"Last-Translator: \n" +"Language: es_CO\n" + +#: models.py:7 models.py:61 +msgid "Content" +msgstr "Contenido" + +#: models.py:8 +msgid "Random" +msgstr "Aleatorio" + +#: models.py:9 +msgid "None" +msgstr "Ninguna" + +#: models.py:17 +msgid "The order in which multichoice answer options are displayed to the user" +msgstr "El orden en que las opciones de respuesta de elección múltiple se muestran al usuario" + +#: models.py:20 +msgid "Answer Order" +msgstr "Orden de las Respuestas" + +#: models.py:50 +msgid "Multiple Choice Question" +msgstr "Pregunta de opción múltiple" + +#: models.py:51 +msgid "Multiple Choice Questions" +msgstr "Preguntas de opción múltiple" + +#: models.py:55 +msgid "Question" +msgstr "Pregunta" + +#: models.py:59 +msgid "Enter the answer text that you want displayed" +msgstr "Ingrese el texto de la respuesta que desea mostrar" + +#: models.py:65 +msgid "Is this a correct answer?" +msgstr "¿Es esta una respuesta correcta?" + +#: models.py:66 +msgid "Correct" +msgstr "Correcto" + +#: models.py:72 +msgid "Answer" +msgstr "Respuesta" + +#: models.py:73 +msgid "Answers" +msgstr "Respuestas" diff --git a/quiz-app/multichoice/locale/it/LC_MESSAGES/django.mo b/quiz-app/multichoice/locale/it/LC_MESSAGES/django.mo new file mode 100644 index 0000000..b0da790 Binary files /dev/null and b/quiz-app/multichoice/locale/it/LC_MESSAGES/django.mo differ diff --git a/quiz-app/multichoice/locale/it/LC_MESSAGES/django.po b/quiz-app/multichoice/locale/it/LC_MESSAGES/django.po new file mode 100644 index 0000000..062c8b5 --- /dev/null +++ b/quiz-app/multichoice/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,74 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-10-31 22:44+0000\n" +"PO-Revision-Date: 2015-10-31 22:19+0000\n" +"Last-Translator: b' <>'\n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Translated-Using: django-rosetta 0.7.6\n" + +#: apps/multichoice/models.py:9 apps/multichoice/models.py:65 +msgid "Content" +msgstr "Contenuto" + +#: apps/multichoice/models.py:10 +msgid "Random" +msgstr "Casuale" + +#: apps/multichoice/models.py:11 +msgid "None" +msgstr "Nessuno" + +#: apps/multichoice/models.py:20 +msgid "The order in which multichoice answer options are displayed to the user" +msgstr "" +"Ordine in cui le risposte multiple disponibili vengono visualizzate " +"all'utente" + +#: apps/multichoice/models.py:23 +msgid "Answer Order" +msgstr "Ordine Risposte" + +#: apps/multichoice/models.py:53 +msgid "Multiple Choice Question" +msgstr "Domanda a risposta multipla" + +#: apps/multichoice/models.py:54 +msgid "Multiple Choice Questions" +msgstr "Domande a risposta multipla" + +#: apps/multichoice/models.py:59 +msgid "Question" +msgstr "Domanda" + +#: apps/multichoice/models.py:63 +msgid "Enter the answer text that you want displayed" +msgstr "Inserisci il testo della risposta che vuoi visualizzare" + +#: apps/multichoice/models.py:69 +msgid "Is this a correct answer?" +msgstr "Questa è la risposta corretta?" + +#: apps/multichoice/models.py:70 +msgid "Correct" +msgstr "Corretto" + +#: apps/multichoice/models.py:76 +msgid "Answer" +msgstr "Risposta" + +#: apps/multichoice/models.py:77 +msgid "Answers" +msgstr "Risposte" diff --git a/quiz-app/multichoice/locale/ru/LC_MESSAGES/django.mo b/quiz-app/multichoice/locale/ru/LC_MESSAGES/django.mo new file mode 100644 index 0000000..a1753d6 Binary files /dev/null and b/quiz-app/multichoice/locale/ru/LC_MESSAGES/django.mo differ diff --git a/quiz-app/multichoice/locale/ru/LC_MESSAGES/django.po b/quiz-app/multichoice/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 0000000..f856b33 --- /dev/null +++ b/quiz-app/multichoice/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,71 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# , 2014. +msgid "" +msgstr "" +"Project-Id-Version: django-quiz-multichoice\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-10-02 16:35+0000\n" +"PO-Revision-Date: 2015-08-21 19:39+0500\n" +"Last-Translator: Eugena Mihailikova \n" +"Language-Team: American English \n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Poedit 1.5.4\n" + +#: models.py:7 models.py:61 +msgid "Content" +msgstr "Содержание" + +#: models.py:8 +msgid "Random" +msgstr "Случайно" + +#: models.py:9 +msgid "None" +msgstr "Ничего" + +#: models.py:17 +msgid "The order in which multichoice answer options are displayed to the user" +msgstr "Порядок отображения вопросов" + +#: models.py:20 +msgid "Answer Order" +msgstr "Порядок вопросов" + +#: models.py:50 +msgid "Multiple Choice Question" +msgstr "Вопрос с несколькими вариантами ответов" + +#: models.py:51 +msgid "Multiple Choice Questions" +msgstr "Вопросы с несколькими вариантами ответов" + +#: models.py:55 +msgid "Question" +msgstr "Вопрос" + +#: models.py:59 +msgid "Enter the answer text that you want displayed" +msgstr "Введите текст ответа" + +#: models.py:65 +msgid "Is this a correct answer?" +msgstr "Это правильный ответ?" + +#: models.py:66 +msgid "Correct" +msgstr "Правильно" + +#: models.py:72 +msgid "Answer" +msgstr "Вопрос" + +#: models.py:73 +msgid "Answers" +msgstr "Вопросы" diff --git a/quiz-app/multichoice/locale/zh_CN/LC_MESSAGES/django.mo b/quiz-app/multichoice/locale/zh_CN/LC_MESSAGES/django.mo new file mode 100644 index 0000000..a2e640b Binary files /dev/null and b/quiz-app/multichoice/locale/zh_CN/LC_MESSAGES/django.mo differ diff --git a/quiz-app/multichoice/locale/zh_CN/LC_MESSAGES/django.po b/quiz-app/multichoice/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 0000000..1c7d991 --- /dev/null +++ b/quiz-app/multichoice/locale/zh_CN/LC_MESSAGES/django.po @@ -0,0 +1,72 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-10-31 22:44+0000\n" +"PO-Revision-Date: 2015-10-31 22:19+0000\n" +"Last-Translator: b' <>'\n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Translated-Using: django-rosetta 0.7.6\n" + +#: apps/multichoice/models.py:9 apps/multichoice/models.py:65 +msgid "Content" +msgstr "内容" + +#: apps/multichoice/models.py:10 +msgid "Random" +msgstr "随机" + +#: apps/multichoice/models.py:11 +msgid "None" +msgstr "无" + +#: apps/multichoice/models.py:20 +msgid "The order in which multichoice answer options are displayed to the user" +msgstr "向用户显示多选答案选项时的次序" + +#: apps/multichoice/models.py:23 +msgid "Answer Order" +msgstr "答案次序" + +#: apps/multichoice/models.py:53 +msgid "Multiple Choice Question" +msgstr "多选题" + +#: apps/multichoice/models.py:54 +msgid "Multiple Choice Questions" +msgstr "多选题" + +#: apps/multichoice/models.py:59 +msgid "Question" +msgstr "问题" + +#: apps/multichoice/models.py:63 +msgid "Enter the answer text that you want displayed" +msgstr "输入你想显示的答案文本" + +#: apps/multichoice/models.py:69 +msgid "Is this a correct answer?" +msgstr "这个答案正确吗?" + +#: apps/multichoice/models.py:70 +msgid "Correct" +msgstr "正确" + +#: apps/multichoice/models.py:76 +msgid "Answer" +msgstr "答案" + +#: apps/multichoice/models.py:77 +msgid "Answers" +msgstr "答案" diff --git a/quiz-app/multichoice/migrations/0001_initial.py b/quiz-app/multichoice/migrations/0001_initial.py new file mode 100644 index 0000000..2d856cb --- /dev/null +++ b/quiz-app/multichoice/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-06-22 11:20 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('quiz', '__first__'), + ] + + operations = [ + migrations.CreateModel( + name='Answer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content', models.CharField(help_text='Enter the answer text that you want displayed', max_length=1000, verbose_name='Content')), + ('correct', models.BooleanField(default=False, help_text='Is this a correct answer?', verbose_name='Correct')), + ], + options={ + 'verbose_name': 'Answer', + 'verbose_name_plural': 'Answers', + }, + ), + migrations.CreateModel( + name='MCQuestion', + fields=[ + ('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')), + ('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')), + ], + options={ + 'verbose_name': 'Multiple Choice Question', + 'verbose_name_plural': 'Multiple Choice Questions', + }, + bases=('quiz.question',), + ), + migrations.AddField( + model_name='answer', + name='question', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='multichoice.MCQuestion', verbose_name='Question'), + ), + ] diff --git a/quiz-app/multichoice/migrations/__init__.py b/quiz-app/multichoice/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/quiz-app/multichoice/migrations/__pycache__/0001_initial.cpython-311.pyc b/quiz-app/multichoice/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..df09073 Binary files /dev/null and b/quiz-app/multichoice/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/quiz-app/multichoice/migrations/__pycache__/0001_initial.cpython-37.pyc b/quiz-app/multichoice/migrations/__pycache__/0001_initial.cpython-37.pyc new file mode 100644 index 0000000..acd053b Binary files /dev/null and b/quiz-app/multichoice/migrations/__pycache__/0001_initial.cpython-37.pyc differ diff --git a/quiz-app/multichoice/migrations/__pycache__/0001_initial.cpython-38.pyc b/quiz-app/multichoice/migrations/__pycache__/0001_initial.cpython-38.pyc new file mode 100644 index 0000000..487fb92 Binary files /dev/null and b/quiz-app/multichoice/migrations/__pycache__/0001_initial.cpython-38.pyc differ diff --git a/quiz-app/multichoice/migrations/__pycache__/0001_initial.cpython-39.pyc b/quiz-app/multichoice/migrations/__pycache__/0001_initial.cpython-39.pyc new file mode 100644 index 0000000..c352128 Binary files /dev/null and b/quiz-app/multichoice/migrations/__pycache__/0001_initial.cpython-39.pyc differ diff --git a/quiz-app/multichoice/migrations/__pycache__/__init__.cpython-311.pyc b/quiz-app/multichoice/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..5a094a5 Binary files /dev/null and b/quiz-app/multichoice/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/quiz-app/multichoice/migrations/__pycache__/__init__.cpython-37.pyc b/quiz-app/multichoice/migrations/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..8d0d8b3 Binary files /dev/null and b/quiz-app/multichoice/migrations/__pycache__/__init__.cpython-37.pyc differ diff --git a/quiz-app/multichoice/migrations/__pycache__/__init__.cpython-38.pyc b/quiz-app/multichoice/migrations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..293e44a Binary files /dev/null and b/quiz-app/multichoice/migrations/__pycache__/__init__.cpython-38.pyc differ diff --git a/quiz-app/multichoice/migrations/__pycache__/__init__.cpython-39.pyc b/quiz-app/multichoice/migrations/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..c359270 Binary files /dev/null and b/quiz-app/multichoice/migrations/__pycache__/__init__.cpython-39.pyc differ diff --git a/quiz-app/multichoice/models.py b/quiz-app/multichoice/models.py new file mode 100644 index 0000000..ef7f462 --- /dev/null +++ b/quiz-app/multichoice/models.py @@ -0,0 +1,78 @@ +from __future__ import unicode_literals +# from django.utils.encoding import python_2_unicode_compatible +from six import python_2_unicode_compatible +from django.utils.translation import gettext_lazy as _ +from django.db import models +from quiz.models import Question + + +ANSWER_ORDER_OPTIONS = ( + ('content', _('Content')), + ('random', _('Random')), + ('none', _('None')) +) + + +class MCQuestion(Question): + + answer_order = models.CharField( + max_length=30, null=True, blank=True, + choices=ANSWER_ORDER_OPTIONS, + help_text=_("The order in which multichoice " + "answer options are displayed " + "to the user"), + verbose_name=_("Answer Order")) + + def check_if_correct(self, guess): + answer = Answer.objects.get(id=guess) + + if answer.correct is True: + return True + else: + return False + + def order_answers(self, queryset): + if self.answer_order == 'content': + return queryset.order_by('content') + if self.answer_order == 'random': + return queryset.order_by('?') + if self.answer_order == 'none': + return queryset.order_by() + return queryset + + def get_answers(self): + return self.order_answers(Answer.objects.filter(question=self)) + + def get_answers_list(self): + return [(answer.id, answer.content) for answer in + self.order_answers(Answer.objects.filter(question=self))] + + def answer_choice_to_string(self, guess): + return Answer.objects.get(id=guess).content + + class Meta: + verbose_name = _("Multiple Choice Question") + verbose_name_plural = _("Multiple Choice Questions") + + +@python_2_unicode_compatible +class Answer(models.Model): + question = models.ForeignKey(MCQuestion, verbose_name=_("Question"), on_delete=models.CASCADE) + + content = models.CharField(max_length=1000, + blank=False, + help_text=_("Enter the answer text that " + "you want displayed"), + verbose_name=_("Content")) + + correct = models.BooleanField(blank=False, + default=False, + help_text=_("Is this a correct answer?"), + verbose_name=_("Correct")) + + def __str__(self): + return self.content + + class Meta: + verbose_name = _("Answer") + verbose_name_plural = _("Answers") diff --git a/quiz-app/multichoice/static/logo.png b/quiz-app/multichoice/static/logo.png new file mode 100644 index 0000000..f9bba13 Binary files /dev/null and b/quiz-app/multichoice/static/logo.png differ diff --git a/quiz-app/multichoice/tests.py b/quiz-app/multichoice/tests.py new file mode 100644 index 0000000..1b66d26 --- /dev/null +++ b/quiz-app/multichoice/tests.py @@ -0,0 +1,52 @@ +from django.core.files.base import ContentFile +from django.db.models.fields.files import ImageFieldFile +from django.test import TestCase +from django.utils.six import StringIO + +from .models import MCQuestion, Answer + + +class TestMCQuestionModel(TestCase): + def setUp(self): + self.q = MCQuestion.objects.create(id=1, + content=("WHAT is the airspeed" + + "velocity of an unladen" + + "swallow?"), + explanation="I, I don't know that!") + + self.answer1 = Answer.objects.create(id=123, + question=self.q, + content="African", + correct=False) + + self.answer2 = Answer.objects.create(id=456, + question=self.q, + content="European", + correct=True) + + def test_answers(self): + answers = Answer.objects.filter(question=self.q) + correct_a = Answer.objects.get(question=self.q, + correct=True) + answers_by_method = self.q.get_answers() + + self.assertEqual(answers.count(), 2) + self.assertEqual(correct_a.content, "European") + self.assertEqual(self.q.check_if_correct(123), False) + self.assertEqual(self.q.check_if_correct(456), True) + self.assertEqual(answers_by_method.count(), 2) + self.assertEqual(self.q.answer_choice_to_string(123), + self.answer1.content) + + def test_figure(self): + # http://stackoverflow.com/a/2473445/1694979 + imgfile = StringIO( + 'GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,' + '\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;') + imgfile.name = 'test_img_file.gif' + + self.q.figure.save('image', ContentFile(imgfile.read())) + self.assertIsInstance(self.q.figure, ImageFieldFile) + + def test_answer_to_string(self): + self.assertEqual('African', self.q.answer_choice_to_string(123)) diff --git a/quiz-app/quiz/__pycache__/admin.cpython-311.pyc b/quiz-app/quiz/__pycache__/admin.cpython-311.pyc new file mode 100644 index 0000000..bb71a40 Binary files /dev/null and b/quiz-app/quiz/__pycache__/admin.cpython-311.pyc differ diff --git a/quiz-app/quiz/__pycache__/forms.cpython-311.pyc b/quiz-app/quiz/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..a6f714c Binary files /dev/null and b/quiz-app/quiz/__pycache__/forms.cpython-311.pyc differ diff --git a/quiz-app/quiz/__pycache__/models.cpython-311.pyc b/quiz-app/quiz/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..bb70ccb Binary files /dev/null and b/quiz-app/quiz/__pycache__/models.cpython-311.pyc differ diff --git a/quiz-app/quiz/__pycache__/urls.cpython-311.pyc b/quiz-app/quiz/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000..d528f55 Binary files /dev/null and b/quiz-app/quiz/__pycache__/urls.cpython-311.pyc differ diff --git a/quiz-app/quiz/__pycache__/views.cpython-311.pyc b/quiz-app/quiz/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000..2d644cd Binary files /dev/null and b/quiz-app/quiz/__pycache__/views.cpython-311.pyc differ diff --git a/quiz-app/quiz/admin.py b/quiz-app/quiz/admin.py new file mode 100644 index 0000000..173ca4c --- /dev/null +++ b/quiz-app/quiz/admin.py @@ -0,0 +1,112 @@ +from django import forms +from django.contrib import admin +from django.contrib.admin.widgets import FilteredSelectMultiple +from django.utils.translation import gettext_lazy as _ + +from .models import Quiz, Category, SubCategory, Progress, Question +from multichoice.models import MCQuestion, Answer +from true_false.models import TF_Question +from essay.models import Essay_Question + + +class AnswerInline(admin.TabularInline): + model = Answer + + +class QuizAdminForm(forms.ModelForm): + """ + below is from + http://stackoverflow.com/questions/11657682/ + django-admin-interface-using-horizontal-filter-with- + inline-manytomany-field + """ + + class Meta: + model = Quiz + exclude = [] + + questions = forms.ModelMultipleChoiceField( + queryset=Question.objects.all().select_subclasses(), + required=False, + label=_("Questions"), + widget=FilteredSelectMultiple( + verbose_name=_("Questions"), + is_stacked=False)) + + def __init__(self, *args, **kwargs): + super(QuizAdminForm, self).__init__(*args, **kwargs) + if self.instance.pk: + self.fields['questions'].initial =\ + self.instance.question_set.all().select_subclasses() + + def save(self, commit=True): + quiz = super(QuizAdminForm, self).save(commit=False) + quiz.save() + quiz.question_set.set(self.cleaned_data['questions']) + self.save_m2m() + return quiz + + +class QuizAdmin(admin.ModelAdmin): + form = QuizAdminForm + + list_display = ('title', 'category', ) + list_filter = ('category',) + search_fields = ('description', 'category', ) + + +class CategoryAdmin(admin.ModelAdmin): + search_fields = ('category', ) + + +class SubCategoryAdmin(admin.ModelAdmin): + search_fields = ('sub_category', ) + list_display = ('sub_category', 'category',) + list_filter = ('category',) + + +class MCQuestionAdmin(admin.ModelAdmin): + list_display = ('content', 'category', 'sub_category') + list_filter = ('category', 'sub_category') + fields = ('content', 'category', 'sub_category', + 'figure', 'quiz', 'explanation', 'answer_order') + + search_fields = ('content', 'explanation') + filter_horizontal = ('quiz',) + + inlines = [AnswerInline] + + +class ProgressAdmin(admin.ModelAdmin): + """ + to do: + create a user section + """ + #search_fields = ('user', 'score', ) + #fields = ('user_answers',) + + +class TFQuestionAdmin(admin.ModelAdmin): + list_display = ('content', 'category', ) + list_filter = ('category',) + fields = ('content', 'category', 'sub_category', + 'figure', 'quiz', 'explanation', 'correct',) + + search_fields = ('content', 'explanation') + filter_horizontal = ('quiz',) + + +class EssayQuestionAdmin(admin.ModelAdmin): + list_display = ('content', 'category', ) + list_filter = ('category',) + fields = ('content', 'category', 'sub_category', 'quiz', 'explanation', ) + search_fields = ('content', 'explanation') + filter_horizontal = ('quiz',) + +admin.site.register(Quiz, QuizAdmin) +admin.site.register(Category, CategoryAdmin) +admin.site.register(SubCategory, SubCategoryAdmin) +admin.site.register(MCQuestion, MCQuestionAdmin) +admin.site.register(Progress, ProgressAdmin) +admin.site.register(TF_Question, TFQuestionAdmin) +admin.site.register(Essay_Question, EssayQuestionAdmin) diff --git a/quiz-app/quiz/forms.py b/quiz-app/quiz/forms.py new file mode 100644 index 0000000..c7ddf69 --- /dev/null +++ b/quiz-app/quiz/forms.py @@ -0,0 +1,17 @@ +from django import forms +from django.forms.widgets import RadioSelect, Textarea + + +class QuestionForm(forms.Form): + def __init__(self, question, *args, **kwargs): + super(QuestionForm, self).__init__(*args, **kwargs) + choice_list = [x for x in question.get_answers_list()] + self.fields["answers"] = forms.ChoiceField(choices=choice_list, + widget=RadioSelect) + + +class EssayForm(forms.Form): + def __init__(self, question, *args, **kwargs): + super(EssayForm, self).__init__(*args, **kwargs) + self.fields["answers"] = forms.CharField( + widget=Textarea(attrs={'style': 'width:100%'})) diff --git a/quiz-app/quiz/locale/de/LC_MESSAGES/django.mo b/quiz-app/quiz/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000..c7a20b0 Binary files /dev/null and b/quiz-app/quiz/locale/de/LC_MESSAGES/django.mo differ diff --git a/quiz-app/quiz/locale/de/LC_MESSAGES/django.po b/quiz-app/quiz/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000..0a62833 --- /dev/null +++ b/quiz-app/quiz/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,476 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-10-31 22:44+0000\n" +"PO-Revision-Date: 2019-03-07 21:14+0100\n" +"Last-Translator: Reiner Mayers\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Translated-Using: django-rosetta 0.7.6\n" +"Language-Team: \n" +"X-Generator: Poedit 2.0.6\n" + +#: apps/quiz/models.py:30 apps/quiz/models.py:37 apps/quiz/models.py:53 +#: apps/quiz/models.py:83 apps/quiz/models.py:545 +#: apps/quiz/templates/progress.html:19 +#: apps/quiz/templates/quiz/quiz_detail.html:9 +#: apps/quiz/templates/quiz/quiz_list.html:13 +#: apps/quiz/templates/quiz/sitting_detail.html:10 +msgid "Category" +msgstr "Kategorie" + +#: apps/quiz/models.py:38 +msgid "Categories" +msgstr "Kategorien" + +#: apps/quiz/models.py:48 apps/quiz/models.py:58 apps/quiz/models.py:550 +msgid "Sub-Category" +msgstr "Unterkategorie" + +#: apps/quiz/models.py:59 +msgid "Sub-Categories" +msgstr "Unterkategorien" + +#: apps/quiz/models.py:69 apps/quiz/templates/quiz/quiz_list.html:12 +msgid "Title" +msgstr "Titel" + +#: apps/quiz/models.py:73 +msgid "Description" +msgstr "Beschreibung" + +#: apps/quiz/models.py:74 +msgid "a description of the quiz" +msgstr "Eine Beschreibung des Quiz" + +#: apps/quiz/models.py:78 +msgid "a user friendly url" +msgstr "Eine benutzerfreundliche url" + +#: apps/quiz/models.py:79 +msgid "user friendly url" +msgstr "benutzerfreundliche url" + +#: apps/quiz/models.py:87 +msgid "Random Order" +msgstr "Zufällige Reihenfolge" + +#: apps/quiz/models.py:88 +msgid "Display the questions in a random order or as they are set?" +msgstr "Fragen zufällig anzeigen oder in der Eingabereihenfolge?" + +#: apps/quiz/models.py:93 +msgid "Max Questions" +msgstr "Maximale Anzahl der Fragen" + +#: apps/quiz/models.py:94 +msgid "Number of questions to be answered on each attempt." +msgstr "Anzahl der Fragen die bei jedem Versuch gestellt werden." + +#: apps/quiz/models.py:98 +msgid "" +"Correct answer is NOT shown after question. Answers displayed at the end." +msgstr "" +"Die Richtige Antwort wird nicht nach der Frage angezeigt sondern am Ende." + +#: apps/quiz/models.py:100 +msgid "Answers at end" +msgstr "Antworten am Ende" + +#: apps/quiz/models.py:104 +msgid "" +"If yes, the result of each attempt by a user will be stored. Necessary for " +"marking." +msgstr "" +"Wenn ja, wird das Ergebnis jeden Versuchs gespeichert. Zum markieren ist die " +"notwendig." + +#: apps/quiz/models.py:107 +msgid "Exam Paper" +msgstr "Prüfungsarbeit" + +#: apps/quiz/models.py:111 +msgid "" +"If yes, only one attempt by a user will be permitted. Non users cannot sit " +"this exam." +msgstr "" +"Wenn ja wird nur ein Versuch pro Anwender zugelassen. Nur angemeldete " +"Anwender können diese Prüfung machen." + +#: apps/quiz/models.py:114 +msgid "Single Attempt" +msgstr "Nur ein Versuch" + +#: apps/quiz/models.py:118 +msgid "Percentage required to pass exam." +msgstr "Prozent zum bestehen der Prüfung." + +#: apps/quiz/models.py:122 +msgid "Displayed if user passes." +msgstr "Angezeigt wenn der Anwender besteht." + +#: apps/quiz/models.py:123 +msgid "Success Text" +msgstr "Text für den Erfolg" + +#: apps/quiz/models.py:126 +msgid "Fail Text" +msgstr "Text für den Misserfolg" + +#: apps/quiz/models.py:127 +msgid "Displayed if user fails." +msgstr "Wird angezeigt wenn der Anwender durchfällt." + +#: apps/quiz/models.py:131 +msgid "Draft" +msgstr "Entwurf" + +#: apps/quiz/models.py:132 +msgid "" +"If yes, the quiz is not displayed in the quiz list and can only be taken by " +"users who can edit quizzes." +msgstr "" +"Wenn ja wird die Prüfung nicht in der Prüfungsliste angezeigt und kann nur " +"von Anwendern ausgeführt werden welche Prüfungen bearbeiten können." + +#: apps/quiz/models.py:152 apps/quiz/models.py:372 apps/quiz/models.py:541 +#: apps/quiz/templates/quiz/sitting_list.html:14 +msgid "Quiz" +msgstr "Quiz" + +#: apps/quiz/models.py:153 +msgid "Quizzes" +msgstr "Quizze" + +#: apps/quiz/models.py:192 apps/quiz/models.py:370 +#: apps/quiz/templates/quiz/sitting_detail.html:13 +#: apps/quiz/templates/quiz/sitting_list.html:13 +msgid "User" +msgstr "Anwender" + +#: apps/quiz/models.py:195 apps/quiz/templates/progress.html:60 +#: apps/quiz/templates/quiz/sitting_detail.html:15 +#: apps/quiz/templates/quiz/sitting_list.html:16 +msgid "Score" +msgstr "Punkte" + +#: apps/quiz/models.py:200 +msgid "User Progress" +msgstr "Anwender Fortschritt" + +#: apps/quiz/models.py:201 +msgid "User progress records" +msgstr "Fortschrittsaufzeichnung des Anwenders" + +#: apps/quiz/models.py:261 +msgid "error" +msgstr "Fehler" + +#: apps/quiz/models.py:261 +msgid "category does not exist or invalid score" +msgstr "Diese Kategorie existiert nicht oder das Ergebnis ist ungültig" + +#: apps/quiz/models.py:375 +msgid "Question Order" +msgstr "Reihenfolge der Fragen" + +#: apps/quiz/models.py:378 +msgid "Question List" +msgstr "Liste der Fragen" + +#: apps/quiz/models.py:381 +msgid "Incorrect questions" +msgstr "Falsche Antworten" + +#: apps/quiz/models.py:383 +msgid "Current Score" +msgstr "Aktueller Punktestand" + +#: apps/quiz/models.py:386 +msgid "Complete" +msgstr "Vollständig" + +#: apps/quiz/models.py:389 +msgid "User Answers" +msgstr "Antworten des Anwenders" + +#: apps/quiz/models.py:392 +msgid "Start" +msgstr "Anfang" + +#: apps/quiz/models.py:394 +msgid "End" +msgstr "Ende" + +#: apps/quiz/models.py:399 +msgid "Can see completed exams." +msgstr "Kann abgelegte Prüfungen sehen." + +#: apps/quiz/models.py:557 +msgid "Figure" +msgstr "Abbildung" + +#: apps/quiz/models.py:561 +msgid "Enter the question text that you want displayed" +msgstr "Geben Sie die Frage ein welche angezeigt werden soll" + +#: apps/quiz/models.py:563 apps/quiz/models.py:575 +#: apps/quiz/templates/question.html:47 +#: apps/quiz/templates/quiz/sitting_detail.html:21 +msgid "Question" +msgstr "Frage" + +#: apps/quiz/models.py:567 +msgid "Explanation to be shown after the question has been answered." +msgstr "Erklärung welche nach der Antwort angezeigt wird." + +#: apps/quiz/models.py:570 apps/quiz/templates/question.html:32 +#: apps/quiz/templates/result.html:21 apps/quiz/templates/result.html.py:87 +msgid "Explanation" +msgstr "Erklärung" + +#: apps/quiz/models.py:576 +msgid "Questions" +msgstr "Fragen" + +#: apps/quiz/templates/base.html:7 +msgid "Example Quiz Website" +msgstr "Beispiel Quiz Webseite" + +#: apps/quiz/templates/correct_answer.html:6 +msgid "You answered the above question incorrectly" +msgstr "Sie haben die obenstehende Frage falsch beantwortet" + +#: apps/quiz/templates/correct_answer.html:16 +msgid "This is the correct answer" +msgstr "Dies ist die richtige Antwort" + +#: apps/quiz/templates/correct_answer.html:23 +msgid "This was your answer." +msgstr "Dies war Ihre Antwort." + +#: apps/quiz/templates/progress.html:6 +msgid "Progress Page" +msgstr "Fortschrittsseite" + +#: apps/quiz/templates/progress.html:7 +msgid "User Progress Page" +msgstr "Fortschrittsseite des Anwenders" + +#: apps/quiz/templates/progress.html:13 +msgid "Question Category Scores" +msgstr "Punkte in dieser Fragenkategorie" + +#: apps/quiz/templates/progress.html:20 +msgid "Correctly answererd" +msgstr "Die Antwort ist richtig" + +#: apps/quiz/templates/progress.html:21 +msgid "Incorrect" +msgstr "Falsch" + +#: apps/quiz/templates/progress.html:50 +msgid "Previous exam papers" +msgstr "Vorhergehende Prüfung" + +#: apps/quiz/templates/progress.html:52 +msgid "Below are the results of exams that you have sat." +msgstr "Im Folgenden sehen Sie die Ergebnisse Ihrer Prüfungen." + +#: apps/quiz/templates/progress.html:59 +msgid "Quiz Title" +msgstr "Titel des Quiz" + +#: apps/quiz/templates/progress.html:61 +msgid "Possible Score" +msgstr "Mögliche Punkte" + +#: apps/quiz/templates/question.html:13 apps/quiz/templates/result.html:13 +msgid "The previous question" +msgstr "Die vorherige Frage" + +#: apps/quiz/templates/question.html:22 +msgid "Your answer was" +msgstr "Ihre Antwort war" + +#: apps/quiz/templates/question.html:47 +msgid "of" +msgstr "von" + +#: apps/quiz/templates/question.html:52 +msgid "Question category" +msgstr "Fragenkategorie" + +#: apps/quiz/templates/question.html:74 +msgid "Check" +msgstr "Überprüfung" + +#: apps/quiz/templates/quiz/category_list.html:3 +#: apps/quiz/templates/quiz/quiz_list.html:3 +#: apps/quiz/templates/quiz/sitting_list.html:3 +msgid "All Quizzes" +msgstr "Alle Quizze" + +#: apps/quiz/templates/quiz/category_list.html:6 +msgid "Category list" +msgstr "Liste der Kategorien" + +#: apps/quiz/templates/quiz/quiz_detail.html:11 +msgid "You will only get one attempt at this quiz" +msgstr "Sie haben nur einen Versuch bei dieser Prüfung" + +#: apps/quiz/templates/quiz/quiz_detail.html:16 +msgid "Start quiz" +msgstr "Quiz beginnen" + +#: apps/quiz/templates/quiz/quiz_list.html:6 +msgid "List of quizzes" +msgstr "Liste der Quizze" + +#: apps/quiz/templates/quiz/quiz_list.html:14 +msgid "Exam" +msgstr "Prüfung" + +#: apps/quiz/templates/quiz/quiz_list.html:15 +msgid "Single attempt" +msgstr "Einzelversuch" + +#: apps/quiz/templates/quiz/quiz_list.html:31 +#: apps/quiz/templates/quiz/sitting_list.html:42 +msgid "View details" +msgstr "Details ansehen" + +#: apps/quiz/templates/quiz/quiz_list.html:41 +msgid "There are no available quizzes" +msgstr "Es sind keine Prüfungen verfügbar" + +#: apps/quiz/templates/quiz/sitting_detail.html:5 +msgid "Result of" +msgstr "Resultat von" + +#: apps/quiz/templates/quiz/sitting_detail.html:5 +msgid "for" +msgstr "für" + +#: apps/quiz/templates/quiz/sitting_detail.html:9 +msgid "Quiz title" +msgstr "Quiztitel" + +#: apps/quiz/templates/quiz/sitting_detail.html:14 +#: apps/quiz/templates/quiz/sitting_list.html:15 +msgid "Completed" +msgstr "Vollständig" + +#: apps/quiz/templates/quiz/sitting_detail.html:22 +msgid "User answer" +msgstr "Anwender Antwort" + +#: apps/quiz/templates/quiz/sitting_detail.html:41 +msgid "incorrect" +msgstr "falsch" + +#: apps/quiz/templates/quiz/sitting_detail.html:43 +msgid "Correct" +msgstr "Richtig" + +#: apps/quiz/templates/quiz/sitting_detail.html:49 +msgid "Toggle whether correct" +msgstr "Markieren wenn richtig" + +#: apps/quiz/templates/quiz/sitting_list.html:6 +msgid "List of complete exams" +msgstr "Liste der abgelegten Prüfungen" + +#: apps/quiz/templates/quiz/sitting_list.html:28 +msgid "Filter" +msgstr "Filter" + +#: apps/quiz/templates/quiz/sitting_list.html:52 +msgid "There are no matching quizzes" +msgstr "Es gibt keine übereinstimmenden Prüfungen" + +#: apps/quiz/templates/result.html:7 +msgid "Exam Results for" +msgstr "Prüfungsergebnisse für" + +#: apps/quiz/templates/result.html:32 +msgid "Exam results" +msgstr "Prüfungsergebnisse" + +#: apps/quiz/templates/result.html:34 +msgid "Exam title" +msgstr "Prüfungstitel" + +#: apps/quiz/templates/result.html:38 +msgid "You answered" +msgstr "Ihre Antwort" + +#: apps/quiz/templates/result.html:38 +msgid "questions correctly out of" +msgstr "Fragen richtig von " + +#: apps/quiz/templates/result.html:38 +msgid "giving you" +msgstr "Sie erhalten" + +#: apps/quiz/templates/result.html:38 +msgid "percent correct" +msgstr "Prozent korrekt" + +#: apps/quiz/templates/result.html:48 +msgid "Review the questions below and try the exam again in the future" +msgstr "Sehen Sie sich die folgenden Fragen an und wiederholen Sie die Prüfung" + +#: apps/quiz/templates/result.html:52 +msgid "" +"The result of this exam will be stored in your progress section so you can " +"review and monitor your progression" +msgstr "" +"Das Resultat dieser Prüfung wird gespeichert und Sie können sich Ihren " +"Fortschritt ansehen" + +#: apps/quiz/templates/result.html:66 +msgid "Your session score is" +msgstr "Der Punktestand dieser Sitzung ist" + +#: apps/quiz/templates/result.html:66 +msgid "out of a possible" +msgstr "von möglichen" + +#: apps/quiz/templates/result.html:84 +msgid "Your answer" +msgstr "Ihre Antwort" + +#: apps/quiz/templates/single_complete.html:13 +msgid "You have already sat this exam and only one sitting is permitted" +msgstr "" +"Sie haben diese Prüfung schon abgelegt und dies kann nur einmal erfolgen" + +#: apps/quiz/templates/single_complete.html:15 +msgid "This exam is only accessible to signed in users" +msgstr "Diese Prüfung ist angemeldeten Anwendern vorbehalten" + +#: apps/quiz/templates/view_quiz_category.html:3 +msgid "Quizzes related to" +msgstr "Quizze bezogen auf" + +#: apps/quiz/templates/view_quiz_category.html:6 +msgid "Quizzes in the" +msgstr "Quizze in der" + +#: apps/quiz/templates/view_quiz_category.html:6 +msgid "category" +msgstr "Kategorie" + +#: apps/quiz/templates/view_quiz_category.html:20 +msgid "There are no quizzes" +msgstr "Es gibt keine Quizze" diff --git a/quiz-app/quiz/locale/es_CO/LC_MESSAGES/django.mo b/quiz-app/quiz/locale/es_CO/LC_MESSAGES/django.mo new file mode 100644 index 0000000..383bc49 Binary files /dev/null and b/quiz-app/quiz/locale/es_CO/LC_MESSAGES/django.mo differ diff --git a/quiz-app/quiz/locale/es_CO/LC_MESSAGES/django.po b/quiz-app/quiz/locale/es_CO/LC_MESSAGES/django.po new file mode 100644 index 0000000..099afe6 --- /dev/null +++ b/quiz-app/quiz/locale/es_CO/LC_MESSAGES/django.po @@ -0,0 +1,473 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: django-quiz\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-01-01 11:37+0400\n" +"PO-Revision-Date: 2016-03-24 16:26-0500\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 1.8.4\n" +"Language-Team: \n" +"Last-Translator: \n" +"Language: es_CO\n" + +#: admin.py:31 admin.py:33 models.py:576 +msgid "Questions" +msgstr "Preguntas" + +#: models.py:30 models.py:37 models.py:53 models.py:83 models.py:545 +#: templates/progress.html:19 templates/quiz/quiz_detail.html:9 +#: templates/quiz/quiz_list.html:13 templates/quiz/sitting_detail.html:10 +msgid "Category" +msgstr "Categoría" + +#: models.py:38 +msgid "Categories" +msgstr "Categorías" + +#: models.py:48 models.py:58 models.py:550 +msgid "Sub-Category" +msgstr "Sub-Categoría" + +#: models.py:59 +msgid "Sub-Categories" +msgstr "Sub-Categorías" + +#: models.py:69 templates/quiz/quiz_list.html:12 +msgid "Title" +msgstr "Título" + +#: models.py:73 +msgid "Description" +msgstr "Descripción" + +#: models.py:74 +msgid "a description of the quiz" +msgstr "una descripción del quiz" + +#: models.py:78 +msgid "a user friendly url" +msgstr "una url amigable para el usuario" + +#: models.py:79 +msgid "user friendly url" +msgstr "url amigable para el usuario" + +#: models.py:87 +msgid "Random Order" +msgstr "Orden Aleatorio" + +#: models.py:88 +msgid "Display the questions in a random order or as they are set?" +msgstr "¿Muestra las preguntas en un orden aleatorio o como se crearon?" + +#: models.py:93 +msgid "Max Questions" +msgstr "Número Máximo de Preguntas" + +#: models.py:94 +msgid "Number of questions to be answered on each attempt." +msgstr "Número de preguntas a ser resueltas por cada intento" + +#: models.py:98 +msgid "" +"Correct answer is NOT shown after question. Answers displayed at the end." +msgstr "" +"Respuestas correctas NO serán mostradas después de la pregunta. Las " +"respuestas se mostrarán al final" + +#: models.py:100 +msgid "Answers at end" +msgstr "Respuestas al final" + +#: models.py:104 +msgid "" +"If yes, the result of each attempt by a user will be stored. Necessary for " +"marking." +msgstr "" +"Si escoge si, el resultado de cada intento por usuario será guardado. " +"Necesario para marcar" + +#: models.py:107 +msgid "Exam Paper" +msgstr "Papel del Exámen" + +#: models.py:111 +msgid "" +"If yes, only one attempt by a user will be permitted. Non users cannot sit " +"this exam." +msgstr "" +"Si escoge si, solo un intento será permitido por usuario. Los que no sean " +"usuarios no tomarán este exámen" + +#: models.py:114 +msgid "Single Attempt" +msgstr "Un Intento" + +#: apps/quiz/models.py:116 +msgid "Pass Mark" +msgstr "Mínimo exigido para aprobar" + +#: models.py:118 +msgid "Percentage required to pass exam." +msgstr "Porcentaje requerido para aprobar el examen" + +#: models.py:122 +msgid "Displayed if user passes." +msgstr "Mostrar si el usuario pasa" + +#: models.py:123 +msgid "Success Text" +msgstr "Texto de Éxito" + +#: models.py:126 +msgid "Fail Text" +msgstr "Texto de Fracaso" + +#: models.py:127 +msgid "Displayed if user fails." +msgstr "Mostrar si el usuario fracasa" + +#: models.py:131 +msgid "Draft" +msgstr "Borrador" + +#: models.py:132 +msgid "" +"If yes, the quiz is not displayed in the quiz list and can only be taken by " +"users who can edit quizzes." +msgstr "" +"Si escoge si, este quiz no será mostrado en la lista de quizes y solo podrá " +"ser tomado por usuarios que puedan editar quizes" + +#: models.py:152 models.py:372 models.py:541 +#: templates/quiz/sitting_list.html:14 +msgid "Quiz" +msgstr "Quiz" + +#: models.py:153 +msgid "Quizzes" +msgstr "Quizes" + +#: models.py:192 models.py:370 templates/quiz/sitting_detail.html:13 +#: templates/quiz/sitting_list.html:13 +msgid "User" +msgstr "Usuario" + +#: models.py:195 templates/progress.html:60 +#: templates/quiz/sitting_detail.html:15 templates/quiz/sitting_list.html:16 +msgid "Score" +msgstr "Puntaje" + +#: models.py:200 +msgid "User Progress" +msgstr "Progreso de Usuario" + +#: models.py:201 +msgid "User progress records" +msgstr "Registros de progreso del usuario" + +#: models.py:261 +msgid "error" +msgstr "error" + +#: models.py:261 +msgid "category does not exist or invalid score" +msgstr "categoría no existe o puntaje inválido" + +#: models.py:375 +msgid "Question Order" +msgstr "Orden de las Preguntas" + +#: models.py:378 +msgid "Question List" +msgstr "Lista de Preguntas" + +#: models.py:381 +msgid "Incorrect questions" +msgstr "Preguntas Incorrectas" + +#: models.py:383 +msgid "Current Score" +msgstr "Puntaje Actual" + +#: models.py:386 +msgid "Complete" +msgstr "Completo" + +#: models.py:389 +msgid "User Answers" +msgstr "Respuestas de los Usuarios" + +#: models.py:392 +msgid "Start" +msgstr "Inicio" + +#: models.py:394 +msgid "End" +msgstr "Fin" + +#: models.py:399 +msgid "Can see completed exams." +msgstr "Puede ver exámenes completados" + +#: models.py:557 +msgid "Figure" +msgstr "Figura" + +#: models.py:561 +msgid "Enter the question text that you want displayed" +msgstr "Ingresa el texto de la pregunta que quiere que aparezca" + +#: models.py:563 models.py:575 templates/question.html:47 +#: templates/quiz/sitting_detail.html:21 +msgid "Question" +msgstr "Pregunta" + +#: models.py:567 +msgid "Explanation to be shown after the question has been answered." +msgstr "" +"Explicación que debe ser mostrada después de que la pregunta fue respondida" + +#: models.py:570 templates/question.html:32 templates/result.html:21 +#: templates/result.html.py:87 +msgid "Explanation" +msgstr "Explicación" + +#: templates/base.html:7 +msgid "Example Quiz Website" +msgstr "Sitio Web Ejemplo Quiz" + +#: templates/correct_answer.html:6 +msgid "You answered the above question incorrectly" +msgstr "Ha respondido a la pregunta anterior de forma incorrecta" + +#: templates/correct_answer.html:16 +msgid "This is the correct answer" +msgstr "Esta es la respuesta correcta" + +#: templates/correct_answer.html:23 +msgid "This was your answer." +msgstr "Esta fue tu respuesta" + +#: templates/progress.html:6 +msgid "Progress Page" +msgstr "Página de Progreso" + +#: templates/progress.html:7 +msgid "User Progress Page" +msgstr "Página Progreso de Usuario" + +#: templates/progress.html:13 +msgid "Question Category Scores" +msgstr "Pregunta Puntajes de las Categorías" + +#: templates/progress.html:20 +msgid "Correctly answererd" +msgstr "Correctamente contestada" + +#: templates/progress.html:21 +msgid "Incorrect" +msgstr "Incorrecta" + +#: templates/progress.html:50 +msgid "Previous exam papers" +msgstr "Exámenes anteriores" + +#: templates/progress.html:52 +msgid "Below are the results of exams that you have sat." +msgstr "Abajo están los resultados de los exámenes que se ha realizado." + +#: templates/progress.html:59 +msgid "Quiz Title" +msgstr "Título del Quiz" + +#: templates/progress.html:61 +msgid "Possible Score" +msgstr "Puntos posibles" + +#: templates/question.html:13 templates/result.html:13 +msgid "The previous question" +msgstr "La pregunta anterior" + +#: templates/question.html:22 +msgid "Your answer was" +msgstr "Tu respuesta fue" + +#: templates/question.html:47 +msgid "of" +msgstr "de" + +#: templates/question.html:52 +msgid "Question category" +msgstr "Categoría de la pregunta" + +#: templates/question.html:74 +msgid "Check" +msgstr "Verificar" + +#: templates/quiz/category_list.html:3 templates/quiz/quiz_list.html:3 +#: templates/quiz/sitting_list.html:3 +msgid "All Quizzes" +msgstr "Todos los Quizes" + +#: templates/quiz/category_list.html:6 +msgid "Category list" +msgstr "Lista de Categorías" + +#: templates/quiz/quiz_detail.html:11 +msgid "You will only get one attempt at this quiz" +msgstr "Solo se tendrá un intento para este quiz" + +#: templates/quiz/quiz_detail.html:16 +msgid "Start quiz" +msgstr "Empezar quiz" + +#: templates/quiz/quiz_list.html:6 +msgid "List of quizzes" +msgstr "Lista de quizes" + +#: templates/quiz/quiz_list.html:14 +msgid "Exam" +msgstr "Examen" + +#: templates/quiz/quiz_list.html:15 +msgid "Single attempt" +msgstr "Un solo intento" + +#: templates/quiz/quiz_list.html:31 templates/quiz/sitting_list.html:42 +msgid "View details" +msgstr "Ver detalles" + +#: templates/quiz/quiz_list.html:41 +msgid "There are no available quizzes" +msgstr "No hay quizes disponibles" + +#: templates/quiz/sitting_detail.html:5 +msgid "Result of" +msgstr "Resultado de" + +#: templates/quiz/sitting_detail.html:5 +msgid "for" +msgstr "para" + +#: templates/quiz/sitting_detail.html:9 +msgid "Quiz title" +msgstr "Título de Quiz" + +#: templates/quiz/sitting_detail.html:14 templates/quiz/sitting_list.html:15 +msgid "Completed" +msgstr "Completado" + +#: templates/quiz/sitting_detail.html:22 +msgid "User answer" +msgstr "Respuesta de usuario" + +#: templates/quiz/sitting_detail.html:41 +msgid "incorrect" +msgstr "Incorrecta" + +#: templates/quiz/sitting_detail.html:43 +msgid "Correct" +msgstr "Correcto" + +#: templates/quiz/sitting_detail.html:49 +msgid "Toggle whether correct" +msgstr "Alternar si es correcta" + +#: templates/quiz/sitting_list.html:6 +msgid "List of complete exams" +msgstr "Lista de examenes completos" + +#: templates/quiz/sitting_list.html:28 +msgid "Filter" +msgstr "Filtro" + +#: templates/quiz/sitting_list.html:52 +msgid "There are no matching quizzes" +msgstr "No hay quizes que coincidan" + +#: templates/result.html:7 +msgid "Exam Results for" +msgstr "Resultados del exámen de" + +#: templates/result.html:32 +msgid "Exam results" +msgstr "Resultados del exámen" + +#: templates/result.html:34 +msgid "Exam title" +msgstr "Título del exámen" + +#: templates/result.html:38 +msgid "You answered" +msgstr "Tu respondiste" + +#: templates/result.html:38 +msgid "questions correctly out of" +msgstr "preguntas correctas de cada" + +#: templates/result.html:38 +msgid "giving you" +msgstr "darle" + +#: templates/result.html:38 +msgid "percent correct" +msgstr "porcentaje de respuestas correctas" + +#: templates/result.html:48 +msgid "Review the questions below and try the exam again in the future" +msgstr "" +"Revisa las preguntas a continuación e intente el examen de nuevo en el " +"futuro" + +#: templates/result.html:52 +msgid "" +"The result of this exam will be stored in your progress section so you can " +"review and monitor your progression" +msgstr "" +"El resultado de este examen se almacenará en su sección de progreso para " +"que pueda revisar y supervisar su progreso" + +#: templates/result.html:66 +msgid "Your session score is" +msgstr "Su puntuación de sesión es" + +#: templates/result.html:66 +msgid "out of a possible" +msgstr "de un total posible" + +#: templates/result.html:84 +msgid "Your answer" +msgstr "Tu respuesta" + +#: templates/single_complete.html:13 +msgid "You have already sat this exam and only one sitting is permitted" +msgstr "Usted ya ha enviado este examen, y sólo se permite una sola sesión" + +#: templates/single_complete.html:15 +msgid "This exam is only accessible to signed in users" +msgstr "Este examen es sólo accesible para los usuarios inscritos" + +#: templates/view_quiz_category.html:3 +msgid "Quizzes related to" +msgstr "Quizes relacionados con" + +#: templates/view_quiz_category.html:6 +msgid "Quizzes in the" +msgstr "Quizes en el" + +#: templates/view_quiz_category.html:6 +msgid "category" +msgstr "categoría" + +#: templates/view_quiz_category.html:20 +msgid "There are no quizzes" +msgstr "No hay quizes" diff --git a/quiz-app/quiz/locale/fr/LC_MESSAGES/django.mo b/quiz-app/quiz/locale/fr/LC_MESSAGES/django.mo new file mode 100644 index 0000000..81d5d82 Binary files /dev/null and b/quiz-app/quiz/locale/fr/LC_MESSAGES/django.mo differ diff --git a/quiz-app/quiz/locale/fr/LC_MESSAGES/django.po b/quiz-app/quiz/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 0000000..a6c2355 --- /dev/null +++ b/quiz-app/quiz/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,330 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-05-28 17:45+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: quiz/templates/base.html:8 +msgid "Garage Numérique Quizzes" +msgstr "Quiz du Garage Numérique" + +#: quiz/templates/base.html:27 +msgid "Hi " +msgstr "Bonjour" + +#: quiz/templates/base.html:28 +msgid "Disconnect" +msgstr "Se déconnecter" + +#: quiz/templates/base.html:29 +#, fuzzy +#| msgid "User Progress" +msgid "Progress" +msgstr "Progression de l'utilisateur" + +#: quiz/templates/base.html:31 +#, fuzzy +#| msgid "Result of" +msgid "Results" +msgstr "Resultat de" + +#: quiz/templates/base.html:35 +msgid "Connect" +msgstr "Se connecter" + +#: quiz/templates/correct_answer.html:6 +msgid "You answered incorrectly to this answer" +msgstr "Vous avez répondu incorrectement à cette question" + +#: quiz/templates/correct_answer.html:16 +#, fuzzy +#| msgid "This is the correct answer" +msgid "It's a correct answer" +msgstr "C'est la bonne réponse" + +#: quiz/templates/correct_answer.html:23 quiz/templates/question.html:22 +msgid "Your answer was" +msgstr "Voici la réponse que vous avez donnée" + +#: quiz/templates/progress.html:6 +msgid "Progress Page" +msgstr "Page de progression" + +#: quiz/templates/progress.html:7 +msgid "User Progress Page" +msgstr "Page de progression de l'utilisateur" + +#: quiz/templates/progress.html:13 +msgid "Question Category Scores" +msgstr "Scores par catégorie" + +#: quiz/templates/progress.html:19 quiz/templates/quiz/quiz_detail.html:9 +#: quiz/templates/quiz/quiz_list.html:13 +msgid "Category" +msgstr "Categorie" + +#: quiz/templates/progress.html:20 +msgid "Correctly answererd" +msgstr "Correctement répondues" + +#: quiz/templates/progress.html:21 quiz/templates/quiz/sitting_detail.html:45 +msgid "Incorrect" +msgstr "Incorrecte" + +#: quiz/templates/progress.html:50 +msgid "Previous exam papers" +msgstr "Examens précédents" + +#: quiz/templates/progress.html:52 +msgid "Below are the results of exams that you have sat." +msgstr "Voici les résultats des examens que vous avez réalisés." + +#: quiz/templates/progress.html:59 +msgid "Quiz Title" +msgstr "Titre du quiz" + +#: quiz/templates/progress.html:60 quiz/templates/quiz/sitting_detail.html:18 +#: quiz/templates/quiz/sitting_list.html:20 +msgid "Score" +msgstr "Score" + +#: quiz/templates/progress.html:61 +msgid "Possible Score" +msgstr "Score max." + +#: quiz/templates/question.html:13 quiz/templates/result.html:13 +msgid "The previous question" +msgstr "La question précédente" + +#: quiz/templates/question.html:32 quiz/templates/result.html:21 +#: quiz/templates/result.html:87 +msgid "Explanation" +msgstr "Explication" + +#: quiz/templates/question.html:47 quiz/templates/quiz/sitting_detail.html:24 +msgid "Question" +msgstr "Question" + +#: quiz/templates/question.html:47 +msgid "of" +msgstr "de" + +#: quiz/templates/question.html:52 +msgid "Question category" +msgstr "Catégorie de la question" + +#: quiz/templates/question.html:74 +msgid "Check" +msgstr "Verifier" + +#: quiz/templates/quiz/category_list.html:3 +#: quiz/templates/quiz/sitting_list.html:3 +msgid "All Quizzes" +msgstr "Tous les quiz" + +#: quiz/templates/quiz/category_list.html:6 +msgid "Category list" +msgstr "Liste de catégories" + +#: quiz/templates/quiz/quiz_detail.html:11 +msgid "You will only get one attempt at this quiz" +msgstr "Vous n'aurez qu'un seul essai pour ce quiz" + +#: quiz/templates/quiz/quiz_detail.html:16 +msgid "Start quiz" +msgstr "Commencer le quiz" + +#: quiz/templates/quiz/quiz_list.html:3 +#, fuzzy +#| msgid "List of quizzes" +msgid "Tout les quizzs" +msgstr "Liste des quizs" + +#: quiz/templates/quiz/quiz_list.html:6 +msgid "List of quizzes" +msgstr "Liste des quizs" + +#: quiz/templates/quiz/quiz_list.html:12 +msgid "Title" +msgstr "Titre" + +#: quiz/templates/quiz/quiz_list.html:14 +msgid "Exam" +msgstr "Examen" + +#: quiz/templates/quiz/quiz_list.html:15 +msgid "Single attempt" +msgstr "Un seul essai" + +#: quiz/templates/quiz/quiz_list.html:31 +#: quiz/templates/quiz/sitting_list.html:46 +msgid "View details" +msgstr "Voir les détails" + +#: quiz/templates/quiz/quiz_list.html:41 +msgid "There are no available quizzes" +msgstr "Il n'y a pas de quiz disponibles" + +#: quiz/templates/quiz/sitting_detail.html:5 +#, fuzzy +#| msgid "Result of" +msgid "Results of " +msgstr "Resultat de" + +#: quiz/templates/quiz/sitting_detail.html:5 +msgid "for " +msgstr "pour " + +#: quiz/templates/quiz/sitting_detail.html:12 +msgid "Quiz title" +msgstr "Titre du quiz" + +#: quiz/templates/quiz/sitting_detail.html:13 +#, fuzzy +#| msgid "Category" +msgid "Catégory" +msgstr "Categorie" + +#: quiz/templates/quiz/sitting_detail.html:16 +#: quiz/templates/quiz/sitting_list.html:17 +msgid "User" +msgstr "Utilisateur" + +#: quiz/templates/quiz/sitting_detail.html:17 +#: quiz/templates/quiz/sitting_list.html:19 +msgid "Completed" +msgstr "Completé" + +#: quiz/templates/quiz/sitting_detail.html:25 +msgid "User answer" +msgstr "Réponse de l'utilisateur" + +#: quiz/templates/quiz/sitting_detail.html:47 +msgid "Correct" +msgstr "Correcte" + +#: quiz/templates/quiz/sitting_detail.html:60 +#: quiz/templates/quiz/sitting_list.html:60 +msgid "You don't have access to this page" +msgstr "Vous n'avez pas accès à cette page" + +#: quiz/templates/quiz/sitting_detail.html:66 +msgid "Toggle whether correct" +msgstr "[Dés]Activer si c'est correcte" + +#: quiz/templates/quiz/sitting_list.html:10 +msgid "List of complete exams" +msgstr "Liste des examens complétés" + +#: quiz/templates/quiz/sitting_list.html:18 +msgid "Quiz" +msgstr "Quiz" + +#: quiz/templates/quiz/sitting_list.html:32 +msgid "Filter" +msgstr "Filtre" + +#: quiz/templates/quiz/sitting_list.html:56 +msgid "There are no matching quizzes" +msgstr "Il n'y a pas de quiz correspondant" + +#: quiz/templates/result.html:7 +#, fuzzy +#| msgid "Exam Results for" +msgid "Exam results for" +msgstr "Résultats de l'examen de" + +#: quiz/templates/result.html:32 +msgid "Exam results" +msgstr "Résultats de l'examen" + +#: quiz/templates/result.html:34 +msgid "Exam title" +msgstr "Titre de l'examen" + +#: quiz/templates/result.html:38 +msgid "You answered" +msgstr "Vous avez donné la bonne réponse pour " + +#: quiz/templates/result.html:38 +#, fuzzy +#| msgid "questions correctly out of" +msgid "questions correctly on " +msgstr "questions sur un total de" + +#: quiz/templates/result.html:38 +msgid "giving you" +msgstr "ce qui vous donne" + +#: quiz/templates/result.html:38 +#, fuzzy +#| msgid "This is the correct answer" +msgid " of correct answers" +msgstr "C'est la bonne réponse" + +#: quiz/templates/result.html:48 +msgid "Review the questions below and try the exam again in the future" +msgstr "" +"Revoyez les questions suivantes et essayez à nouveau l'examen une autre fois" + +#: quiz/templates/result.html:52 +msgid "" +"The result of this exam will be stored in your progress section so you can " +"review and monitor your progression" +msgstr "" +"Le résultat de cet examen sera enregistré pour que vous puissiez suivre " +"votre progression" + +#: quiz/templates/result.html:66 +msgid "Your session score is" +msgstr "Votre score pour cette session est" + +#: quiz/templates/result.html:66 +msgid "out of a possible" +msgstr "sur un maximum de" + +#: quiz/templates/result.html:84 +msgid "Your answer" +msgstr "Votre réponse" + +#: quiz/templates/single_complete.html:13 +msgid "You already did this quiz" +msgstr "Vous avez déjà éffectué ce quiz" + +#: quiz/templates/single_complete.html:15 +#, fuzzy +#| msgid "This exam is only accessible to signed in users" +msgid "This quiz is only accessible to authentified users" +msgstr "Cet examen n'est accessible qu'aux utilisateurs enregistrés" + +#: quiz/templates/view_quiz_category.html:3 +msgid "Quizzes related to" +msgstr "Quiz pour la catégorie" + +#: quiz/templates/view_quiz_category.html:6 +msgid "Quizzes in the" +msgstr "Quizs dans la catégorie" + +#: quiz/templates/view_quiz_category.html:6 +#, fuzzy +#| msgid "category" +msgid "category" +msgstr "catégorie" + +#: quiz/templates/view_quiz_category.html:20 +msgid "There are no quizzes" +msgstr "Il n'y a pas de quiz" diff --git a/quiz-app/quiz/locale/it/LC_MESSAGES/django.mo b/quiz-app/quiz/locale/it/LC_MESSAGES/django.mo new file mode 100644 index 0000000..926bc4c Binary files /dev/null and b/quiz-app/quiz/locale/it/LC_MESSAGES/django.mo differ diff --git a/quiz-app/quiz/locale/it/LC_MESSAGES/django.po b/quiz-app/quiz/locale/it/LC_MESSAGES/django.po new file mode 100644 index 0000000..7a027bf --- /dev/null +++ b/quiz-app/quiz/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,479 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-10-31 22:44+0000\n" +"PO-Revision-Date: 2015-10-31 22:43+0000\n" +"Last-Translator: b' <>'\n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Translated-Using: django-rosetta 0.7.6\n" + +#: apps/quiz/models.py:30 apps/quiz/models.py:37 apps/quiz/models.py:53 +#: apps/quiz/models.py:83 apps/quiz/models.py:545 +#: apps/quiz/templates/progress.html:19 +#: apps/quiz/templates/quiz/quiz_detail.html:9 +#: apps/quiz/templates/quiz/quiz_list.html:13 +#: apps/quiz/templates/quiz/sitting_detail.html:10 +msgid "Category" +msgstr "Categoria" + +#: apps/quiz/models.py:38 +msgid "Categories" +msgstr "Categorie" + +#: apps/quiz/models.py:48 apps/quiz/models.py:58 apps/quiz/models.py:550 +msgid "Sub-Category" +msgstr "Sotto-Categoria" + +#: apps/quiz/models.py:59 +msgid "Sub-Categories" +msgstr "Sotto-categorie" + +#: apps/quiz/models.py:69 apps/quiz/templates/quiz/quiz_list.html:12 +msgid "Title" +msgstr "Titolo" + +#: apps/quiz/models.py:73 +msgid "Description" +msgstr "Descrizione" + +#: apps/quiz/models.py:74 +msgid "a description of the quiz" +msgstr "una descrizione del quiz" + +#: apps/quiz/models.py:78 +msgid "a user friendly url" +msgstr "un url amichevole" + +#: apps/quiz/models.py:79 +msgid "user friendly url" +msgstr "url amichevole" + +#: apps/quiz/models.py:87 +msgid "Random Order" +msgstr "Ordine Casuale" + +#: apps/quiz/models.py:88 +msgid "Display the questions in a random order or as they are set?" +msgstr "" +"Visualizzare le domande in un ordine casuale o nell'ordine in cui sono state " +"inserite?" + +#: apps/quiz/models.py:93 +msgid "Max Questions" +msgstr "Numero massimo di domande" + +#: apps/quiz/models.py:94 +msgid "Number of questions to be answered on each attempt." +msgstr "Numero di domande cui rispondere in ogni tentativo." + +#: apps/quiz/models.py:98 +msgid "" +"Correct answer is NOT shown after question. Answers displayed at the end." +msgstr "" +"La risposta corretta NON è visualizzata dopo la domanda. Le risposte vengono " +"visualizzate alla fine." + +#: apps/quiz/models.py:100 +msgid "Answers at end" +msgstr "Risposte alla fine" + +#: apps/quiz/models.py:104 +msgid "" +"If yes, the result of each attempt by a user will be stored. Necessary for " +"marking." +msgstr "" +"Se si, il risultato di ogni tentativo da parte di un utente verrà salvato. " +"Necessario per segnare." + +#: apps/quiz/models.py:107 +msgid "Exam Paper" +msgstr "Foglio dell'esame" + +#: apps/quiz/models.py:111 +msgid "" +"If yes, only one attempt by a user will be permitted. Non users cannot sit " +"this exam." +msgstr "" +"Se si, per ogni utente sarà permesso un solo tentativo. I non tutenti non " +"potranno sostenere l'esame." + +#: apps/quiz/models.py:114 +msgid "Single Attempt" +msgstr "Songolo tentativo" + +#: apps/quiz/models.py:118 +msgid "Percentage required to pass exam." +msgstr "Percentuale richiesta per superare l'esame." + +#: apps/quiz/models.py:122 +msgid "Displayed if user passes." +msgstr "Visualizzato se l'utente passa." + +#: apps/quiz/models.py:123 +msgid "Success Text" +msgstr "Testo Risposta Corretta" + +#: apps/quiz/models.py:126 +msgid "Fail Text" +msgstr "Test risposta Sbagliata" + +#: apps/quiz/models.py:127 +msgid "Displayed if user fails." +msgstr "Visualizzato se l'utente fallisce." + +#: apps/quiz/models.py:131 +msgid "Draft" +msgstr "Bozza" + +#: apps/quiz/models.py:132 +msgid "" +"If yes, the quiz is not displayed in the quiz list and can only be taken by " +"users who can edit quizzes." +msgstr "" +"Se sì, il quiz non viene visualizzato nell'elenco quiz e può essere " +"selezionato solo dagli utenti che possono modificare i quiz." + +#: apps/quiz/models.py:152 apps/quiz/models.py:372 apps/quiz/models.py:541 +#: apps/quiz/templates/quiz/sitting_list.html:14 +msgid "Quiz" +msgstr "Quiz" + +#: apps/quiz/models.py:153 +msgid "Quizzes" +msgstr "Quiz" + +#: apps/quiz/models.py:192 apps/quiz/models.py:370 +#: apps/quiz/templates/quiz/sitting_detail.html:13 +#: apps/quiz/templates/quiz/sitting_list.html:13 +msgid "User" +msgstr "Utente" + +#: apps/quiz/models.py:195 apps/quiz/templates/progress.html:60 +#: apps/quiz/templates/quiz/sitting_detail.html:15 +#: apps/quiz/templates/quiz/sitting_list.html:16 +msgid "Score" +msgstr "Punteggio" + +#: apps/quiz/models.py:200 +msgid "User Progress" +msgstr "Progressi dell'utente" + +#: apps/quiz/models.py:201 +msgid "User progress records" +msgstr "registarzione progressi utente" + +#: apps/quiz/models.py:261 +msgid "error" +msgstr "errore" + +#: apps/quiz/models.py:261 +msgid "category does not exist or invalid score" +msgstr "categoria inesistente o punteggio non valido" + +#: apps/quiz/models.py:375 +msgid "Question Order" +msgstr "ordine Domande" + +#: apps/quiz/models.py:378 +msgid "Question List" +msgstr "Elenca domande" + +#: apps/quiz/models.py:381 +msgid "Incorrect questions" +msgstr "Domande sbagliate" + +#: apps/quiz/models.py:383 +msgid "Current Score" +msgstr "Punteggio Attuale" + +#: apps/quiz/models.py:386 +msgid "Complete" +msgstr "Completo" + +#: apps/quiz/models.py:389 +msgid "User Answers" +msgstr "Risposte utente" + +#: apps/quiz/models.py:392 +msgid "Start" +msgstr "Inizio" + +#: apps/quiz/models.py:394 +msgid "End" +msgstr "Fine" + +#: apps/quiz/models.py:399 +msgid "Can see completed exams." +msgstr "Puoi vedere gli esami completati." + +#: apps/quiz/models.py:557 +msgid "Figure" +msgstr "Immagine" + +#: apps/quiz/models.py:561 +msgid "Enter the question text that you want displayed" +msgstr "Inserisci il testo della domanda che desideri visualizzare" + +#: apps/quiz/models.py:563 apps/quiz/models.py:575 +#: apps/quiz/templates/question.html:47 +#: apps/quiz/templates/quiz/sitting_detail.html:21 +msgid "Question" +msgstr "Domanda" + +#: apps/quiz/models.py:567 +msgid "Explanation to be shown after the question has been answered." +msgstr "" +"Spiegazione da visulizzare dopo che è stata data la risposta alla domanda." + +#: apps/quiz/models.py:570 apps/quiz/templates/question.html:32 +#: apps/quiz/templates/result.html:21 apps/quiz/templates/result.html.py:87 +msgid "Explanation" +msgstr "Spiegazione" + +#: apps/quiz/models.py:576 +msgid "Questions" +msgstr "Domande" + +#: apps/quiz/templates/base.html:7 +msgid "Example Quiz Website" +msgstr "Sito Quiz di esempio" + +#: apps/quiz/templates/correct_answer.html:6 +msgid "You answered the above question incorrectly" +msgstr "Hai risposto alla domanda di cui sopra in modo non corretto" + +#: apps/quiz/templates/correct_answer.html:16 +msgid "This is the correct answer" +msgstr "Questa è la risposta corretta" + +#: apps/quiz/templates/correct_answer.html:23 +msgid "This was your answer." +msgstr "Questa è stata la tua risposta " + +#: apps/quiz/templates/progress.html:6 +msgid "Progress Page" +msgstr "Pagina dei progressi" + +#: apps/quiz/templates/progress.html:7 +msgid "User Progress Page" +msgstr "Pagina dei progressi dell'utente" + +#: apps/quiz/templates/progress.html:13 +msgid "Question Category Scores" +msgstr "Punteggi Categoria Domanda " + +#: apps/quiz/templates/progress.html:20 +msgid "Correctly answererd" +msgstr "Risposto correttamente" + +#: apps/quiz/templates/progress.html:21 +msgid "Incorrect" +msgstr "Sbagliato" + +#: apps/quiz/templates/progress.html:50 +msgid "Previous exam papers" +msgstr "Fogli esami precedenti" + +#: apps/quiz/templates/progress.html:52 +msgid "Below are the results of exams that you have sat." +msgstr "Di seguito sono riportati i risultati degli esami sostenuti." + +#: apps/quiz/templates/progress.html:59 +msgid "Quiz Title" +msgstr "Titolo del Quiz" + +#: apps/quiz/templates/progress.html:61 +msgid "Possible Score" +msgstr "Possibile Punteggio" + +#: apps/quiz/templates/question.html:13 apps/quiz/templates/result.html:13 +msgid "The previous question" +msgstr "La domanda precedente" + +#: apps/quiz/templates/question.html:22 +msgid "Your answer was" +msgstr "La tua risposta è stata" + +#: apps/quiz/templates/question.html:47 +msgid "of" +msgstr "di" + +#: apps/quiz/templates/question.html:52 +msgid "Question category" +msgstr "Categoria domanda" + +#: apps/quiz/templates/question.html:74 +msgid "Check" +msgstr "Verifica" + +#: apps/quiz/templates/quiz/category_list.html:3 +#: apps/quiz/templates/quiz/quiz_list.html:3 +#: apps/quiz/templates/quiz/sitting_list.html:3 +msgid "All Quizzes" +msgstr "Tutti i Quiz" + +#: apps/quiz/templates/quiz/category_list.html:6 +msgid "Category list" +msgstr "Lista Categorie" + +#: apps/quiz/templates/quiz/quiz_detail.html:11 +msgid "You will only get one attempt at this quiz" +msgstr "Avrai un solo tentativo a disposizione per questo quiz" + +#: apps/quiz/templates/quiz/quiz_detail.html:16 +msgid "Start quiz" +msgstr "Comincia il Quiz" + +#: apps/quiz/templates/quiz/quiz_list.html:6 +msgid "List of quizzes" +msgstr "Lista dei quiz" + +#: apps/quiz/templates/quiz/quiz_list.html:14 +msgid "Exam" +msgstr "Esame" + +#: apps/quiz/templates/quiz/quiz_list.html:15 +msgid "Single attempt" +msgstr "Singolo tentivo" + +#: apps/quiz/templates/quiz/quiz_list.html:31 +#: apps/quiz/templates/quiz/sitting_list.html:42 +msgid "View details" +msgstr "Vedi i dettagli" + +#: apps/quiz/templates/quiz/quiz_list.html:41 +msgid "There are no available quizzes" +msgstr "Non ci sono quiz disponibili" + +#: apps/quiz/templates/quiz/sitting_detail.html:5 +msgid "Result of" +msgstr "Risultato di" + +#: apps/quiz/templates/quiz/sitting_detail.html:5 +msgid "for" +msgstr "per" + +#: apps/quiz/templates/quiz/sitting_detail.html:9 +msgid "Quiz title" +msgstr "Titolo quiz" + +#: apps/quiz/templates/quiz/sitting_detail.html:14 +#: apps/quiz/templates/quiz/sitting_list.html:15 +msgid "Completed" +msgstr "Completato" + +#: apps/quiz/templates/quiz/sitting_detail.html:22 +msgid "User answer" +msgstr "Risposta utente" + +#: apps/quiz/templates/quiz/sitting_detail.html:41 +msgid "incorrect" +msgstr "errato" + +#: apps/quiz/templates/quiz/sitting_detail.html:43 +msgid "Correct" +msgstr "Corretto" + +#: apps/quiz/templates/quiz/sitting_detail.html:49 +msgid "Toggle whether correct" +msgstr "Seleziona se corretto" + +#: apps/quiz/templates/quiz/sitting_list.html:6 +msgid "List of complete exams" +msgstr "Lista edgli esami completati" + +#: apps/quiz/templates/quiz/sitting_list.html:28 +msgid "Filter" +msgstr "Filtro" + +#: apps/quiz/templates/quiz/sitting_list.html:52 +msgid "There are no matching quizzes" +msgstr "Non ci sono quiz corrispondenti" + +#: apps/quiz/templates/result.html:7 +msgid "Exam Results for" +msgstr "Risultati esame per" + +#: apps/quiz/templates/result.html:32 +msgid "Exam results" +msgstr "Risultati esame" + +#: apps/quiz/templates/result.html:34 +msgid "Exam title" +msgstr "Titolo esame" + +#: apps/quiz/templates/result.html:38 +msgid "You answered" +msgstr "Hai risposto" + +#: apps/quiz/templates/result.html:38 +msgid "questions correctly out of" +msgstr "domande cui hai correttamente risposto su" + +#: apps/quiz/templates/result.html:38 +msgid "giving you" +msgstr "dandoti" + +#: apps/quiz/templates/result.html:38 +msgid "percent correct" +msgstr "percentuale corretta" + +#: apps/quiz/templates/result.html:48 +msgid "Review the questions below and try the exam again in the future" +msgstr "Riesamina le domande qui sotto e riprova l'esame in futuro" + +#: apps/quiz/templates/result.html:52 +msgid "" +"The result of this exam will be stored in your progress section so you can " +"review and monitor your progression" +msgstr "" +" Il risultato di questo esame verrà memorizzato nella sezione di avanzamento " +"in modo da poter verificare e monitorare i tuoi progressi" + +#: apps/quiz/templates/result.html:66 +msgid "Your session score is" +msgstr "Il punteggio di questa sessione è " + +#: apps/quiz/templates/result.html:66 +msgid "out of a possible" +msgstr "su un massimo di" + +#: apps/quiz/templates/result.html:84 +msgid "Your answer" +msgstr "La tua risposta" + +#: apps/quiz/templates/single_complete.html:13 +msgid "You have already sat this exam and only one sitting is permitted" +msgstr "Hai già sostenuto questo esame ed è consentito farlo solo una volta" + +#: apps/quiz/templates/single_complete.html:15 +msgid "This exam is only accessible to signed in users" +msgstr "Questo esame è disponibile solo per gli utenti collegati" + +#: apps/quiz/templates/view_quiz_category.html:3 +msgid "Quizzes related to" +msgstr "Quiz collegati a" + +#: apps/quiz/templates/view_quiz_category.html:6 +msgid "Quizzes in the" +msgstr "Quiz nella" + +#: apps/quiz/templates/view_quiz_category.html:6 +msgid "category" +msgstr "categoria" + +#: apps/quiz/templates/view_quiz_category.html:20 +msgid "There are no quizzes" +msgstr "Non ci sono quiz" diff --git a/quiz-app/quiz/locale/ru/LC_MESSAGES/django.mo b/quiz-app/quiz/locale/ru/LC_MESSAGES/django.mo new file mode 100644 index 0000000..b8be314 Binary files /dev/null and b/quiz-app/quiz/locale/ru/LC_MESSAGES/django.mo differ diff --git a/quiz-app/quiz/locale/ru/LC_MESSAGES/django.po b/quiz-app/quiz/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 0000000..851778d --- /dev/null +++ b/quiz-app/quiz/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,464 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: django-quiz\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-01-01 11:37+0400\n" +"PO-Revision-Date: 2015-08-21 19:40+0500\n" +"Last-Translator: Eugena Mihailikova \n" +"Language-Team: LANGUAGE \n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Poedit 1.5.4\n" + +#: admin.py:31 admin.py:33 models.py:576 +msgid "Questions" +msgstr "Вопросы" + +#: models.py:30 models.py:37 models.py:53 models.py:83 models.py:545 +#: templates/progress.html:19 templates/quiz/quiz_detail.html:9 +#: templates/quiz/quiz_list.html:13 templates/quiz/sitting_detail.html:10 +msgid "Category" +msgstr "Категория" + +#: models.py:38 +msgid "Categories" +msgstr "Категории" + +#: models.py:48 models.py:58 models.py:550 +msgid "Sub-Category" +msgstr "Подкатегория" + +#: models.py:59 +msgid "Sub-Categories" +msgstr "Подкатегории" + +#: models.py:69 templates/quiz/quiz_list.html:12 +msgid "Title" +msgstr "Название" + +#: models.py:73 +msgid "Description" +msgstr "Описание" + +#: models.py:74 +msgid "a description of the quiz" +msgstr "описание теста" + +#: models.py:78 +msgid "a user friendly url" +msgstr "url теста" + +#: models.py:79 +msgid "user friendly url" +msgstr "url теста" + +#: models.py:87 +msgid "Random Order" +msgstr "Случайный порядок" + +#: models.py:88 +msgid "Display the questions in a random order or as they are set?" +msgstr "Отображать вопросы в случайном порядке или в порядке добавления?" + +#: models.py:93 +msgid "Max Questions" +msgstr "Максимальное количество вопросов" + +#: models.py:94 +msgid "Number of questions to be answered on each attempt." +msgstr "" +"Количество вопросов, на которые должны быть даны ответы при каждой попытке" + +#: models.py:98 +msgid "" +"Correct answer is NOT shown after question. Answers displayed at the end." +msgstr "" +"Правильный ответ НЕ показан после вопроса. Ответы отображаются после " +"прохождения теста" + +#: models.py:100 +msgid "Answers at end" +msgstr "Ответы в конце" + +#: models.py:104 +msgid "" +"If yes, the result of each attempt by a user will be stored. Necessary for " +"marking." +msgstr "Если отмечено, результаты каждой попытки пользователя будет сохранен" + +#: models.py:107 +msgid "Exam Paper" +msgstr "Экзаменационный лист" + +#: models.py:111 +msgid "" +"If yes, only one attempt by a user will be permitted. Non users cannot sit " +"this exam." +msgstr "Если отмечено, пользователю будет разрешена только одна попытка" + +#: models.py:114 +msgid "Single Attempt" +msgstr "Единственная попытка" + +#: models.py:118 +msgid "Percentage required to pass exam." +msgstr "Процент правильных ответов для прохождения теста" + +#: models.py:122 +msgid "Displayed if user passes." +msgstr "Отображается, если пользователь успешно прошел тест" + +#: models.py:123 +msgid "Success Text" +msgstr "Текст в случае успеха" + +#: models.py:126 +msgid "Fail Text" +msgstr "Текст в случае неудачи" + +#: models.py:127 +msgid "Displayed if user fails." +msgstr "Отображается, если пользователь провалил тест" + +#: models.py:131 +msgid "Draft" +msgstr "Черновик" + +#: models.py:132 +msgid "" +"If yes, the quiz is not displayed in the quiz list and can only be taken by " +"users who can edit quizzes." +msgstr "Если отмечено, то не отображается в публичном списке и может быть " +"взято только пользователями с соответствующим правом" + +#: models.py:152 models.py:372 models.py:541 +#: templates/quiz/sitting_list.html:14 +msgid "Quiz" +msgstr "Тест" + +#: models.py:153 +msgid "Quizzes" +msgstr "Тесты" + +#: models.py:192 models.py:370 templates/quiz/sitting_detail.html:13 +#: templates/quiz/sitting_list.html:13 +msgid "User" +msgstr "Пользователь" + +#: models.py:195 templates/progress.html:60 +#: templates/quiz/sitting_detail.html:15 templates/quiz/sitting_list.html:16 +msgid "Score" +msgstr "Баллы" + +#: models.py:200 +msgid "User Progress" +msgstr "Прогресс пользователя" + +#: models.py:201 +msgid "User progress records" +msgstr "Прогресс пользователя" + +#: models.py:261 +msgid "error" +msgstr "ошибка" + +#: models.py:261 +msgid "category does not exist or invalid score" +msgstr "категории не существует или недопустимый балл" + +#: models.py:375 +msgid "Question Order" +msgstr "Порядок вопросов" + +#: models.py:378 +msgid "Question List" +msgstr "Список вопросов" + +#: models.py:381 +msgid "Incorrect questions" +msgstr "Вопросы, на которые дан неверный ответ" + +#: models.py:383 +msgid "Current Score" +msgstr "Текущий балл" + +#: models.py:386 +msgid "Complete" +msgstr "Завершен" + +#: models.py:389 +msgid "User Answers" +msgstr "Ответы пользователя" + +#: models.py:392 +msgid "Start" +msgstr "Начало" + +#: models.py:394 +msgid "End" +msgstr "Окончание" + +#: models.py:399 +msgid "Can see completed exams." +msgstr "Может просматривать оконченные тесты" + +#: models.py:557 +msgid "Figure" +msgstr "Рисунок" + +#: models.py:561 +msgid "Enter the question text that you want displayed" +msgstr "Введите текст вопроса, который должен отобразиться" + +#: models.py:563 models.py:575 templates/question.html:47 +#: templates/quiz/sitting_detail.html:21 +msgid "Question" +msgstr "Вопрос" + +#: models.py:567 +msgid "Explanation to be shown after the question has been answered." +msgstr "Объяснение показывается после того, как дан ответ на вопрос" + +#: models.py:570 templates/question.html:32 templates/result.html:21 +#: templates/result.html.py:87 +msgid "Explanation" +msgstr "Объяснение" + +#: templates/base.html:7 +msgid "Example Quiz Website" +msgstr "Тесты" + +#: templates/correct_answer.html:6 +msgid "You answered the above question incorrectly" +msgstr "Вы дали неверный ответ" + +#: templates/correct_answer.html:16 +msgid "This is the correct answer" +msgstr "Это правильный ответ" + +#: templates/correct_answer.html:23 +msgid "This was your answer." +msgstr "Это был ваш ответ" + +#: templates/progress.html:6 +msgid "Progress Page" +msgstr "Страница прогесса" + +#: templates/progress.html:7 +msgid "User Progress Page" +msgstr "Страница прогресса пользователя" + +#: templates/progress.html:13 +msgid "Question Category Scores" +msgstr "Баллы по категориям вопросов" + +#: templates/progress.html:20 +msgid "Correctly answererd" +msgstr "Верных ответов" + +#: templates/progress.html:21 +msgid "Incorrect" +msgstr "Неверных ответов" + +#: templates/progress.html:50 +msgid "Previous exam papers" +msgstr "Список предыдущих экзаменов" + +#: templates/progress.html:52 +msgid "Below are the results of exams that you have sat." +msgstr "Ниже представлены результаты пройденных Вами тестов" + +#: templates/progress.html:59 +msgid "Quiz Title" +msgstr "Название теста" + +#: templates/progress.html:61 +msgid "Possible Score" +msgstr "Возможный балл" + +#: templates/question.html:13 templates/result.html:13 +msgid "The previous question" +msgstr "Предыдущий вопрос" + +#: templates/question.html:22 +msgid "Your answer was" +msgstr "Ваш ответ был" + +#: templates/question.html:47 +msgid "of" +msgstr "из" + +#: templates/question.html:52 +msgid "Question category" +msgstr "Категория вопроса" + +#: templates/question.html:74 +msgid "Check" +msgstr "Ответить" + +#: templates/quiz/category_list.html:3 templates/quiz/quiz_list.html:3 +#: templates/quiz/sitting_list.html:3 +msgid "All Quizzes" +msgstr "Все тесты" + +#: templates/quiz/category_list.html:6 +msgid "Category list" +msgstr "Список категорий" + +#: templates/quiz/quiz_detail.html:11 +msgid "You will only get one attempt at this quiz" +msgstr "У вас есть одна попытка для прохождения данного теста" + +#: templates/quiz/quiz_detail.html:16 +msgid "Start quiz" +msgstr "Начать тест" + +#: templates/quiz/quiz_list.html:6 +msgid "List of quizzes" +msgstr "Список тестов" + +#: templates/quiz/quiz_list.html:14 +msgid "Exam" +msgstr "Тестирование" + +#: templates/quiz/quiz_list.html:15 +msgid "Single attempt" +msgstr "Единственная попытка" + +#: templates/quiz/quiz_list.html:31 templates/quiz/sitting_list.html:42 +msgid "View details" +msgstr "Подробнее" + +#: templates/quiz/quiz_list.html:41 +msgid "There are no available quizzes" +msgstr "Доступных тестов нет" + +#: templates/quiz/sitting_detail.html:5 +msgid "Result of" +msgstr "Результаты" + +#: templates/quiz/sitting_detail.html:5 +msgid "for" +msgstr "для" + +#: templates/quiz/sitting_detail.html:9 +msgid "Quiz title" +msgstr "Назвние теста" + +#: templates/quiz/sitting_detail.html:14 templates/quiz/sitting_list.html:15 +msgid "Completed" +msgstr "Завершено" + +#: templates/quiz/sitting_detail.html:22 +msgid "User answer" +msgstr "Ответ пользователя" + +#: templates/quiz/sitting_detail.html:41 +msgid "incorrect" +msgstr "Неверно" + +#: templates/quiz/sitting_detail.html:43 +msgid "Correct" +msgstr "Верно" + +#: templates/quiz/sitting_detail.html:49 +msgid "Toggle whether correct" +msgstr "Изменить результат" + +#: templates/quiz/sitting_list.html:6 +msgid "List of complete exams" +msgstr "Список завершенных тестов" + +#: templates/quiz/sitting_list.html:28 +msgid "Filter" +msgstr "Фильтр" + +#: templates/quiz/sitting_list.html:52 +msgid "There are no matching quizzes" +msgstr "Подходящих тестов нет" + +#: templates/result.html:7 +msgid "Exam Results for" +msgstr "Результат теста для" + +#: templates/result.html:32 +msgid "Exam results" +msgstr "Результаты тестирования" + +#: templates/result.html:34 +msgid "Exam title" +msgstr "Название теста" + +#: templates/result.html:38 +msgid "You answered" +msgstr "Ваш результат" + +#: templates/result.html:38 +msgid "questions correctly out of" +msgstr "правильных ответов из" + +#: templates/result.html:38 +msgid "giving you" +msgstr "вы дали" + +#: templates/result.html:38 +msgid "percent correct" +msgstr "процент правильных ответов" + +#: templates/result.html:48 +msgid "Review the questions below and try the exam again in the future" +msgstr "" +"Просмотрите вопросы, представленные ниже и попробуйте пройти тест еще раз" + +#: templates/result.html:52 +msgid "" +"The result of this exam will be stored in your progress section so you can " +"review and monitor your progression" +msgstr "" +"Результаты данного экзамена будут сохранены. Вы сможете просматривать ваш " +"прогресс" + +#: templates/result.html:66 +msgid "Your session score is" +msgstr "Балл вашей сессии" + +#: templates/result.html:66 +msgid "out of a possible" +msgstr "из возможных" + +#: templates/result.html:84 +msgid "Your answer" +msgstr "Ваш ответ" + +#: templates/single_complete.html:13 +msgid "You have already sat this exam and only one sitting is permitted" +msgstr "Вы уже прошли данный тест. Разрешена только одна попытка" + +#: templates/single_complete.html:15 +msgid "This exam is only accessible to signed in users" +msgstr "Этот тест доступен только зарегистрированным пользователям" + +#: templates/view_quiz_category.html:3 +msgid "Quizzes related to" +msgstr "Тесты относятся к" + +#: templates/view_quiz_category.html:6 +msgid "Quizzes in the" +msgstr "Тесты в" + +#: templates/view_quiz_category.html:6 +msgid "category" +msgstr "категория" + +#: templates/view_quiz_category.html:20 +msgid "There are no quizzes" +msgstr "Тестов нет" diff --git a/quiz-app/quiz/locale/zh_CN/LC_MESSAGES/django.mo b/quiz-app/quiz/locale/zh_CN/LC_MESSAGES/django.mo new file mode 100644 index 0000000..cf83061 Binary files /dev/null and b/quiz-app/quiz/locale/zh_CN/LC_MESSAGES/django.mo differ diff --git a/quiz-app/quiz/locale/zh_CN/LC_MESSAGES/django.po b/quiz-app/quiz/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 0000000..d4edd83 --- /dev/null +++ b/quiz-app/quiz/locale/zh_CN/LC_MESSAGES/django.po @@ -0,0 +1,474 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-10-31 22:44+0000\n" +"PO-Revision-Date: 2015-10-31 22:43+0000\n" +"Last-Translator: b' <>'\n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Translated-Using: django-rosetta 0.7.6\n" + +#: apps/quiz/models.py:30 apps/quiz/models.py:37 apps/quiz/models.py:53 +#: apps/quiz/models.py:83 apps/quiz/models.py:545 +#: apps/quiz/templates/progress.html:19 +#: apps/quiz/templates/quiz/quiz_detail.html:9 +#: apps/quiz/templates/quiz/quiz_list.html:13 +#: apps/quiz/templates/quiz/sitting_detail.html:10 +msgid "Category" +msgstr "类别" + +#: apps/quiz/models.py:38 +msgid "Categories" +msgstr "类别" + +#: apps/quiz/models.py:48 apps/quiz/models.py:58 apps/quiz/models.py:550 +msgid "Sub-Category" +msgstr "子类" + +#: apps/quiz/models.py:59 +msgid "Sub-Categories" +msgstr "子类" + +#: apps/quiz/models.py:69 apps/quiz/templates/quiz/quiz_list.html:12 +msgid "Title" +msgstr "标题" + +#: apps/quiz/models.py:73 +msgid "Description" +msgstr "描述" + +#: apps/quiz/models.py:74 +msgid "a description of the quiz" +msgstr "测试的描述" + +#: apps/quiz/models.py:78 +msgid "a user friendly url" +msgstr "一个用户友好的URL" + +#: apps/quiz/models.py:79 +msgid "user friendly url" +msgstr "用户友好的URL" + +#: apps/quiz/models.py:87 +msgid "Random Order" +msgstr "随机次序" + +#: apps/quiz/models.py:88 +msgid "Display the questions in a random order or as they are set?" +msgstr "按设定的次序还是随机次序显示问题?" + +#: apps/quiz/models.py:93 +msgid "Max Questions" +msgstr "最大问题数量" + +#: apps/quiz/models.py:94 +msgid "Number of questions to be answered on each attempt." +msgstr "每次尝试可回答的问题数量。" + +#: apps/quiz/models.py:98 +msgid "" +"Correct answer is NOT shown after question. Answers displayed at the end." +msgstr "" +"正确答案不会在问题后显示。回答显示在最后。" + +#: apps/quiz/models.py:100 +msgid "Answers at end" +msgstr "最后给出答案" + +#: apps/quiz/models.py:104 +msgid "" +"If yes, the result of each attempt by a user will be stored. Necessary for " +"marking." +msgstr "" +"如果是,用户每次尝试的结果都将记录。如需打分此项必选。" + +#: apps/quiz/models.py:107 +msgid "Exam Paper" +msgstr "试卷" + +#: apps/quiz/models.py:111 +msgid "" +"If yes, only one attempt by a user will be permitted. Non users cannot sit " +"this exam." +msgstr "" +"如果是,一个用户只允许尝试一次。非注册用户不能参加本考试。" + +#: apps/quiz/models.py:114 +msgid "Single Attempt" +msgstr "单次尝试" + +#: apps/quiz/models.py:116 +msgid "Pass Mark" +msgstr "通过分数" + +#: apps/quiz/models.py:118 +msgid "Percentage required to pass exam." +msgstr "通过考试需要的百分数。" + +#: apps/quiz/models.py:122 +msgid "Displayed if user passes." +msgstr "如果用户通过则显示。" + +#: apps/quiz/models.py:123 +msgid "Success Text" +msgstr "成功文本" + +#: apps/quiz/models.py:126 +msgid "Fail Text" +msgstr "失败文本" + +#: apps/quiz/models.py:127 +msgid "Displayed if user fails." +msgstr "如果用户未通过则显示" + +#: apps/quiz/models.py:131 +msgid "Draft" +msgstr "草稿" + +#: apps/quiz/models.py:132 +msgid "" +"If yes, the quiz is not displayed in the quiz list and can only be taken by " +"users who can edit quizzes." +msgstr "" +"如果是,测试不会显示在测试列表里,只能被可以编辑测试的用户操作。" + +#: apps/quiz/models.py:152 apps/quiz/models.py:372 apps/quiz/models.py:541 +#: apps/quiz/templates/quiz/sitting_list.html:14 +msgid "Quiz" +msgstr "测试" + +#: apps/quiz/models.py:153 +msgid "Quizzes" +msgstr "测试" + +#: apps/quiz/models.py:192 apps/quiz/models.py:370 +#: apps/quiz/templates/quiz/sitting_detail.html:13 +#: apps/quiz/templates/quiz/sitting_list.html:13 +msgid "User" +msgstr "用户" + +#: apps/quiz/models.py:195 apps/quiz/templates/progress.html:60 +#: apps/quiz/templates/quiz/sitting_detail.html:15 +#: apps/quiz/templates/quiz/sitting_list.html:16 +msgid "Score" +msgstr "得分" + +#: apps/quiz/models.py:200 +msgid "User Progress" +msgstr "用户进度" + +#: apps/quiz/models.py:201 +msgid "User progress records" +msgstr "用户进度记录" + +#: apps/quiz/models.py:261 +msgid "error" +msgstr "错误" + +#: apps/quiz/models.py:261 +msgid "category does not exist or invalid score" +msgstr "类别不存在或得分无效" + +#: apps/quiz/models.py:375 +msgid "Question Order" +msgstr "问题次序" + +#: apps/quiz/models.py:378 +msgid "Question List" +msgstr "问题列表" + +#: apps/quiz/models.py:381 +msgid "Incorrect questions" +msgstr "不正确的答案" + +#: apps/quiz/models.py:383 +msgid "Current Score" +msgstr "当前得分" + +#: apps/quiz/models.py:386 +msgid "Complete" +msgstr "完成" + +#: apps/quiz/models.py:389 +msgid "User Answers" +msgstr "用户回答" + +#: apps/quiz/models.py:392 +msgid "Start" +msgstr "开始" + +#: apps/quiz/models.py:394 +msgid "End" +msgstr "结束" + +#: apps/quiz/models.py:399 +msgid "Can see completed exams." +msgstr "可以看到已完成的考试" + +#: apps/quiz/models.py:557 +msgid "Figure" +msgstr "图像" + +#: apps/quiz/models.py:561 +msgid "Enter the question text that you want displayed" +msgstr "输入你想显示的问题文本" + +#: apps/quiz/models.py:563 apps/quiz/models.py:575 +#: apps/quiz/templates/question.html:47 +#: apps/quiz/templates/quiz/sitting_detail.html:21 +msgid "Question" +msgstr "问题" + +#: apps/quiz/models.py:567 +msgid "Explanation to be shown after the question has been answered." +msgstr "回答问题后将展示题解。" + +#: apps/quiz/models.py:570 apps/quiz/templates/question.html:32 +#: apps/quiz/templates/result.html:21 apps/quiz/templates/result.html.py:87 +msgid "Explanation" +msgstr "题解" + +#: apps/quiz/models.py:576 +msgid "Questions" +msgstr "问题" + +#: apps/quiz/templates/base.html:7 +msgid "Example Quiz Website" +msgstr "测试样例网站" + +#: apps/quiz/templates/correct_answer.html:6 +msgid "You answered the above question incorrectly" +msgstr "你错误回答了以上问题" + +#: apps/quiz/templates/correct_answer.html:16 +msgid "This is the correct answer" +msgstr "这是正确答案" + +#: apps/quiz/templates/correct_answer.html:23 +msgid "This was your answer." +msgstr "这是你的回答。" + +#: apps/quiz/templates/progress.html:6 +msgid "Progress Page" +msgstr "进度页面" + +#: apps/quiz/templates/progress.html:7 +msgid "User Progress Page" +msgstr "用户进度页面" + +#: apps/quiz/templates/progress.html:13 +msgid "Question Category Scores" +msgstr "问题类别得分" + +#: apps/quiz/templates/progress.html:20 +msgid "Correctly answererd" +msgstr "正确回答了" + +#: apps/quiz/templates/progress.html:21 +msgid "Incorrect" +msgstr "不正确" + +#: apps/quiz/templates/progress.html:50 +msgid "Previous exam papers" +msgstr "前一试卷" + +#: apps/quiz/templates/progress.html:52 +msgid "Below are the results of exams that you have sat." +msgstr "以下是你已经参加的考试的结果。" + +#: apps/quiz/templates/progress.html:59 +msgid "Quiz Title" +msgstr "测试标题" + +#: apps/quiz/templates/progress.html:61 +msgid "Possible Score" +msgstr "可能得分" + +#: apps/quiz/templates/question.html:13 apps/quiz/templates/result.html:13 +msgid "The previous question" +msgstr "前一问题" + +#: apps/quiz/templates/question.html:22 +msgid "Your answer was" +msgstr "你的回答是" + +#: apps/quiz/templates/question.html:47 +msgid "of" +msgstr "属于" + +#: apps/quiz/templates/question.html:52 +msgid "Question category" +msgstr "问题类别" + +#: apps/quiz/templates/question.html:74 +msgid "Check" +msgstr "检查" + +#: apps/quiz/templates/quiz/category_list.html:3 +#: apps/quiz/templates/quiz/quiz_list.html:3 +#: apps/quiz/templates/quiz/sitting_list.html:3 +msgid "All Quizzes" +msgstr "全部测试" + +#: apps/quiz/templates/quiz/category_list.html:6 +msgid "Category list" +msgstr "类别列表" + +#: apps/quiz/templates/quiz/quiz_detail.html:11 +msgid "You will only get one attempt at this quiz" +msgstr "本测试你只有一次尝试的机会" + +#: apps/quiz/templates/quiz/quiz_detail.html:16 +msgid "Start quiz" +msgstr "开始测试" + +#: apps/quiz/templates/quiz/quiz_list.html:6 +msgid "List of quizzes" +msgstr "测试列表" + +#: apps/quiz/templates/quiz/quiz_list.html:14 +msgid "Exam" +msgstr "考试" + +#: apps/quiz/templates/quiz/quiz_list.html:15 +msgid "Single attempt" +msgstr "单一尝试" + +#: apps/quiz/templates/quiz/quiz_list.html:31 +#: apps/quiz/templates/quiz/sitting_list.html:42 +msgid "View details" +msgstr "显示细节" + +#: apps/quiz/templates/quiz/quiz_list.html:41 +msgid "There are no available quizzes" +msgstr "没有可做的测试" + +#: apps/quiz/templates/quiz/sitting_detail.html:5 +msgid "Result of" +msgstr "结果属于" + +#: apps/quiz/templates/quiz/sitting_detail.html:5 +msgid "for" +msgstr "为了" + +#: apps/quiz/templates/quiz/sitting_detail.html:9 +msgid "Quiz title" +msgstr "测试标题" + +#: apps/quiz/templates/quiz/sitting_detail.html:14 +#: apps/quiz/templates/quiz/sitting_list.html:15 +msgid "Completed" +msgstr "已完成" + +#: apps/quiz/templates/quiz/sitting_detail.html:22 +msgid "User answer" +msgstr "用户的回答" + +#: apps/quiz/templates/quiz/sitting_detail.html:41 +msgid "incorrect" +msgstr "不正确" + +#: apps/quiz/templates/quiz/sitting_detail.html:43 +msgid "Correct" +msgstr "正确" + +#: apps/quiz/templates/quiz/sitting_detail.html:49 +msgid "Toggle whether correct" +msgstr "切换是否正确" + +#: apps/quiz/templates/quiz/sitting_list.html:6 +msgid "List of complete exams" +msgstr "已完成考试列表" + +#: apps/quiz/templates/quiz/sitting_list.html:28 +msgid "Filter" +msgstr "过滤" + +#: apps/quiz/templates/quiz/sitting_list.html:52 +msgid "There are no matching quizzes" +msgstr "没有符合条件的测试" + +#: apps/quiz/templates/result.html:7 +msgid "Exam Results for" +msgstr "考试结果" + +#: apps/quiz/templates/result.html:32 +msgid "Exam results" +msgstr "考试结果" + +#: apps/quiz/templates/result.html:34 +msgid "Exam title" +msgstr "考试标题" + +#: apps/quiz/templates/result.html:38 +msgid "You answered" +msgstr "你正确回答了" + +#: apps/quiz/templates/result.html:38 +msgid "questions correctly out of" +msgstr "个问题,总共" + +#: apps/quiz/templates/result.html:38 +msgid "giving you" +msgstr "个问题" + +#: apps/quiz/templates/result.html:38 +msgid "percent correct" +msgstr "%正确率" + +#: apps/quiz/templates/result.html:48 +msgid "Review the questions below and try the exam again in the future" +msgstr "回顾以下问题,将来可以再尝试本考试" + +#: apps/quiz/templates/result.html:52 +msgid "" +"The result of this exam will be stored in your progress section so you can " +"review and monitor your progression" +msgstr "本考试的结果将储存于你的进度区域,你可以查看和监控你的进度。" + +#: apps/quiz/templates/result.html:66 +msgid "Your session score is" +msgstr "你目前得分为" + +#: apps/quiz/templates/result.html:66 +msgid "out of a possible" +msgstr "而最多可能得分为" + +#: apps/quiz/templates/result.html:84 +msgid "Your answer" +msgstr "你的答案" + +#: apps/quiz/templates/single_complete.html:13 +msgid "You have already sat this exam and only one sitting is permitted" +msgstr "你已经参加本考试,只能参加一次" + +#: apps/quiz/templates/single_complete.html:15 +msgid "This exam is only accessible to signed in users" +msgstr "用户需登录方可访问本考试" + +#: apps/quiz/templates/view_quiz_category.html:3 +msgid "Quizzes related to" +msgstr "测试有关于" + +#: apps/quiz/templates/view_quiz_category.html:6 +msgid "Quizzes in the" +msgstr "测试属于" + +#: apps/quiz/templates/view_quiz_category.html:6 +msgid "category" +msgstr "类别" + +#: apps/quiz/templates/view_quiz_category.html:20 +msgid "There are no quizzes" +msgstr "没有测试" diff --git a/quiz-app/quiz/migrations/0001_initial.py b/quiz-app/quiz/migrations/0001_initial.py new file mode 100644 index 0000000..486249b --- /dev/null +++ b/quiz-app/quiz/migrations/0001_initial.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-06-22 11:20 +from __future__ import unicode_literals + +from django.conf import settings +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import re + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('category', models.CharField(blank=True, max_length=250, null=True, unique=True, verbose_name='Category')), + ], + options={ + 'verbose_name': 'Category', + 'verbose_name_plural': 'Categories', + }, + ), + migrations.CreateModel( + name='Progress', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'verbose_name': 'User Progress', + 'verbose_name_plural': 'User progress records', + }, + ), + migrations.CreateModel( + name='Question', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('figure', models.ImageField(blank=True, null=True, upload_to='uploads/%Y/%m/%d', verbose_name='Figure')), + ('content', models.CharField(help_text='Enter the question text that you want displayed', max_length=1000, verbose_name='Question')), + ('explanation', models.TextField(blank=True, help_text='Explanation to be shown after the question has been answered.', max_length=2000, verbose_name='Explanation')), + ('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='quiz.Category', verbose_name='Category')), + ], + options={ + 'verbose_name': 'Question', + 'verbose_name_plural': 'Questions', + 'ordering': ['category'], + }, + ), + migrations.CreateModel( + name='Quiz', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=60, verbose_name='Title')), + ('description', models.TextField(blank=True, help_text='a description of the quiz', verbose_name='Description')), + ('url', models.SlugField(help_text='a user friendly url', max_length=60, verbose_name='user friendly url')), + ('random_order', models.BooleanField(default=False, help_text='Display the questions in a random order or as they are set?', verbose_name='Random Order')), + ('max_questions', models.PositiveIntegerField(blank=True, help_text='Number of questions to be answered on each attempt.', null=True, verbose_name='Max Questions')), + ('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')), + ('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')), + ('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')), + ('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')), + ('success_text', models.TextField(blank=True, help_text='Displayed if user passes.', verbose_name='Success Text')), + ('fail_text', models.TextField(blank=True, help_text='Displayed if user fails.', verbose_name='Fail Text')), + ('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')), + ('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='quiz.Category', verbose_name='Category')), + ], + options={ + 'verbose_name': 'Quiz', + 'verbose_name_plural': 'Quizzes', + }, + ), + migrations.CreateModel( + name='Sitting', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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')), + ('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')), + ('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')), + ('current_score', models.IntegerField(verbose_name='Current Score')), + ('complete', models.BooleanField(default=False, verbose_name='Complete')), + ('user_answers', models.TextField(blank=True, default='{}', verbose_name='User Answers')), + ('start', models.DateTimeField(auto_now_add=True, verbose_name='Start')), + ('end', models.DateTimeField(blank=True, null=True, verbose_name='End')), + ('quiz', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='quiz.Quiz', verbose_name='Quiz')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'permissions': (('view_sittings', 'Can see completed exams.'),), + }, + ), + migrations.CreateModel( + name='SubCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sub_category', models.CharField(blank=True, max_length=250, null=True, verbose_name='Sub-Category')), + ('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='quiz.Category', verbose_name='Category')), + ], + options={ + 'verbose_name': 'Sub-Category', + 'verbose_name_plural': 'Sub-Categories', + }, + ), + migrations.AddField( + model_name='question', + name='quiz', + field=models.ManyToManyField(blank=True, to='quiz.Quiz', verbose_name='Quiz'), + ), + migrations.AddField( + model_name='question', + name='sub_category', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='quiz.SubCategory', verbose_name='Sub-Category'), + ), + ] diff --git a/quiz-app/quiz/migrations/0002_auto_20210417_1052.py b/quiz-app/quiz/migrations/0002_auto_20210417_1052.py new file mode 100644 index 0000000..5e152e8 --- /dev/null +++ b/quiz-app/quiz/migrations/0002_auto_20210417_1052.py @@ -0,0 +1,40 @@ +# Generated by Django 2.2.9 on 2021-04-17 10:52 + +import django.core.validators +from django.db import migrations, models +import re + + +class Migration(migrations.Migration): + + dependencies = [ + ('quiz', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='progress', + name='score', + field=models.CharField(max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name='Score'), + ), + migrations.AlterField( + model_name='quiz', + name='draft', + field=models.BooleanField(blank=True, 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'), + ), + migrations.AlterField( + model_name='sitting', + name='incorrect_questions', + field=models.CharField(blank=True, max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name='Incorrect questions'), + ), + migrations.AlterField( + model_name='sitting', + name='question_list', + field=models.CharField(max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name='Question List'), + ), + migrations.AlterField( + model_name='sitting', + name='question_order', + field=models.CharField(max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name='Question Order'), + ), + ] diff --git a/quiz-app/quiz/migrations/0003_progress_user_answers.py b/quiz-app/quiz/migrations/0003_progress_user_answers.py new file mode 100644 index 0000000..96edb83 --- /dev/null +++ b/quiz-app/quiz/migrations/0003_progress_user_answers.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2 on 2021-05-11 13:59 + +import django.core.validators +from django.db import migrations, models +import re + + +class Migration(migrations.Migration): + + dependencies = [ + ('quiz', '0002_auto_20210417_1052'), + ] + + operations = [ + migrations.AddField( + model_name='progress', + name='user_answers', + field=models.CharField(default=0, max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name='User_answers'), + preserve_default=False, + ), + ] diff --git a/quiz-app/quiz/migrations/0004_remove_progress_user_answers.py b/quiz-app/quiz/migrations/0004_remove_progress_user_answers.py new file mode 100644 index 0000000..e2649b9 --- /dev/null +++ b/quiz-app/quiz/migrations/0004_remove_progress_user_answers.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2 on 2021-05-11 14:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('quiz', '0003_progress_user_answers'), + ] + + operations = [ + migrations.RemoveField( + model_name='progress', + name='user_answers', + ), + ] diff --git a/quiz-app/quiz/migrations/0005_progress_user_answers.py b/quiz-app/quiz/migrations/0005_progress_user_answers.py new file mode 100644 index 0000000..367320f --- /dev/null +++ b/quiz-app/quiz/migrations/0005_progress_user_answers.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2 on 2021-05-11 14:33 + +import django.core.validators +from django.db import migrations, models +import re + + +class Migration(migrations.Migration): + + dependencies = [ + ('quiz', '0004_remove_progress_user_answers'), + ] + + operations = [ + migrations.AddField( + model_name='progress', + name='user_answers', + field=models.CharField(default='{}', max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name='User_answers'), + ), + ] diff --git a/quiz-app/quiz/migrations/0006_alter_progress_user_answers.py b/quiz-app/quiz/migrations/0006_alter_progress_user_answers.py new file mode 100644 index 0000000..288c7ec --- /dev/null +++ b/quiz-app/quiz/migrations/0006_alter_progress_user_answers.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2 on 2021-05-11 14:42 + +import django.core.validators +from django.db import migrations, models +import re + + +class Migration(migrations.Migration): + + dependencies = [ + ('quiz', '0005_progress_user_answers'), + ] + + operations = [ + migrations.AlterField( + model_name='progress', + name='user_answers', + field=models.CharField(default='{}', max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name='Réponses'), + ), + ] diff --git a/quiz-app/quiz/migrations/0007_remove_progress_user_answers.py b/quiz-app/quiz/migrations/0007_remove_progress_user_answers.py new file mode 100644 index 0000000..c122aea --- /dev/null +++ b/quiz-app/quiz/migrations/0007_remove_progress_user_answers.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2 on 2021-05-23 18:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('quiz', '0006_alter_progress_user_answers'), + ] + + operations = [ + migrations.RemoveField( + model_name='progress', + name='user_answers', + ), + ] diff --git a/quiz-app/quiz/migrations/__init__.py b/quiz-app/quiz/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/quiz-app/quiz/migrations/__pycache__/0001_initial.cpython-311.pyc b/quiz-app/quiz/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..ecfab04 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0001_initial.cpython-37.pyc b/quiz-app/quiz/migrations/__pycache__/0001_initial.cpython-37.pyc new file mode 100644 index 0000000..a27a2c7 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0001_initial.cpython-37.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0001_initial.cpython-38.pyc b/quiz-app/quiz/migrations/__pycache__/0001_initial.cpython-38.pyc new file mode 100644 index 0000000..76cace6 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0001_initial.cpython-38.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0001_initial.cpython-39.pyc b/quiz-app/quiz/migrations/__pycache__/0001_initial.cpython-39.pyc new file mode 100644 index 0000000..ec2eb30 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0001_initial.cpython-39.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0002_auto_20210417_1052.cpython-311.pyc b/quiz-app/quiz/migrations/__pycache__/0002_auto_20210417_1052.cpython-311.pyc new file mode 100644 index 0000000..2c3c78a Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0002_auto_20210417_1052.cpython-311.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0002_auto_20210417_1052.cpython-37.pyc b/quiz-app/quiz/migrations/__pycache__/0002_auto_20210417_1052.cpython-37.pyc new file mode 100644 index 0000000..c04f95e Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0002_auto_20210417_1052.cpython-37.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0002_auto_20210417_1052.cpython-38.pyc b/quiz-app/quiz/migrations/__pycache__/0002_auto_20210417_1052.cpython-38.pyc new file mode 100644 index 0000000..3bc0ae5 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0002_auto_20210417_1052.cpython-38.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0002_auto_20210417_1052.cpython-39.pyc b/quiz-app/quiz/migrations/__pycache__/0002_auto_20210417_1052.cpython-39.pyc new file mode 100644 index 0000000..50567e6 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0002_auto_20210417_1052.cpython-39.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0003_progress_user_answers.cpython-311.pyc b/quiz-app/quiz/migrations/__pycache__/0003_progress_user_answers.cpython-311.pyc new file mode 100644 index 0000000..52f3edd Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0003_progress_user_answers.cpython-311.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0003_progress_user_answers.cpython-37.pyc b/quiz-app/quiz/migrations/__pycache__/0003_progress_user_answers.cpython-37.pyc new file mode 100644 index 0000000..23b1800 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0003_progress_user_answers.cpython-37.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0003_progress_user_answers.cpython-38.pyc b/quiz-app/quiz/migrations/__pycache__/0003_progress_user_answers.cpython-38.pyc new file mode 100644 index 0000000..143cc8b Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0003_progress_user_answers.cpython-38.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0003_progress_user_answers.cpython-39.pyc b/quiz-app/quiz/migrations/__pycache__/0003_progress_user_answers.cpython-39.pyc new file mode 100644 index 0000000..e62ae02 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0003_progress_user_answers.cpython-39.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0004_remove_progress_user_answers.cpython-311.pyc b/quiz-app/quiz/migrations/__pycache__/0004_remove_progress_user_answers.cpython-311.pyc new file mode 100644 index 0000000..ace0825 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0004_remove_progress_user_answers.cpython-311.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0004_remove_progress_user_answers.cpython-37.pyc b/quiz-app/quiz/migrations/__pycache__/0004_remove_progress_user_answers.cpython-37.pyc new file mode 100644 index 0000000..d18054c Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0004_remove_progress_user_answers.cpython-37.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0004_remove_progress_user_answers.cpython-38.pyc b/quiz-app/quiz/migrations/__pycache__/0004_remove_progress_user_answers.cpython-38.pyc new file mode 100644 index 0000000..5e84331 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0004_remove_progress_user_answers.cpython-38.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0004_remove_progress_user_answers.cpython-39.pyc b/quiz-app/quiz/migrations/__pycache__/0004_remove_progress_user_answers.cpython-39.pyc new file mode 100644 index 0000000..3625b7f Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0004_remove_progress_user_answers.cpython-39.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0005_progress_user_answers.cpython-311.pyc b/quiz-app/quiz/migrations/__pycache__/0005_progress_user_answers.cpython-311.pyc new file mode 100644 index 0000000..e1a2713 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0005_progress_user_answers.cpython-311.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0005_progress_user_answers.cpython-37.pyc b/quiz-app/quiz/migrations/__pycache__/0005_progress_user_answers.cpython-37.pyc new file mode 100644 index 0000000..4345ea9 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0005_progress_user_answers.cpython-37.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0005_progress_user_answers.cpython-38.pyc b/quiz-app/quiz/migrations/__pycache__/0005_progress_user_answers.cpython-38.pyc new file mode 100644 index 0000000..5f9476f Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0005_progress_user_answers.cpython-38.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0005_progress_user_answers.cpython-39.pyc b/quiz-app/quiz/migrations/__pycache__/0005_progress_user_answers.cpython-39.pyc new file mode 100644 index 0000000..394bde6 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0005_progress_user_answers.cpython-39.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0006_alter_progress_user_answers.cpython-311.pyc b/quiz-app/quiz/migrations/__pycache__/0006_alter_progress_user_answers.cpython-311.pyc new file mode 100644 index 0000000..94eac3d Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0006_alter_progress_user_answers.cpython-311.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0006_alter_progress_user_answers.cpython-37.pyc b/quiz-app/quiz/migrations/__pycache__/0006_alter_progress_user_answers.cpython-37.pyc new file mode 100644 index 0000000..7796c85 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0006_alter_progress_user_answers.cpython-37.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0006_alter_progress_user_answers.cpython-38.pyc b/quiz-app/quiz/migrations/__pycache__/0006_alter_progress_user_answers.cpython-38.pyc new file mode 100644 index 0000000..6b6b4e1 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0006_alter_progress_user_answers.cpython-38.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0006_alter_progress_user_answers.cpython-39.pyc b/quiz-app/quiz/migrations/__pycache__/0006_alter_progress_user_answers.cpython-39.pyc new file mode 100644 index 0000000..6e4ccaf Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0006_alter_progress_user_answers.cpython-39.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0007_remove_progress_user_answers.cpython-311.pyc b/quiz-app/quiz/migrations/__pycache__/0007_remove_progress_user_answers.cpython-311.pyc new file mode 100644 index 0000000..044f076 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0007_remove_progress_user_answers.cpython-311.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0007_remove_progress_user_answers.cpython-37.pyc b/quiz-app/quiz/migrations/__pycache__/0007_remove_progress_user_answers.cpython-37.pyc new file mode 100644 index 0000000..6cc9569 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0007_remove_progress_user_answers.cpython-37.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0007_remove_progress_user_answers.cpython-38.pyc b/quiz-app/quiz/migrations/__pycache__/0007_remove_progress_user_answers.cpython-38.pyc new file mode 100644 index 0000000..29b8de2 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0007_remove_progress_user_answers.cpython-38.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/0007_remove_progress_user_answers.cpython-39.pyc b/quiz-app/quiz/migrations/__pycache__/0007_remove_progress_user_answers.cpython-39.pyc new file mode 100644 index 0000000..33083e7 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/0007_remove_progress_user_answers.cpython-39.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/__init__.cpython-311.pyc b/quiz-app/quiz/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..cae3a1d Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/__init__.cpython-37.pyc b/quiz-app/quiz/migrations/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..6207771 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/__init__.cpython-37.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/__init__.cpython-38.pyc b/quiz-app/quiz/migrations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..1967024 Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/__init__.cpython-38.pyc differ diff --git a/quiz-app/quiz/migrations/__pycache__/__init__.cpython-39.pyc b/quiz-app/quiz/migrations/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..1e54c5a Binary files /dev/null and b/quiz-app/quiz/migrations/__pycache__/__init__.cpython-39.pyc differ diff --git a/quiz-app/quiz/models.py b/quiz-app/quiz/models.py new file mode 100644 index 0000000..89d3992 --- /dev/null +++ b/quiz-app/quiz/models.py @@ -0,0 +1,621 @@ +from __future__ import unicode_literals +import re +import json + +from django.db import models +from django.core.exceptions import ValidationError, ImproperlyConfigured +from django.core.validators import ( + MaxValueValidator, validate_comma_separated_integer_list, +) +from django.utils.translation import gettext_lazy as _ +from django.utils.timezone import now +from six import python_2_unicode_compatible +#from django.utils.encoding import python_2_unicode_compatible +from django.conf import settings +from django.contrib.auth.models import User + +from model_utils.managers import InheritanceManager + +from datetime import datetime, timedelta, timezone + +class CategoryManager(models.Manager): + + def new_category(self, category): + new_category = self.create(category=re.sub('\s+', '-', category) + .lower()) + + new_category.save() + return new_category + + +@python_2_unicode_compatible +class Category(models.Model): + + category = models.CharField( + verbose_name=_("Category"), + max_length=250, blank=True, + unique=True, null=True) + + objects = CategoryManager() + + class Meta: + verbose_name = _("Category") + verbose_name_plural = _("Categories") + + def __str__(self): + return self.category + + +@python_2_unicode_compatible +class SubCategory(models.Model): + + sub_category = models.CharField( + verbose_name=_("Sub-Category"), + max_length=250, blank=True, null=True) + + category = models.ForeignKey( + Category, null=True, blank=True, + verbose_name=_("Category"), on_delete=models.CASCADE) + + objects = CategoryManager() + + class Meta: + verbose_name = _("Sub-Category") + verbose_name_plural = _("Sub-Categories") + + def __str__(self): + return self.sub_category + " (" + self.category.category + ")" + + +@python_2_unicode_compatible +class Quiz(models.Model): + + title = models.CharField( + verbose_name=_("Title"), + max_length=60, blank=False) + + description = models.TextField( + verbose_name=_("Description"), + blank=True, help_text=_("a description of the quiz")) + + url = models.SlugField( + max_length=60, blank=False, + help_text=_("a user friendly url"), + verbose_name=_("user friendly url")) + + category = models.ForeignKey( + Category, null=True, blank=True, + verbose_name=_("Category"), on_delete=models.CASCADE) + +# sub_category = models.ForeignKey( +# SubCategory, null=True, blank=True, +# verbose_name=_("SubCategory"), on_delete=models.CASCADE) + + random_order = models.BooleanField( + blank=False, default=False, + verbose_name=_("Random Order"), + help_text=_("Display the questions in " + "a random order or as they " + "are set?")) + + max_questions = models.PositiveIntegerField( + blank=True, null=True, verbose_name=_("Max Questions"), + help_text=_("Number of questions to be answered on each attempt.")) + + answers_at_end = models.BooleanField( + blank=False, default=False, + help_text=_("Correct answer is NOT shown after question." + " Answers displayed at the end."), + verbose_name=_("Answers at end")) + + exam_paper = models.BooleanField( + blank=False, default=False, + help_text=_("If yes, the result of each" + " attempt by a user will be" + " stored. Necessary for marking."), + verbose_name=_("Exam Paper")) + + single_attempt = models.BooleanField( + blank=False, 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")) + + pass_mark = models.SmallIntegerField( + blank=True, default=0, + verbose_name=_("Pass Mark"), + help_text=_("Percentage required to pass exam."), + validators=[MaxValueValidator(100)]) + + success_text = models.TextField( + blank=True, help_text=_("Displayed if user passes."), + verbose_name=_("Success Text")) + + fail_text = models.TextField( + verbose_name=_("Fail Text"), + blank=True, help_text=_("Displayed if user fails.")) + + draft = models.BooleanField( + blank=True, default=False, + verbose_name=_("Draft"), + help_text=_("If yes, the quiz is not displayed" + " in the quiz list and can only be" + " taken by users who can edit" + " quizzes.")) + + def save(self, force_insert=False, force_update=False, *args, **kwargs): + self.url = re.sub('\s+', '-', self.url).lower() + + self.url = ''.join(letter for letter in self.url if + letter.isalnum() or letter == '-') + + if self.single_attempt is True: + self.exam_paper = True + + if self.pass_mark > 100: + raise ValidationError('%s is above 100' % self.pass_mark) + + super(Quiz, self).save(force_insert, force_update, *args, **kwargs) + + class Meta: + verbose_name = _("Quiz") + verbose_name_plural = _("Quizzes") + + def __str__(self): + return self.title + + def get_questions(self): + return self.question_set.all().select_subclasses() + + @property + def get_max_score(self): + return self.get_questions().count() + + def anon_score_id(self): + return str(self.id) + "_score" + + def anon_q_list(self): + return str(self.id) + "_q_list" + + def anon_q_data(self): + return str(self.id) + "_data" + + +class ProgressManager(models.Manager): + + def new_progress(self, user): + new_progress = self.create(user=user, + score="",) + new_progress.save() + return new_progress + + +class Progress(models.Model): + """ + Progress is used to track an individual signed in users score on different + quiz's and categories + + Data stored in csv using the format: + category, score, possible, category, score, possible, ... + """ + user = models.OneToOneField(settings.AUTH_USER_MODEL, verbose_name=_("User"), on_delete=models.CASCADE) + + score = models.CharField(max_length=1024, + verbose_name=_("Score"), + validators=[validate_comma_separated_integer_list]) + + objects = ProgressManager() + + class Meta: + verbose_name = _("User Progress") + verbose_name_plural = _("User progress records") + + @property + def list_all_cat_scores(self): + """ + Returns a dict in which the key is the category name and the item is + a list of three integers. + + The first is the number of questions correct, + the second is the possible best score, + the third is the percentage correct. + + The dict will have one key for every category that you have defined + """ + score_before = self.score + output = {} + + for cat in Category.objects.all(): + to_find = re.escape(cat.category) + r",(\d+),(\d+)," + # group 1 is score, group 2 is highest possible + + match = re.search(to_find, self.score, re.IGNORECASE) + + if match: + score = int(match.group(1)) + possible = int(match.group(2)) + + try: + percent = int(round((float(score) / float(possible)) + * 100)) + except: + percent = 0 + + output[cat.category] = [score, possible, percent] + + else: # if category has not been added yet, add it. + self.score += cat.category + ",0,0," + output[cat.category] = [0, 0] + + if len(self.score) > len(score_before): + # If a new category has been added, save changes. + self.save() + + return output + + def update_score(self, question, score_to_add=0, possible_to_add=0): + """ + Pass in question object, amount to increase score + and max possible. + + Does not return anything. + """ + category_test = Category.objects.filter(category=question.category)\ + .exists() + + if any([item is False for item in [category_test, + score_to_add, + possible_to_add, + isinstance(score_to_add, int), + isinstance(possible_to_add, int)]]): + return _("error"), _("category does not exist or invalid score") + + to_find = re.escape(str(question.category)) +\ + r",(?P\d+),(?P\d+)," + + match = re.search(to_find, self.score, re.IGNORECASE) + + if match: + updated_score = int(match.group('score')) + abs(score_to_add) + updated_possible = int(match.group('possible')) +\ + abs(possible_to_add) + + new_score = ",".join( + [ + str(question.category), + str(updated_score), + str(updated_possible), + "" + ]) + + # swap old score for the new one + self.score = self.score.replace(match.group(), new_score) + self.save() + + else: + # if not present but existing, add with the points passed in + self.score += ",".join( + [ + str(question.category), + str(score_to_add), + str(possible_to_add), + "" + ]) + self.save() + + def show_exams(self): + """ + Finds the previous quizzes marked as 'exam papers'. + Returns a queryset of complete exams. + """ + return Sitting.objects.filter(user=self.user, complete=True) + +### TROUVER COMMENT RECUP QUIZ_SITTING USER_ANSWERS {} + #print(self.user) + + +class SittingManager(models.Manager): + + def new_sitting(self, user, quiz): + if quiz.random_order is True: + question_set = quiz.question_set.all() \ + .select_subclasses() \ + .order_by('?') + else: + question_set = quiz.question_set.all() \ + .select_subclasses() + + question_set = [item.id for item in question_set] + + if len(question_set) == 0: + raise ImproperlyConfigured('Question set of the quiz is empty. ' + 'Please configure questions properly') + + if quiz.max_questions and quiz.max_questions < len(question_set): + question_set = question_set[:quiz.max_questions] + + questions = ",".join(map(str, question_set)) + "," + + new_sitting = self.create(user=user, + quiz=quiz, + question_order=questions, + question_list=questions, + incorrect_questions="", + current_score=0, + complete=False, + user_answers='{}') + return new_sitting + + def user_sitting(self, user, quiz): + if quiz.single_attempt is True and self.filter(user=user, + quiz=quiz, + complete=True)\ + .exists(): + return False + + try: + sitting = self.get(user=user, quiz=quiz, complete=False) + except Sitting.DoesNotExist: + sitting = self.new_sitting(user, quiz) + except Sitting.MultipleObjectsReturned: + sitting = self.filter(user=user, quiz=quiz, complete=False)[0] + return sitting + + +class Sitting(models.Model): + """ + Used to store the progress of logged in users sitting a quiz. + Replaces the session system used by anon users. + + Question_order is a list of integer pks of all the questions in the + quiz, in order. + + Question_list is a list of integers which represent id's of + the unanswered questions in csv format. + + Incorrect_questions is a list in the same format. + + Sitting deleted when quiz finished unless quiz.exam_paper is true. + + User_answers is a json object in which the question PK is stored + with the answer the user gave. + """ + + user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_("User"), on_delete=models.CASCADE) + + quiz = models.ForeignKey(Quiz, verbose_name=_("Quiz"), on_delete=models.CASCADE) + + question_order = models.CharField( + max_length=1024, + verbose_name=_("Question Order"), + validators=[validate_comma_separated_integer_list]) + + question_list = models.CharField( + max_length=1024, + verbose_name=_("Question List"), + validators=[validate_comma_separated_integer_list]) + + incorrect_questions = models.CharField( + max_length=1024, + blank=True, + verbose_name=_("Incorrect questions"), + validators=[validate_comma_separated_integer_list]) + + current_score = models.IntegerField(verbose_name=_("Current Score")) + + complete = models.BooleanField(default=False, blank=False, + verbose_name=_("Complete")) + + user_answers = models.TextField(blank=True, default='{}', + verbose_name=_("User Answers")) + + start = models.DateTimeField(auto_now_add=True, + verbose_name=_("Start")) + + end = models.DateTimeField(null=True, blank=True, verbose_name=_("End")) + + objects = SittingManager() + + class Meta: + permissions = (("view_sittings", _("Can see completed exams.")),) + + def get_first_question(self): + """ + Returns the next question. + If no question is found, returns False + Does NOT remove the question from the front of the list. + """ + if not self.question_list: + return False + + first, _ = self.question_list.split(',', 1) + question_id = int(first) + return Question.objects.get_subclass(id=question_id) + + def remove_first_question(self): + if not self.question_list: + return + + _, others = self.question_list.split(',', 1) + self.question_list = others + self.save() + + def add_to_score(self, points): + self.current_score += int(points) + self.save() + + @property + def get_current_score(self): + return self.current_score + + def _question_ids(self): + return [int(n) for n in self.question_order.split(',') if n] + + @property + def get_percent_correct(self): + dividend = float(self.current_score) + divisor = len(self._question_ids()) + if divisor < 1: + return 0 # prevent divide by zero error + + if dividend > divisor: + return 100 + + correct = int(round((dividend / divisor) * 100)) + + if correct >= 1: + return correct + else: + return 0 + + @property + # add by makayabou, 30/5/2021 + def get_duration(self): + time_delta = self.end - self.start + time_delta_in_seconds = time_delta.total_seconds() + min, sec = divmod(time_delta_in_seconds, 60) + return "%d minutes et %d secondes" % (min, sec) + + + def mark_quiz_complete(self): + self.complete = True + self.end = now() + self.save() + + def add_incorrect_question(self, question): + """ + Adds uid of incorrect question to the list. + The question object must be passed in. + """ + if len(self.incorrect_questions) > 0: + self.incorrect_questions += ',' + self.incorrect_questions += str(question.id) + "," + if self.complete: + self.add_to_score(-1) + self.save() + + @property + def get_incorrect_questions(self): + """ + Returns a list of non empty integers, representing the pk of + questions + """ + return [int(q) for q in self.incorrect_questions.split(',') if q] + + def remove_incorrect_question(self, question): + current = self.get_incorrect_questions + current.remove(question.id) + self.incorrect_questions = ','.join(map(str, current)) + self.add_to_score(1) + self.save() + + @property + def check_if_passed(self): + return self.get_percent_correct >= self.quiz.pass_mark + + @property + def result_message(self): + if self.check_if_passed: + return self.quiz.success_text + else: + return self.quiz.fail_text + + def add_user_answer(self, question, guess): + current = json.loads(self.user_answers) + current[question.id] = guess + self.user_answers = json.dumps(current) + self.save() + + def get_questions(self, with_answers=False): + question_ids = self._question_ids() + questions = sorted( + self.quiz.question_set.filter(id__in=question_ids) + .select_subclasses(), + key=lambda q: question_ids.index(q.id)) + + if with_answers: + user_answers = json.loads(self.user_answers) + for question in questions: + question.user_answer = user_answers[str(question.id)] + + return questions + + @property + def questions_with_user_answers(self): + return { + q: q.user_answer for q in self.get_questions(with_answers=True) + } + + @property + def get_max_score(self): + return len(self._question_ids()) + + def progress(self): + """ + Returns the number of questions answered so far and the total number of + questions. + """ + answered = len(json.loads(self.user_answers)) + total = self.get_max_score + #answerlist = self.questions_with_user_answers + return answered, total #answerlist + + +### AJOUT ### + def get_user_answers(self): + reponses = self.user_answers + return reponses +############# + + +@python_2_unicode_compatible +class Question(models.Model): + """ + Base class for all question types. + Shared properties placed here. + """ + + quiz = models.ManyToManyField(Quiz, + verbose_name=_("Quiz"), + blank=True) + + category = models.ForeignKey(Category, + verbose_name=_("Category"), + blank=True, + null=True, + on_delete=models.CASCADE) + + sub_category = models.ForeignKey(SubCategory, + verbose_name=_("Sub-Category"), + blank=True, + null=True, + on_delete=models.CASCADE) + + figure = models.ImageField(upload_to='uploads/%Y/%m/%d', + blank=True, + null=True, + verbose_name=_("Figure")) + + content = models.CharField(max_length=1000, + blank=False, + help_text=_("Enter the question text that " + "you want displayed"), + verbose_name=_('Question')) + + explanation = models.TextField(max_length=2000, + blank=True, + help_text=_("Explanation to be shown " + "after the question has " + "been answered."), + verbose_name=_('Explanation')) + + objects = InheritanceManager() + + class Meta: + verbose_name = _("Question") + verbose_name_plural = _("Questions") + ordering = ['category'] + + def __str__(self): + return self.content diff --git a/quiz-app/quiz/static/css/style.css b/quiz-app/quiz/static/css/style.css new file mode 100644 index 0000000..3df87fc --- /dev/null +++ b/quiz-app/quiz/static/css/style.css @@ -0,0 +1,17 @@ +@import url('https://fonts.googleapis.com/css2?family=Play&display=swap'); + +/* Appliquer les polices aux éléments du document */ +body { + font-family: 'Play'; /* Aladin pour le corps du document */ + /* font-size: 1.5em; */ +} + +.button-link { + background: none; + border: none; + color: #497AA1; + text-decoration: underline; + cursor: pointer; + padding: 0; + font: inherit; +} \ No newline at end of file diff --git a/quiz-app/quiz/static/favicon.ico b/quiz-app/quiz/static/favicon.ico new file mode 100644 index 0000000..02fb53a Binary files /dev/null and b/quiz-app/quiz/static/favicon.ico differ diff --git a/quiz-app/quiz/static/images/wp.jpg b/quiz-app/quiz/static/images/wp.jpg new file mode 100644 index 0000000..bfb2c9f Binary files /dev/null and b/quiz-app/quiz/static/images/wp.jpg differ diff --git a/quiz-app/quiz/static/logo.png b/quiz-app/quiz/static/logo.png new file mode 100644 index 0000000..f9bba13 Binary files /dev/null and b/quiz-app/quiz/static/logo.png differ diff --git a/quiz-app/quiz/templates/base.html b/quiz-app/quiz/templates/base.html new file mode 100644 index 0000000..b9ac3cc --- /dev/null +++ b/quiz-app/quiz/templates/base.html @@ -0,0 +1,63 @@ +{% load i18n %} +{% load static %} + + + + + +{% trans "Garage Numérique Quizzes" %} | {% block title %}{% endblock %} + + + + + + + + + + + +
+ {% block sidebar %} + + {% endblock %} +
+ + + + +
+ +{% block content %} + +{% endblock %} + +
+ + +
+ +
+ + + + + diff --git a/quiz-app/quiz/templates/correct_answer.html b/quiz-app/quiz/templates/correct_answer.html new file mode 100644 index 0000000..b7753d7 --- /dev/null +++ b/quiz-app/quiz/templates/correct_answer.html @@ -0,0 +1,32 @@ +{% load i18n %} +{% if previous.answers %} + + {% if user_was_incorrect %} +
+ {% trans "You answered incorrectly to this answer" %} +
+ {% endif %} + + + + {% for answer in previous.answers %} + {% if answer.correct %} + + + + {% else %} + + + + {% endif %} + + {% endfor %} + +
{{ answer.content }}{% trans "It's a correct answer" %}
{{ answer.content }} + {% if previous.question_type.MCQuestion %} + {% if answer.id|add:"0" == previous.previous_answer|add:"0" %} + {% trans "Your answer was" %} + {% endif %} + {% endif %} +
+{% endif %} diff --git a/quiz-app/quiz/templates/progress.html b/quiz-app/quiz/templates/progress.html new file mode 100644 index 0000000..0bf63c5 --- /dev/null +++ b/quiz-app/quiz/templates/progress.html @@ -0,0 +1,85 @@ +{% extends "base.html" %} +{% load i18n %} + +{% load quiz_tags %} + +{% block title %} {% trans "Progress Page" %} {% endblock %} +{% block description %} {% trans "User Progress Page" %} {% endblock %} + +{% block content %} + + {% if cat_scores %} + +

{% trans "Question Category Scores" %}

+ + + + + + + + + + + + + + + + {% for cat, value in cat_scores.items %} + + + + + + + + {% endfor %} + + + +
{% trans "Category" %}{% trans "Correctly answererd" %}{% trans "Incorrect" %}%
{{ cat }}{{ value.0 }}{{ value.1 }}{{ value.2 }}
+ + + {% endif %} + + {% if exams %} + +
+ +

{% trans "Previous exam papers" %}

+

+ {% trans "Below are the results of exams that you have sat." %} +

+ + + + + + + + + + + + + + + {% for exam in exams %} + + + + + + + + + + {% endfor %} + + + +
{% trans "Quiz Title" %}{% trans "Score" %}{% trans "Possible Score" %}%
{{ exam.quiz.title }}{{ exam.current_score }}{{ exam.get_max_score }}{{ exam.get_percent_correct }}
+ + {% endif %} +{% endblock %} diff --git a/quiz-app/quiz/templates/question.html b/quiz-app/quiz/templates/question.html new file mode 100644 index 0000000..c95b79d --- /dev/null +++ b/quiz-app/quiz/templates/question.html @@ -0,0 +1,82 @@ +{% extends "base.html" %} +{% load i18n%} + +{% load quiz_tags %} + +{% block title %} {{ quiz.title }} {% endblock %} +{% block description %} {{ quiz.title }} - {{ quiz.description }} {% endblock %} + +{% block content %} + +{% if previous.answers %} + +

{% trans "The previous question" %}:

+

{{ previous.previous_question }}

+ + {% if previous.previous_outcome %} +
+ {% else %} +
+ {% endif %} +

+ {% trans "Your answer was" %} + + {{ previous.previous_outcome|yesno:"correct,incorrect" }} + +

+ +
+ + {% include 'correct_answer.html' %} + +

{% trans "Explanation" %}:

+
+

{{ previous.previous_question.explanation }}

+
+ +
+ +{% endif %} + +
+ +{% if question %} + +{% if progress %} +
+{% trans "Question" %} {{ progress.0|add:1 }} {% trans "of" %} {{ progress.1 }} +
+{% endif %} + +

+ {% trans "Question category" %}: + {{ question.sub_category }} +

+ +

{{ question.content }}

+ +{% if question.figure %} + {{ question.content }} +{% endif %} + +
{% csrf_token %} + + +
    + + {% for answer in form.answers %} +
  • + {{ answer }} +
  • + {% endfor %} + +
+ +
+ +{% endif %} + +
+ + +{% endblock %} diff --git a/quiz-app/quiz/templates/quiz/category_list.html b/quiz-app/quiz/templates/quiz/category_list.html new file mode 100644 index 0000000..90ffe0a --- /dev/null +++ b/quiz-app/quiz/templates/quiz/category_list.html @@ -0,0 +1,18 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "All Quizzes" %}{% endblock %} + +{% block content %} +

{% trans "Category list" %}

+ + + +{% endblock %} diff --git a/quiz-app/quiz/templates/quiz/quiz_detail.html b/quiz-app/quiz/templates/quiz/quiz_detail.html new file mode 100644 index 0000000..2681f77 --- /dev/null +++ b/quiz-app/quiz/templates/quiz/quiz_detail.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %} +{{ quiz.title }} +{% endblock %} + +{% block content %} +

{{ quiz.title }}

+

{% trans "Category" %}: {{ quiz.category }}

+{% if quiz.single_attempt %} +

{% trans "You will only get one attempt at this quiz" %}.

+{% endif %} +

{{ quiz.description }}

+

+ + {% trans "Start quiz" %} + +

+{% endblock %} diff --git a/quiz-app/quiz/templates/quiz/quiz_list.html b/quiz-app/quiz/templates/quiz/quiz_list.html new file mode 100644 index 0000000..11f9ce2 --- /dev/null +++ b/quiz-app/quiz/templates/quiz/quiz_list.html @@ -0,0 +1,43 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Tout les quizzs" %}{% endblock %} + +{% block content %} +

{% trans "List of quizzes" %}

+ {% if quiz_list %} + + + + + + + + + + + + + + + {% for quiz in quiz_list %} + + + + + + + + + {% endfor %} + + +
{% trans "Title" %}{% trans "Category" %}{% trans "Exam" %}{% trans "Single attempt" %}
{{ quiz.title }}{{ quiz.category }}{{ quiz.exam_paper }}{{ quiz.single_attempt }} + + {% trans "View details" %} + +
+ + {% else %} +

{% trans "There are no available quizzes" %}.

+ {% endif %} +{% endblock %} diff --git a/quiz-app/quiz/templates/quiz/sitting_detail.html b/quiz-app/quiz/templates/quiz/sitting_detail.html new file mode 100644 index 0000000..6977d86 --- /dev/null +++ b/quiz-app/quiz/templates/quiz/sitting_detail.html @@ -0,0 +1,68 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load quiz_tags %} +{% block title %} +{% trans "Results of " %} {{ sitting.quiz.title }} {% trans "for " %} {{ sitting.user }} +{% endblock %} + +{% block content %} + +{% if sitting.user.username == request.user.username or request.user.is_superuser %} + +

{% trans "Quiz title" %}: {{ sitting.quiz.title }}

+

{% trans "Catégory" %}: {{ sitting.quiz.category }}

+

{{ sitting.quiz.description }}

+
+

{% trans "User" %}: {{ sitting.user }}

+

{% trans "Completed" %}: {{ sitting.end|date }}

+

{% trans "Score" %}: {{ sitting.get_percent_correct }}%

+ + + + + + + + + + + + + + + +{% for question in questions %} + + + + + {% if question.explanation %} + + {% endif %} + +{% endfor %} + + + +
{% trans "Question" %}{% trans "User answer" %}RéponseExplication
+ {{ question.content }} + {% if question.figure %} +
{{ question.figure }}
+ {% endif %} +
{{ question|answer_choice_to_string:question.user_answer }} + {% if question.id in sitting.get_incorrect_questions %} +

{% trans "Incorrect" %}

+ {% else %} +

{% trans "Correct" %}

+ {% endif %} +
{{ question.explanation }}
+{% else %} +

{% trans "You don't have access to this page" %}

+{% endif %} +{% endblock %} + \ No newline at end of file diff --git a/quiz-app/quiz/templates/quiz/sitting_list.html b/quiz-app/quiz/templates/quiz/sitting_list.html new file mode 100644 index 0000000..1e3377d --- /dev/null +++ b/quiz-app/quiz/templates/quiz/sitting_list.html @@ -0,0 +1,64 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "All Quizzes" %}{% endblock %} + +{% block content %} + + +{% if user.is_superuser %} + +

{% trans "List of complete exams" %}

+ {% if sitting_list %} + + + + + + + + + + + + + + + + + + + + + + + + + + + {% for sitting in sitting_list %} + + + + + + + + + + {% endfor %} + + + +
{% trans "User" %}{% trans "Quiz" %}{% trans "Completed" %}{% trans "Duration(in minutes)" %}{% trans "Score" %}(%)
{{ sitting.user }}{{ sitting.quiz }}{{ sitting.end|date }}{{ sitting.get_duration }}{{ sitting.get_percent_correct }} + + {% trans "View details" %} + +
+ {% else %} +

{% trans "There are no matching quizzes" %}.

+ {% endif %} + +{% else %} +

{% trans "You don't have access to this page" %}

+{% endif %} +{% endblock %} diff --git a/quiz-app/quiz/templates/result.html b/quiz-app/quiz/templates/result.html new file mode 100644 index 0000000..1a9831c --- /dev/null +++ b/quiz-app/quiz/templates/result.html @@ -0,0 +1,99 @@ +{% extends "base.html" %} +{% load i18n %} + +{% load quiz_tags %} + +{% block title %} {{ quiz.title}} {% endblock %} +{% block description %} {% trans "Exam results for" %} {{ quiz.title }} {% endblock %} + +{% block content %} + + {% if previous.answers %} + +

{% trans "The previous question" %}:

+

{{ previous.previous_question }}

+

Your answer was + + {{ previous.previous_outcome|yesno:"correct,incorrect" }} + +

+ {% include 'correct_answer.html' %} +

{% trans "Explanation" %}:

+
+

{{ previous.previous_question.explanation }}

+
+
+ + {% endif %} + + {% if max_score %} + +
+

{% trans "Exam results" %}

+

+ {% trans "Exam title" %}: + {{ quiz.title }}

+ +

+ {% trans "You answered" %} {{ score }} {% trans "questions correctly on " %} {{ max_score }}, {% trans "giving you" %} {{ percent }} {% trans " of correct answers" %} +

+ + {% if quiz.pass_mark %} +
+

{{ sitting.result_message }}

+
+ + {% endif %} + +

{% trans "Review the questions below and try the exam again in the future"%}.

+ + {% if user.is_authenticated %} + +

{% trans "The result of this exam will be stored in your progress section so you can review and monitor your progression" %}.

+ + {% endif %} +
+ + + {% endif %} + + +
+ + {% if possible %} + +

+ {% trans "Your session score is" %} {{ session }} {% trans "out of a possible" %} {{ possible }} +

+ +
+ + {% endif %} + + {% if questions %} + + {% for question in questions %} + +

+ {{ question.content }} +

+ + {% correct_answer_for_all question %} + + {% if question.user_answer %} +

{% trans "Your answer" %}: {{ question|answer_choice_to_string:question.user_answer }}

+ {% endif %} + +

{% trans "Explanation" %}:

+
+

{{ question.explanation|safe }}

+
+ +
+ + {% endfor %} + + {% endif %} + + +{% endblock %} diff --git a/quiz-app/quiz/templates/single_complete.html b/quiz-app/quiz/templates/single_complete.html new file mode 100644 index 0000000..7dba18b --- /dev/null +++ b/quiz-app/quiz/templates/single_complete.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% load i18n %} + +{% load quiz_tags %} + +{% block title %} {{ quiz.title }} {% endblock %} +{% block description %} {{ quiz.title }} - {{ quiz.description }} {% endblock %} + +{% block content %} + + +{% if user.is_authenticated %} +

{% trans "You already did this quiz" %}.

+{% else %} +

{% trans "This quiz is only accessible to authentified users" %}.

+{% endif %} + +{% endblock %} diff --git a/quiz-app/quiz/templates/view_quiz_category.html b/quiz-app/quiz/templates/view_quiz_category.html new file mode 100644 index 0000000..29aeda6 --- /dev/null +++ b/quiz-app/quiz/templates/view_quiz_category.html @@ -0,0 +1,23 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Quizzes related to" %} {{ category.category }}{% endblock %} + +{% block content %} +

{% trans "Quizzes in the" %} {{ category.category }} {% trans "category" %}

+ + {% with object_list as quizzes %} + {% if quizzes %} + + {% else %} +

{% trans "There are no quizzes" %}

+ {% endif %} + {% endwith %} +{% endblock %} diff --git a/quiz-app/quiz/templatetags/__init__.py b/quiz-app/quiz/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/quiz-app/quiz/templatetags/__pycache__/__init__.cpython-311.pyc b/quiz-app/quiz/templatetags/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..af53ac6 Binary files /dev/null and b/quiz-app/quiz/templatetags/__pycache__/__init__.cpython-311.pyc differ diff --git a/quiz-app/quiz/templatetags/__pycache__/__init__.cpython-37.pyc b/quiz-app/quiz/templatetags/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..657770f Binary files /dev/null and b/quiz-app/quiz/templatetags/__pycache__/__init__.cpython-37.pyc differ diff --git a/quiz-app/quiz/templatetags/__pycache__/__init__.cpython-38.pyc b/quiz-app/quiz/templatetags/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..b51eb4d Binary files /dev/null and b/quiz-app/quiz/templatetags/__pycache__/__init__.cpython-38.pyc differ diff --git a/quiz-app/quiz/templatetags/__pycache__/__init__.cpython-39.pyc b/quiz-app/quiz/templatetags/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..b05d2a2 Binary files /dev/null and b/quiz-app/quiz/templatetags/__pycache__/__init__.cpython-39.pyc differ diff --git a/quiz-app/quiz/templatetags/__pycache__/quiz_tags.cpython-311.pyc b/quiz-app/quiz/templatetags/__pycache__/quiz_tags.cpython-311.pyc new file mode 100644 index 0000000..759527e Binary files /dev/null and b/quiz-app/quiz/templatetags/__pycache__/quiz_tags.cpython-311.pyc differ diff --git a/quiz-app/quiz/templatetags/__pycache__/quiz_tags.cpython-37.pyc b/quiz-app/quiz/templatetags/__pycache__/quiz_tags.cpython-37.pyc new file mode 100644 index 0000000..d2ec7a4 Binary files /dev/null and b/quiz-app/quiz/templatetags/__pycache__/quiz_tags.cpython-37.pyc differ diff --git a/quiz-app/quiz/templatetags/__pycache__/quiz_tags.cpython-38.pyc b/quiz-app/quiz/templatetags/__pycache__/quiz_tags.cpython-38.pyc new file mode 100644 index 0000000..a6a453e Binary files /dev/null and b/quiz-app/quiz/templatetags/__pycache__/quiz_tags.cpython-38.pyc differ diff --git a/quiz-app/quiz/templatetags/__pycache__/quiz_tags.cpython-39.pyc b/quiz-app/quiz/templatetags/__pycache__/quiz_tags.cpython-39.pyc new file mode 100644 index 0000000..5b14013 Binary files /dev/null and b/quiz-app/quiz/templatetags/__pycache__/quiz_tags.cpython-39.pyc differ diff --git a/quiz-app/quiz/templatetags/quiz_tags.py b/quiz-app/quiz/templatetags/quiz_tags.py new file mode 100644 index 0000000..2ce044c --- /dev/null +++ b/quiz-app/quiz/templatetags/quiz_tags.py @@ -0,0 +1,25 @@ +from django import template + +register = template.Library() + + +@register.inclusion_tag('correct_answer.html', takes_context=True) +def correct_answer_for_all(context, question): + """ + processes the correct answer based on a given question object + if the answer is incorrect, informs the user + """ + answers = question.get_answers() + incorrect_list = context.get('incorrect_questions', []) + if question.id in incorrect_list: + user_was_incorrect = True + else: + user_was_incorrect = False + + return {'previous': {'answers': answers}, + 'user_was_incorrect': user_was_incorrect} + + +@register.filter +def answer_choice_to_string(question, answer): + return question.answer_choice_to_string(answer) diff --git a/quiz-app/quiz/tests.py b/quiz-app/quiz/tests.py new file mode 100644 index 0000000..e1db0db --- /dev/null +++ b/quiz-app/quiz/tests.py @@ -0,0 +1,993 @@ +# -*- coding: iso-8859-15 -*- +from importlib import import_module + +from django.conf import settings +from django.contrib.auth.models import User, Permission +from django.core.exceptions import ValidationError +from django.core.files.base import ContentFile +try: + from django.core.urlresolvers import resolve +except ImportError: + from django.urls import resolve +from django.http import HttpRequest +from django.template import Template, Context +from django.test import TestCase +from django.utils.six import StringIO +from django.utils.translation import ugettext_lazy as _ + +from .models import Category, Quiz, Progress, Sitting, SubCategory +from .views import (anon_session_score, QuizListView, CategoriesListView, + QuizDetailView) + +from multichoice.models import MCQuestion, Answer +from true_false.models import TF_Question +from essay.models import Essay_Question + + +class TestCategory(TestCase): + def setUp(self): + self.c1 = Category.objects.new_category(category='squishy berries') + + self.sub1 = SubCategory.objects.create(sub_category='Red', + category=self.c1) + + def test_categories(self): + self.assertEqual(self.c1.category, 'squishy-berries') + + def test_sub_categories(self): + self.assertEqual(self.sub1.category, self.c1) + + +class TestQuiz(TestCase): + def setUp(self): + self.c1 = Category.objects.new_category(category='elderberries') + + self.quiz1 = Quiz.objects.create(id=1, + title='test quiz 1', + description='d1', + url='tq1') + self.quiz2 = Quiz.objects.create(id=2, + title='test quiz 2', + description='d2', + url='t q2') + self.quiz3 = Quiz.objects.create(id=3, + title='test quiz 3', + description='d3', + url='t q3') + self.quiz4 = Quiz.objects.create(id=4, + title='test quiz 4', + description='d4', + url='T-!$%^&*Q4') + + self.question1 = MCQuestion.objects.create(id=1, + content='squawk') + self.question1.quiz.add(self.quiz1) + + def test_quiz_url(self): + self.assertEqual(self.quiz1.url, 'tq1') + self.assertEqual(self.quiz2.url, 't-q2') + self.assertEqual(self.quiz3.url, 't-q3') + self.assertEqual(self.quiz4.url, 't-q4') + + def test_quiz_options(self): + q5 = Quiz.objects.create(id=5, + title='test quiz 5', + description='d5', + url='tq5', + category=self.c1, + exam_paper=True) + + self.assertEqual(q5.category.category, self.c1.category) + self.assertEqual(q5.random_order, False) + self.assertEqual(q5.answers_at_end, False) + self.assertEqual(q5.exam_paper, True) + + def test_quiz_single_attempt(self): + self.quiz1.single_attempt = True + self.quiz1.save() + + self.assertEqual(self.quiz1.exam_paper, True) + + def test_get_max_score(self): + self.assertEqual(self.quiz1.get_max_score, 1) + + def test_get_questions(self): + self.assertIn(self.question1, self.quiz1.get_questions()) + + def test_anon_score_id(self): + self.assertEqual(self.quiz1.anon_score_id(), '1_score') + + def test_anon_q_list(self): + self.assertEqual(self.quiz1.anon_q_list(), '1_q_list') + + def test_pass_mark(self): + self.assertEqual(self.quiz1.pass_mark, False) + self.quiz1.pass_mark = 50 + self.assertEqual(self.quiz1.pass_mark, 50) + self.quiz1.pass_mark = 101 + with self.assertRaises(ValidationError): + self.quiz1.save() + + +class TestProgress(TestCase): + def setUp(self): + self.c1 = Category.objects.new_category(category='elderberries') + + self.quiz1 = Quiz.objects.create(id=1, + title='test quiz 1', + description='d1', + url='tq1') + + self.question1 = MCQuestion.objects.create(content='squawk', + category=self.c1) + + self.user = User.objects.create_user(username='jacob', + email='jacob@jacob.com', + password='top_secret') + + self.p1 = Progress.objects.new_progress(self.user) + + def test_list_all_empty(self): + self.assertEqual(self.p1.score, '') + + category_dict = self.p1.list_all_cat_scores + + self.assertIn(str(list(category_dict.keys())[0]), self.p1.score) + + self.assertIn(self.c1.category, self.p1.score) + + Category.objects.new_category(category='cheese') + + self.p1.list_all_cat_scores + + self.assertIn('cheese', self.p1.score) + + def test_subcategory_all_empty(self): + SubCategory.objects.create(sub_category='pickles', + category=self.c1) + # self.p1.list_all_cat_scores + # self.assertIn('pickles', self.p1.score) + # TODO: test after implementing subcategory scoring on progress page + + def test_update_score(self): + self.p1.list_all_cat_scores + self.p1.update_score(self.question1, 1, 2) + self.assertIn('elderberries', self.p1.list_all_cat_scores) + + cheese = Category.objects.new_category(category='cheese') + question2 = MCQuestion.objects.create(content='squeek', + category=cheese) + self.p1.update_score(question2, 3, 4) + + self.assertIn('cheese', self.p1.list_all_cat_scores) + self.assertEqual([3, 4, 75], self.p1.list_all_cat_scores['cheese']) + + # pass in string instead of question instance + with self.assertRaises(AttributeError): + self.p1.update_score('hamster', 3, 4) + + non_int = self.p1.update_score(question2, '1', 2) + self.assertIn(_('error'), non_int) + + # negative possible score + self.p1.update_score(question2, 0, -1) + self.assertEqual([3, 5, 60], self.p1.list_all_cat_scores['cheese']) + + # negative added score + self.p1.update_score(question2, -1, 1) + self.assertEqual([4, 6, 67], self.p1.list_all_cat_scores['cheese']) + + +class TestSitting(TestCase): + def setUp(self): + self.quiz1 = Quiz.objects.create(id=1, + title='test quiz 1', + description='d1', + url='tq1', + pass_mark=50, + success_text="Well done", + fail_text="Bad luck") + + self.question1 = MCQuestion.objects.create(id=1, + content='squawk') + self.question1.quiz.add(self.quiz1) + + self.answer1 = Answer.objects.create(id=123, + question=self.question1, + content='bing', + correct=False) + + self.question2 = MCQuestion.objects.create(id=2, + content='squeek') + self.question2.quiz.add(self.quiz1) + + self.answer2 = Answer.objects.create(id=456, + question=self.question2, + content='bong', + correct=True) + + self.user = User.objects.create_user(username='jacob', + email='jacob@jacob.com', + password='top_secret') + + self.sitting = Sitting.objects.new_sitting(self.user, self.quiz1) + + def test_max_questions_subsetting(self): + quiz2 = Quiz.objects.create(id=2, + title='test quiz 2', + description='d2', + url='tq2', + max_questions=1) + self.question1.quiz.add(quiz2) + self.question2.quiz.add(quiz2) + sub_sitting = Sitting.objects.new_sitting(self.user, quiz2) + + self.assertNotIn('2', sub_sitting.question_list) + + def test_get_next_remove_first(self): + self.assertEqual(self.sitting.get_first_question(), + self.question1) + + self.sitting.remove_first_question() + self.assertEqual(self.sitting.get_first_question(), + self.question2) + + self.sitting.remove_first_question() + self.assertEqual(self.sitting.get_first_question(), False) + + self.sitting.remove_first_question() + self.assertEqual(self.sitting.get_first_question(), False) + + def test_scoring(self): + self.assertEqual(self.sitting.get_current_score, 0) + self.assertEqual(self.sitting.check_if_passed, False) + self.assertEqual(self.sitting.result_message, 'Bad luck') + + self.sitting.add_to_score(1) + self.assertEqual(self.sitting.get_current_score, 1) + self.assertEqual(self.sitting.get_percent_correct, 50) + + self.sitting.add_to_score(1) + self.assertEqual(self.sitting.get_current_score, 2) + self.assertEqual(self.sitting.get_percent_correct, 100) + + self.sitting.add_to_score(1) + self.assertEqual(self.sitting.get_current_score, 3) + self.assertEqual(self.sitting.get_percent_correct, 100) + + self.assertEqual(self.sitting.check_if_passed, True) + self.assertEqual(self.sitting.result_message, 'Well done') + + def test_incorrect_and_complete(self): + self.assertEqual(self.sitting.get_incorrect_questions, []) + + self.sitting.add_incorrect_question(self.question1) + self.assertIn(1, self.sitting.get_incorrect_questions) + + question3 = TF_Question.objects.create(id=3, + content='oink') + self.sitting.add_incorrect_question(question3) + self.assertIn(3, self.sitting.get_incorrect_questions) + + self.assertEqual(self.sitting.complete, False) + self.sitting.mark_quiz_complete() + self.assertEqual(self.sitting.complete, True) + + self.assertEqual(self.sitting.current_score, 0) + self.sitting.add_incorrect_question(self.question2) + self.assertEqual(self.sitting.current_score, -1) + + def test_add_user_answer(self): + guess = '123' + self.sitting.add_user_answer(self.question1, guess) + + self.assertIn('123', self.sitting.user_answers) + + def test_return_questions_with_answers(self): + ''' + Also tests sitting.get_questions(with_answers=True) + ''' + self.sitting.add_user_answer(self.question1, '123') + self.sitting.add_user_answer(self.question2, '456') + + user_answers = self.sitting.questions_with_user_answers + self.assertEqual('123', user_answers[self.question1]) + self.assertEqual('456', user_answers[self.question2]) + + def test_remove_incorrect_answer(self): + self.sitting.add_incorrect_question(self.question1) + self.sitting.add_incorrect_question(self.question2) + self.sitting.remove_incorrect_question(self.question1) + self.assertEqual(self.sitting.incorrect_questions, '2') + self.assertEqual(self.sitting.current_score, 1) + + def test_return_user_sitting(self): + via_manager = Sitting.objects.user_sitting(self.user, self.quiz1) + self.assertEqual(self.sitting, via_manager) + + def test_progress_tracker(self): + self.assertEqual(self.sitting.progress(), (0, 2)) + self.sitting.add_user_answer(self.question1, '123') + self.assertEqual(self.sitting.progress(), (1, 2)) + + +class TestNonQuestionViews(TestCase): + ''' + Starting on views not directly involved with questions. + ''' + urls = 'quiz.urls' + + def setUp(self): + self.c1 = Category.objects.new_category(category='elderberries') + self.c2 = Category.objects.new_category(category='straw.berries') + self.c3 = Category.objects.new_category(category='black berries') + + self.quiz1 = Quiz.objects.create(id=1, + title='test quiz 1', + description='d1', + url='tq1', + category=self.c1, + single_attempt=True) + self.quiz2 = Quiz.objects.create(id=2, + title='test quiz 2', + description='d2', + url='t q2') + + def test_index(self): + # unit + view = QuizListView() + self.assertEqual(view.get_queryset().count(), 2) + + # integration test + response = self.client.get('/') + self.assertContains(response, 'test quiz 1') + self.assertTemplateUsed('quiz_list.html') + + def test_index_with_drafts(self): + self.quiz3 = Quiz.objects.create(id=3, + title='test quiz 3', + description='draft', + url='draft', + draft=True) + + view = QuizListView() + self.assertEqual(view.get_queryset().count(), 2) + + def test_list_categories(self): + # unit + view = CategoriesListView() + self.assertEqual(view.get_queryset().count(), 3) + + # integration test + response = self.client.get('/category/') + + self.assertContains(response, 'elderberries') + self.assertContains(response, 'straw.berries') + self.assertContains(response, 'black-berries') + + def test_view_cat(self): + # unit + view = CategoriesListView() + self.assertEqual(view.get_queryset().count(), 3) + + # integration test + response = self.client.get('/category/elderberries/') + + self.assertContains(response, 'test quiz 1') + self.assertNotContains(response, 'test quiz 2') + + def test_progress_anon(self): + response = self.client.get('/progress/', follow=False) + self.assertTemplateNotUsed(response, 'progress.html') + + def test_progress_user(self): + user = User.objects.create_user(username='jacob', + email='jacob@jacob.com', + password='top_secret') + question1 = MCQuestion.objects.create(content='squawk', + category=self.c1) + + self.client.login(username='jacob', password='top_secret') + p1 = Progress.objects.new_progress(user) + p1.update_score(question1, 1, 2) + + response = self.client.get('/progress/') + + self.assertContains(response, 'elderberries') + self.assertIn('straw.berries', response.context['cat_scores']) + self.assertEqual([1, 2, 50], + response.context['cat_scores']['elderberries']) + + def test_quiz_start_page(self): + # unit + view = QuizDetailView() + view.kwargs = dict(slug='tq1') + self.assertEqual(view.get_object().category, self.c1) + + # integration test + response = self.client.get('/tq1/') + + self.assertContains(response, 'd1') + self.assertContains(response, 'attempt') + self.assertContains(response, 'href="/tq1/take/"') + self.assertTemplateUsed(response, 'quiz/quiz_detail.html') + + def test_anon_session_score(self): + request = HttpRequest() + engine = import_module(settings.SESSION_ENGINE) + request.session = engine.SessionStore(None) + score, possible = anon_session_score(request.session) + self.assertEqual((score, possible), (0, 0)) + + score, possible = anon_session_score(request.session, 1, 0) + self.assertEqual((score, possible), (0, 0)) + + score, possible = anon_session_score(request.session, 1, 1) + self.assertEqual((score, possible), (1, 1)) + + score, possible = anon_session_score(request.session, -0.5, 1) + self.assertEqual((score, possible), (0.5, 2)) + + score, possible = anon_session_score(request.session) + self.assertEqual((score, possible), (0.5, 2)) + + +class TestQuestionMarking(TestCase): + urls = 'quiz.urls' + + def setUp(self): + self.c1 = Category.objects.new_category(category='elderberries') + self.student = User.objects.create_user(username='luke', + email='luke@rebels.com', + password='top_secret') + self.teacher = User.objects.create_user(username='yoda', + email='yoda@jedis.com', + password='use_d@_force') + self.teacher.user_permissions.add( + Permission.objects.get(codename='view_sittings')) + + self.quiz1 = Quiz.objects.create(id=1, + title='test quiz 1', + description='d1', + url='tq1', + category=self.c1, + single_attempt=True) + self.quiz2 = Quiz.objects.create(id=2, + title='test quiz 2', + description='d2', + url='tq2', + category=self.c1, + single_attempt=True) + + self.question1 = MCQuestion.objects.create(id=1, content='squawk') + self.question1.quiz.add(self.quiz1) + + self.question2 = MCQuestion.objects.create(id=2, content='shriek') + self.question2.quiz.add(self.quiz2) + + self.answer1 = Answer.objects.create(id=123, + question=self.question1, + content='bing', + correct=False) + + sitting1 = Sitting.objects.new_sitting(self.student, self.quiz1) + sitting2 = Sitting.objects.new_sitting(self.student, self.quiz2) + sitting1.complete = True + sitting1.incorrect_questions = '1' + sitting1.save() + sitting2.complete = True + sitting2.save() + + sitting1.add_user_answer(self.question1, '123') + + def test_paper_marking_list_view(self): + response = self.client.get('/marking/') + self.assertRedirects(response, '/accounts/login/?next=/marking/', + status_code=302, target_status_code=404 or 200) + + self.assertFalse(self.teacher.has_perm('view_sittings', self.student)) + + self.client.login(username='luke', password='top_secret') + response = self.client.get('/marking/') + self.assertRedirects(response, '/accounts/login/?next=/marking/', + status_code=302, target_status_code=404 or 200) + + self.client.login(username='yoda', password='use_d@_force') + response = self.client.get('/marking/') + self.assertContains(response, 'test quiz 1') + self.assertContains(response, 'test quiz 2') + self.assertContains(response, 'luke') + + def test_paper_marking_list_view_filter_user(self): + new_student = User.objects.create_user(username='chewy', + email='chewy@rebels.com', + password='maaaawwwww') + chewy_sitting = Sitting.objects.new_sitting(new_student, self.quiz1) + chewy_sitting.complete = True + chewy_sitting.save() + + self.client.login(username='yoda', password='use_d@_force') + response = self.client.get('/marking/', + {'user_filter': 'Hans'}) + + self.assertNotContains(response, 'chewy') + self.assertNotContains(response, 'luke') + + response = self.client.get('/marking/', + {'user_filter': 'chewy'}) + + self.assertContains(response, 'chewy') + self.assertNotContains(response, 'luke') + + def test_paper_marking_list_view_filter_quiz(self): + self.client.login(username='yoda', password='use_d@_force') + response = self.client.get('/marking/', + {'quiz_filter': '1'}) + + self.assertContains(response, 'quiz 1') + self.assertNotContains(response, 'quiz 2') + + def test_paper_marking_detail_view(self): + self.client.login(username='yoda', password='use_d@_force') + response = self.client.get('/marking/1/') + + self.assertContains(response, 'test quiz 1') + self.assertContains(response, 'squawk') + self.assertContains(response, 'incorrect') + + def test_paper_marking_detail_toggle_correct(self): + question2 = Essay_Question.objects.create(id=3, content='scribble') + question2.quiz.add(self.quiz1) + + sitting3 = Sitting.objects.new_sitting(self.student, self.quiz1) + sitting3.complete = True + sitting3.incorrect_questions = '1,2,3' + sitting3.add_user_answer(self.question1, '123') + sitting3.add_user_answer(question2, 'Blah blah blah') + sitting3.save() + + self.client.login(username='yoda', password='use_d@_force') + response = self.client.get('/marking/3/') + self.assertContains(response, 'button') + self.assertNotContains(response, 'Correct') + + response = self.client.post('/marking/3/', {'qid': 3}) + self.assertContains(response, 'Correct') + + response = self.client.post('/marking/3/', {'qid': 3}) + self.assertNotContains(response, 'Correct') + + +class TestQuestionViewsAnon(TestCase): + urls = 'quiz.urls' + + def setUp(self): + self.c1 = Category.objects.new_category(category='elderberries') + + self.quiz1 = Quiz.objects.create(id=1, + title='test quiz 1', + description='d1', + url='tq1', + category=self.c1) + + self.question1 = MCQuestion.objects.create(id=1, + content='squawk') + self.question1.quiz.add(self.quiz1) + + self.question2 = MCQuestion.objects.create(id=2, + content='squeek') + self.question2.quiz.add(self.quiz1) + + self.answer1 = Answer.objects.create(id=123, + question=self.question1, + content='bing', + correct=False) + + self.answer2 = Answer.objects.create(id=456, + question=self.question2, + content='bong', + correct=True) + + def test_quiz_take_anon_view_only(self): + found = resolve('/tq1/take/') + + self.assertEqual(found.kwargs, {'quiz_name': 'tq1'}) + self.assertEqual(found.url_name, 'quiz_question') + + response = self.client.get('/tq1/take/') + + self.assertContains(response, 'squawk', status_code=200) + self.assertEqual(self.client.session.get_expiry_age(), 259200) + self.assertEqual(self.client.session['1_q_list'], [1, 2]) + self.assertEqual(self.client.session['1_score'], 0) + self.assertEqual(response.context['quiz'].id, self.quiz1.id) + self.assertEqual(response.context['question'].content, + self.question1.content) + self.assertNotIn('previous', response.context) + self.assertTemplateUsed('question.html') + + session = self.client.session + session.set_expiry(1) # session is set when user first starts a + session.save() # quiz, not on subsequent visits + + self.client.get('/tq1/take/') + self.assertEqual(self.client.session.get_expiry_age(), 1) + self.assertEqual(self.client.session['1_q_list'], [1, 2]) + self.assertEqual(self.client.session['1_score'], 0) + + def test_image_in_question(self): + imgfile = StringIO( + 'GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,' + '\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;') + imgfile.name = 'test_img_file.gif' + + self.question1.figure.save('image', ContentFile(imgfile.read())) + response = self.client.get('/tq1/take/') + + self.assertContains(response, '' + str(self.question1.content))
+
+    def test_quiz_take_anon_submit(self):
+        # show first question
+        response = self.client.get('/tq1/take/')
+        self.assertNotContains(response, 'previous question')
+        # submit first answer
+        response = self.client.post('/tq1/take/',
+                                    {'answers': '123',
+                                     'question_id':
+                                     self.client.session['1_q_list'][0]})
+
+        self.assertContains(response, 'previous', status_code=200)
+        self.assertContains(response, 'incorrect')
+        self.assertContains(response, 'Explanation:')
+        self.assertContains(response, 'squeek')
+        self.assertEqual(self.client.session['1_q_list'], [2])
+        self.assertEqual(self.client.session['session_score'], 0)
+        self.assertEqual(self.client.session['session_score_possible'], 1)
+        self.assertEqual(response.context['previous']['question_type'],
+                         {self.question1.__class__.__name__: True})
+        self.assertIn(self.answer1, response.context['previous']['answers'])
+        self.assertTemplateUsed('question.html')
+        second_question = response.context['question']
+
+        # submit second and final answer of quiz, show final result page
+        response = self.client.post('/tq1/take/',
+                                    {'answers': '456',
+                                     'question_id':
+                                     self.client.session['1_q_list'][0]})
+
+        self.assertContains(response, 'previous question', status_code=200)
+        self.assertNotContains(response, 'incorrect')
+        self.assertContains(response, 'Explanation:')
+        self.assertContains(response, 'results')
+        self.assertNotIn('1_q_list', self.client.session)
+        self.assertEqual(response.context['score'], 1)
+        self.assertEqual(response.context['max_score'], 2)
+        self.assertEqual(response.context['percent'], 50)
+        self.assertEqual(response.context['session'], 1)
+        self.assertEqual(response.context['possible'], 2)
+        self.assertEqual(response.context['previous']['previous_answer'],
+                         '456')
+        self.assertEqual(response.context['previous']['previous_outcome'],
+                         True)
+        self.assertEqual(response.context['previous']['previous_question'],
+                         second_question)
+        self.assertTemplateUsed('result.html')
+
+        # quiz restarts
+        response = self.client.get('/tq1/take/')
+        self.assertNotContains(response, 'previous question')
+
+        # session score continues to increase
+        response = self.client.post('/tq1/take/',
+                                    {'answers': '123',
+                                     'question_id':
+                                     self.client.session['1_q_list'][0]})
+        self.assertEqual(self.client.session['session_score'], 1)
+        self.assertEqual(self.client.session['session_score_possible'], 3)
+
+    def test_anon_cannot_sit_single_attempt(self):
+        self.quiz1.single_attempt = True
+        self.quiz1.save()
+        response = self.client.get('/tq1/take/')
+
+        self.assertContains(response, 'accessible')
+        self.assertTemplateUsed('single_complete.html')
+
+    def test_anon_progress(self):
+        response = self.client.get('/tq1/take/')
+        self.assertEqual(response.context['progress'], (0, 2))
+        response = self.client.post('/tq1/take/',
+                                    {'answers': '123',
+                                     'question_id':
+                                     self.client.session['1_q_list'][0]})
+        self.assertEqual(response.context['progress'], (1, 2))
+
+
+class TestQuestionViewsUser(TestCase):
+    urls = 'quiz.urls'
+
+    def setUp(self):
+        self.c1 = Category.objects.new_category(category='elderberries')
+
+        self.quiz1 = Quiz.objects.create(id=1,
+                                         title='test quiz 1',
+                                         description='d1',
+                                         url='tq1',
+                                         category=self.c1,
+                                         pass_mark=50,
+                                         success_text=[\w|\W-]+)/$', + view=ViewQuizListByCategory.as_view(), + name='quiz_category_list_matching'), + +# PROGRESS VIEW + url(r'^progress/$', + view=QuizUserProgressView.as_view(), + name='quiz_progress'), + +# PROGRESS QUIZ DETAILS + #url(r'^marking/(?P[\d.]+)/$', + #view=QuizMarkingDetail.as_view(), + #name='quiz_marking_detail'), + + url(r'^marking/$', + view=QuizMarkingList.as_view(), + name='quiz_marking'), + + url(r'^marking/(?P[\d.]+)/$', + view=QuizMarkingDetail.as_view(), + name='quiz_marking_detail'), + + # passes variable 'quiz_name' to quiz_take view + url(r'^(?P[\w-]+)/$', + view=QuizDetailView.as_view(), + name='quiz_start_page'), + + url(r'^(?P[\w-]+)/take/$', + view=QuizTake.as_view(), + name='quiz_question'), + +] diff --git a/quiz-app/quiz/views.py b/quiz-app/quiz/views.py new file mode 100644 index 0000000..0abcd17 --- /dev/null +++ b/quiz-app/quiz/views.py @@ -0,0 +1,389 @@ +import random + +from django.contrib.auth.decorators import login_required, permission_required +from django.core.exceptions import PermissionDenied +from django.shortcuts import get_object_or_404, render +from django.utils.decorators import method_decorator +from django.views.generic import DetailView, ListView, TemplateView, FormView + +from .forms import QuestionForm, EssayForm +from .models import Quiz, Category, Progress, Sitting, Question + +from essay.models import Essay_Question + +#from django.core.mail import send_mail + +#send_mail('Subject here', 'Here is the message.', 'from@example.com', ['to@example.com'], fail_silently=False) + +class QuizMarkerMixin(object): + @method_decorator(login_required) + @method_decorator(permission_required('quiz.view_sittings')) + def dispatch(self, *args, **kwargs): + return super(QuizMarkerMixin, self).dispatch(*args, **kwargs) + + +class SittingFilterTitleMixin(object): + def get_queryset(self): + queryset = super(SittingFilterTitleMixin, self).get_queryset() + quiz_filter = self.request.GET.get('quiz_filter') + if quiz_filter: + queryset = queryset.filter(quiz__title__icontains=quiz_filter) + + return queryset + + +class QuizListView(ListView): + model = Quiz + + def get_queryset(self): + queryset = super(QuizListView, self).get_queryset() + return queryset.filter(draft=False) + + +class QuizDetailView(DetailView): + model = Quiz + slug_field = 'url' + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + + if self.object.draft and not request.user.has_perm('quiz.change_quiz'): + raise PermissionDenied + + context = self.get_context_data(object=self.object) + return self.render_to_response(context) + + +class CategoriesListView(ListView): + model = Category + + +class ViewQuizListByCategory(ListView): + model = Quiz + template_name = 'view_quiz_category.html' + + def dispatch(self, request, *args, **kwargs): + self.category = get_object_or_404( + Category, + category=self.kwargs['category_name'] + ) + + return super(ViewQuizListByCategory, self).\ + dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super(ViewQuizListByCategory, self)\ + .get_context_data(**kwargs) + + context['category'] = self.category + return context + + def get_queryset(self): + queryset = super(ViewQuizListByCategory, self).get_queryset() + return queryset.filter(category=self.category, draft=False) + + +class QuizUserProgressView(TemplateView): + template_name = 'progress.html' + + @method_decorator(login_required) + def dispatch(self, request, *args, **kwargs): + return super(QuizUserProgressView, self)\ + .dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super(QuizUserProgressView, self).get_context_data(**kwargs) + progress, c = Progress.objects.get_or_create(user=self.request.user) + context['cat_scores'] = progress.list_all_cat_scores + context['exams'] = progress.show_exams() + return context + + +class QuizMarkingList(QuizMarkerMixin, SittingFilterTitleMixin, ListView): + model = Sitting + + def get_queryset(self): + queryset = super(QuizMarkingList, self).get_queryset()\ + .filter(complete=True) + + user_filter = self.request.GET.get('user_filter') + if user_filter: + queryset = queryset.filter(user__username__icontains=user_filter) + + return queryset + + +class QuizMarkingDetail(QuizMarkerMixin, DetailView): + model = Sitting + + def post(self, request, *args, **kwargs): + sitting = self.get_object() + + q_to_toggle = request.POST.get('qid', None) + if q_to_toggle: + q = Question.objects.get_subclass(id=int(q_to_toggle)) + if int(q_to_toggle) in sitting.get_incorrect_questions: + sitting.remove_incorrect_question(q) + else: + sitting.add_incorrect_question(q) + + return self.get(request) + + def get_context_data(self, **kwargs): + context = super(QuizMarkingDetail, self).get_context_data(**kwargs) + context['questions'] =\ + context['sitting'].get_questions(with_answers=True) + return context + + +class QuizTake(FormView): + form_class = QuestionForm + template_name = 'question.html' + result_template_name = 'result.html' + single_complete_template_name = 'single_complete.html' + + def dispatch(self, request, *args, **kwargs): + self.quiz = get_object_or_404(Quiz, url=self.kwargs['quiz_name']) + if self.quiz.draft and not request.user.has_perm('quiz.change_quiz'): + raise PermissionDenied + + try: + self.logged_in_user = self.request.user.is_authenticated() + except TypeError: + self.logged_in_user = self.request.user.is_authenticated + + if self.logged_in_user: + self.sitting = Sitting.objects.user_sitting(request.user, + self.quiz) + else: + self.sitting = self.anon_load_sitting() + + if self.sitting is False: + return render(request, self.single_complete_template_name) + + return super(QuizTake, self).dispatch(request, *args, **kwargs) + + def get_form(self, *args, **kwargs): + if self.logged_in_user: + self.question = self.sitting.get_first_question() + self.progress = self.sitting.progress() + else: + self.question = self.anon_next_question() + self.progress = self.anon_sitting_progress() + + if self.question.__class__ is Essay_Question: + form_class = EssayForm + else: + form_class = self.form_class + + return form_class(**self.get_form_kwargs()) + + def get_form_kwargs(self): + kwargs = super(QuizTake, self).get_form_kwargs() + + return dict(kwargs, question=self.question) + + def form_valid(self, form): + if self.logged_in_user: + self.form_valid_user(form) + if self.sitting.get_first_question() is False: + return self.final_result_user() + else: + self.form_valid_anon(form) + if not self.request.session[self.quiz.anon_q_list()]: + return self.final_result_anon() + + self.request.POST = {} + + return super(QuizTake, self).get(self, self.request) + + def get_context_data(self, **kwargs): + context = super(QuizTake, self).get_context_data(**kwargs) + context['question'] = self.question + context['quiz'] = self.quiz + if hasattr(self, 'previous'): + context['previous'] = self.previous + if hasattr(self, 'progress'): + context['progress'] = self.progress + return context + + def form_valid_user(self, form): + progress, c = Progress.objects.get_or_create(user=self.request.user) + guess = form.cleaned_data['answers'] + is_correct = self.question.check_if_correct(guess) + + if is_correct is True: + self.sitting.add_to_score(1) + progress.update_score(self.question, 1, 1) + else: + self.sitting.add_incorrect_question(self.question) + progress.update_score(self.question, 0, 1) + + if self.quiz.answers_at_end is not True: + self.previous = {'previous_answer': guess, + 'previous_outcome': is_correct, + 'previous_question': self.question, + 'answers': self.question.get_answers(), + 'question_type': {self.question + .__class__.__name__: True}} + else: + self.previous = {} + + self.sitting.add_user_answer(self.question, guess) + self.sitting.remove_first_question() + + def final_result_user(self): + results = { + 'quiz': self.quiz, + 'score': self.sitting.get_current_score, + 'max_score': self.sitting.get_max_score, + 'percent': self.sitting.get_percent_correct, + 'sitting': self.sitting, + 'previous': self.previous, + } + + self.sitting.mark_quiz_complete() + + if self.quiz.answers_at_end: + results['questions'] =\ + self.sitting.get_questions(with_answers=True) + results['incorrect_questions'] =\ + self.sitting.get_incorrect_questions + + if self.quiz.exam_paper is False: + self.sitting.delete() + + return render(self.request, self.result_template_name, results) + + def anon_load_sitting(self): + if self.quiz.single_attempt is True: + return False + + if self.quiz.anon_q_list() in self.request.session: + return self.request.session[self.quiz.anon_q_list()] + else: + return self.new_anon_quiz_session() + + def new_anon_quiz_session(self): + """ + Sets the session variables when starting a quiz for the first time + as a non signed-in user + """ + self.request.session.set_expiry(259200) # expires after 3 days + questions = self.quiz.get_questions() + question_list = [question.id for question in questions] + + if self.quiz.random_order is True: + random.shuffle(question_list) + + if self.quiz.max_questions and (self.quiz.max_questions + < len(question_list)): + question_list = question_list[:self.quiz.max_questions] + + # session score for anon users + self.request.session[self.quiz.anon_score_id()] = 0 + + # session list of questions + self.request.session[self.quiz.anon_q_list()] = question_list + + # session list of question order and incorrect questions + self.request.session[self.quiz.anon_q_data()] = dict( + incorrect_questions=[], + order=question_list, + ) + + return self.request.session[self.quiz.anon_q_list()] + + def anon_next_question(self): + next_question_id = self.request.session[self.quiz.anon_q_list()][0] + return Question.objects.get_subclass(id=next_question_id) + + def anon_sitting_progress(self): + total = len(self.request.session[self.quiz.anon_q_data()]['order']) + answered = total - len(self.request.session[self.quiz.anon_q_list()]) + return (answered, total) + + def form_valid_anon(self, form): + guess = form.cleaned_data['answers'] + is_correct = self.question.check_if_correct(guess) + + if is_correct: + self.request.session[self.quiz.anon_score_id()] += 1 + anon_session_score(self.request.session, 1, 1) + else: + anon_session_score(self.request.session, 0, 1) + self.request\ + .session[self.quiz.anon_q_data()]['incorrect_questions']\ + .append(self.question.id) + + self.previous = {} + if self.quiz.answers_at_end is not True: + self.previous = {'previous_answer': guess, + 'previous_outcome': is_correct, + 'previous_question': self.question, + 'answers': self.question.get_answers(), + 'question_type': {self.question + .__class__.__name__: True}} + + self.request.session[self.quiz.anon_q_list()] =\ + self.request.session[self.quiz.anon_q_list()][1:] + + def final_result_anon(self): + score = self.request.session[self.quiz.anon_score_id()] + q_order = self.request.session[self.quiz.anon_q_data()]['order'] + max_score = len(q_order) + percent = int(round((float(score) / max_score) * 100)) + session, session_possible = anon_session_score(self.request.session) + if score is 0: + score = "0" + + results = { + 'score': score, + 'max_score': max_score, + 'percent': percent, + 'session': session, + 'possible': session_possible + } + + del self.request.session[self.quiz.anon_q_list()] + + if self.quiz.answers_at_end: + results['questions'] = sorted( + self.quiz.question_set.filter(id__in=q_order) + .select_subclasses(), + key=lambda q: q_order.index(q.id)) + + results['incorrect_questions'] = ( + self.request + .session[self.quiz.anon_q_data()]['incorrect_questions']) + + else: + results['previous'] = self.previous + + del self.request.session[self.quiz.anon_q_data()] + + return render(self.request, 'result.html', results) + + +def anon_session_score(session, to_add=0, possible=0): + """ + Returns the session score for non-signed in users. + If number passed in then add this to the running total and + return session score. + + examples: + anon_session_score(1, 1) will add 1 out of a possible 1 + anon_session_score(0, 2) will add 0 out of a possible 2 + x, y = anon_session_score() will return the session score + without modification + + Left this as an individual function for unit testing + """ + if "session_score" not in session: + session["session_score"], session["session_score_possible"] = 0, 0 + + if possible > 0: + session["session_score"] += to_add + session["session_score_possible"] += possible + + return session["session_score"], session["session_score_possible"] diff --git a/quiz-app/registration/locale/fr/LC_MESSAGES/django.mo b/quiz-app/registration/locale/fr/LC_MESSAGES/django.mo new file mode 100644 index 0000000..20dc403 Binary files /dev/null and b/quiz-app/registration/locale/fr/LC_MESSAGES/django.mo differ diff --git a/quiz-app/registration/locale/fr/LC_MESSAGES/django.po b/quiz-app/registration/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 0000000..d87e6eb --- /dev/null +++ b/quiz-app/registration/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,109 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2021-05-28 17:45+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: registration/templates/base_generic.html:20 +msgid "Hi " +msgstr "Bonjour " + +#: registration/templates/base_generic.html:21 +msgid "Disconnect" +msgstr "Se déconnecter" + +#: registration/templates/base_generic.html:24 +msgid "Connect" +msgstr "Se connecter" + +#: registration/templates/registration/logged_out.html:5 +#, fuzzy +msgid "Logged out" +msgstr "Déconnecté !" + +#: registration/templates/registration/logged_out.html:6 +msgid "Click here to connect again" +msgstr "Clique ici pour te reconnecter" + +#: registration/templates/registration/login.html:7 +msgid "Wrong username or/and password" +msgstr "Combinaison utilisateur/mot de passe faux" + +#: registration/templates/registration/login.html:12 +msgid "You don't have access to this page" +msgstr "Vous n'avez pas accès à cette page" + +#: registration/templates/registration/login.html:14 +msgid "Connect to see this page" +msgstr "Connectez-vous pour accéder à cette page" + +#: registration/templates/registration/login.html:36 +msgid "Forgotten password" +msgstr "Mot de passe oublié" + +#: registration/templates/registration/password_reset_complete.html:5 +msgid "The password has been changed" +msgstr "Le mot de passe a été changé" + +#: registration/templates/registration/password_reset_complete.html:6 +#, fuzzy +msgid "Reconnect?" +msgstr "Se connecter" + +#: registration/templates/registration/password_reset_confirm.html:6 +msgid "Please enter your new password" +msgstr "Veuillez entrez le nouveau mot de passe" + +#: registration/templates/registration/password_reset_confirm.html:12 +msgid "New password :" +msgstr "Nouveau mot de passe :" + +#: registration/templates/registration/password_reset_confirm.html:17 +msgid "Confirm password :" +msgstr "Confirmez le mot de passe :" + +#: registration/templates/registration/password_reset_confirm.html:27 +msgid "Password reset failed :" +msgstr "Réinitialisation du mot de passe échouée" + +#: registration/templates/registration/password_reset_confirm.html:28 +msgid "" +"The reset password link wasn't valid. Maybe it has been already used. Please " +"ask to reset password again" +msgstr "" +"Le lien de réinitialisation du mot de passe est invalide. Il a peux être " +"été déjà utilisé. Veuillez réitérer votre requête" + +#: registration/templates/registration/password_reset_done.html:5 +msgid "" +"An email with the reset password instructions has been sent to the given " +"email" +msgstr "" +"Un email avec les instructions pour réinitialiser votre mot de passe a été " +"envoyé" + +#: registration/templates/registration/password_reset_email.html:3 +msgid "You asked to reset your password" +msgstr "Vous avez demandé à modifier le mot de passe" + +#: registration/templates/registration/password_reset_email.html:3 +msgid "Please follow the link below :" +msgstr "Veuillez suivre le lien ci-dessous" + +#: registration/templates/registration/password_reset_form.html:5 +msgid "Enter the recovery email :" +msgstr "Veuillez entrer l'adresse email de récupération" diff --git a/quiz-app/registration/static/css/style.css b/quiz-app/registration/static/css/style.css new file mode 100644 index 0000000..3df87fc --- /dev/null +++ b/quiz-app/registration/static/css/style.css @@ -0,0 +1,17 @@ +@import url('https://fonts.googleapis.com/css2?family=Play&display=swap'); + +/* Appliquer les polices aux éléments du document */ +body { + font-family: 'Play'; /* Aladin pour le corps du document */ + /* font-size: 1.5em; */ +} + +.button-link { + background: none; + border: none; + color: #497AA1; + text-decoration: underline; + cursor: pointer; + padding: 0; + font: inherit; +} \ No newline at end of file diff --git a/quiz-app/registration/static/favicon.ico b/quiz-app/registration/static/favicon.ico new file mode 100644 index 0000000..02fb53a Binary files /dev/null and b/quiz-app/registration/static/favicon.ico differ diff --git a/quiz-app/registration/static/images/wp.jpg b/quiz-app/registration/static/images/wp.jpg new file mode 100644 index 0000000..bfb2c9f Binary files /dev/null and b/quiz-app/registration/static/images/wp.jpg differ diff --git a/quiz-app/registration/static/logo.png b/quiz-app/registration/static/logo.png new file mode 100644 index 0000000..f9bba13 Binary files /dev/null and b/quiz-app/registration/static/logo.png differ diff --git a/quiz-app/registration/templates/base_generic.html b/quiz-app/registration/templates/base_generic.html new file mode 100644 index 0000000..4513889 --- /dev/null +++ b/quiz-app/registration/templates/base_generic.html @@ -0,0 +1,53 @@ +{% load i18n %} + + + + {% block title %}Connexion{% endblock %} + + + + + {% load static %} + + + + +
+
+
+ {% block sidebar %} + +
+
+ {% block content %} + {% endblock %} +
+
+
+
+ +
+ + \ No newline at end of file diff --git a/quiz-app/registration/templates/registration/logged_out.html b/quiz-app/registration/templates/registration/logged_out.html new file mode 100644 index 0000000..25e3c38 --- /dev/null +++ b/quiz-app/registration/templates/registration/logged_out.html @@ -0,0 +1,7 @@ +{% extends "base_generic.html" %} +{% load i18n %} + +{% block content %} +

{% trans "Logged out" %}

+ {% trans "Click here to connect again" %} +{% endblock %} \ No newline at end of file diff --git a/quiz-app/registration/templates/registration/login.html b/quiz-app/registration/templates/registration/login.html new file mode 100644 index 0000000..a18947e --- /dev/null +++ b/quiz-app/registration/templates/registration/login.html @@ -0,0 +1,38 @@ +{% extends "base_generic.html" %} +{% load i18n %} + +{% block content %} + + {% if form.errors %} +

{% trans "Wrong username or/and password" %}

+ {% endif %} + + {% if next %} + {% if user.is_authenticated %} +

{% trans "You don't have access to this page" %}

+ {% else %} +

{% trans "Connect to see this page" %}

+ {% endif %} + {% endif %} +
+
+ {% csrf_token %} + + + + + + + + + +
{{ form.username.label_tag }}{{ form.username }}
{{ form.password.label_tag }}{{ form.password }}

+
+ +
+

+ + {# Assumes you setup the password_reset view in your URLconf #} + + +{% endblock %} \ No newline at end of file diff --git a/quiz-app/registration/templates/registration/password_reset_complete.html b/quiz-app/registration/templates/registration/password_reset_complete.html new file mode 100644 index 0000000..50120fc --- /dev/null +++ b/quiz-app/registration/templates/registration/password_reset_complete.html @@ -0,0 +1,7 @@ +{% extends "base_generic.html" %} +{% load i18n %} + +{% block content %} +

{% trans "The password has been changed" %}

+

{% trans "Reconnect?" %}

+{% endblock %} \ No newline at end of file diff --git a/quiz-app/registration/templates/registration/password_reset_confirm.html b/quiz-app/registration/templates/registration/password_reset_confirm.html new file mode 100644 index 0000000..383429d --- /dev/null +++ b/quiz-app/registration/templates/registration/password_reset_confirm.html @@ -0,0 +1,30 @@ +{% extends "base_generic.html" %} +{% load i18n %} + +{% block content %} + {% if validlink %} +

{% trans "Please enter your new password" %}

+
+ {% csrf_token %} + + + + + + + + + + + + + +
{{ form.new_password1.errors }} + {{ form.new_password1 }}
{{ form.new_password2.errors }} + {{ form.new_password2 }}
+
+ {% else %} +

{% trans "Password reset failed :" %}

+

{% trans "The reset password link wasn't valid. Maybe it has been already used. Please ask to reset password again" %}.

+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/quiz-app/registration/templates/registration/password_reset_done.html b/quiz-app/registration/templates/registration/password_reset_done.html new file mode 100644 index 0000000..9c06616 --- /dev/null +++ b/quiz-app/registration/templates/registration/password_reset_done.html @@ -0,0 +1,6 @@ +{% extends "base_generic.html" %} +{% load i18n %} + +{% block content %} +

{% trans "An email with the reset password instructions has been sent to the given email" %}

+{% endblock %} \ No newline at end of file diff --git a/quiz-app/registration/templates/registration/password_reset_email.html b/quiz-app/registration/templates/registration/password_reset_email.html new file mode 100644 index 0000000..6874612 --- /dev/null +++ b/quiz-app/registration/templates/registration/password_reset_email.html @@ -0,0 +1,4 @@ +{% load i18n %} + +{% trans "You asked to reset your password" %}. {% trans "Please follow the link below :" %} +{{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} \ No newline at end of file diff --git a/quiz-app/registration/templates/registration/password_reset_form.html b/quiz-app/registration/templates/registration/password_reset_form.html new file mode 100644 index 0000000..511afba --- /dev/null +++ b/quiz-app/registration/templates/registration/password_reset_form.html @@ -0,0 +1,14 @@ +{% extends "base_generic.html" %} +{% load i18n %} + +{% block content %} +

{% trans "Enter the recovery email :" %}

+
+ {% csrf_token %} + {% if form.email.errors %} + {{ form.email.errors }} + {% endif %} +

{{ form.email }}

+ +
+{% endblock %} \ No newline at end of file diff --git a/quiz-app/requirements.txt b/quiz-app/requirements.txt new file mode 100644 index 0000000..e3415e2 --- /dev/null +++ b/quiz-app/requirements.txt @@ -0,0 +1,7 @@ +django>=2.2.9 +django-admin +django-model-utils>=3.1.1 +Pillow>=4.0.0 +psycopg2-binary +six +environs diff --git a/quiz-app/true_false/__pycache__/models.cpython-311.pyc b/quiz-app/true_false/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..4f5f362 Binary files /dev/null and b/quiz-app/true_false/__pycache__/models.cpython-311.pyc differ diff --git a/quiz-app/true_false/locale/de/LC_MESSAGES/django.mo b/quiz-app/true_false/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000..6bda4d3 Binary files /dev/null and b/quiz-app/true_false/locale/de/LC_MESSAGES/django.mo differ diff --git a/quiz-app/true_false/locale/de/LC_MESSAGES/django.po b/quiz-app/true_false/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000..1db9e72 --- /dev/null +++ b/quiz-app/true_false/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,36 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-10-31 22:44+0000\n" +"PO-Revision-Date: 2019-03-07 21:12+0100\n" +"Last-Translator: Reiner Mayers\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Translated-Using: django-rosetta 0.7.6\n" +"Language-Team: \n" +"X-Generator: Poedit 2.0.6\n" + +#: apps/true_false/models.py:11 +msgid "Tick this if the question is true. Leave it blank for false." +msgstr "Markieren Sie, wenn die Frage stimmt. Freilassen wenn falsch." + +#: apps/true_false/models.py:14 +msgid "Correct" +msgstr "Korrekt" + +#: apps/true_false/models.py:42 +msgid "True/False Question" +msgstr "Wahr/Falsch Frage" + +#: apps/true_false/models.py:43 +msgid "True/False Questions" +msgstr "Wahr/Falsch Fragen" diff --git a/quiz-app/true_false/locale/es_CO/LC_MESSAGES/django.mo b/quiz-app/true_false/locale/es_CO/LC_MESSAGES/django.mo new file mode 100644 index 0000000..6e65a73 Binary files /dev/null and b/quiz-app/true_false/locale/es_CO/LC_MESSAGES/django.mo differ diff --git a/quiz-app/true_false/locale/es_CO/LC_MESSAGES/django.po b/quiz-app/true_false/locale/es_CO/LC_MESSAGES/django.po new file mode 100644 index 0000000..5f8dbdf --- /dev/null +++ b/quiz-app/true_false/locale/es_CO/LC_MESSAGES/django.po @@ -0,0 +1,35 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2016 +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , 2016. +# +msgid "" +msgstr "" +"Project-Id-Version: django-quiz-true-false\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-10-01 17:56+0000\n" +"PO-Revision-Date: 2016-03-24 16:14-0500\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 1.8.4\n" +"Language-Team: \n" +"Last-Translator: \n" +"Language: es_CO\n" + +#: models.py:9 +msgid "Tick this if the question is true. Leave it blank for false." +msgstr "Marque esto si la pregunta es verdadera. Déjelo en blanco si es falsa." + +#: models.py:12 +msgid "Correct" +msgstr "Correcto" + +#: models.py:40 +msgid "True/False Question" +msgstr "Pregunta de Verdadero/Falso" + +#: models.py:41 +msgid "True/False Questions" +msgstr "Preguntas de Verdadero/Falso" diff --git a/quiz-app/true_false/locale/it/LC_MESSAGES/django.mo b/quiz-app/true_false/locale/it/LC_MESSAGES/django.mo new file mode 100644 index 0000000..8047563 Binary files /dev/null and b/quiz-app/true_false/locale/it/LC_MESSAGES/django.mo differ diff --git a/quiz-app/true_false/locale/it/LC_MESSAGES/django.po b/quiz-app/true_false/locale/it/LC_MESSAGES/django.po new file mode 100644 index 0000000..d06536c --- /dev/null +++ b/quiz-app/true_false/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,37 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-10-31 22:44+0000\n" +"PO-Revision-Date: 2015-10-31 22:12+0000\n" +"Last-Translator: b' <>'\n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Translated-Using: django-rosetta 0.7.6\n" + +#: apps/true_false/models.py:11 +msgid "Tick this if the question is true. Leave it blank for false." +msgstr "" +"Seleziona se la risposta è corretta. Lascia bianco se la risposta è falsa." + +#: apps/true_false/models.py:14 +msgid "Correct" +msgstr "Corretto" + +#: apps/true_false/models.py:42 +msgid "True/False Question" +msgstr "Domanda Vero/Falso" + +#: apps/true_false/models.py:43 +msgid "True/False Questions" +msgstr "DOmande Vero/Falso" diff --git a/quiz-app/true_false/locale/ru/LC_MESSAGES/django.mo b/quiz-app/true_false/locale/ru/LC_MESSAGES/django.mo new file mode 100644 index 0000000..2fb51e4 Binary files /dev/null and b/quiz-app/true_false/locale/ru/LC_MESSAGES/django.mo differ diff --git a/quiz-app/true_false/locale/ru/LC_MESSAGES/django.po b/quiz-app/true_false/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 0000000..56385ce --- /dev/null +++ b/quiz-app/true_false/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,36 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: django-quiz-true-false\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-10-01 17:56+0000\n" +"PO-Revision-Date: 2015-08-21 19:40+0500\n" +"Last-Translator: Eugena Mihailikova \n" +"Language-Team: LANGUAGE \n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Poedit 1.5.4\n" + +#: models.py:9 +msgid "Tick this if the question is true. Leave it blank for false." +msgstr "Отметьте, если ответ на вопрос 'Да'. Оставьте пустым, если ответ 'Нет'" + +#: models.py:12 +msgid "Correct" +msgstr "Правильно" + +#: models.py:40 +msgid "True/False Question" +msgstr "Вопрос с ответом Да/Нет" + +#: models.py:41 +msgid "True/False Questions" +msgstr "Вопросы с ответом Да/Нет" diff --git a/quiz-app/true_false/locale/zh_CN/LC_MESSAGES/django.mo b/quiz-app/true_false/locale/zh_CN/LC_MESSAGES/django.mo new file mode 100644 index 0000000..6247891 Binary files /dev/null and b/quiz-app/true_false/locale/zh_CN/LC_MESSAGES/django.mo differ diff --git a/quiz-app/true_false/locale/zh_CN/LC_MESSAGES/django.po b/quiz-app/true_false/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 0000000..9be8b70 --- /dev/null +++ b/quiz-app/true_false/locale/zh_CN/LC_MESSAGES/django.po @@ -0,0 +1,36 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-10-31 22:44+0000\n" +"PO-Revision-Date: 2015-10-31 22:12+0000\n" +"Last-Translator: b' <>'\n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Translated-Using: django-rosetta 0.7.6\n" + +#: apps/true_false/models.py:11 +msgid "Tick this if the question is true. Leave it blank for false." +msgstr "如果题目为真,勾选此处。不选为假。" + +#: apps/true_false/models.py:14 +msgid "Correct" +msgstr "正确" + +#: apps/true_false/models.py:42 +msgid "True/False Question" +msgstr "判断题" + +#: apps/true_false/models.py:43 +msgid "True/False Questions" +msgstr "判断题" diff --git a/quiz-app/true_false/migrations/0001_initial.py b/quiz-app/true_false/migrations/0001_initial.py new file mode 100644 index 0000000..bac3c43 --- /dev/null +++ b/quiz-app/true_false/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-06-22 11:20 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('quiz', '__first__'), + ] + + operations = [ + migrations.CreateModel( + name='TF_Question', + fields=[ + ('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')), + ('correct', models.BooleanField(default=False, help_text='Tick this if the question is true. Leave it blank for false.', verbose_name='Correct')), + ], + options={ + 'verbose_name': 'True/False Question', + 'verbose_name_plural': 'True/False Questions', + 'ordering': ['category'], + }, + bases=('quiz.question',), + ), + ] diff --git a/quiz-app/true_false/migrations/__init__.py b/quiz-app/true_false/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/quiz-app/true_false/migrations/__pycache__/0001_initial.cpython-311.pyc b/quiz-app/true_false/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..2a7362c Binary files /dev/null and b/quiz-app/true_false/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/quiz-app/true_false/migrations/__pycache__/0001_initial.cpython-37.pyc b/quiz-app/true_false/migrations/__pycache__/0001_initial.cpython-37.pyc new file mode 100644 index 0000000..e5ef2ff Binary files /dev/null and b/quiz-app/true_false/migrations/__pycache__/0001_initial.cpython-37.pyc differ diff --git a/quiz-app/true_false/migrations/__pycache__/0001_initial.cpython-38.pyc b/quiz-app/true_false/migrations/__pycache__/0001_initial.cpython-38.pyc new file mode 100644 index 0000000..1b35337 Binary files /dev/null and b/quiz-app/true_false/migrations/__pycache__/0001_initial.cpython-38.pyc differ diff --git a/quiz-app/true_false/migrations/__pycache__/0001_initial.cpython-39.pyc b/quiz-app/true_false/migrations/__pycache__/0001_initial.cpython-39.pyc new file mode 100644 index 0000000..402f3b8 Binary files /dev/null and b/quiz-app/true_false/migrations/__pycache__/0001_initial.cpython-39.pyc differ diff --git a/quiz-app/true_false/migrations/__pycache__/__init__.cpython-311.pyc b/quiz-app/true_false/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..7b60e00 Binary files /dev/null and b/quiz-app/true_false/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/quiz-app/true_false/migrations/__pycache__/__init__.cpython-37.pyc b/quiz-app/true_false/migrations/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..f1d9a36 Binary files /dev/null and b/quiz-app/true_false/migrations/__pycache__/__init__.cpython-37.pyc differ diff --git a/quiz-app/true_false/migrations/__pycache__/__init__.cpython-38.pyc b/quiz-app/true_false/migrations/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..b253a8e Binary files /dev/null and b/quiz-app/true_false/migrations/__pycache__/__init__.cpython-38.pyc differ diff --git a/quiz-app/true_false/migrations/__pycache__/__init__.cpython-39.pyc b/quiz-app/true_false/migrations/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..5b9a741 Binary files /dev/null and b/quiz-app/true_false/migrations/__pycache__/__init__.cpython-39.pyc differ diff --git a/quiz-app/true_false/models.py b/quiz-app/true_false/models.py new file mode 100644 index 0000000..308df72 --- /dev/null +++ b/quiz-app/true_false/models.py @@ -0,0 +1,45 @@ +from __future__ import unicode_literals +# from django.utils.encoding import python_2_unicode_compatible +from six import python_2_unicode_compatible +from django.utils.translation import gettext_lazy as _ +from django.db import models +from quiz.models import Question + + +class TF_Question(Question): + correct = models.BooleanField(blank=False, + default=False, + help_text=_("Tick this if the question " + "is true. Leave it blank for" + " false."), + verbose_name=_("Correct")) + + def check_if_correct(self, guess): + if guess == "True": + guess_bool = True + elif guess == "False": + guess_bool = False + else: + return False + + if guess_bool == self.correct: + return True + else: + return False + + def get_answers(self): + return [{'correct': self.check_if_correct("True"), + 'content': 'True'}, + {'correct': self.check_if_correct("False"), + 'content': 'False'}] + + def get_answers_list(self): + return [(True, True), (False, False)] + + def answer_choice_to_string(self, guess): + return str(guess) + + class Meta: + verbose_name = _("True/False Question") + verbose_name_plural = _("True/False Questions") + ordering = ['category'] diff --git a/quiz-app/true_false/tests.py b/quiz-app/true_false/tests.py new file mode 100644 index 0000000..317fed3 --- /dev/null +++ b/quiz-app/true_false/tests.py @@ -0,0 +1,35 @@ +from django.test import TestCase + +from .models import TF_Question + + +class TestTrueFalseQuestionModel(TestCase): + def setUp(self): + self.red = TF_Question.objects.create(content="Is red the best?", + explanation="it is", + correct=True,) + self.blue = TF_Question.objects.create(content="Is blue the best?", + explanation="it is not", + correct=False,) + + def test_true_q(self): + self.assertEqual(self.red.correct, True) + self.assertEqual(self.red.check_if_correct("True"), True) + self.assertEqual(self.red.check_if_correct("False"), False) + self.assertEqual(self.red.check_if_correct("4"), False) + + def test_false_q(self): + self.assertEqual(self.blue.correct, False) + self.assertEqual(self.blue.check_if_correct("True"), False) + self.assertEqual(self.blue.check_if_correct("False"), True) + + def test_get_answers(self): + self.assertEqual(self.red.get_answers(), + [{'correct': True, + 'content': 'True'}, + {'correct': False, + 'content': 'False'}]) + self.assertEqual(self.red.answer_choice_to_string('True'), 'True') + + def test_answer_to_string(self): + self.assertEqual('True', self.red.answer_choice_to_string(True))