diff options
author | adityacp | 2020-02-28 10:37:07 +0530 |
---|---|---|
committer | adityacp | 2020-02-28 10:37:07 +0530 |
commit | 78cc032340a8f0c8e0d456770783f2c894935c04 (patch) | |
tree | 3b6af04c555e86c99b7e8fa5a5442765402e9af3 | |
parent | 0d189e0156741685e6f6f1c605bf99b44d082b0b (diff) | |
download | online_test-78cc032340a8f0c8e0d456770783f2c894935c04.tar.gz online_test-78cc032340a8f0c8e0d456770783f2c894935c04.tar.bz2 online_test-78cc032340a8f0c8e0d456770783f2c894935c04.zip |
Bump django to the latest version
-rw-r--r-- | grades/models.py | 5 | ||||
-rw-r--r-- | online_test/settings.py | 2 | ||||
-rw-r--r-- | online_test/urls.py | 9 | ||||
-rw-r--r-- | requirements/requirements-common.txt | 6 | ||||
-rw-r--r-- | setup.py | 6 | ||||
-rw-r--r-- | yaksh/middleware/one_session_per_user.py | 7 | ||||
-rw-r--r-- | yaksh/middleware/user_time_zone.py | 6 | ||||
-rw-r--r-- | yaksh/models.py | 91 | ||||
-rw-r--r-- | yaksh/templates/yaksh/add_question.html | 2 | ||||
-rw-r--r-- | yaksh/templates/yaksh/showquestions.html | 2 | ||||
-rw-r--r-- | yaksh/templatetags/custom_filters.py | 4 | ||||
-rw-r--r-- | yaksh/views.py | 79 |
12 files changed, 104 insertions, 115 deletions
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/online_test/settings.py b/online_test/settings.py index 7129e83..7b0e2d4 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', 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-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,9 +16,9 @@ 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', 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 3894165..2ebfa38 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): @@ -607,10 +610,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) @@ -657,7 +662,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) @@ -834,7 +840,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') @@ -863,7 +870,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() @@ -1117,9 +1125,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) @@ -1131,7 +1140,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) @@ -1173,14 +1182,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) @@ -1258,7 +1267,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) @@ -1533,7 +1543,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) @@ -1567,7 +1578,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) @@ -1642,7 +1653,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) @@ -1928,10 +1939,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") @@ -1973,14 +1984,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: @@ -1998,7 +2010,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]) @@ -2010,15 +2023,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() @@ -2438,17 +2451,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) @@ -2585,10 +2601,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_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/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/views.py b/yaksh/views.py index 8ac8343..6d5ae08 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -114,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) @@ -402,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/') @@ -411,14 +411,7 @@ def prof_manage(request, msg=None): is_trial=False).distinct().order_by("-active") 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) messages.info(request, msg) context = {'user': user, 'objects': courses} return my_render_to_response( @@ -431,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') @@ -1048,14 +1041,7 @@ def courses(request): is_trial=False).order_by('-active').distinct() 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) context = {'objects': courses, 'created': True} return my_render_to_response(request, 'yaksh/courses.html', context) @@ -1240,12 +1226,7 @@ def monitor(request, quiz_id=None, course_id=None): ).order_by("-id").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" } @@ -1270,9 +1251,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: @@ -1317,14 +1299,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, @@ -1518,12 +1495,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 @@ -1709,12 +1681,7 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None, ).order_by("-id").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: @@ -1729,9 +1696,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, @@ -1751,7 +1717,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) @@ -1784,7 +1750,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() @@ -2621,7 +2588,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: @@ -2977,14 +2943,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), |