diff options
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 @@ -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"> 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), |