summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml25
-rw-r--r--grades/models.py5
-rw-r--r--grades/templates/add_grades.html35
-rw-r--r--grades/templates/grading_systems.html2
-rw-r--r--grades/tests/test_views.py2
-rw-r--r--grades/views.py14
-rw-r--r--online_test/settings.py20
-rw-r--r--online_test/urls.py9
-rw-r--r--requirements/requirements-codeserver.txt1
-rw-r--r--requirements/requirements-common.txt6
-rw-r--r--setup.py7
-rw-r--r--yaksh/code_server.py2
-rw-r--r--yaksh/decorators.py2
-rw-r--r--yaksh/evaluator_tests/test_python_evaluation.py6
-rw-r--r--yaksh/language_registry.py3
-rw-r--r--yaksh/middleware/one_session_per_user.py7
-rw-r--r--yaksh/middleware/user_time_zone.py6
-rw-r--r--yaksh/models.py91
-rw-r--r--yaksh/templates/yaksh/add_course.html2
-rw-r--r--yaksh/templates/yaksh/add_question.html2
-rw-r--r--yaksh/templates/yaksh/add_quiz.html12
-rw-r--r--yaksh/templates/yaksh/courses.html2
-rw-r--r--yaksh/templates/yaksh/lessons.html2
-rw-r--r--yaksh/templates/yaksh/modules.html2
-rw-r--r--yaksh/templates/yaksh/quizzes.html2
-rw-r--r--yaksh/templates/yaksh/showquestions.html2
-rw-r--r--yaksh/templatetags/custom_filters.py4
-rw-r--r--yaksh/test_views.py4
-rw-r--r--yaksh/tests/test_code_server.py2
-rw-r--r--yaksh/views.py142
30 files changed, 240 insertions, 181 deletions
diff --git a/.travis.yml b/.travis.yml
index 59eaa66..a2cf266 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,15 +1,15 @@
language: python
python:
- - "2.7"
- - "3.5"
+ - "3.6"
+
+services:
+ - xvfb
before_install:
- sudo apt-get update -qq
- sudo apt-get install -y scilab
- - "export DISPLAY=:99.0"
- - "sh -e /etc/init.d/xvfb start"
- - sleep 3 # give xvfb some time to start
+ - export DISPLAY=:99.0
# command to install dependencies
install:
@@ -17,23 +17,24 @@ install:
- pip install codecov
- python setup.py develop
+before_script:
+ - python manage.py migrate auth
+ - python manage.py migrate
+
# command to run tests and coverage
script:
- coverage erase
- - coverage run -p manage.py test -v 2 --settings online_test.test_settings yaksh
- - coverage run -p manage.py test -v 2 --settings online_test.test_settings grades
- - coverage run -p manage.py test -v 2 --settings online_test.test_settings yaksh.live_server_tests.load_test
+ - coverage run -p manage.py test -v 2 yaksh
+ - coverage run -p manage.py test -v 2 grades
+ - coverage run -p manage.py test -v 2 yaksh.live_server_tests.load_test
after_success:
- coverage combine
- coverage report
- codecov
-sudo:
- required
-
dist:
- trusty
+ xenial
addons:
firefox: "46.0"
diff --git a/grades/models.py b/grades/models.py
index b395a24..656c0d1 100644
--- a/grades/models.py
+++ b/grades/models.py
@@ -5,7 +5,8 @@ from django.contrib.auth.models import User
class GradingSystem(models.Model):
name = models.CharField(max_length=255, unique=True)
description = models.TextField(default='About the grading system!')
- creator = models.ForeignKey(User, null=True, blank=True)
+ creator = models.ForeignKey(User, null=True, blank=True,
+ on_delete=models.CASCADE)
def get_grade(self, marks):
ranges = self.graderange_set.all()
@@ -39,7 +40,7 @@ class GradingSystem(models.Model):
class GradeRange(models.Model):
- system = models.ForeignKey(GradingSystem)
+ system = models.ForeignKey(GradingSystem, on_delete=models.CASCADE)
lower_limit = models.FloatField()
upper_limit = models.FloatField()
grade = models.CharField(max_length=10)
diff --git a/grades/templates/add_grades.html b/grades/templates/add_grades.html
index 59a344d..f53a337 100644
--- a/grades/templates/add_grades.html
+++ b/grades/templates/add_grades.html
@@ -14,7 +14,7 @@
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'yaksh:add_course' %}">
- Add New Course
+ Add/Edit Course
</a>
</li>
<li class="nav-item dropdown hide">
@@ -46,13 +46,44 @@
Note: For grade range lower limit is inclusive and upper limit is exclusive
</div>
<br>
+{% if messages %}
+ {% for message in messages %}
+ <div class="alert alert-dismissible alert-{{ message.tags }}">
+ <button type="button" class="close" data-dismiss="alert">
+ <i class="fa fa-close"></i>
+ </button>
+ <strong>{{ message }}</strong>
+ </div>
+ {% endfor %}
+ <br>
+{% endif %}
{% if not system_id %}
<form action="{% url 'grades:add_grade' %}" method="POST">
{% else %}
<form action="{% url 'grades:edit_grade' system_id %}" method="POST">
{% endif %}
{% csrf_token %}
- <table class="table">
+ <table class="table table-responsive-sm">
+ {% if grade_form.errors %}
+ {% for field in grade_form %}
+ {% for error in field.errors %}
+ <div class="alert alert-dismissible alert-danger">
+ <button type="button" class="close" data-dismiss="alert">
+ <i class="fa fa-close"></i>
+ </button>
+ <strong>{{ error|escape }}</strong>
+ </div>
+ {% endfor %}
+ {% endfor %}
+ {% for error in grade_form.non_field_errors %}
+ <div class="alert alert-dismissible alert-danger">
+ <button type="button" class="close" data-dismiss="alert">
+ <i class="fa fa-close"></i>
+ </button>
+ <strong>{{ error|escape }}</strong>
+ </div>
+ {% endfor %}
+ {% endif %}
{% for field in grade_form %}
{{ field }}
<hr>
diff --git a/grades/templates/grading_systems.html b/grades/templates/grading_systems.html
index 8102230..88adfa0 100644
--- a/grades/templates/grading_systems.html
+++ b/grades/templates/grading_systems.html
@@ -13,7 +13,7 @@
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'yaksh:add_course' %}">
- Add New Course
+ Add/Edit Course
</a>
</li>
<li class="nav-item dropdown hide">
diff --git a/grades/tests/test_views.py b/grades/tests/test_views.py
index 6b76565..6e066a4 100644
--- a/grades/tests/test_views.py
+++ b/grades/tests/test_views.py
@@ -1,6 +1,6 @@
from django.test import TestCase, Client
from django.contrib.auth.models import User
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from grades.models import GradingSystem
diff --git a/grades/views.py b/grades/views.py
index 7403e4e..48cd8e3 100644
--- a/grades/views.py
+++ b/grades/views.py
@@ -1,6 +1,7 @@
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.forms import inlineformset_factory
+from django.contrib import messages
from grades.forms import GradingSystemForm, GradeRangeForm
from grades.models import GradingSystem, GradeRange
@@ -30,12 +31,13 @@ def add_grading_system(request, system_id=None):
formset = GradeRangeFormSet(request.POST, instance=grading_system)
grade_form = GradingSystemForm(request.POST, instance=grading_system)
if grade_form.is_valid():
- system = grade_form.save(commit=False)
- system.creator = user
- system.save()
- system_id = system.id
- if formset.is_valid():
- formset.save()
+ grading_system = grade_form.save(commit=False)
+ grading_system.creator = user
+ grading_system.save()
+ system_id = grading_system.id
+ if formset.is_valid():
+ formset.save()
+ messages.success(request, "Grading system saved successfully")
if 'add' in request.POST:
GradeRangeFormSet = inlineformset_factory(
GradingSystem, GradeRange, GradeRangeForm, extra=1
diff --git a/online_test/settings.py b/online_test/settings.py
index 90cec41..7b9a231 100644
--- a/online_test/settings.py
+++ b/online_test/settings.py
@@ -47,7 +47,7 @@ INSTALLED_APPS = (
'grades',
)
-MIDDLEWARE_CLASSES = (
+MIDDLEWARE = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@@ -189,4 +189,22 @@ SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
'fields': 'id, name, email'
}
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ 'OPTIONS': {
+ 'min_length': 8,
+ }
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
TAGGIT_CASE_INSENSITIVE = True
diff --git a/online_test/urls.py b/online_test/urls.py
index 18ebc54..52cbd40 100644
--- a/online_test/urls.py
+++ b/online_test/urls.py
@@ -10,12 +10,11 @@ urlpatterns = [
# url(r'^$', 'online_test.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
- url(r'^admin/', include(admin.site.urls)),
+ url(r'^admin/', admin.site.urls),
url(r'^$', views.index, name='index'),
- url(r'^exam/', include('yaksh.urls', namespace='yaksh', app_name='yaksh')),
- url(r'^exam/reset/', include('yaksh.urls_password_reset')),
+ url(r'^exam/', include(('yaksh.urls', 'yaksh'))),
+ url(r'^exam/reset/', include('django.contrib.auth.urls')),
url(r'^', include('social_django.urls', namespace='social')),
- url(r'^grades/', include('grades.urls', namespace='grades',
- app_name='grades')),
+ url(r'^grades/', include(('grades.urls', 'grades'))),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/requirements/requirements-codeserver.txt b/requirements/requirements-codeserver.txt
index 11bc0a2..6d0dc51 100644
--- a/requirements/requirements-codeserver.txt
+++ b/requirements/requirements-codeserver.txt
@@ -1,6 +1,5 @@
pytest
python-decouple
-six
requests
tornado==4.5.3
psutil
diff --git a/requirements/requirements-common.txt b/requirements/requirements-common.txt
index 54e4c84..d1fed93 100644
--- a/requirements/requirements-common.txt
+++ b/requirements/requirements-common.txt
@@ -1,8 +1,8 @@
-r requirements-codeserver.txt
invoke==0.21.0
-django==1.11.21
-django-taggit==0.18.1
-pytz==2016.4
+django==3.0.3
+django-taggit==1.2.0
+pytz==2019.3
requests-oauthlib>=0.6.1
social-auth-app-django==3.1.0
selenium==2.53.6
diff --git a/setup.py b/setup.py
index d389584..87ae184 100644
--- a/setup.py
+++ b/setup.py
@@ -16,16 +16,15 @@ def get_version():
install_requires = [
- 'django==1.11.21',
- 'django-taggit==0.18.1',
- 'pytz==2016.4',
+ 'django==3.0.3',
+ 'django-taggit==1.2.0',
+ 'pytz==2019.3',
'requests-oauthlib>=0.6.1',
'python-social-auth==0.2.19',
'tornado',
'psutil',
'ruamel.yaml==0.15.23',
'invoke==0.21.0',
- 'six',
'requests',
'markdown==2.6.9',
]
diff --git a/yaksh/code_server.py b/yaksh/code_server.py
index 75dd9b2..4feb7fd 100644
--- a/yaksh/code_server.py
+++ b/yaksh/code_server.py
@@ -25,7 +25,7 @@ import time
import requests
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
-from six.moves import urllib
+import urllib
# Local imports
from .settings import N_CODE_SERVERS, SERVER_POOL_PORT
diff --git a/yaksh/decorators.py b/yaksh/decorators.py
index 81912f0..d584868 100644
--- a/yaksh/decorators.py
+++ b/yaksh/decorators.py
@@ -40,7 +40,7 @@ def email_verified(func):
user = request.user
context = {}
if not settings.IS_DEVELOPMENT:
- if user.is_authenticated() and user_has_profile(user):
+ if user.is_authenticated and user_has_profile(user):
if not user.profile.is_email_verified:
context['success'] = False
context['msg'] = "Your account is not verified. \
diff --git a/yaksh/evaluator_tests/test_python_evaluation.py b/yaksh/evaluator_tests/test_python_evaluation.py
index ad9b2c2..de973cf 100644
--- a/yaksh/evaluator_tests/test_python_evaluation.py
+++ b/yaksh/evaluator_tests/test_python_evaluation.py
@@ -523,7 +523,6 @@ class PythonStdIOEvaluationTestCases(EvaluatorBaseTest):
"weight": 0.0
}]
user_answer = dedent("""
- from six.moves import input
input_a = input()
input_b = input()
a = [int(i) for i in input_a.split(',')]
@@ -557,9 +556,8 @@ class PythonStdIOEvaluationTestCases(EvaluatorBaseTest):
"weight": 0.0
}]
user_answer = dedent("""
- from six.moves import input
- a = str(input())
- b = str(input())
+ a = input()
+ b = input()
print(a.count(b))
"""
)
diff --git a/yaksh/language_registry.py b/yaksh/language_registry.py
index ec5dae9..8059681 100644
--- a/yaksh/language_registry.py
+++ b/yaksh/language_registry.py
@@ -1,6 +1,5 @@
from __future__ import unicode_literals
import importlib
-import six
# Local imports
from .settings import code_evaluators
@@ -27,7 +26,7 @@ def create_evaluator_instance(metadata, test_case):
class _LanguageRegistry(object):
def __init__(self):
self._register = {}
- for language, module in six.iteritems(code_evaluators):
+ for language, module in code_evaluators.items():
self._register[language] = None
# Public Protocol ##########
diff --git a/yaksh/middleware/one_session_per_user.py b/yaksh/middleware/one_session_per_user.py
index 1ed1786..3b8d302 100644
--- a/yaksh/middleware/one_session_per_user.py
+++ b/yaksh/middleware/one_session_per_user.py
@@ -20,6 +20,13 @@ class OneSessionPerUserMiddleware(object):
Link: https://docs.djangoproject.com/en/1.5/topics/auth/customizing/
#extending-the-existing-user-model
"""
+
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ return self.get_response(request)
+
def process_request(self, request):
"""
# Documentation:
diff --git a/yaksh/middleware/user_time_zone.py b/yaksh/middleware/user_time_zone.py
index 206c08a..92035e8 100644
--- a/yaksh/middleware/user_time_zone.py
+++ b/yaksh/middleware/user_time_zone.py
@@ -8,6 +8,12 @@ class TimezoneMiddleware(object):
if user timezone is not available default value 'Asia/Kolkata'
is activated
"""
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ return self.get_response(request)
+
def process_request(self, request):
user = request.user
user_tz = 'Asia/Kolkata'
diff --git a/yaksh/models.py b/yaksh/models.py
index 065d9cd..12c902b 100644
--- a/yaksh/models.py
+++ b/yaksh/models.py
@@ -258,7 +258,7 @@ class Lesson(models.Model):
html_data = models.TextField(null=True, blank=True)
# Creator of the lesson
- creator = models.ForeignKey(User)
+ creator = models.ForeignKey(User, on_delete=models.CASCADE)
# Activate/Deactivate Lesson
active = models.BooleanField(default=True)
@@ -329,7 +329,8 @@ class Lesson(models.Model):
#############################################################################
class LessonFile(models.Model):
- lesson = models.ForeignKey(Lesson, related_name="lesson")
+ lesson = models.ForeignKey(Lesson, related_name="lesson",
+ on_delete=models.CASCADE)
file = models.FileField(upload_to=get_file_dir, default=None)
def remove(self):
@@ -477,7 +478,7 @@ class Quiz(models.Model):
is_exercise = models.BooleanField(default=False)
- creator = models.ForeignKey(User, null=True)
+ creator = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
objects = QuizManager()
@@ -534,7 +535,7 @@ class Quiz(models.Model):
except QuestionPaper.DoesNotExist:
qp = None
ans_ppr = AnswerPaper.objects.filter(
- user=user, course=course, question_paper=qp
+ user_id=user.id, course_id=course.id, question_paper_id=qp
).order_by("-attempt_number")
if ans_ppr.exists():
status = ans_ppr.first().status
@@ -548,7 +549,7 @@ class Quiz(models.Model):
except QuestionPaper.DoesNotExist:
qp = None
ans_ppr = AnswerPaper.objects.filter(
- user=user, course=course, question_paper=qp
+ user_id=user.id, course_id=course.id, question_paper_id=qp
).order_by("-attempt_number")
if ans_ppr.exists():
return any([paper.passed for paper in ans_ppr])
@@ -593,8 +594,10 @@ class LearningUnit(models.Model):
""" Maintain order of lesson and quiz added in the course """
order = models.IntegerField()
type = models.CharField(max_length=16)
- lesson = models.ForeignKey(Lesson, null=True, blank=True)
- quiz = models.ForeignKey(Quiz, null=True, blank=True)
+ lesson = models.ForeignKey(Lesson, null=True, blank=True,
+ on_delete=models.CASCADE)
+ quiz = models.ForeignKey(Quiz, null=True, blank=True,
+ on_delete=models.CASCADE)
check_prerequisite = models.BooleanField(default=True)
def get_lesson_or_quiz(self):
@@ -612,10 +615,12 @@ class LearningUnit(models.Model):
self.check_prerequisite = True
def get_completion_status(self, user, course):
- course_status = CourseStatus.objects.filter(user=user, course=course)
+ course_status = CourseStatus.objects.filter(
+ user_id=user.id, course_id=course.id
+ )
state = "not attempted"
if course_status.exists():
- if self in course_status.first().completed_units.all():
+ if course_status.first().completed_units.filter(id=self.id):
state = "completed"
elif self.type == "quiz":
state = self.quiz.get_answerpaper_status(user, course)
@@ -670,7 +675,8 @@ class LearningModule(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(default=None, null=True, blank=True)
order = models.IntegerField(default=0)
- creator = models.ForeignKey(User, related_name="module_creator")
+ creator = models.ForeignKey(User, related_name="module_creator",
+ on_delete=models.CASCADE)
check_prerequisite = models.BooleanField(default=True)
check_prerequisite_passes = models.BooleanField(default=False)
html_data = models.TextField(null=True, blank=True)
@@ -847,7 +853,8 @@ class Course(models.Model):
active = models.BooleanField(default=True)
code = models.CharField(max_length=128, null=True, blank=True)
hidden = models.BooleanField(default=False)
- creator = models.ForeignKey(User, related_name='creator')
+ creator = models.ForeignKey(User, related_name='creator',
+ on_delete=models.CASCADE)
students = models.ManyToManyField(User, related_name='students')
requests = models.ManyToManyField(User, related_name='requests')
rejected = models.ManyToManyField(User, related_name='rejected')
@@ -876,7 +883,8 @@ class Course(models.Model):
null=True
)
- grading_system = models.ForeignKey(GradingSystem, null=True, blank=True)
+ grading_system = models.ForeignKey(GradingSystem, null=True, blank=True,
+ on_delete=models.CASCADE)
objects = CourseManager()
@@ -1130,9 +1138,10 @@ class CourseStatus(models.Model):
completed_units = models.ManyToManyField(LearningUnit,
related_name="completed_units")
current_unit = models.ForeignKey(LearningUnit, related_name="current_unit",
- null=True, blank=True)
- course = models.ForeignKey(Course)
- user = models.ForeignKey(User)
+ null=True, blank=True,
+ on_delete=models.CASCADE)
+ course = models.ForeignKey(Course, on_delete=models.CASCADE)
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
grade = models.CharField(max_length=255, null=True, blank=True)
percentage = models.FloatField(default=0.0)
percent_completed = models.IntegerField(default=0)
@@ -1144,7 +1153,7 @@ class CourseStatus(models.Model):
if self.is_course_complete():
self.calculate_percentage()
if self.course.grading_system is None:
- grading_system = GradingSystem.objects.get(name='default')
+ grading_system = GradingSystem.objects.get(name__contains='default')
else:
grading_system = self.course.grading_system
grade = grading_system.get_grade(self.percentage)
@@ -1186,14 +1195,14 @@ class CourseStatus(models.Model):
###############################################################################
class ConcurrentUser(models.Model):
- concurrent_user = models.OneToOneField(User)
+ concurrent_user = models.OneToOneField(User, on_delete=models.CASCADE)
session_key = models.CharField(max_length=40)
###############################################################################
class Profile(models.Model):
"""Profile for a user to store roll number and other details."""
- user = models.OneToOneField(User)
+ user = models.OneToOneField(User, on_delete=models.CASCADE)
roll_number = models.CharField(max_length=20)
institute = models.CharField(max_length=128)
department = models.CharField(max_length=64)
@@ -1271,7 +1280,8 @@ class Question(models.Model):
snippet = models.TextField(blank=True)
# user for particular question
- user = models.ForeignKey(User, related_name="user")
+ user = models.ForeignKey(User, related_name="user",
+ on_delete=models.CASCADE)
# Does this question allow partial grading
partial_grading = models.BooleanField(default=False)
@@ -1546,7 +1556,8 @@ class Question(models.Model):
###############################################################################
class FileUpload(models.Model):
file = models.FileField(upload_to=get_upload_dir, blank=True)
- question = models.ForeignKey(Question, related_name="question")
+ question = models.ForeignKey(Question, related_name="question",
+ on_delete=models.CASCADE)
extract = models.BooleanField(default=False)
hide = models.BooleanField(default=False)
@@ -1580,7 +1591,7 @@ class Answer(models.Model):
"""Answers submitted by the users."""
# The question for which user answers.
- question = models.ForeignKey(Question)
+ question = models.ForeignKey(Question, on_delete=models.CASCADE)
# The answer submitted by the user.
answer = models.TextField(null=True, blank=True)
@@ -1655,7 +1666,7 @@ class QuestionPaper(models.Model):
"""Question paper stores the detail of the questions."""
# Question paper belongs to a particular quiz.
- quiz = models.ForeignKey(Quiz)
+ quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE)
# Questions that will be mandatory in the quiz.
fixed_questions = models.ManyToManyField(Question)
@@ -1941,10 +1952,10 @@ class AnswerPaperManager(models.Manager):
def _get_answerpapers_for_quiz(self, questionpaper_id, course_id,
status=False):
if not status:
- return self.filter(question_paper_id=questionpaper_id,
+ return self.filter(question_paper_id__in=questionpaper_id,
course_id=course_id)
else:
- return self.filter(question_paper_id=questionpaper_id,
+ return self.filter(question_paper_id__in=questionpaper_id,
course_id=course_id,
status="completed")
@@ -1986,14 +1997,15 @@ class AnswerPaperManager(models.Manager):
.distinct()
def get_user_all_attempts(self, questionpaper, user, course_id):
- return self.filter(question_paper=questionpaper, user=user,
+ return self.filter(question_paper_id__in=questionpaper, user_id=user,
course_id=course_id)\
.order_by('-attempt_number')
def get_user_data(self, user, questionpaper_id, course_id,
attempt_number=None):
if attempt_number is not None:
- papers = self.filter(user=user, question_paper_id=questionpaper_id,
+ papers = self.filter(user_id=user.id,
+ question_paper_id__in=questionpaper_id,
course_id=course_id,
attempt_number=attempt_number)
else:
@@ -2011,7 +2023,8 @@ class AnswerPaperManager(models.Manager):
def get_user_best_of_attempts_marks(self, quiz, user_id, course_id):
best_attempt = 0.0
- papers = self.filter(question_paper__quiz=quiz, course_id=course_id,
+ papers = self.filter(question_paper__quiz_id=quiz.id,
+ course_id=course_id,
user=user_id).values("marks_obtained")
if papers:
best_attempt = max([marks["marks_obtained"] for marks in papers])
@@ -2023,15 +2036,15 @@ class AnswerPaper(models.Model):
"""A answer paper for a student -- one per student typically.
"""
# The user taking this question paper.
- user = models.ForeignKey(User)
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
questions = models.ManyToManyField(Question, related_name='questions')
# The Quiz to which this question paper is attached to.
- question_paper = models.ForeignKey(QuestionPaper)
+ question_paper = models.ForeignKey(QuestionPaper, on_delete=models.CASCADE)
# Answepaper will be unique to the course
- course = models.ForeignKey(Course, null=True)
+ course = models.ForeignKey(Course, null=True, on_delete=models.CASCADE)
# The attempt number for the question paper.
attempt_number = models.IntegerField()
@@ -2451,17 +2464,20 @@ class AssignmentUploadManager(models.Manager):
##############################################################################
class AssignmentUpload(models.Model):
- user = models.ForeignKey(User)
- assignmentQuestion = models.ForeignKey(Question)
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+ assignmentQuestion = models.ForeignKey(Question, on_delete=models.CASCADE)
assignmentFile = models.FileField(upload_to=get_assignment_dir)
- question_paper = models.ForeignKey(QuestionPaper, blank=True, null=True)
- course = models.ForeignKey(Course, null=True, blank=True)
+ question_paper = models.ForeignKey(QuestionPaper, blank=True, null=True,
+ on_delete=models.CASCADE)
+ course = models.ForeignKey(Course, null=True, blank=True,
+ on_delete=models.CASCADE)
objects = AssignmentUploadManager()
##############################################################################
class TestCase(models.Model):
- question = models.ForeignKey(Question, blank=True, null=True)
+ question = models.ForeignKey(Question, blank=True, null=True,
+ on_delete=models.CASCADE)
type = models.CharField(max_length=24, choices=test_case_types, null=True)
@@ -2598,10 +2614,11 @@ class TestCaseOrder(models.Model):
"""
# Answerpaper of the user.
- answer_paper = models.ForeignKey(AnswerPaper, related_name="answer_paper")
+ answer_paper = models.ForeignKey(AnswerPaper, related_name="answer_paper",
+ on_delete=models.CASCADE)
# Question in an answerpaper.
- question = models.ForeignKey(Question)
+ question = models.ForeignKey(Question, on_delete=models.CASCADE)
# Order of the test case for a question.
order = models.TextField()
diff --git a/yaksh/templates/yaksh/add_course.html b/yaksh/templates/yaksh/add_course.html
index 72403ce..97c6f56 100644
--- a/yaksh/templates/yaksh/add_course.html
+++ b/yaksh/templates/yaksh/add_course.html
@@ -23,7 +23,7 @@
</li>
<li class="nav-item">
<a class="nav-link active" href="{% url 'yaksh:add_course' %}">
- Add New Course
+ Add/Edit Course
</a>
</li>
<li class="nav-item dropdown hide">
diff --git a/yaksh/templates/yaksh/add_question.html b/yaksh/templates/yaksh/add_question.html
index 1ad7deb..17cdcfe 100644
--- a/yaksh/templates/yaksh/add_question.html
+++ b/yaksh/templates/yaksh/add_question.html
@@ -1,6 +1,6 @@
{% extends "manage.html" %}
{% load custom_filters %}
-{% load staticfiles %}
+{% load static %}
{% block title %} Add Question {% endblock title %}
{% block pagetitle %} Add Question {% endblock pagetitle %}
diff --git a/yaksh/templates/yaksh/add_quiz.html b/yaksh/templates/yaksh/add_quiz.html
index 57b4d77..5497eeb 100644
--- a/yaksh/templates/yaksh/add_quiz.html
+++ b/yaksh/templates/yaksh/add_quiz.html
@@ -14,6 +14,12 @@
<script src="{% static 'yaksh/js/jquery-3.3.1.min.js' %}"></script>
<script src="{% static 'yaksh/js/add_quiz.js' %}"></script>
<script src="{% static 'yaksh/js/jquery.datetimepicker.full.min.js' %}"></script>
+<script type="text/javascript">
+ $(document).ready(function() {
+ $("#id_start_date_time").datetimepicker({format: 'Y-m-d H:i:s'});
+ $("#id_end_date_time").datetimepicker({format: 'Y-m-d H:i:s'});
+ });
+</script>
{% endblock %}
{% block onload %} onload="javascript:test();" {% endblock %}
@@ -44,13 +50,9 @@
<form name=frm id=frm action="" method="post" >
{% csrf_token %}
<center>
- <table class="table">
+ <table class="table table-responsive-sm">
{{ form.as_table }}
</table>
- <script type="text/javascript">
- $("#id_start_date_time").datetimepicker({format: 'Y-m-d H:i:s'});
- $("#id_end_date_time").datetimepicker({format: 'Y-m-d H:i:s'});
- </script>
<br/>
<button class="btn btn-success btn-lg" id="submit" name="questionpaper">
<i class="fa fa-save">&nbsp;Save</i>
diff --git a/yaksh/templates/yaksh/courses.html b/yaksh/templates/yaksh/courses.html
index c890b86..084d0f6 100644
--- a/yaksh/templates/yaksh/courses.html
+++ b/yaksh/templates/yaksh/courses.html
@@ -29,7 +29,7 @@
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'yaksh:add_course' %}">
- Add New Course
+ Add/Edit Course
</a>
</li>
<li class="nav-item dropdown hide">
diff --git a/yaksh/templates/yaksh/lessons.html b/yaksh/templates/yaksh/lessons.html
index d75e3e0..e5af061 100644
--- a/yaksh/templates/yaksh/lessons.html
+++ b/yaksh/templates/yaksh/lessons.html
@@ -13,7 +13,7 @@
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'yaksh:add_course' %}">
- Add New Course
+ Add/Edit Course
</a>
</li>
<li class="nav-item dropdown hide">
diff --git a/yaksh/templates/yaksh/modules.html b/yaksh/templates/yaksh/modules.html
index f98be9d..8da207c 100644
--- a/yaksh/templates/yaksh/modules.html
+++ b/yaksh/templates/yaksh/modules.html
@@ -14,7 +14,7 @@
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'yaksh:add_course' %}">
- Add New Course
+ Add/Edit Course
</a>
</li>
<li class="nav-item dropdown hide">
diff --git a/yaksh/templates/yaksh/quizzes.html b/yaksh/templates/yaksh/quizzes.html
index d374404..58dec99 100644
--- a/yaksh/templates/yaksh/quizzes.html
+++ b/yaksh/templates/yaksh/quizzes.html
@@ -14,7 +14,7 @@
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'yaksh:add_course' %}">
- Add New Course
+ Add/Edit Course
</a>
</li>
<li class="nav-item dropdown hide">
diff --git a/yaksh/templates/yaksh/showquestions.html b/yaksh/templates/yaksh/showquestions.html
index 895c345..fdfcc60 100644
--- a/yaksh/templates/yaksh/showquestions.html
+++ b/yaksh/templates/yaksh/showquestions.html
@@ -1,5 +1,5 @@
{% extends "manage.html" %}
-{% load staticfiles %}
+{% load static %}
{% block title %} Questions {% endblock %}
diff --git a/yaksh/templatetags/custom_filters.py b/yaksh/templatetags/custom_filters.py
index a78440d..7dc29d4 100644
--- a/yaksh/templatetags/custom_filters.py
+++ b/yaksh/templatetags/custom_filters.py
@@ -24,12 +24,12 @@ def escape_quotes(value):
return escape_single_and_double_quotes
-@register.assignment_tag(name="completed")
+@register.simple_tag(name="completed")
def completed(answerpaper):
return answerpaper.filter(status="completed").count()
-@register.assignment_tag(name="inprogress")
+@register.simple_tag(name="inprogress")
def inprogress(answerpaper):
return answerpaper.filter(status="inprogress").count()
diff --git a/yaksh/test_views.py b/yaksh/test_views.py
index 61ed3eb..569d4d7 100644
--- a/yaksh/test_views.py
+++ b/yaksh/test_views.py
@@ -11,7 +11,7 @@ import shutil
from markdown import Markdown
from django.contrib.auth.models import Group
from django.contrib.auth import authenticate
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from django.test import TestCase
from django.test import Client
from django.http import Http404
@@ -3888,7 +3888,7 @@ class TestPasswordReset(TestCase):
# Then
self.assertEqual(response.context['email'], self.user1.email)
self.assertEqual(response.status_code, 302)
- self.assertRedirects(response, '/exam/reset/password_reset/mail_sent/')
+ self.assertRedirects(response, '/exam/reset/password_reset/done/')
def test_password_change_post(self):
"""
diff --git a/yaksh/tests/test_code_server.py b/yaksh/tests/test_code_server.py
index 8237256..f4e73f0 100644
--- a/yaksh/tests/test_code_server.py
+++ b/yaksh/tests/test_code_server.py
@@ -6,7 +6,7 @@ except ImportError:
from queue import Queue
from threading import Thread
import unittest
-from six.moves import urllib
+import urllib
from yaksh.code_server import ServerPool, SERVER_POOL_PORT, submit, get_result
from yaksh import settings
diff --git a/yaksh/views.py b/yaksh/views.py
index cf7f3b4..b54461f 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -21,7 +21,6 @@ from django.contrib import messages
from taggit.models import Tag
from django.urls import reverse
import json
-import six
from textwrap import dedent
import zipfile
from markdown import Markdown
@@ -115,7 +114,7 @@ def index(request, next_url=None):
"""The start page.
"""
user = request.user
- if user.is_authenticated():
+ if user.is_authenticated:
if is_moderator(user):
return my_redirect('/exam/manage/' if not next_url else next_url)
return my_redirect("/exam/quizzes/" if not next_url else next_url)
@@ -128,7 +127,7 @@ def user_register(request):
Create a user and corresponding profile and store roll_number also."""
user = request.user
- if user.is_authenticated():
+ if user.is_authenticated:
return my_redirect("/exam/quizzes/")
context = {}
if request.method == "POST":
@@ -391,7 +390,7 @@ def add_exercise(request, quiz_id=None, course_id=None):
else:
form = ExerciseForm(instance=quiz)
context["exercise"] = quiz
- context["course_id"] = course_id
+ context["course_id"] = course_id
context["form"] = form
return my_render_to_response(request, 'yaksh/add_exercise.html', context)
@@ -403,7 +402,7 @@ def prof_manage(request, msg=None):
"""Take credentials of the user with professor/moderator
rights/permissions and log in."""
user = request.user
- if not user.is_authenticated():
+ if not user.is_authenticated:
return my_redirect('/exam/login')
if not is_moderator(user):
return my_redirect('/exam/')
@@ -412,14 +411,7 @@ def prof_manage(request, msg=None):
is_trial=False).distinct().order_by("-active")
paginator = Paginator(courses, 20)
page = request.GET.get('page')
- try:
- courses = paginator.page(page)
- except PageNotAnInteger:
- # If page is not an integer, deliver first page.
- courses = paginator.page(1)
- except EmptyPage:
- # If page is out of range (e.g. 9999), deliver last page of results.
- courses = paginator.page(paginator.num_pages)
+ courses = paginator.get_page(page)
messages.info(request, msg)
context = {'user': user, 'objects': courses}
return my_render_to_response(
@@ -432,7 +424,7 @@ def user_login(request):
user = request.user
context = {}
- if user.is_authenticated():
+ if user.is_authenticated:
return index(request)
next_url = request.GET.get('next')
@@ -1066,14 +1058,7 @@ def courses(request):
paginator = Paginator(courses, 30)
page = request.GET.get('page')
- try:
- courses = paginator.page(page)
- except PageNotAnInteger:
- # If page is not an integer, deliver first page.
- courses = paginator.page(1)
- except EmptyPage:
- # If page is out of range (e.g. 9999), deliver last page of results.
- courses = paginator.page(paginator.num_pages)
+ courses = paginator.get_page(page)
courses_found = courses.object_list.count()
context = {'objects': courses, 'created': True,
'form': form, 'courses_found': courses_found}
@@ -1260,12 +1245,7 @@ def monitor(request, quiz_id=None, course_id=None):
).order_by("-active").distinct()
paginator = Paginator(courses, 30)
page = request.GET.get('page')
- try:
- courses = paginator.page(page)
- except PageNotAnInteger:
- courses = paginator.page(1)
- except EmptyPage:
- courses = paginator.page(paginator.num_pages)
+ courses = paginator.get_page(page)
context = {
"papers": [], "objects": courses, "msg": "Monitor"
}
@@ -1290,9 +1270,10 @@ def monitor(request, quiz_id=None, course_id=None):
else:
attempt_numbers = []
latest_attempts = []
- papers = AnswerPaper.objects.filter(question_paper=q_paper,
- course_id=course_id).order_by(
- 'user__profile__roll_number'
+ papers = AnswerPaper.objects.filter(
+ question_paper_id=q_paper.first().id,
+ course_id=course_id).order_by(
+ 'user__profile__roll_number'
)
users = papers.values_list('user').distinct()
for auser in users:
@@ -1337,14 +1318,9 @@ def ajax_questions_filter(request):
filter_dict['language'] = str(language)
questions = Question.objects.get_queryset().filter(
**filter_dict).order_by('id')
- paginator = Paginator(questions, 10)
+ paginator = Paginator(questions, 30)
page = request.GET.get('page')
- try:
- questions = paginator.page(page)
- except PageNotAnInteger:
- questions = paginator.page(1)
- except EmptyPage:
- questions = paginator.page(paginator.num_pages)
+ questions = paginator.get_page(page)
return my_render_to_response(
request, 'yaksh/ajax_question_filter.html', {
'questions': questions,
@@ -1538,12 +1514,7 @@ def show_all_questions(request):
upload_form = UploadFileForm()
paginator = Paginator(questions, 30)
page = request.GET.get('page')
- try:
- questions = paginator.page(page)
- except PageNotAnInteger:
- questions = paginator.page(1)
- except EmptyPage:
- questions = paginator.page(paginator.num_pages)
+ questions = paginator.get_page(page)
context['questions'] = questions
context['objects'] = questions
context['all_tags'] = all_tags
@@ -1729,12 +1700,7 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None,
).order_by("-active").distinct()
paginator = Paginator(courses, 30)
page = request.GET.get('page')
- try:
- courses = paginator.page(page)
- except PageNotAnInteger:
- courses = paginator.page(1)
- except EmptyPage:
- courses = paginator.page(paginator.num_pages)
+ courses = paginator.get_page(page)
context = {"objects": courses, "msg": "grade"}
if quiz_id is not None:
@@ -1749,9 +1715,8 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None,
if not course.is_creator(current_user) and not \
course.is_teacher(current_user):
raise Http404('This course does not belong to you')
-
has_quiz_assignments = AssignmentUpload.objects.filter(
- question_paper_id=questionpaper_id
+ question_paper_id__in=questionpaper_id
).exists()
context = {
"users": user_details,
@@ -1771,7 +1736,7 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None,
except IndexError:
raise Http404('No attempts for paper')
has_user_assignments = AssignmentUpload.objects.filter(
- question_paper_id=questionpaper_id,
+ question_paper_id__in=questionpaper_id,
user_id=user_id
).exists()
user = User.objects.get(id=user_id)
@@ -1792,8 +1757,7 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None,
if request.method == "POST":
papers = data['papers']
for paper in papers:
- for question, answers in six.iteritems(
- paper.get_question_answers()):
+ for question, answers in paper.get_question_answers().items():
marks = float(request.POST.get('q%d_marks' % question.id, 0))
answer = answers[-1]['answer']
answer.set_marks(marks)
@@ -1804,7 +1768,8 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None,
paper.save()
messages.success(request, "Student data saved successfully")
- course_status = CourseStatus.objects.filter(course=course, user=user)
+ course_status = CourseStatus.objects.filter(
+ course_id=course.id, user_id=user.id)
if course_status.exists():
course_status.first().set_grade()
@@ -2641,7 +2606,6 @@ def design_module(request, module_id, course_id=None):
if "Change" in request.POST:
order_list = request.POST.get("ordered_list")
- print(order_list)
if order_list:
order_list = order_list.split(",")
for order in order_list:
@@ -2926,35 +2890,58 @@ def design_course(request, course_id):
learning_module.save()
to_add_list.append(learning_module)
course.learning_module.add(*to_add_list)
+ messages.success(request, "Modules added successfully")
+ else:
+ messages.warning(request, "Please select atleast one module")
if "Change" in request.POST:
- order_list = request.POST.get("ordered_list").split(",")
- for order in order_list:
- learning_unit, learning_order = order.split(":")
- if learning_order:
- learning_module = course.learning_module.get(
- id=learning_unit)
- learning_module.order = learning_order
- learning_module.save()
+ order_list = request.POST.get("ordered_list")
+ if order_list:
+ order_list = order_list.split(",")
+ for order in order_list:
+ learning_unit, learning_order = order.split(":")
+ if learning_order:
+ learning_module = course.learning_module.get(
+ id=learning_unit)
+ learning_module.order = learning_order
+ learning_module.save()
+ messages.success(request, "Changed order successfully")
+ else:
+ messages.warning(request, "Please select atleast one module")
if "Remove" in request.POST:
remove_values = request.POST.getlist("delete_list")
if remove_values:
course.learning_module.remove(*remove_values)
+ messages.success(request, "Modules removed successfully")
+ else:
+ messages.warning(request, "Please select atleast one module")
if "change_prerequisite_completion" in request.POST:
unit_list = request.POST.getlist("check_prereq")
- for unit in unit_list:
- learning_module = course.learning_module.get(id=unit)
- learning_module.toggle_check_prerequisite()
- learning_module.save()
+ if unit_list:
+ for unit in unit_list:
+ learning_module = course.learning_module.get(id=unit)
+ learning_module.toggle_check_prerequisite()
+ learning_module.save()
+ messages.success(
+ request, "Changed prerequisite check successfully"
+ )
+ else:
+ messages.warning(request, "Please select atleast one module")
if "change_prerequisite_passing" in request.POST:
unit_list = request.POST.getlist("check_prereq_passes")
- for unit in unit_list:
- learning_module = course.learning_module.get(id=unit)
- learning_module.toggle_check_prerequisite_passes()
- learning_module.save()
+ if unit_list:
+ for unit in unit_list:
+ learning_module = course.learning_module.get(id=unit)
+ learning_module.toggle_check_prerequisite_passes()
+ learning_module.save()
+ messages.success(
+ request, "Changed prerequisite check successfully"
+ )
+ else:
+ messages.warning(request, "Please select atleast one module")
added_learning_modules = course.get_learning_modules()
all_learning_modules = LearningModule.objects.filter(
@@ -3054,14 +3041,7 @@ def course_status(request, course_id):
students_no = students.count()
paginator = Paginator(students, 100)
page = request.GET.get('page')
- try:
- students = paginator.page(page)
- except PageNotAnInteger:
- # If page is not an integer, deliver first page.
- students = paginator.page(1)
- except EmptyPage:
- # If page is out of range (e.g. 9999), deliver last page of results.
- students = paginator.page(paginator.num_pages)
+ students = paginator.get_page(page)
stud_details = [(student, course.get_grade(student),
course.get_completion_percent(student),