import os import csv from django.http import HttpResponse, JsonResponse, HttpResponseRedirect from django.contrib.auth import login, logout, authenticate from django.shortcuts import render, get_object_or_404, redirect from django.template import Context, Template, loader from django.http import Http404 from django.db.models import Max, Q, F from django.db import models from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.decorators import login_required from django.contrib.auth.models import Group from django.forms.models import inlineformset_factory from django.forms import fields from django.utils import timezone from django.core.exceptions import ( MultipleObjectsReturned, ObjectDoesNotExist ) from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.contrib import messages from taggit.models import Tag from django.urls import reverse import json from textwrap import dedent import zipfile import markdown try: from StringIO import StringIO as string_io except ImportError: from io import BytesIO as string_io import re # Local imports. from yaksh.code_server import get_result as get_result_from_code_server from yaksh.models import ( Answer, AnswerPaper, AssignmentUpload, Course, FileUpload, FloatTestCase, HookTestCase, IntegerTestCase, McqTestCase, Profile, QuestionPaper, QuestionSet, Quiz, Question, StandardTestCase, StdIOBasedTestCase, StringTestCase, TestCase, User, get_model_class, FIXTURES_DIR_PATH, MOD_GROUP_NAME, Lesson, LessonFile, LearningUnit, LearningModule, CourseStatus, question_types, Post, Comment, Topic, TableOfContents ) from yaksh.forms import ( UserRegisterForm, UserLoginForm, QuizForm, QuestionForm, QuestionFilterForm, CourseForm, ProfileForm, UploadFileForm, FileForm, QuestionPaperForm, LessonForm, LessonFileForm, LearningModuleForm, ExerciseForm, TestcaseForm, SearchFilterForm, PostForm, CommentForm, TopicForm, VideoQuizForm ) from yaksh.settings import SERVER_POOL_PORT, SERVER_HOST_NAME from .settings import URL_ROOT from .file_utils import extract_files, is_csv from .send_emails import (send_user_mail, generate_activation_key, send_bulk_mail) from .decorators import email_verified, has_profile from .tasks import regrade_papers from notifications_plugin.models import Notification def my_redirect(url): """An overridden redirect to deal with URL_ROOT-ing. See settings.py for details.""" return redirect(URL_ROOT + url) def my_render_to_response(request, template, context=None, **kwargs): """Overridden render_to_response. """ if context is None: context = {'URL_ROOT': URL_ROOT} else: context['URL_ROOT'] = URL_ROOT return render(request, template, context, **kwargs) def is_moderator(user, group_name=MOD_GROUP_NAME): """Check if the user is having moderator rights""" try: group = Group.objects.get(name=group_name) return user.profile.is_moderator and user in group.user_set.all() except Profile.DoesNotExist: return False except Group.DoesNotExist: return False def add_as_moderator(users, group_name=MOD_GROUP_NAME): """ add users to moderator group """ try: Group.objects.get(name=group_name) except Group.DoesNotExist: raise Http404('The Group {0} does not exist.'.format(group_name)) for user in users: if not is_moderator(user): user.profile.is_moderator = True user.profile.save() CSV_FIELDS = ['name', 'username', 'roll_number', 'institute', 'department', 'questions', 'marks_obtained', 'out_of', 'percentage', 'status'] def get_html_text(md_text): """Takes markdown text and converts it to html""" return markdown.markdown( md_text, extensions=['tables', 'fenced_code'] ) def formfield_callback(field): if (isinstance(field, models.TextField) and field.name == 'expected_input'): return fields.CharField(strip=False, required = False) if (isinstance(field, models.TextField) and field.name == 'expected_output'): return fields.CharField(strip=False) return field.formfield() @email_verified def index(request, next_url=None): """The start page. """ user = request.user 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) return my_redirect("/exam/login/") def user_register(request): """ Register a new user. Create a user and corresponding profile and store roll_number also.""" user = request.user if user.is_authenticated: return my_redirect("/exam/quizzes/") context = {} if request.method == "POST": form = UserRegisterForm(request.POST) if form.is_valid(): u_name, pwd, user_email, key = form.save() new_user = authenticate(username=u_name, password=pwd) login(request, new_user) if user_email and key: success, msg = send_user_mail(user_email, key) context = {'activation_msg': msg} return my_render_to_response( request, 'yaksh/activation_status.html', context ) return index(request) else: return my_render_to_response( request, 'yaksh/register.html', {'form': form} ) else: form = UserRegisterForm() return my_render_to_response( request, 'yaksh/register.html', {'form': form} ) def user_logout(request): """Show a page to inform user that the quiz has been compeleted.""" logout(request) context = {'message': "You have been logged out successfully"} return my_render_to_response(request, 'yaksh/complete.html', context) @login_required @has_profile @email_verified def quizlist_user(request, enrolled=None, msg=None): """Show All Quizzes that is available to logged-in user.""" user = request.user courses_data = [] if request.method == "POST": course_code = request.POST.get('course_code') hidden_courses = Course.objects.get_hidden_courses(code=course_code) courses = hidden_courses title = 'Search Results' else: enrolled_courses = user.students.filter(is_trial=False).order_by('-id') remaining_courses = list(Course.objects.filter( active=True, is_trial=False, hidden=False ).exclude( id__in=enrolled_courses.values_list("id", flat=True) ).order_by('-id')) courses = list(enrolled_courses) courses.extend(remaining_courses) title = 'All Courses' for course in courses: if course.students.filter(id=user.id).exists(): _percent = course.get_completion_percent(user) else: _percent = None courses_data.append( { 'data': course, 'completion_percentage': _percent, } ) messages.info(request, msg) context = { 'user': user, 'courses': courses_data, 'title': title } return my_render_to_response(request, "yaksh/quizzes_user.html", context) @login_required @email_verified def results_user(request): """Show list of Results of Quizzes that is taken by logged-in user.""" user = request.user papers = AnswerPaper.objects.get_user_answerpapers(user) context = {'papers': papers} return my_render_to_response(request, "yaksh/results_user.html", context) @login_required @email_verified def add_question(request, question_id=None): user = request.user test_case_type = None if question_id is not None: question = Question.objects.get(id=question_id) uploaded_files = FileUpload.objects.filter(question_id=question.id) else: question = None uploaded_files = [] if request.method == 'POST': qform = QuestionForm(request.POST, instance=question) fileform = FileForm(request.POST, request.FILES) remove_files_id = request.POST.getlist('clear') added_files = request.FILES.getlist('file_field') extract_files_id = request.POST.getlist('extract') hide_files_id = request.POST.getlist('hide') if remove_files_id: files = FileUpload.objects.filter(id__in=remove_files_id) for file in files: file.remove() if extract_files_id: files = FileUpload.objects.filter(id__in=extract_files_id) for file in files: file.set_extract_status() if hide_files_id: files = FileUpload.objects.filter(id__in=hide_files_id) for file in files: file.toggle_hide_status() formsets = [] for testcase in TestCase.__subclasses__(): formset = inlineformset_factory( Question, testcase, extra=0, fields='__all__', form=TestcaseForm, formfield_callback=formfield_callback ) formsets.append(formset( request.POST, request.FILES, instance=question ) ) if qform.is_valid(): question = qform.save(commit=False) question.user = user question.save() # many-to-many field save function used to save the tags if added_files: for file in added_files: FileUpload.objects.get_or_create( question=question, file=file ) qform.save_m2m() for formset in formsets: if formset.is_valid(): formset.save() test_case_type = request.POST.get('case_type', None) uploaded_files = FileUpload.objects.filter(question_id=question.id) messages.success(request, "Question saved successfully") else: context = { 'qform': qform, 'fileform': fileform, 'question': question, 'formsets': formsets, 'uploaded_files': uploaded_files } messages.warning(request, "Unable to save the question") return render(request, "yaksh/add_question.html", context) qform = QuestionForm(instance=question) fileform = FileForm() formsets = [] for testcase in TestCase.__subclasses__(): if test_case_type == testcase.__name__.lower(): formset = inlineformset_factory( Question, testcase, extra=1, fields='__all__', form=TestcaseForm ) else: formset = inlineformset_factory( Question, testcase, extra=0, fields='__all__', form=TestcaseForm ) formsets.append( formset( instance=question, initial=[{'type': test_case_type}] ) ) context = {'qform': qform, 'fileform': fileform, 'question': question, 'formsets': formsets, 'uploaded_files': uploaded_files} if question is not None: context["testcase_options"] = question.get_test_case_options() return render(request, "yaksh/add_question.html", context) @login_required @email_verified def add_quiz(request, course_id=None, module_id=None, quiz_id=None): """To add a new quiz in the database. Create a new quiz and store it.""" user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this course !') if quiz_id: quiz = get_object_or_404(Quiz, pk=quiz_id) if quiz.creator != user and not course_id: raise Http404('This quiz does not belong to you') else: quiz = None if module_id: module = get_object_or_404(LearningModule, id=module_id) if course_id: course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This quiz does not belong to you') context = {} if request.method == "POST": form = QuizForm(request.POST, instance=quiz) if form.is_valid(): if quiz is None: last_unit = module.get_learning_units().last() order = last_unit.order + 1 if last_unit else 1 form.instance.creator = user else: order = module.get_unit_order("quiz", quiz) added_quiz = form.save() unit, created = LearningUnit.objects.get_or_create( type="quiz", quiz=added_quiz, order=order ) if created: module.learning_unit.add(unit.id) messages.success(request, "Quiz saved successfully") return redirect( reverse("yaksh:edit_quiz", args=[course_id, module_id, added_quiz.id]) ) else: form = QuizForm(instance=quiz) context["course_id"] = course_id context["quiz"] = quiz context["form"] = form return my_render_to_response(request, 'yaksh/add_quiz.html', context) @login_required @email_verified def add_exercise(request, course_id=None, module_id=None, quiz_id=None): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this course !') if quiz_id: quiz = get_object_or_404(Quiz, pk=quiz_id) if quiz.creator != user and not course_id: raise Http404('This quiz does not belong to you') else: quiz = None if module_id: module = get_object_or_404(LearningModule, id=module_id) if course_id: course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This Course does not belong to you') context = {} if request.method == "POST": form = ExerciseForm(request.POST, instance=quiz) if form.is_valid(): if quiz is None: last_unit = module.get_learning_units().last() order = last_unit.order + 1 if last_unit else 1 form.instance.creator = user else: order = module.get_unit_order("quiz", quiz) quiz = form.save(commit=False) quiz.is_exercise = True quiz.time_between_attempts = 0 quiz.weightage = 0 quiz.allow_skip = False quiz.attempts_allowed = -1 quiz.duration = 1000 quiz.pass_criteria = 0 quiz.save() unit, created = LearningUnit.objects.get_or_create( type="quiz", quiz=quiz, order=order ) if created: module.learning_unit.add(unit.id) messages.success( request, "{0} saved successfully".format(quiz.description) ) return redirect( reverse("yaksh:edit_exercise", args=[course_id, module_id, quiz.id]) ) else: form = ExerciseForm(instance=quiz) context["exercise"] = quiz context["course_id"] = course_id context["form"] = form return my_render_to_response(request, 'yaksh/add_exercise.html', context) @login_required @has_profile @email_verified 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: return my_redirect('/exam/login') if not is_moderator(user): return my_redirect('/exam/') courses = Course.objects.get_queryset().filter( Q(creator=user) | Q(teachers=user), is_trial=False).distinct().order_by("-active") paginator = Paginator(courses, 20) page = request.GET.get('page') courses = paginator.get_page(page) messages.info(request, msg) context = {'user': user, 'objects': courses} return my_render_to_response( request, 'yaksh/moderator_dashboard.html', context ) def user_login(request): """Take the credentials of the user and log the user in.""" user = request.user context = {} if user.is_authenticated: return index(request) next_url = request.GET.get('next') if request.method == "POST": form = UserLoginForm(request.POST) if form.is_valid(): user = form.cleaned_data login(request, user) return index(request, next_url) else: context = {"form": form} else: form = UserLoginForm() context = {"form": form} return my_render_to_response(request, 'yaksh/login.html', context) @login_required @email_verified def start(request, questionpaper_id=None, attempt_num=None, course_id=None, module_id=None): """Check the user cedentials and if any quiz is available, start the exam.""" user = request.user # check conditions try: quest_paper = QuestionPaper.objects.get(id=questionpaper_id) except QuestionPaper.DoesNotExist: msg = 'Quiz not found, please contact your '\ 'instructor/administrator.' if is_moderator(user): return prof_manage(request, msg=msg) return view_module(request, module_id=module_id, course_id=course_id, msg=msg) if not quest_paper.has_questions(): msg = 'Quiz does not have Questions, please contact your '\ 'instructor/administrator.' if is_moderator(user): return prof_manage(request, msg=msg) return view_module(request, module_id=module_id, course_id=course_id, msg=msg) course = Course.objects.get(id=course_id) learning_module = course.learning_module.get(id=module_id) learning_unit = learning_module.learning_unit.get(quiz=quest_paper.quiz.id) # unit module active status if not learning_module.active: return view_module(request, module_id, course_id) # unit module prerequiste check if learning_module.has_prerequisite(): if not learning_module.is_prerequisite_complete(user, course): msg = "You have not completed the module previous to {0}".format( learning_module.name) return course_modules(request, course_id, msg) if learning_module.check_prerequisite_passes: if not learning_module.is_prerequisite_passed(user, course): msg = ( "You have not successfully passed the module" " previous to {0}".format(learning_module.name) ) return course_modules(request, course_id, msg) # is user enrolled in the course if not course.is_enrolled(user): msg = 'You are not enrolled in {0} course'.format(course.name) if is_moderator(user) and course.is_trial: return prof_manage(request, msg=msg) return quizlist_user(request, msg=msg) # if course is active and is not expired if not course.active or not course.is_active_enrollment(): msg = "{0} is either expired or not active".format(course.name) if is_moderator(user) and course.is_trial: return prof_manage(request, msg=msg) return quizlist_user(request, msg=msg) # is quiz is active and is not expired if quest_paper.quiz.is_expired() or not quest_paper.quiz.active: msg = "{0} is either expired or not active".format( quest_paper.quiz.description) if is_moderator(user) and course.is_trial: return prof_manage(request, msg=msg) return view_module(request, module_id=module_id, course_id=course_id, msg=msg) # prerequisite check and passing criteria for quiz if learning_unit.has_prerequisite(): if not learning_unit.is_prerequisite_complete( user, learning_module, course): msg = "You have not completed the previous Lesson/Quiz/Exercise" if is_moderator(user) and course.is_trial: return prof_manage(request, msg=msg) return view_module(request, module_id=module_id, course_id=course_id, msg=msg) # update course status with current unit _update_unit_status(course_id, user, learning_unit) # if any previous attempt last_attempt = AnswerPaper.objects.get_user_last_attempt( quest_paper, user, course_id) if last_attempt: if last_attempt.is_attempt_inprogress(): return show_question( request, last_attempt.current_question(), last_attempt, course_id=course_id, module_id=module_id, previous_question=last_attempt.current_question() ) attempt_number = last_attempt.attempt_number + 1 else: attempt_number = 1 # allowed to start if not quest_paper.can_attempt_now(user, course_id)[0]: msg = quest_paper.can_attempt_now(user, course_id)[1] if is_moderator(user): return prof_manage(request, msg=msg) return complete( request, msg, last_attempt.attempt_number, quest_paper.id, course_id=course_id, module_id=module_id ) if attempt_num is None and not quest_paper.quiz.is_exercise: context = { 'user': user, 'questionpaper': quest_paper, 'attempt_num': attempt_number, 'course': course, 'module': learning_module, } if is_moderator(user): context["status"] = "moderator" return my_render_to_response(request, 'yaksh/intro.html', context) else: ip = request.META['REMOTE_ADDR'] if not hasattr(user, 'profile'): msg = 'You do not have a profile and cannot take the quiz!' raise Http404(msg) new_paper = quest_paper.make_answerpaper(user, ip, attempt_number, course_id) if new_paper.status == 'inprogress': return show_question( request, new_paper.current_question(), new_paper, course_id=course_id, module_id=module_id, previous_question=None ) else: msg = 'You have already finished the quiz!' raise Http404(msg) @login_required @email_verified def show_question(request, question, paper, error_message=None, notification=None, course_id=None, module_id=None, previous_question=None): """Show a question if possible.""" quiz = paper.question_paper.quiz quiz_type = 'Exam' can_skip = False if previous_question: delay_time = paper.time_left_on_question(previous_question) else: delay_time = paper.time_left_on_question(question) if previous_question and quiz.is_exercise: if (delay_time <= 0 or previous_question in paper.questions_answered.all()): can_skip = True question = previous_question if not question: msg = 'Congratulations! You have successfully completed the quiz.' return complete( request, msg, paper.attempt_number, paper.question_paper.id, course_id=course_id, module_id=module_id ) if not quiz.active: reason = 'The quiz has been deactivated!' return complete( request, reason, paper.attempt_number, paper.question_paper.id, course_id=course_id, module_id=module_id ) if not quiz.is_exercise: if paper.time_left() <= 0: reason = 'Your time is up!' return complete( request, reason, paper.attempt_number, paper.question_paper.id, course_id, module_id=module_id ) else: quiz_type = 'Exercise' if question in paper.questions_answered.all(): notification = ( 'You have already attempted this question successfully' if question.type == "code" else 'You have already attempted this question' ) if question.type in ['mcc', 'mcq', 'arrange']: test_cases = question.get_ordered_test_cases(paper) else: test_cases = question.get_test_cases() files = FileUpload.objects.filter(question_id=question.id, hide=False) course = Course.objects.get(id=course_id) module = course.learning_module.get(id=module_id) all_modules = course.get_learning_modules() context = { 'question': question, 'paper': paper, 'quiz': quiz, 'error_message': error_message, 'test_cases': test_cases, 'files': files, 'notification': notification, 'last_attempt': question.snippet.encode('unicode-escape'), 'course': course, 'module': module, 'can_skip': can_skip, 'delay_time': delay_time, 'quiz_type': quiz_type, 'all_modules': all_modules, } answers = paper.get_previous_answers(question) if answers: last_attempt = answers[0].answer if last_attempt: context['last_attempt'] = last_attempt.encode('unicode-escape') return my_render_to_response(request, 'yaksh/question.html', context) @login_required @email_verified def skip(request, q_id, next_q=None, attempt_num=None, questionpaper_id=None, course_id=None, module_id=None): paper = get_object_or_404( AnswerPaper, user=request.user, attempt_number=attempt_num, question_paper=questionpaper_id, course_id=course_id ) question = get_object_or_404(Question, pk=q_id) if paper.question_paper.quiz.is_exercise: paper.start_time = timezone.now() paper.save() if request.method == 'POST' and question.type == 'code': if not paper.answers.filter(question=question, correct=True).exists(): user_code = request.POST.get('answer') new_answer = Answer( question=question, answer=user_code, correct=False, skipped=True, error=json.dumps([]) ) new_answer.save() paper.answers.add(new_answer) if next_q is not None: next_q = get_object_or_404(Question, pk=next_q) else: next_q = paper.next_question(q_id) return show_question(request, next_q, paper, course_id=course_id, module_id=module_id) @login_required @email_verified def check(request, q_id, attempt_num=None, questionpaper_id=None, course_id=None, module_id=None): """Checks the answers of the user for particular question""" user = request.user paper = get_object_or_404( AnswerPaper, user=request.user, attempt_number=attempt_num, question_paper=questionpaper_id, course_id=course_id ) current_question = get_object_or_404(Question, pk=q_id) if request.method == 'POST': # Add the answer submitted, regardless of it being correct or not. if (paper.time_left() <= -10 or paper.status == "completed"): reason = 'Your time is up!' return complete( request, reason, paper.attempt_number, paper.question_paper.id, course_id, module_id=module_id ) if current_question.type == 'mcq': user_answer = request.POST.get('answer') elif current_question.type == 'integer': try: user_answer = int(request.POST.get('answer')) except ValueError: msg = "Please enter an Integer Value" return show_question( request, current_question, paper, notification=msg, course_id=course_id, module_id=module_id, previous_question=current_question ) elif current_question.type == 'float': try: user_answer = float(request.POST.get('answer')) except ValueError: msg = "Please enter a Float Value" return show_question(request, current_question, paper, notification=msg, course_id=course_id, module_id=module_id, previous_question=current_question) elif current_question.type == 'string': user_answer = str(request.POST.get('answer')) elif current_question.type == 'mcc': user_answer = request.POST.getlist('answer') elif current_question.type == 'arrange': user_answer_ids = request.POST.get('answer').split(',') user_answer = [int(ids) for ids in user_answer_ids] elif current_question.type == 'upload': # if time-up at upload question then the form is submitted without # validation assignment_filename = request.FILES.getlist('assignment') if not assignment_filename: msg = "Please upload assignment file" return show_question( request, current_question, paper, notification=msg, course_id=course_id, module_id=module_id, previous_question=current_question ) for fname in assignment_filename: fname._name = fname._name.replace(" ", "_") assignment_files = AssignmentUpload.objects.filter( assignmentQuestion=current_question, course_id=course_id, assignmentFile__icontains=fname, user=user, question_paper=questionpaper_id) if assignment_files.exists(): assign_file = assignment_files.first() if os.path.exists(assign_file.assignmentFile.path): os.remove(assign_file.assignmentFile.path) assign_file.delete() AssignmentUpload.objects.create( user=user, assignmentQuestion=current_question, course_id=course_id, assignmentFile=fname, question_paper_id=questionpaper_id ) user_answer = 'ASSIGNMENT UPLOADED' if not current_question.grade_assignment_upload: new_answer = Answer( question=current_question, answer=user_answer, correct=False, error=json.dumps([]) ) new_answer.save() paper.answers.add(new_answer) next_q = paper.add_completed_question(current_question.id) return show_question(request, next_q, paper, course_id=course_id, module_id=module_id, previous_question=current_question) else: user_answer = request.POST.get('answer') if not user_answer: msg = "Please submit a valid answer." return show_question( request, current_question, paper, notification=msg, course_id=course_id, module_id=module_id, previous_question=current_question ) if current_question in paper.get_questions_answered()\ and current_question.type not in ['code', 'upload']: new_answer = paper.get_latest_answer(current_question.id) new_answer.answer = user_answer new_answer.correct = False else: new_answer = Answer( question=current_question, answer=user_answer, correct=False, error=json.dumps([]) ) new_answer.save() uid = new_answer.id paper.answers.add(new_answer) # If we were not skipped, we were asked to check. For any non-mcq # questions, we obtain the results via XML-RPC with the code executed # safely in a separate process (the code_server.py) running as nobody. json_data = current_question.consolidate_answer_data( user_answer, user) if current_question.type == 'code' or \ current_question.type == 'upload' else None result = paper.validate_answer( user_answer, current_question, json_data, uid ) if current_question.type in ['code', 'upload']: if (paper.time_left() <= 0 and not paper.question_paper.quiz.is_exercise): url = '{0}:{1}'.format(SERVER_HOST_NAME, SERVER_POOL_PORT) result_details = get_result_from_code_server(url, uid, block=True) result = json.loads(result_details.get('result')) next_question, error_message, paper = _update_paper( request, uid, result) return show_question(request, next_question, paper, error_message, course_id=course_id, module_id=module_id, previous_question=current_question) else: return JsonResponse(result) else: next_question, error_message, paper = _update_paper( request, uid, result) return show_question(request, next_question, paper, error_message, course_id=course_id, module_id=module_id, previous_question=current_question) else: return show_question(request, current_question, paper, course_id=course_id, module_id=module_id, previous_question=current_question) @csrf_exempt def get_result(request, uid, course_id, module_id): result = {} url = '{0}:{1}'.format(SERVER_HOST_NAME, SERVER_POOL_PORT) result_state = get_result_from_code_server(url, uid) result['status'] = result_state.get('status') if result['status'] == 'done': result = json.loads(result_state.get('result')) template_path = os.path.join(*[os.path.dirname(__file__), 'templates', 'yaksh', 'error_template.html' ] ) next_question, error_message, paper = _update_paper(request, uid, result ) answer = Answer.objects.get(id=uid) current_question = answer.question if result.get('success'): return show_question(request, next_question, paper, error_message, course_id=course_id, module_id=module_id, previous_question=current_question) else: with open(template_path) as f: template_data = f.read() template = Template(template_data) context = Context({"error_message": result.get('error')}) render_error = template.render(context) result["error"] = render_error return JsonResponse(result) def _update_paper(request, uid, result): new_answer = Answer.objects.get(id=uid) current_question = new_answer.question paper = new_answer.answerpaper_set.first() if result.get('success'): new_answer.marks = (current_question.points * result['weight'] / current_question.get_maximum_test_case_weight()) \ if current_question.partial_grading and \ current_question.type == 'code' or \ current_question.type == 'upload' else current_question.points new_answer.correct = result.get('success') error_message = None new_answer.error = json.dumps(result.get('error')) next_question = paper.add_completed_question(current_question.id) else: new_answer.marks = (current_question.points * result['weight'] / current_question.get_maximum_test_case_weight()) \ if current_question.partial_grading and \ current_question.type == 'code' or \ current_question.type == 'upload' \ else 0 error_message = result.get('error') \ if current_question.type == 'code' or \ current_question.type == 'upload' \ else None new_answer.error = json.dumps(result.get('error')) next_question = current_question if current_question.type == 'code' \ or current_question.type == 'upload' \ else paper.add_completed_question(current_question.id) new_answer.save() paper.update_marks('inprogress') paper.set_end_time(timezone.now()) return next_question, error_message, paper @login_required @email_verified def quit(request, reason=None, attempt_num=None, questionpaper_id=None, course_id=None, module_id=None): """Show the quit page when the user logs out.""" paper = AnswerPaper.objects.get(user=request.user, attempt_number=attempt_num, question_paper=questionpaper_id, course_id=course_id) context = {'paper': paper, 'message': reason, 'course_id': course_id, 'module_id': module_id} return my_render_to_response(request, 'yaksh/quit.html', context) @login_required @email_verified def complete(request, reason=None, attempt_num=None, questionpaper_id=None, course_id=None, module_id=None): """Show a page to inform user that the quiz has been completed.""" user = request.user if questionpaper_id is None: message = reason or ("An Unexpected Error occurred. Please " "contact your instructor/administrator." ) context = {'message': message} return my_render_to_response(request, 'yaksh/complete.html', context) else: q_paper = QuestionPaper.objects.get(id=questionpaper_id) paper = AnswerPaper.objects.get( user=user, question_paper=q_paper, attempt_number=attempt_num, course_id=course_id ) course = Course.objects.get(id=course_id) learning_module = course.learning_module.get(id=module_id) learning_unit = learning_module.learning_unit.get(quiz=q_paper.quiz) paper.update_marks() paper.set_end_time(timezone.now()) message = reason or "Quiz has been submitted" context = {'message': message, 'paper': paper, 'module_id': learning_module.id, 'course_id': course_id, 'learning_unit': learning_unit} if is_moderator(user): context['user'] = "moderator" return my_render_to_response(request, 'yaksh/complete.html', context) @login_required @email_verified def add_course(request, course_id=None): user = request.user if course_id: course = Course.objects.get(id=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404("You are not allowed to view this course") else: course = None if not is_moderator(user): raise Http404('You are not allowed to view this page') if request.method == 'POST': form = CourseForm(user, request.POST, instance=course) if form.is_valid(): new_course = form.save(commit=False) if course_id is None: new_course.creator = user new_course.save() messages.success( request, "Saved {0} successfully".format(new_course.name) ) return my_redirect('/exam/manage/courses') else: return my_render_to_response( request, 'yaksh/add_course.html', {'form': form} ) else: form = CourseForm(user, instance=course) return my_render_to_response( request, 'yaksh/add_course.html', {'form': form} ) @login_required @email_verified def enroll_request(request, course_id): user = request.user course = get_object_or_404(Course, pk=course_id) if not course.is_active_enrollment() and course.hidden: msg = ( 'Unable to add enrollments for this course, please contact your ' 'instructor/administrator.' ) messages.warning(request, msg) else: course.request(user) messages.success( request, "Enrollment request sent for {0} by {1}".format( course.name, course.creator.get_full_name() ) ) if is_moderator(user): return my_redirect('/exam/manage/courses') else: return my_redirect('/exam/quizzes/') @login_required @email_verified def self_enroll(request, course_id): user = request.user course = get_object_or_404(Course, pk=course_id) if course.is_self_enroll(): was_rejected = False course.enroll(was_rejected, user) messages.success( request, "Enrolled in {0} by {1}".format( course.name, course.creator.get_full_name() ) ) if is_moderator(user): return my_redirect('/exam/manage/') else: return my_redirect('/exam/quizzes/') @login_required @email_verified def courses(request): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page') courses = Course.objects.filter( Q(creator=user) | Q(teachers=user), is_trial=False).order_by('-active').distinct() tags = request.GET.get('search_tags') status = request.GET.get('search_status') form = SearchFilterForm(tags=tags, status=status) if status == 'select' and tags: courses = courses.filter( name__icontains=tags) elif status == 'active': courses = courses.filter( name__icontains=tags, active=True) elif status == 'closed': courses = courses.filter( name__icontains=tags, active=False) paginator = Paginator(courses, 30) page = request.GET.get('page') courses = paginator.get_page(page) courses_found = courses.object_list.count() context = {'objects': courses, 'created': True, 'form': form, 'courses_found': courses_found} return my_render_to_response(request, 'yaksh/courses.html', context) @login_required @email_verified def course_detail(request, course_id): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page') course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This course does not belong to you') return my_render_to_response( request, 'yaksh/course_detail.html', {'course': course} ) @login_required @email_verified def enroll_user(request, course_id, user_id=None, was_rejected=False): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page') course = get_object_or_404(Course, id=course_id) if not course.is_active_enrollment(): msg = ( 'Enrollment for this course has been closed,' ' please contact your ' 'instructor/administrator.' ) messages.warning(request, msg) return redirect('yaksh:course_students', course_id=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This course does not belong to you') user = User.objects.get(id=user_id) course.enroll(was_rejected, user) messages.success(request, 'Enrolled student successfully') return redirect('yaksh:course_students', course_id=course_id) @login_required @email_verified def reject_user(request, course_id, user_id=None, was_enrolled=False): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page') course = get_object_or_404(Course, id=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This course does not belong to you') user = User.objects.get(id=user_id) course.reject(was_enrolled, user) messages.success(request, "Rejected students successfully") return redirect('yaksh:course_students', course_id=course_id) @login_required @email_verified def enroll_reject_user(request, course_id, was_enrolled=False, was_rejected=False): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page') course = get_object_or_404(Course, id=course_id) if not course.is_active_enrollment(): msg = ( 'Enrollment for this course has been closed,' ' please contact your ' 'instructor/administrator.' ) messages.warning(request, msg) return redirect('yaksh:course_students', course_id=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This course does not belong to you') if request.method == 'POST': if 'enroll' in request.POST: enroll_ids = request.POST.getlist('check') if not enroll_ids: messages.warning(request, "Please select atleast one student") return redirect('yaksh:course_students', course_id=course_id) users = User.objects.filter(id__in=enroll_ids) course.enroll(was_rejected, *users) messages.success(request, "Enrolled student(s) successfully") return redirect('yaksh:course_students', course_id=course_id) if 'reject' in request.POST: reject_ids = request.POST.getlist('check') if not reject_ids: messages.warning(request, "Please select atleast one student") return redirect('yaksh:course_students', course_id=course_id) users = User.objects.filter(id__in=reject_ids) course.reject(was_enrolled, *users) messages.success(request, "Rejected students successfully") return redirect('yaksh:course_students', course_id=course_id) return redirect('yaksh:course_students', course_id=course_id) @login_required @email_verified def send_mail(request, course_id, user_id=None): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page') course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This course does not belong to you') message = None if request.method == 'POST': user_ids = request.POST.getlist('check') if request.POST.get('send_mail') == 'send_mail': users = User.objects.filter(id__in=user_ids) recipients = [student.email for student in users] email_body = request.POST.get('body') subject = request.POST.get('subject') attachments = request.FILES.getlist('email_attach') message = send_bulk_mail( subject, email_body, recipients, attachments ) messages.info(request, message) context = { 'course': course, 'message': message, 'enrolled': course.get_enrolled(), 'is_mail': True } return my_render_to_response(request, 'yaksh/course_detail.html', context) @login_required @email_verified def toggle_course_status(request, course_id): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page') course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This course does not belong to you') if course.active: course.deactivate() message = '{0} deactivated successfully'.format(course.name) else: course.activate() message = '{0} activated successfully'.format(course.name) course.save() messages.info(request, message) return my_redirect("/exam/manage/courses") @login_required @email_verified def show_statistics(request, questionpaper_id, attempt_number=None, course_id=None): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page') attempt_numbers = AnswerPaper.objects.get_attempt_numbers( questionpaper_id, course_id) quiz = get_object_or_404(QuestionPaper, pk=questionpaper_id).quiz if attempt_number is None: context = {'quiz': quiz, 'attempts': attempt_numbers, 'questionpaper_id': questionpaper_id, 'course_id': course_id} return my_render_to_response( request, 'yaksh/statistics_question.html', context ) total_attempt = AnswerPaper.objects.get_count(questionpaper_id, attempt_number, course_id) if not AnswerPaper.objects.has_attempt(questionpaper_id, attempt_number, course_id): return my_redirect('/exam/manage/') question_stats = AnswerPaper.objects.get_question_statistics( questionpaper_id, attempt_number, course_id ) context = {'question_stats': question_stats, 'quiz': quiz, 'questionpaper_id': questionpaper_id, 'attempts': attempt_numbers, 'total': total_attempt, 'course_id': course_id} return my_render_to_response( request, 'yaksh/statistics_question.html', context ) @login_required @email_verified def monitor(request, quiz_id=None, course_id=None): """Monitor the progress of the papers taken so far.""" user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') # quiz_id is not None. try: quiz = get_object_or_404(Quiz, id=quiz_id) course = get_object_or_404(Course, id=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This course does not belong to you') q_paper = QuestionPaper.objects.filter(quiz__is_trial=False, quiz_id=quiz_id).distinct() except (QuestionPaper.DoesNotExist, Course.DoesNotExist): papers = [] q_paper = None latest_attempts = [] attempt_numbers = [] else: if q_paper: attempt_numbers = AnswerPaper.objects.get_attempt_numbers( q_paper.last().id, course.id) else: attempt_numbers = [] latest_attempts = [] 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: last_attempt = papers.filter(user__in=auser).aggregate( last_attempt_num=Max('attempt_number') ) latest_attempts.append( papers.get( user__in=auser, attempt_number=last_attempt['last_attempt_num'] ) ) csv_fields = CSV_FIELDS context = { "papers": papers, "quiz": quiz, "msg": "Quiz Results", "latest_attempts": latest_attempts, "csv_fields": csv_fields, "attempt_numbers": attempt_numbers, "course": course } return my_render_to_response(request, 'yaksh/monitor.html', context) def _get_questions(user, question_type, marks): if question_type is None and marks is None: return None if question_type: questions = Question.objects.filter( type=question_type, user=user, active=True ) if marks: questions = questions.filter(points=marks) return questions def _remove_already_present(questionpaper_id, questions): if questionpaper_id is None: return questions questionpaper = QuestionPaper.objects.get(pk=questionpaper_id) questions = questions.exclude( id__in=questionpaper.fixed_questions.values_list('id', flat=True)) for random_set in questionpaper.random_questions.all(): questions = questions.exclude( id__in=random_set.questions.values_list('id', flat=True)) return questions def _get_questions_from_tags(question_tags, user, active=True, questions=None): search_tags = [] for tags in question_tags: search_tags.extend(re.split('[; |, |\*|\n]', tags)) if questions: search = questions.filter(tags__name__in=search_tags) else: search = Question.objects.get_queryset().filter( tags__name__in=search_tags, user=user, active=active).distinct() return search @login_required @email_verified def design_questionpaper(request, course_id, quiz_id, questionpaper_id=None): user = request.user que_tags = Question.objects.filter( active=True, user=user).values_list('tags', flat=True).distinct() all_tags = Tag.objects.filter(id__in=que_tags) if not is_moderator(user): raise Http404('You are not allowed to view this page!') if quiz_id: quiz = get_object_or_404(Quiz, pk=quiz_id) if quiz.creator != user and not course_id: raise Http404('This quiz does not belong to you') if course_id: course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This Course does not belong to you') filter_form = QuestionFilterForm(user=user) questions = None marks = None state = None if questionpaper_id is None: question_paper = QuestionPaper.objects.get_or_create( quiz_id=quiz_id)[0] else: question_paper = get_object_or_404(QuestionPaper, id=questionpaper_id, quiz_id=quiz_id) qpaper_form = QuestionPaperForm(instance=question_paper) if request.method == 'POST': filter_form = QuestionFilterForm(request.POST, user=user) qpaper_form = QuestionPaperForm(request.POST, instance=question_paper) question_type = request.POST.get('question_type', None) marks = request.POST.get('marks', None) state = request.POST.get('is_active', None) tags = request.POST.get('question_tags', None) if 'add-fixed' in request.POST: question_ids = request.POST.get('checked_ques', None) if question_ids: if question_paper.fixed_question_order: ques_order = ( question_paper.fixed_question_order.split(",") + question_ids.split(",") ) questions_order = ",".join(ques_order) else: questions_order = question_ids questions = Question.objects.filter( id__in=question_ids.split(',') ) question_paper.fixed_question_order = questions_order question_paper.save() question_paper.fixed_questions.add(*questions) messages.success(request, "Questions added successfully") else: messages.warning(request, "Please select atleast one question") if 'remove-fixed' in request.POST: question_ids = request.POST.getlist('added-questions', None) if question_ids: if question_paper.fixed_question_order: que_order = question_paper.fixed_question_order.split(",") for qid in question_ids: que_order.remove(qid) if que_order: question_paper.fixed_question_order = ",".join( que_order) else: question_paper.fixed_question_order = "" question_paper.save() question_paper.fixed_questions.remove(*question_ids) messages.success(request, "Questions removed successfully") else: messages.warning(request, "Please select atleast one question") if 'add-random' in request.POST: question_ids = request.POST.getlist('random_questions', None) num_of_questions = request.POST.get('num_of_questions', 1) if question_ids and marks: random_set = QuestionSet(marks=marks, num_questions=num_of_questions) random_set.save() random_ques = Question.objects.filter(id__in=question_ids) random_set.questions.add(*random_ques) question_paper.random_questions.add(random_set) messages.success(request, "Questions removed successfully") else: messages.warning(request, "Please select atleast one question") if 'remove-random' in request.POST: random_set_ids = request.POST.getlist('random_sets', None) if random_set_ids: question_paper.random_questions.remove(*random_set_ids) messages.success(request, "Questions removed successfully") else: messages.warning(request, "Please select question set") if 'save' in request.POST or 'back' in request.POST: qpaper_form.save() messages.success(request, "Question Paper saved successfully") if marks: questions = _get_questions(user, question_type, marks) elif tags: que_tags = request.POST.getlist('question_tags', None) questions = _get_questions_from_tags(que_tags, user) if questions: questions = _remove_already_present(questionpaper_id, questions) question_paper.update_total_marks() question_paper.save() random_sets = question_paper.random_questions.all() fixed_questions = question_paper.get_ordered_questions() context = { 'qpaper_form': qpaper_form, 'filter_form': filter_form, 'qpaper': question_paper, 'questions': questions, 'fixed_questions': fixed_questions, 'state': state, 'random_sets': random_sets, 'course_id': course_id, 'all_tags': all_tags } return my_render_to_response( request, 'yaksh/design_questionpaper.html', context ) @login_required @email_verified def show_all_questions(request): """Show a list of all the questions currently in the database.""" user = request.user context = {} message = None if not is_moderator(user): raise Http404("You are not allowed to view this page !") if request.method == 'POST': if request.POST.get('delete') == 'delete': data = request.POST.getlist('question') if data is not None: questions = Question.objects.filter( id__in=data, user_id=user.id, active=True) for question in questions: question.active = False question.save() message = "Questions deleted successfully" if request.POST.get('upload') == 'upload': form = UploadFileForm(request.POST, request.FILES) if form.is_valid(): questions_file = request.FILES['file'] file_extension = questions_file.name.split('.')[-1] ques = Question() if file_extension == "zip": files, extract_path = extract_files(questions_file) message = ques.read_yaml(extract_path, user, files) elif file_extension in ["yaml", "yml"]: questions = questions_file.read() message = ques.load_questions(questions, user) else: message = "Please Upload a ZIP file or YAML file" if request.POST.get('download') == 'download': question_ids = request.POST.getlist('question') if question_ids: question = Question() zip_file = question.dump_questions(question_ids, user) response = HttpResponse(content_type='application/zip') response['Content-Disposition'] = dedent( '''attachment; filename={0}_questions.zip'''.format(user) ) zip_file.seek(0) response.write(zip_file.read()) return response else: message = "Please select atleast one question to download" if request.POST.get('test') == 'test': question_ids = request.POST.getlist("question") if question_ids: trial_paper, trial_course, trial_module = test_mode( user, False, question_ids, None) trial_paper.update_total_marks() trial_paper.save() return my_redirect( reverse("yaksh:start_quiz", args=[1, trial_module.id, trial_paper.id, trial_course.id] ) ) else: message = "Please select atleast one question to test" questions = Question.objects.get_queryset().filter( user_id=user.id, active=True).order_by('-id') form = QuestionFilterForm(user=user) user_tags = questions.values_list('tags', flat=True).distinct() all_tags = Tag.objects.filter(id__in=user_tags) upload_form = UploadFileForm() paginator = Paginator(questions, 30) page = request.GET.get('page') questions = paginator.get_page(page) context['objects'] = questions context['all_tags'] = all_tags context['form'] = form context['upload_form'] = upload_form messages.info(request, message) return my_render_to_response(request, 'yaksh/showquestions.html', context) @login_required @email_verified def questions_filter(request): """Filter questions by type, language or marks.""" user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') questions = Question.objects.get_queryset().filter( user_id=user.id, active=True).order_by('-id') user_tags = questions.values_list('tags', flat=True).distinct() all_tags = Tag.objects.filter(id__in=user_tags) upload_form = UploadFileForm() filter_dict = {} question_type = request.GET.get('question_type') marks = request.GET.get('marks') language = request.GET.get('language') form = QuestionFilterForm( user=user, language=language, marks=marks, type=question_type ) if question_type: filter_dict['type'] = str(question_type) if marks: filter_dict['points'] = marks if language: filter_dict['language'] = str(language) questions = questions.filter(**filter_dict).order_by('-id') paginator = Paginator(questions, 30) page = request.GET.get('page') questions = paginator.get_page(page) context = {'form': form, 'upload_form': upload_form, 'all_tags': all_tags, 'objects': questions} return my_render_to_response( request, 'yaksh/showquestions.html', context ) @login_required @email_verified def delete_question(request, question_id): user = request.user if not is_moderator(user): raise Http404("You are not allowed to view this page !") question = get_object_or_404(Question, pk=question_id) question.active = False question.save() messages.success(request, "Deleted Question Successfully") return my_redirect(reverse("yaksh:show_questions")) @login_required @email_verified def download_question(request, question_id): user = request.user if not is_moderator(user): raise Http404("You are not allowed to view this page !") question = Question() zip_file = question.dump_questions([question_id], user) response = HttpResponse(content_type='application/zip') response['Content-Disposition'] = dedent( '''attachment; filename={0}_question.zip'''.format(user) ) zip_file.seek(0) response.write(zip_file.read()) return response @login_required @email_verified def test_question(request, question_id): user = request.user if not is_moderator(user): raise Http404("You are not allowed to view this page !") trial_paper, trial_course, trial_module = test_mode( user, False, [question_id], None) trial_paper.update_total_marks() trial_paper.save() return my_redirect( reverse("yaksh:start_quiz", args=[1, trial_module.id, trial_paper.id, trial_course.id] ) ) @login_required @email_verified def search_questions_by_tags(request): user = request.user if not is_moderator(user): raise Http404("You are not allowed to view this page !") questions = Question.objects.get_queryset().filter( user_id=user.id, active=True).order_by('-id') form = QuestionFilterForm(user=user) user_tags = questions.values_list('tags', flat=True).distinct() all_tags = Tag.objects.filter(id__in=user_tags) form = QuestionFilterForm(user=user) upload_form = UploadFileForm() question_tags = request.GET.getlist("question_tags") questions = _get_questions_from_tags( question_tags, user, questions=questions ) paginator = Paginator(questions, 30) page = request.GET.get('page') questions = paginator.get_page(page) context = {'form': form, 'upload_form': upload_form, 'all_tags': all_tags, 'objects': questions} return my_render_to_response(request, 'yaksh/showquestions.html', context) @login_required @email_verified def user_data(request, user_id, questionpaper_id=None, course_id=None): """Render user data.""" current_user = request.user if not is_moderator(current_user): raise Http404('You are not allowed to view this page!') user = User.objects.get(id=user_id) data = AnswerPaper.objects.get_user_data(user, questionpaper_id, course_id) context = {'data': data, 'course_id': course_id} return my_render_to_response(request, 'yaksh/user_data.html', context) def _expand_questions(questions, field_list): i = field_list.index('questions') field_list.remove('questions') for question in questions: field_list.insert( i, '{0}-{1}'.format(question.summary, question.points)) return field_list @login_required @email_verified def download_quiz_csv(request, course_id, quiz_id): current_user = request.user if not is_moderator(current_user): raise Http404('You are not allowed to view this page!') course = get_object_or_404(Course, id=course_id) quiz = get_object_or_404(Quiz, id=quiz_id) if not course.is_creator(current_user) and \ not course.is_teacher(current_user): raise Http404('The quiz does not belong to your course') users = course.get_enrolled().order_by('first_name') if not users: return monitor(request, quiz_id) csv_fields = [] attempt_number = None question_paper = quiz.questionpaper_set.last() last_attempt_number = AnswerPaper.objects.get_attempt_numbers( question_paper.id, course.id).last() if request.method == 'POST': csv_fields = request.POST.getlist('csv_fields') attempt_number = request.POST.get('attempt_number', last_attempt_number) if not csv_fields: csv_fields = CSV_FIELDS if not attempt_number: attempt_number = last_attempt_number questions = question_paper.get_question_bank() answerpapers = AnswerPaper.objects.filter( question_paper=question_paper, attempt_number=attempt_number, course_id=course_id) if not answerpapers: return monitor(request, quiz_id, course_id) response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = \ 'attachment; filename="{0}-{1}-attempt{2}.csv"'.format( course.name.replace('.', ''), quiz.description.replace('.', ''), attempt_number) writer = csv.writer(response) if 'questions' in csv_fields: csv_fields = _expand_questions(questions, csv_fields) writer.writerow(csv_fields) csv_fields_values = { 'name': 'user.get_full_name().title()', 'roll_number': 'user.profile.roll_number', 'institute': 'user.profile.institute', 'department': 'user.profile.department', 'username': 'user.username', 'marks_obtained': 'answerpaper.marks_obtained', 'out_of': 'question_paper.total_marks', 'percentage': 'answerpaper.percent', 'status': 'answerpaper.status'} questions_scores = {} for question in questions: questions_scores['{0}-{1}'.format(question.summary, question.points)] \ = 'answerpaper.get_per_question_score({0})'.format(question.id) csv_fields_values.update(questions_scores) users = users.exclude(id=course.creator.id).exclude( id__in=course.teachers.all()) for user in users: row = [] answerpaper = None papers = answerpapers.filter(user=user) if papers: answerpaper = papers.first() for field in csv_fields: try: row.append(eval(csv_fields_values[field])) except AttributeError: row.append('-') writer.writerow(row) return response @login_required @email_verified def grade_user(request, quiz_id=None, user_id=None, attempt_number=None, course_id=None, extra_context=None): """Present an interface with which we can easily grade a user's papers and update all their marks and also give comments for each paper. """ current_user = request.user if not is_moderator(current_user): raise Http404('You are not allowed to view this page!') if not course_id: courses = Course.objects.filter( Q(creator=current_user) | Q(teachers=current_user), is_trial=False ).order_by("-active").distinct() paginator = Paginator(courses, 30) page = request.GET.get('page') courses = paginator.get_page(page) context = {"objects": courses, "msg": "grade"} if quiz_id is not None: questionpaper_id = QuestionPaper.objects.filter( quiz_id=quiz_id ).values("id") user_details = AnswerPaper.objects.get_users_for_questionpaper( questionpaper_id, course_id ) quiz = get_object_or_404(Quiz, id=quiz_id) course = get_object_or_404(Course, id=course_id) 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__in=questionpaper_id ).exists() context = { "users": user_details, "quiz_id": quiz_id, "quiz": quiz, "has_quiz_assignments": has_quiz_assignments, "course_id": course_id, "status": "grade" } if user_id is not None: attempts = AnswerPaper.objects.get_user_all_attempts( questionpaper_id, user_id, course_id ) try: if attempt_number is None: attempt_number = attempts[0].attempt_number except IndexError: raise Http404('No attempts for paper') has_user_assignments = AssignmentUpload.objects.filter( question_paper_id__in=questionpaper_id, user_id=user_id ).exists() user = User.objects.get(id=user_id) data = AnswerPaper.objects.get_user_data( user, questionpaper_id, course_id, attempt_number ) context = { "data": data, "quiz_id": quiz_id, "quiz": quiz, "users": user_details, "attempts": attempts, "user_id": user_id, "has_user_assignments": has_user_assignments, "has_quiz_assignments": has_quiz_assignments, "course_id": course_id, "status": "grade" } if request.method == "POST": papers = data['papers'] for paper in papers: 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) answer.save() paper.update_marks() paper.comments = request.POST.get( 'comments_%d' % paper.question_paper.id, 'No comments') paper.save() messages.success(request, "Student data saved successfully") course_status = CourseStatus.objects.filter( course_id=course.id, user_id=user.id) if course_status.exists(): course_status.first().set_grade() if extra_context: context.update(extra_context) return my_render_to_response(request, 'yaksh/grade_user.html', context) @login_required @has_profile @email_verified def view_profile(request): """ view moderators and users profile """ user = request.user if is_moderator(user): template = 'manage.html' else: template = 'user.html' context = {'template': template, 'user': user} return my_render_to_response(request, 'yaksh/view_profile.html', context) @login_required @email_verified def edit_profile(request): """ edit profile details facility for moderator and students """ user = request.user if is_moderator(user): template = 'manage.html' else: template = 'user.html' context = {'template': template} try: profile = Profile.objects.get(user_id=user.id) except Profile.DoesNotExist: profile = None if request.method == 'POST': form = ProfileForm(request.POST, user=user, instance=profile) if form.is_valid(): form_data = form.save(commit=False) form_data.user = user form_data.user.first_name = request.POST['first_name'] form_data.user.last_name = request.POST['last_name'] form_data.user.save() form_data.save() return my_render_to_response(request, 'yaksh/profile_updated.html') else: context['form'] = form return my_render_to_response( request, 'yaksh/editprofile.html', context ) else: form = ProfileForm(user=user, instance=profile) context['form'] = form return my_render_to_response( request, 'yaksh/editprofile.html', context ) @login_required @email_verified def search_teacher(request, course_id): """ search teachers for the course """ user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') context = {'success': False} course = get_object_or_404(Course, pk=course_id) context['course'] = course if user != course.creator and user not in course.teachers.all(): raise Http404('You are not allowed to view this page!') if request.method == 'POST': u_name = request.POST.get('uname') if not len(u_name) == 0: teachers = User.objects.filter( Q(username__icontains=u_name) | Q(first_name__icontains=u_name) | Q(last_name__icontains=u_name) | Q(email__icontains=u_name) ).exclude( Q(id=user.id) | Q(is_superuser=1) | Q(id=course.creator.id) ) context['success'] = True context['teachers'] = teachers context['is_add_teacher'] = True return my_render_to_response(request, 'yaksh/course_detail.html', context) @login_required @email_verified def toggle_moderator_role(request): """ Allow moderator to switch to student and back """ user = request.user try: group = Group.objects.get(name='moderator') except Group.DoesNotExist: raise Http404('The Moderator group does not exist') if not user.profile.is_moderator: raise Http404('You are not allowed to view this page!') if user not in group.user_set.all(): group.user_set.add(user) else: group.user_set.remove(user) return my_redirect('/exam/') @login_required @email_verified def add_teacher(request, course_id): """ add teachers to the course """ user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') context = {} course = get_object_or_404(Course, pk=course_id) if course.is_creator(user) or course.is_teacher(user): context['course'] = course else: raise Http404('You are not allowed to view this page!') if request.method == 'POST': teacher_ids = request.POST.getlist('check') teachers = User.objects.filter(id__in=teacher_ids) add_as_moderator(teachers) course.add_teachers(*teachers) context['status'] = True context['teachers_added'] = teachers messages.success(request, "Added teachers successfully") context['is_add_teacher'] = True return my_render_to_response(request, 'yaksh/course_detail.html', context) @login_required @email_verified def remove_teachers(request, course_id): """ remove user from a course """ user = request.user course = get_object_or_404(Course, pk=course_id) if not is_moderator(user) and (not course.is_creator(user) and course.is_teacher(user)): raise Http404('You are not allowed to view this page!') if request.method == "POST": teacher_ids = request.POST.getlist('remove') if teacher_ids: teachers = User.objects.filter(id__in=teacher_ids) course.remove_teachers(*teachers) messages.success(request, "Removed teachers successfully") else: messages.warning(request, "Please select atleast one teacher") return course_teachers(request, course_id) def test_mode(user, godmode=False, questions_list=None, quiz_id=None, course_id=None): """creates a trial question paper for the moderators""" if questions_list is not None: trial_course = Course.objects.create_trial_course(user) trial_quiz = Quiz.objects.create_trial_quiz(user) trial_questionpaper = QuestionPaper.objects. \ create_trial_paper_to_test_questions( trial_quiz, questions_list ) trial_unit, created = LearningUnit.objects.get_or_create( order=1, type="quiz", quiz=trial_quiz, check_prerequisite=False) module, created = LearningModule.objects.get_or_create( order=1, creator=user, check_prerequisite=False, name="Trial for {0}".format(trial_course.name)) module.learning_unit.add(trial_unit) trial_course.learning_module.add(module.id) else: trial_quiz, trial_course, module = Quiz.objects.create_trial_from_quiz( quiz_id, user, godmode, course_id ) trial_questionpaper = QuestionPaper.objects. \ create_trial_paper_to_test_quiz( trial_quiz, quiz_id ) return trial_questionpaper, trial_course, module @login_required @email_verified def test_quiz(request, mode, quiz_id, course_id=None): """creates a trial quiz for the moderators""" godmode = True if mode == "godmode" else False current_user = request.user quiz = Quiz.objects.get(id=quiz_id) if (quiz.is_expired() or not quiz.active) and not godmode: messages.warning( request, "{0} is either expired or inactive".format(quiz.description) ) return my_redirect('/exam/manage') trial_questionpaper, trial_course, trial_module = test_mode( current_user, godmode, None, quiz_id, course_id) return my_redirect("/exam/start/{0}/{1}/{2}".format( trial_questionpaper.id, trial_module.id, trial_course.id)) @login_required @email_verified def view_answerpaper(request, questionpaper_id, course_id): user = request.user quiz = get_object_or_404(QuestionPaper, pk=questionpaper_id).quiz course = get_object_or_404(Course, pk=course_id) if quiz.view_answerpaper and user in course.students.all(): data = AnswerPaper.objects.get_user_data(user, questionpaper_id, course_id) has_user_assignment = AssignmentUpload.objects.filter( user=user, course_id=course.id, question_paper_id=questionpaper_id ).exists() context = {'data': data, 'quiz': quiz, 'course_id': course.id, "has_user_assignment": has_user_assignment} return my_render_to_response( request, 'yaksh/view_answerpaper.html', context ) else: return my_redirect('/exam/quizzes/') @login_required @email_verified def create_demo_course(request): """ creates a demo course for user """ user = request.user if not is_moderator(user): raise Http404("You are not allowed to view this page") demo_course = Course() success = demo_course.create_demo(user) if success: msg = "Created Demo course successfully" else: msg = "Demo course already created" return prof_manage(request, msg) @login_required @email_verified def regrade(request, course_id, questionpaper_id, question_id=None, answerpaper_id=None): user = request.user course = get_object_or_404(Course, pk=course_id) if not is_moderator(user) or (course.is_creator(user) and course.is_teacher(user)): raise Http404('You are not allowed to view this page!') questionpaper = get_object_or_404(QuestionPaper, pk=questionpaper_id) details = [] quiz = questionpaper.quiz data = {"user_id": user.id, "course_id": course_id, "questionpaper_id": questionpaper_id, "question_id": question_id, "answerpaper_id": answerpaper_id, "quiz_id": quiz.id, "quiz_name": quiz.description, "course_name": course.name } regrade_papers.delay(data) msg = dedent(""" {0} is submitted for re-evaluation. You will receive a notification for the re-evaluation status """.format(quiz.description) ) messages.info(request, msg) return redirect( reverse("yaksh:grade_user", args=[quiz.id, course_id]) ) @login_required @email_verified def download_course_csv(request, course_id): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') course = Course.objects.prefetch_related("learning_module").get( id=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('The question paper does not belong to your course') students = course.get_only_students().annotate( roll_number=F('profile__roll_number'), institute=F('profile__institute') ).values( "id", "first_name", "last_name", "email", "institute", "roll_number" ) quizzes = course.get_quizzes() for student in students: total_course_marks = 0.0 user_course_marks = 0.0 for quiz in quizzes: quiz_best_marks = AnswerPaper.objects. \ get_user_best_of_attempts_marks(quiz, student["id"], course_id) user_course_marks += quiz_best_marks total_course_marks += quiz.questionpaper_set.values_list( "total_marks", flat=True)[0] student["{}".format(quiz.description)] = quiz_best_marks student["total_scored"] = user_course_marks student["out_of"] = total_course_marks response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="{0}.csv"'.format( (course.name).lower().replace('.', '')) header = ['first_name', 'last_name', "roll_number", "email", "institute"]\ + [quiz.description for quiz in quizzes] + ['total_scored', 'out_of'] writer = csv.DictWriter(response, fieldnames=header, extrasaction='ignore') writer.writeheader() for student in students: writer.writerow(student) return response def activate_user(request, key): profile = get_object_or_404(Profile, activation_key=key) context = {} context['success'] = False if profile.is_email_verified: context['activation_msg'] = "Your account is already verified" return my_render_to_response( request, 'yaksh/activation_status.html', context ) if timezone.now() > profile.key_expiry_time: context['msg'] = dedent(""" Your activation time expired. Please try again. """) else: context['success'] = True profile.is_email_verified = True profile.save() context['msg'] = "Your account is activated" return my_render_to_response( request, 'yaksh/activation_status.html', context ) def new_activation(request, email=None): context = {} if request.method == "POST": email = request.POST.get('email') try: user = User.objects.get(email=email) except MultipleObjectsReturned: context['email_err_msg'] = "Multiple entries found for this email "\ "Please change your email" return my_render_to_response( request, 'yaksh/activation_status.html', context ) except ObjectDoesNotExist: context['success'] = False context['msg'] = "Your account is not verified. \ Please verify your account" return my_render_to_response( request, 'yaksh/activation_status.html', context ) if not user.profile.is_email_verified: user.profile.activation_key = generate_activation_key(user.username) user.profile.key_expiry_time = timezone.now() + \ timezone.timedelta(minutes=20) user.profile.save() new_user_data = User.objects.get(email=email) success, msg = send_user_mail(new_user_data.email, new_user_data.profile.activation_key ) if success: context['activation_msg'] = msg else: context['msg'] = msg else: context['activation_msg'] = "Your account is already verified" return my_render_to_response( request, 'yaksh/activation_status.html', context ) def update_email(request): context = {} if request.method == "POST": email = request.POST.get('email') username = request.POST.get('username') user = get_object_or_404(User, username=username) user.email = email user.save() return new_activation(request, email) else: context['email_err_msg'] = "Please Update your email" return my_render_to_response( request, 'yaksh/activation_status.html', context ) @login_required @email_verified def download_assignment_file(request, quiz_id, course_id, question_id=None, user_id=None): user = request.user course = get_object_or_404(Course, pk=course_id) if (not course.is_creator(user) and not course.is_teacher(user) and not course.is_student(user)): raise Http404("You are not allowed to download files for {0}".format( course.name) ) qp = get_object_or_404(QuestionPaper, quiz_id=quiz_id) assignment_files, file_name = AssignmentUpload.objects.get_assignments( qp, question_id, user_id, course_id ) zipfile_name = string_io() zip_file = zipfile.ZipFile(zipfile_name, "w") for f_name in assignment_files: folder = f_name.user.get_full_name().replace(" ", "_") sub_folder = f_name.assignmentQuestion.summary.replace(" ", "_") folder_name = os.sep.join((folder, sub_folder, os.path.basename( f_name.assignmentFile.name)) ) zip_file.write( f_name.assignmentFile.path, folder_name ) zip_file.close() zipfile_name.seek(0) response = HttpResponse(content_type='application/zip') response['Content-Disposition'] = 'attachment; filename={0}.zip'.format( file_name.replace(" ", "_") ) response.write(zipfile_name.read()) return response @login_required @email_verified def upload_users(request, course_id): user = request.user course = get_object_or_404(Course, pk=course_id) context = {'course': course} if not (course.is_teacher(user) or course.is_creator(user)): raise Http404('You are not allowed to view this page!') if request.method == 'POST': if 'csv_file' not in request.FILES: messages.warning(request, "Please upload a CSV file.") return my_redirect(reverse('yaksh:course_students', args=[course_id])) csv_file = request.FILES['csv_file'] is_csv_file, dialect = is_csv(csv_file) if not is_csv_file: messages.warning(request, "The file uploaded is not a CSV file.") return my_redirect(reverse('yaksh:course_students', args=[course_id])) required_fields = ['firstname', 'lastname', 'email'] try: reader = csv.DictReader( csv_file.read().decode('utf-8').splitlines(), dialect=dialect) except TypeError: messages.warning(request, "Bad CSV file") return my_redirect(reverse('yaksh:course_students', args=[course_id])) stripped_fieldnames = [ field.strip().lower() for field in reader.fieldnames] for field in required_fields: if field not in stripped_fieldnames: msg = "The CSV file does not contain the required headers" messages.warning(request, msg) return my_redirect(reverse('yaksh:course_students', args=[course_id])) reader.fieldnames = stripped_fieldnames _read_user_csv(request, reader, course) return my_redirect(reverse('yaksh:course_students', args=[course_id])) def _read_user_csv(request, reader, course): fields = reader.fieldnames counter = 0 for row in reader: counter += 1 (username, email, first_name, last_name, password, roll_no, institute, department, remove) = _get_csv_values(row, fields) if not email or not first_name or not last_name: messages.info(request, "{0} -- Missing Values".format(counter)) continue users = User.objects.filter(username=username) if users.exists(): user = users[0] if remove.strip().lower() == 'true': _remove_from_course(user, course) messages.info(request, "{0} -- {1} -- User rejected".format( counter, user.username)) else: _add_to_course(user, course) messages.info( request, "{0} -- {1} -- User Added Successfully".format( counter, user.username)) continue user_defaults = {'email': email, 'first_name': first_name, 'last_name': last_name} user, created = _create_or_update_user(username, password, user_defaults) profile_defaults = {'institute': institute, 'roll_number': roll_no, 'department': department, 'is_email_verified': True} _create_or_update_profile(user, profile_defaults) if created: state = "Added" course.students.add(user) else: state = "Updated" messages.info(request, "{0} -- {1} -- User {2} Successfully".format( counter, user.username, state)) if counter == 0: messages.warning(request, "No rows in the CSV file") def _get_csv_values(row, fields): roll_no, institute, department = "", "", "" remove = "false" email, first_name, last_name = map(str.strip, [row['email'], row['firstname'], row['lastname']]) password = email username = email if 'password' in fields and row['password']: password = row['password'].strip() if 'roll_no' in fields: roll_no = row['roll_no'].strip() if 'institute' in fields: institute = row['institute'].strip() if 'department' in fields: department = row['department'].strip() if 'remove' in fields: remove = row['remove'].strip() if 'username' in fields and row['username']: username = row['username'].strip() if 'remove' in fields: remove = row['remove'] return (username, email, first_name, last_name, password, roll_no, institute, department, remove) def _remove_from_course(user, course): if user in course.get_enrolled(): course.reject(True, user) else: course.rejected.add(user) def _add_to_course(user, course): if user in course.get_rejected(): course.enroll(True, user) else: course.students.add(user) def _create_or_update_user(username, password, defaults): user, created = User.objects.update_or_create(username=username, defaults=defaults) user.set_password(password) user.save() return user, created def _create_or_update_profile(user, defaults): Profile.objects.update_or_create(user=user, defaults=defaults) @login_required @email_verified def download_sample_csv(request): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') csv_file_path = os.path.join(FIXTURES_DIR_PATH, "sample_user_upload.csv") with open(csv_file_path, 'rb') as csv_file: response = HttpResponse(csv_file.read(), content_type='text/csv') response['Content-Disposition'] = ( 'attachment; filename="sample_user_upload"' ) return response @login_required @email_verified def duplicate_course(request, course_id): user = request.user course = Course.objects.get(id=course_id) if not is_moderator(user): raise Http404('You are not allowed to view this page!') if course.is_teacher(user) or course.is_creator(user): # Create new entries of modules, lessons/quizzes # from current course to copied course duplicate_course = course.create_duplicate_course(user) msg = dedent( '''\ Course duplication successful with the name {0}'''.format( duplicate_course.name ) ) messages.success(request, msg) else: msg = dedent( '''\ You do not have permissions to clone {0} course, please contact your instructor/administrator.'''.format(course.name) ) messages.warning(request, msg) return my_redirect(reverse('yaksh:courses')) @login_required @email_verified def download_yaml_template(request): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') template_path = os.path.join(os.path.dirname(__file__), "fixtures", "demo_questions.zip" ) yaml_file = zipfile.ZipFile(template_path, 'r') template_yaml = yaml_file.open('questions_dump.yaml', 'r') response = HttpResponse(template_yaml, content_type='text/yaml') response['Content-Disposition'] = ( 'attachment; filename="questions_dump.yaml"' ) return response @login_required @email_verified def edit_lesson(request, course_id=None, module_id=None, lesson_id=None): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') if lesson_id: lesson = Lesson.objects.get(id=lesson_id) if not lesson.creator == user and not course_id: raise Http404('This Lesson does not belong to you') else: lesson = None if module_id: module = get_object_or_404(LearningModule, id=module_id) if course_id: course = get_object_or_404(Course, id=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This Lesson does not belong to you') context = {} lesson_form = LessonForm(instance=lesson) lesson_files_form = LessonFileForm() if request.method == "POST": if "Save" in request.POST: lesson_form = LessonForm(request.POST, request.FILES, instance=lesson) lesson_file_form = LessonFileForm(request.POST, request.FILES) lessonfiles = request.FILES.getlist('Lesson_files') clear = request.POST.get("video_file-clear") video_file = request.FILES.get("video_file") if (clear or video_file) and lesson: # Remove previous video file if new file is uploaded or # if clear is selected lesson.remove_file() if lesson_form.is_valid(): if lesson is None: last_unit = module.get_learning_units().last() order = last_unit.order + 1 if last_unit else 1 lesson_form.instance.creator = user else: order = module.get_unit_order("lesson", lesson) lesson = lesson_form.save() lesson.html_data = get_html_text(lesson.description) lesson.save() if lessonfiles: for les_file in lessonfiles: LessonFile.objects.get_or_create( lesson=lesson, file=les_file ) unit, created = LearningUnit.objects.get_or_create( type="lesson", lesson=lesson, order=order ) if created: module.learning_unit.add(unit.id) messages.success( request, "Saved {0} successfully".format(lesson.name) ) return redirect( reverse("yaksh:edit_lesson", args=[course_id, module_id, lesson.id]) ) if 'Delete' in request.POST: remove_files_id = request.POST.getlist('delete_files') if remove_files_id: files = LessonFile.objects.filter(id__in=remove_files_id) for file in files: file.remove() messages.success( request, "Deleted files successfully" ) else: messages.warning( request, "Please select atleast one file to delete" ) contents = TableOfContents.objects.filter( course_id=course_id, lesson_id=lesson_id ) data = loader.render_to_string( "yaksh/show_toc.html", context={'contents': contents}, request=request ) context['toc'] = data lesson_files = LessonFile.objects.filter(lesson=lesson) context['lesson_form'] = lesson_form context['lesson_file_form'] = lesson_files_form context['lesson_files'] = lesson_files context['course_id'] = course_id return my_render_to_response(request, 'yaksh/add_lesson.html', context) @login_required @email_verified def show_lesson(request, lesson_id, module_id, course_id): user = request.user course = Course.objects.get(id=course_id) if user not in course.students.all(): raise Http404('This course does not belong to you') if not course.active or not course.is_active_enrollment(): msg = "{0} is either expired or not active".format(course.name) return quizlist_user(request, msg=msg) learn_module = course.learning_module.get(id=module_id) learn_unit = learn_module.learning_unit.get(lesson_id=lesson_id) learning_units = learn_module.get_learning_units() if not learn_module.active: return view_module(request, module_id, course_id) if not learn_unit.lesson.active: msg = "{0} is not active".format(learn_unit.lesson.name) return view_module(request, module_id, course_id, msg) if learn_module.has_prerequisite(): if not learn_module.is_prerequisite_complete(user, course): msg = "You have not completed the module previous to {0}".format( learn_module.name) return view_module(request, module_id, course_id, msg) if learn_module.check_prerequisite_passes: if not learn_module.is_prerequisite_passed(user, course): msg = ( "You have not successfully passed the module" " previous to {0}".format(learn_module.name) ) return view_module(request, module_id, course_id, msg) # update course status with current unit _update_unit_status(course_id, user, learn_unit) all_modules = course.get_learning_modules() if learn_unit.has_prerequisite(): if not learn_unit.is_prerequisite_complete(user, learn_module, course): msg = "You have not completed previous Lesson/Quiz/Exercise" return view_module(request, learn_module.id, course_id, msg=msg) context = {'lesson': learn_unit.lesson, 'user': user, 'course': course, 'state': "lesson", "all_modules": all_modules, 'learning_units': learning_units, "current_unit": learn_unit, 'learning_module': learn_module} return my_render_to_response(request, 'yaksh/show_video.html', context) @login_required @email_verified def design_module(request, module_id, course_id=None): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') context = {} if course_id: course = Course.objects.get(id=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This course does not belong to you') learning_module = LearningModule.objects.get(id=module_id) if request.method == "POST": if "Add" in request.POST: add_values = request.POST.get("chosen_list") to_add_list = [] if add_values: add_values = add_values.split(',') ordered_units = learning_module.get_learning_units() if ordered_units.exists(): start_val = ordered_units.last().order + 1 else: start_val = 1 for order, value in enumerate(add_values, start_val): learning_id, type = value.split(":") if type == "quiz": unit, status = LearningUnit.objects.get_or_create( order=order, quiz_id=learning_id, type=type) else: unit, status = LearningUnit.objects.get_or_create( order=order, lesson_id=learning_id, type=type) to_add_list.append(unit) learning_module.learning_unit.add(*to_add_list) messages.success(request, "Lesson/Quiz added successfully") else: messages.warning(request, "Please select a lesson/quiz to add") if "Change" in request.POST: 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_unit = learning_module.learning_unit.get( id=learning_unit) learning_unit.order = learning_order learning_unit.save() messages.success(request, "Order changed successfully") else: messages.warning( request, "Please select a lesson/quiz to change" ) if "Remove" in request.POST: remove_values = request.POST.getlist("delete_list") if remove_values: learning_module.learning_unit.remove(*remove_values) LearningUnit.objects.filter(id__in=remove_values).delete() messages.success( request, "Lessons/quizzes deleted successfully" ) else: messages.warning( request, "Please select a lesson/quiz to remove" ) if "Change_prerequisite" in request.POST: unit_list = request.POST.getlist("check_prereq") if unit_list: for unit in unit_list: learning_unit = learning_module.learning_unit.get(id=unit) learning_unit.toggle_check_prerequisite() learning_unit.save() messages.success( request, "Changed prerequisite status successfully" ) else: messages.warning( request, "Please select a lesson/quiz to change prerequisite" ) added_quiz_lesson = learning_module.get_added_quiz_lesson() quizzes = [("quiz", quiz) for quiz in Quiz.objects.filter( creator=user, is_trial=False)] lessons = [("lesson", lesson) for lesson in Lesson.objects.filter(creator=user)] quiz_les_list = set(quizzes + lessons) - set(added_quiz_lesson) context['quiz_les_list'] = quiz_les_list context['learning_units'] = learning_module.get_learning_units() context['status'] = 'design' context['module_id'] = module_id context['course_id'] = course_id context['module'] = learning_module return my_render_to_response(request, 'yaksh/add_module.html', context) @login_required @email_verified def add_module(request, course_id=None, module_id=None): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') if course_id: course = Course.objects.get(id=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This course does not belong to you') if module_id: module = LearningModule.objects.get(id=module_id) if not module.creator == user and not course_id: raise Http404('This Learning Module does not belong to you') else: module = None context = {} if request.method == "POST": if "Save" in request.POST: module_form = LearningModuleForm(request.POST, instance=module) if module_form.is_valid(): if module is None: last_module = course.get_learning_modules().last() module_form.instance.creator = user if last_module: module_form.instance.order = last_module.order + 1 module = module_form.save() module.html_data = get_html_text(module.description) module.save() course.learning_module.add(module.id) messages.success( request, "Saved {0} successfully".format(module.name) ) else: context['module_form'] = module_form module_form = LearningModuleForm(instance=module) context['module_form'] = module_form context['course_id'] = course_id context['status'] = "add" return my_render_to_response(request, "yaksh/add_module.html", context) @login_required @email_verified def preview_html_text(request): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') response_kwargs = {} response_kwargs['content_type'] = 'application/json' request_data = json.loads(request.body.decode("utf-8")) html_text = get_html_text(request_data['description']) return HttpResponse(json.dumps({"data": html_text}), **response_kwargs) @login_required @email_verified def get_next_unit(request, course_id, module_id, current_unit_id=None, first_unit=None): user = request.user course = Course.objects.prefetch_related("learning_module").get( id=course_id) if not course.students.filter(id=user.id).exists(): raise Http404('You are not enrolled for this course!') learning_module = course.learning_module.prefetch_related( "learning_unit").get(id=module_id) if current_unit_id: current_learning_unit = learning_module.learning_unit.get( id=current_unit_id) else: next_module = course.next_module(learning_module.id) return my_redirect("/exam/quizzes/view_module/{0}/{1}".format( next_module.id, course_id)) if first_unit: next_unit = current_learning_unit else: next_unit = learning_module.get_next_unit(current_learning_unit.id) course_status, created = CourseStatus.objects.get_or_create( user=user, course_id=course_id, ) # Add learning unit to completed units list if not first_unit: course_status.completed_units.add(current_learning_unit.id) # Update course completion percentage _update_course_percent(course, user) # if last unit of current module go to next module is_last_unit = course.is_last_unit(learning_module, current_learning_unit.id) if is_last_unit: next_module = course.next_module(learning_module.id) return my_redirect("/exam/quizzes/view_module/{0}/{1}/".format( next_module.id, course.id)) if next_unit.type == "quiz": return my_redirect("/exam/start/{0}/{1}/{2}".format( next_unit.quiz.questionpaper_set.get().id, module_id, course_id)) else: return my_redirect("/exam/show_lesson/{0}/{1}/{2}".format( next_unit.lesson.id, module_id, course_id)) @login_required @email_verified def design_course(request, course_id): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') course = Course.objects.get(id=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This course does not belong to you') context = {} if request.method == "POST": if "Add" in request.POST: add_values = request.POST.getlist("module_list") to_add_list = [] if add_values: ordered_modules = course.get_learning_modules() if ordered_modules.exists(): start_val = ordered_modules.last().order + 1 else: start_val = 1 for order, value in enumerate(add_values, start_val): module, created = LearningModule.objects.get_or_create( id=int(value) ) module.order = order module.save() to_add_list.append(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") 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") 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") 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( creator=user, is_trial=False) learning_modules = set(all_learning_modules) - set(added_learning_modules) context['added_learning_modules'] = added_learning_modules context['learning_modules'] = learning_modules context['course'] = course context['is_design_course'] = True return my_render_to_response( request, 'yaksh/course_detail.html', context ) @login_required @email_verified def view_module(request, module_id, course_id, msg=None): user = request.user course = Course.objects.get(id=course_id) if user not in course.students.all(): raise Http404('You are not enrolled for this course!') context = {} if not course.active or not course.is_active_enrollment(): msg = "{0} is either expired or not active".format(course.name) return course_modules(request, course_id, msg) learning_module = course.learning_module.get(id=module_id) if not learning_module.active: msg = "{0} is not active".format(learning_module.name) return course_modules(request, course_id, msg) all_modules = course.get_learning_modules() if learning_module.has_prerequisite(): if not learning_module.is_prerequisite_complete(user, course): msg = "You have not completed the module previous to {0}".format( learning_module.name) return course_modules(request, course_id, msg) if learning_module.check_prerequisite_passes: if not learning_module.is_prerequisite_passed(user, course): msg = ( "You have not successfully passed the module" " previous to {0}".format(learning_module.name) ) return course_modules(request, course_id, msg) learning_units = learning_module.get_learning_units() context['learning_units'] = learning_units context['learning_module'] = learning_module context['first_unit'] = learning_units.first() context['all_modules'] = all_modules context['user'] = user context['course'] = course context['state'] = "module" context['msg'] = msg return my_render_to_response(request, 'yaksh/show_video.html', context) @login_required @email_verified def course_modules(request, course_id, msg=None): user = request.user course = Course.objects.get(id=course_id) if user not in course.students.all(): msg = 'You are not enrolled for this course!' return quizlist_user(request, msg=msg) if not course.active or not course.is_active_enrollment(): msg = "{0} is either expired or not active".format(course.name) return quizlist_user(request, msg=msg) learning_modules = course.get_learning_modules() context = {"course": course, "user": user, "msg": msg} course_status = CourseStatus.objects.filter(course=course, user=user) context['course_percentage'] = course.get_completion_percent(user) context['modules'] = [ (module, module.get_module_complete_percent(course, user)) for module in learning_modules ] if course_status.exists(): course_status = course_status.first() if not course_status.grade: course_status.set_grade() context['grade'] = course_status.get_grade() return my_render_to_response(request, 'yaksh/course_modules.html', context) @login_required @email_verified def course_status(request, course_id): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This course does not belong to you') students = course.students.order_by("-id") students_no = students.count() paginator = Paginator(students, 100) page = request.GET.get('page') students = paginator.get_page(page) stud_details = [(student, course.get_grade(student), course.get_completion_percent(student), course.get_current_unit(student)) for student in students.object_list] context = { 'course': course, 'objects': students, 'is_progress': True, 'student_details': stud_details, 'students_no': students_no } return my_render_to_response(request, 'yaksh/course_detail.html', context) def _update_unit_status(course_id, user, unit): """ Update course status with current unit """ course_status, created = CourseStatus.objects.get_or_create( user=user, course_id=course_id, ) # make next available unit as current unit course_status.set_current_unit(unit) def _update_course_percent(course, user): course_status, created = CourseStatus.objects.get_or_create( user=user, course=course, ) # Update course completion percent modules = course.get_learning_modules() course_status.percent_completed = course.percent_completed(user, modules) course_status.save() @login_required @email_verified def preview_questionpaper(request, questionpaper_id): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') paper = QuestionPaper.objects.get(id=questionpaper_id) context = { 'questions': paper._get_questions_for_answerpaper(), 'paper': paper, } return my_render_to_response( request, 'yaksh/preview_questionpaper.html', context ) @login_required @email_verified def get_user_data(request, course_id, student_id): user = request.user data = {} response_kwargs = {} response_kwargs['content_type'] = 'application/json' course = Course.objects.get(id=course_id) if not is_moderator(user): data['msg'] = 'You are not a moderator' data['status'] = False elif not course.is_creator(user) and not course.is_teacher(user): msg = dedent( """\ You are neither course creator nor course teacher for {0} """.format(course.name) ) data['msg'] = msg data['status'] = False else: student = User.objects.get(id=student_id) data['status'] = True modules = course.get_learning_modules() module_percent = [ (module, module.get_module_complete_percent(course, student)) for module in modules ] data['modules'] = module_percent _update_course_percent(course, student) data['course_percentage'] = course.get_completion_percent(student) data['student'] = student template_path = os.path.join( os.path.dirname(__file__), "templates", "yaksh", "user_status.html" ) with open(template_path) as f: template_data = f.read() template = Template(template_data) context = Context(data) data = template.render(context) return HttpResponse(json.dumps({"user_data": data}), **response_kwargs) @login_required @email_verified def download_course(request, course_id): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') course = get_object_or_404(Course, pk=course_id) if (not course.is_creator(user) and not course.is_teacher(user) and not course.is_student(user)): raise Http404("You are not allowed to download {0} course".format( course.name)) if not course.has_lessons(): raise Http404("{0} course does not have any lessons".format( course.name)) current_dir = os.path.dirname(__file__) course_name = course.name.replace(" ", "_") # Static files required for styling in html template static_files = {"js": ["bootstrap.min.js", "jquery-3.3.1.min.js", "video.js"], "css": ["bootstrap.min.css", "video-js.css", "offline.css", "yakshcustom.css"], "images": ["yaksh_banner.png"]} zip_file = course.create_zip(current_dir, static_files) zip_file.seek(0) response = HttpResponse(content_type='application/zip') response['Content-Disposition'] = 'attachment; filename={0}.zip'.format( course_name ) response.write(zip_file.read()) return response @login_required @email_verified def course_students(request, course_id): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404("You are not allowed to view {0}".format( course.name)) enrolled_users = course.get_enrolled() requested_users = course.get_requests() rejected_users = course.get_rejected() context = { "enrolled_users": enrolled_users, "requested_users": requested_users, "course": course, "rejected_users": rejected_users, "is_students": True } return my_render_to_response(request, 'yaksh/course_detail.html', context) @login_required @email_verified def course_teachers(request, course_id): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404("You are not allowed to view {0}".format( course.name)) teachers = course.get_teachers() context = {"teachers": teachers, "is_teachers": True, "course": course} return my_render_to_response(request, 'yaksh/course_detail.html', context) @login_required @email_verified def get_course_modules(request, course_id): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404("You are not allowed to view {0}".format( course.name)) modules = course.get_learning_modules() context = {"modules": modules, "is_modules": True, "course": course} return my_render_to_response(request, 'yaksh/course_detail.html', context) @login_required @email_verified def download_course_progress(request, course_id): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This course does not belong to you') students = course.students.order_by("-id") stud_details = [(student.get_full_name(), course.get_grade(student), course.get_completion_percent(student), course.get_current_unit(student)) for student in students] response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="{0}.csv"'.format( (course.name).lower().replace(' ', '_')) header = ['Name', 'Grade', 'Completion Percent', 'Current Unit'] writer = csv.writer(response) writer.writerow(header) for student in stud_details: writer.writerow(student) return response @login_required @email_verified def view_notifications(request): user = request.user notifcations = Notification.objects.get_unread_receiver_notifications( user.id ) if is_moderator(user): template = "manage.html" else: template = "user.html" context = {"template": template, "notifications": notifcations, "current_date_time": timezone.now()} return my_render_to_response( request, 'yaksh/view_notifications.html', context ) @login_required @email_verified def mark_notification(request, message_uid=None): user = request.user if message_uid: Notification.objects.mark_single_notification( user.id, message_uid, True ) else: if request.method == 'POST': msg_uuids = request.POST.getlist("uid") Notification.objects.mark_bulk_msg_notifications( user.id, msg_uuids, True) messages.success(request, "Marked notifcation(s) as read") return redirect(reverse("yaksh:view_notifications")) @login_required @email_verified def course_forum(request, course_id): user = request.user base_template = 'user.html' moderator = False if is_moderator(user): base_template = 'manage.html' moderator = True course = get_object_or_404(Course, id=course_id) if (not course.is_creator(user) and not course.is_teacher(user) and not course.is_student(user)): raise Http404('You are not enrolled in {0} course'.format(course.name)) search_term = request.GET.get('search_post') if search_term: posts = course.post.get_queryset().filter( active=True, title__icontains=search_term) else: posts = course.post.get_queryset().filter( active=True).order_by('-modified_at') paginator = Paginator(posts, 1) page = request.GET.get('page') posts = paginator.get_page(page) if request.method == "POST": form = PostForm(request.POST, request.FILES) if form.is_valid(): new_post = form.save(commit=False) new_post.creator = user new_post.course = course new_post.save() return redirect('yaksh:post_comments', course_id=course.id, uuid=new_post.uid) else: form = PostForm() return render(request, 'yaksh/course_forum.html', { 'user': user, 'course': course, 'base_template': base_template, 'moderator': moderator, 'objects': posts, 'form': form, 'user': user }) @login_required @email_verified def post_comments(request, course_id, uuid): user = request.user base_template = 'user.html' if is_moderator(user): base_template = 'manage.html' post = get_object_or_404(Post, uid=uuid) comments = post.comment.filter(active=True) course = get_object_or_404(Course, id=course_id) if (not course.is_creator(user) and not course.is_teacher(user) and not course.is_student(user)): raise Http404('You are not enrolled in {0} course'.format(course.name)) form = CommentForm() if request.method == "POST": form = CommentForm(request.POST, request.FILES) if form.is_valid(): new_comment = form.save(commit=False) new_comment.creator = request.user new_comment.post_field = post new_comment.save() return redirect(request.path_info) return render(request, 'yaksh/post_comments.html', { 'post': post, 'comments': comments, 'base_template': base_template, 'form': form, 'user': user }) @login_required @email_verified def hide_post(request, course_id, uuid): user = request.user course = get_object_or_404(Course, id=course_id) if (not course.is_creator(user) and not course.is_teacher(user)): raise Http404('You are not enrolled in {0} course'.format(course.name)) post = get_object_or_404(Post, uid=uuid) post.comment.active = False post.active = False post.save() return redirect('yaksh:course_forum', course_id) @login_required @email_verified def hide_comment(request, course_id, uuid): user = request.user course = get_object_or_404(Course, id=course_id) if (not course.is_creator(user) and not course.is_teacher(user)): raise Http404('You are not enrolled in {0} course'.format(course.name)) comment = get_object_or_404(Comment, uid=uuid) post_uid = comment.post_field.uid comment.active = False comment.save() return redirect('yaksh:post_comments', course_id, post_uid) @login_required @email_verified def add_marker(request, course_id, lesson_id): user = request.user course = get_object_or_404(Course, pk=course_id) if (not is_moderator(user) or not course.is_creator(user) or not course.is_creator(user)): raise Http404("You are not allowed to view this page") content_type = request.POST.get("content") question_type = request.POST.get("type") if content_type == '1': form = TopicForm() template_name = 'yaksh/add_topic.html' status = 1 formset = None tc_class = None else: if not question_type: question_type = "mcq" form = VideoQuizForm(question_type=question_type) formset, tc_class = get_tc_formset(question_type) template_name = 'yaksh/add_video_quiz.html' status = 2 context = {'form': form, 'course_id': course.id, 'lesson_id': lesson_id, 'formset': formset, 'tc_class': tc_class, 'content_type': content_type} data = loader.render_to_string( template_name, context=context, request=request ) return JsonResponse( {'success': True, 'data': data, 'content_type': content_type, 'status': status} ) def get_tc_formset(question_type, post=None, question=None): tc, tc_class = McqTestCase, 'mcqtestcase' if question_type == 'mcq' or question_type == 'mcc': tc, tc_class = McqTestCase, 'mcqtestcase' elif question_type == 'integer': tc, tc_class = IntegerTestCase, 'integertestcase' elif question_type == 'float': tc, tc_class = FloatTestCase, 'floattestcase' elif question_type == 'string': tc, tc_class = StringTestCase, 'stringtestcase' TestcaseFormset = inlineformset_factory( Question, tc, form=TestcaseForm, extra=1, fields="__all__", ) formset = TestcaseFormset( post, initial=[{'type': tc_class}], instance=question ) return formset, tc_class @login_required @email_verified def add_topic(request, content_type, course_id, lesson_id, topic_id=None): user = request.user course = get_object_or_404(Course, pk=course_id) if (not is_moderator(user) or not course.is_creator(user) or not course.is_creator(user)): raise Http404("You are not allowed to view this page") if topic_id: topic = get_object_or_404(Topic, pk=topic_id) else: topic = None context = {} if request.method == "POST": form = TopicForm(request.POST, instance=topic) if form.is_valid(): form.save() if not topic: TableOfContents.objects.create( content_object=form.instance, course_id=course_id, lesson_id=lesson_id, content=content_type, time=request.POST.get("timer") ) contents = TableOfContents.objects.filter( course_id=course_id, lesson_id=lesson_id ) data = loader.render_to_string( "yaksh/show_toc.html", context={'contents': contents}, request=request ) context['toc'] = data status_code = 200 context['success'] = True context['message'] = 'Added topic successfully' else: status_code = 400 context['success'] = False context['message'] = form.errors.as_json() return JsonResponse(context, status=status_code) @login_required @email_verified def add_marker_quiz(request, content_type, course_id, lesson_id, question_id=None): user = request.user course = get_object_or_404(Course, pk=course_id) if (not is_moderator(user) or not course.is_creator(user) or not course.is_creator(user)): raise Http404("You are not allowed to view this page") if question_id: question = get_object_or_404(Question, pk=question_id) else: question = None context = {} if request.method == "POST": qform = VideoQuizForm(request.POST, instance=question) if qform.is_valid(): qform.save(commit=False) qform.instance.user = user qform.save() formset, tc_class = get_tc_formset( qform.instance.type, request.POST, qform.instance ) if formset.is_valid(): formset.save() if not question: TableOfContents.objects.create( content_object=qform.instance, course_id=course_id, lesson_id=lesson_id, content=content_type, time=request.POST.get("timer") ) contents = TableOfContents.objects.filter( course_id=course_id, lesson_id=lesson_id ) data = loader.render_to_string( "yaksh/show_toc.html", context={'contents': contents}, request=request ) context['toc'] = data status_code = 200 context['success'] = True context['message'] = 'Added question successfully' context['content_type'] = content_type else: status_code = 400 context['success'] = False context['message'] = formset.errors.as_json() else: status_code = 400 context['success'] = False context['message'] = qform.errors.as_json() return JsonResponse(context, status=status_code)