summaryrefslogtreecommitdiff
path: root/yaksh/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'yaksh/views.py')
-rw-r--r--yaksh/views.py830
1 files changed, 775 insertions, 55 deletions
diff --git a/yaksh/views.py b/yaksh/views.py
index 3adb536..95a7218 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -3,13 +3,14 @@ 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
+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.contrib.contenttypes.models import ContentType
from django.forms.models import inlineformset_factory
from django.forms import fields
from django.utils import timezone
@@ -23,7 +24,8 @@ from django.urls import reverse
import json
from textwrap import dedent
import zipfile
-from markdown import Markdown
+import markdown
+import ruamel
try:
from StringIO import StringIO as string_io
except ImportError:
@@ -37,14 +39,16 @@ from yaksh.models import (
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
+ LearningUnit, LearningModule, CourseStatus, question_types, Post, Comment,
+ Topic, TableOfContents, LessonQuizAnswer, MicroManager
)
+from stats.models import TrackLesson
from yaksh.forms import (
UserRegisterForm, UserLoginForm, QuizForm, QuestionForm,
QuestionFilterForm, CourseForm, ProfileForm,
UploadFileForm, FileForm, QuestionPaperForm, LessonForm,
LessonFileForm, LearningModuleForm, ExerciseForm, TestcaseForm,
- SearchFilterForm, PostForm, CommentForm
+ SearchFilterForm, PostForm, CommentForm, TopicForm, VideoQuizForm
)
from yaksh.settings import SERVER_POOL_PORT, SERVER_HOST_NAME
from .settings import URL_ROOT
@@ -101,7 +105,9 @@ CSV_FIELDS = ['name', 'username', 'roll_number', 'institute', 'department',
def get_html_text(md_text):
"""Takes markdown text and converts it to html"""
- return Markdown().convert(md_text)
+ return markdown.markdown(
+ md_text, extensions=['tables', 'fenced_code']
+ )
def formfield_callback(field):
@@ -481,6 +487,46 @@ def user_login(request):
@login_required
@email_verified
+def special_start(request, micromanager_id=None):
+ user = request.user
+ micromanager = get_object_or_404(MicroManager, pk=micromanager_id,
+ student=user)
+ course = micromanager.course
+ quiz = micromanager.quiz
+ module = course.get_learning_module(quiz)
+ quest_paper = get_object_or_404(QuestionPaper, quiz=quiz)
+
+ if not course.is_enrolled(user):
+ msg = 'You are not enrolled in {0} course'.format(course.name)
+ return quizlist_user(request, msg=msg)
+
+ if not micromanager.can_student_attempt():
+ msg = 'Your special attempts are exhausted for {0}'.format(
+ quiz.description)
+ return quizlist_user(request, msg=msg)
+
+ 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_num = micromanager.get_attempt_number()
+ ip = request.META['REMOTE_ADDR']
+ new_paper = quest_paper.make_answerpaper(user, ip, attempt_num, course.id,
+ special=True)
+ micromanager.increment_attempts_utilised()
+ return show_question(request, new_paper.current_question(), new_paper,
+ course_id=course.id, module_id=module.id)
+
+
+@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,
@@ -625,6 +671,7 @@ def show_question(request, question, paper, error_message=None,
quiz = paper.question_paper.quiz
quiz_type = 'Exam'
can_skip = False
+ assignment_files = []
if previous_question:
delay_time = paper.time_left_on_question(previous_question)
else:
@@ -641,7 +688,7 @@ def show_question(request, question, paper, error_message=None,
request, msg, paper.attempt_number, paper.question_paper.id,
course_id=course_id, module_id=module_id
)
- if not quiz.active:
+ if not quiz.active and not paper.is_special:
reason = 'The quiz has been deactivated!'
return complete(
request, reason, paper.attempt_number, paper.question_paper.id,
@@ -666,6 +713,13 @@ def show_question(request, question, paper, error_message=None,
test_cases = question.get_ordered_test_cases(paper)
else:
test_cases = question.get_test_cases()
+ if question.type == 'upload':
+ assignment_files = AssignmentUpload.objects.filter(
+ assignmentQuestion_id=question.id,
+ course_id=course_id,
+ user=request.user,
+ question_paper_id=paper.question_paper_id
+ )
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)
@@ -685,6 +739,7 @@ def show_question(request, question, paper, error_message=None,
'delay_time': delay_time,
'quiz_type': quiz_type,
'all_modules': all_modules,
+ 'assignment_files': assignment_files,
}
answers = paper.get_previous_answers(question)
if answers:
@@ -819,7 +874,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None,
previous_question=current_question)
else:
user_answer = request.POST.get('answer')
- if not user_answer:
+ if not str(user_answer):
msg = "Please submit a valid answer."
return show_question(
request, current_question, paper, notification=msg,
@@ -1444,6 +1499,13 @@ def design_questionpaper(request, course_id, quiz_id, questionpaper_id=None):
question_paper.save()
question_paper.fixed_questions.add(*questions)
messages.success(request, "Questions added successfully")
+ return redirect(
+ 'yaksh:designquestionpaper',
+ course_id=course_id,
+ quiz_id=quiz_id,
+ questionpaper_id=questionpaper_id
+ )
+
else:
messages.warning(request, "Please select atleast one question")
@@ -1462,6 +1524,12 @@ def design_questionpaper(request, course_id, quiz_id, questionpaper_id=None):
question_paper.save()
question_paper.fixed_questions.remove(*question_ids)
messages.success(request, "Questions removed successfully")
+ return redirect(
+ 'yaksh:designquestionpaper',
+ course_id=course_id,
+ quiz_id=quiz_id,
+ questionpaper_id=questionpaper_id
+ )
else:
messages.warning(request, "Please select atleast one question")
@@ -1476,6 +1544,12 @@ def design_questionpaper(request, course_id, quiz_id, questionpaper_id=None):
random_set.questions.add(*random_ques)
question_paper.random_questions.add(random_set)
messages.success(request, "Questions removed successfully")
+ return redirect(
+ 'yaksh:designquestionpaper',
+ course_id=course_id,
+ quiz_id=quiz_id,
+ questionpaper_id=questionpaper_id
+ )
else:
messages.warning(request, "Please select atleast one question")
@@ -1484,6 +1558,12 @@ def design_questionpaper(request, course_id, quiz_id, questionpaper_id=None):
if random_set_ids:
question_paper.random_questions.remove(*random_set_ids)
messages.success(request, "Questions removed successfully")
+ return redirect(
+ 'yaksh:designquestionpaper',
+ course_id=course_id,
+ quiz_id=quiz_id,
+ questionpaper_id=questionpaper_id
+ )
else:
messages.warning(request, "Please select question set")
@@ -1500,8 +1580,8 @@ def design_questionpaper(request, course_id, quiz_id, questionpaper_id=None):
if questions:
questions = _remove_already_present(questionpaper_id, questions)
- question_paper.update_total_marks()
- question_paper.save()
+ question_paper.update_total_marks()
+ question_paper.save()
random_sets = question_paper.random_questions.all()
fixed_questions = question_paper.get_ordered_questions()
context = {
@@ -1730,7 +1810,6 @@ def user_data(request, user_id, questionpaper_id=None, course_id=None):
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)
@@ -1740,7 +1819,10 @@ def _expand_questions(questions, field_list):
field_list.remove('questions')
for question in questions:
field_list.insert(
- i, '{0}-{1}'.format(question.summary, question.points))
+ i, 'Q-{0}-{1}-{2}-marks'.format(question.id, question.summary,
+ question.points))
+ field_list.insert(
+ i+1, 'Q-{0}-{1}-comments'.format(question.id, question.summary))
return field_list
@@ -1800,8 +1882,18 @@ def download_quiz_csv(request, course_id, quiz_id):
'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)
+ questions_scores['Q-{0}-{1}-{2}-marks'.format(
+ question.id, question.summary, question.points)] \
+ = 'answerpaper.get_per_question_score({0})'.format(question.id)
+ answer = question.answer_set.last()
+ comment = None
+ if answer:
+ comment = answer.comment
+ else:
+ comment = ''
+ questions_scores['Q-{0}-{1}-comments'.format(
+ question.id, question.summary)] \
+ = 'answerpaper.get_answer_comment({0})'.format(question.id)
csv_fields_values.update(questions_scores)
users = users.exclude(id=course.creator.id).exclude(
@@ -1853,8 +1945,8 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None,
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()
+ course_id=course_id, question_paper_id__in=questionpaper_id
+ ).exists()
context = {
"users": user_details,
"quiz_id": quiz_id,
@@ -1873,9 +1965,9 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None,
except IndexError:
raise Http404('No attempts for paper')
has_user_assignments = AssignmentUpload.objects.filter(
- question_paper_id__in=questionpaper_id,
- user_id=user_id
- ).exists()
+ course_id=course_id, 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
@@ -1897,7 +1989,7 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None,
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 = answers[0]['answer']
answer.set_marks(marks)
answer.save()
paper.update_marks()
@@ -2136,12 +2228,12 @@ def view_answerpaper(request, questionpaper_id, 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(
+ has_user_assignments = 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}
+ "has_user_assignments": has_user_assignments}
return my_render_to_response(
request, 'yaksh/view_answerpaper.html', context
)
@@ -2409,8 +2501,10 @@ def _read_user_csv(request, reader, course):
messages.info(request, "{0} -- Missing Values".format(counter))
continue
users = User.objects.filter(username=username)
+ if not users.exists():
+ users = User.objects.filter(email=email)
if users.exists():
- user = users[0]
+ user = users.last()
if remove.strip().lower() == 'true':
_remove_from_course(user, course)
messages.info(request, "{0} -- {1} -- User rejected".format(
@@ -2504,7 +2598,23 @@ def download_sample_csv(request):
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"'
+ 'attachment; filename="sample_user_upload.csv"'
+ )
+ return response
+
+
+@login_required
+@email_verified
+def download_sample_toc(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_lesson_toc.yaml")
+ with open(csv_file_path, 'rb') as csv_file:
+ response = HttpResponse(csv_file.read(), content_type='text/yaml')
+ response['Content-Disposition'] = (
+ 'attachment; filename="sample_lesson_toc.yaml"'
)
return response
@@ -2576,6 +2686,8 @@ def edit_lesson(request, course_id=None, module_id=None, lesson_id=None):
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,
@@ -2615,10 +2727,6 @@ def edit_lesson(request, course_id=None, module_id=None, lesson_id=None):
reverse("yaksh:edit_lesson",
args=[course_id, module_id, lesson.id])
)
- else:
- context['lesson_form'] = lesson_form
- context['error'] = lesson_form["video_file"].errors
- context['lesson_file_form'] = lesson_file_form
if 'Delete' in request.POST:
remove_files_id = request.POST.getlist('delete_files')
@@ -2634,9 +2742,29 @@ def edit_lesson(request, course_id=None, module_id=None, lesson_id=None):
request, "Please select atleast one file to delete"
)
+ if 'upload_toc' in request.POST:
+ toc_file = request.FILES.get('toc')
+ file_extension = os.path.splitext(toc_file.name)[1][1:]
+ if file_extension not in ['yaml', 'yml']:
+ messages.warning(
+ request, "Please upload yaml or yml type file"
+ )
+ else:
+ try:
+ toc_data = ruamel.yaml.safe_load_all(toc_file.read())
+ results = TableOfContents.objects.add_contents(
+ course_id, lesson_id, user, toc_data)
+ for status, msg in results:
+ if status == True:
+ messages.success(request, msg)
+ else:
+ messages.warning(request, msg)
+ except Exception as e:
+ messages.warning(request, f"Error parsing yaml: {e}")
+
+ data = get_toc_contents(request, course_id, lesson_id)
+ context['toc'] = data
lesson_files = LessonFile.objects.filter(lesson=lesson)
- lesson_files_form = LessonFileForm()
- lesson_form = LessonForm(instance=lesson)
context['lesson_form'] = lesson_form
context['lesson_file_form'] = lesson_files_form
context['lesson_files'] = lesson_files
@@ -2679,16 +2807,54 @@ def show_lesson(request, lesson_id, module_id, course_id):
# update course status with current unit
_update_unit_status(course_id, user, learn_unit)
-
+ toc = TableOfContents.objects.filter(
+ course_id=course_id, lesson_id=lesson_id
+ ).order_by("time")
+ contents_by_time = json.dumps(
+ list(toc.values("id", "content", "time"))
+ )
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)
+ track, created = TrackLesson.objects.get_or_create(
+ user_id=user.id, course_id=course_id, lesson_id=lesson_id
+ )
+
+ lesson_ct = ContentType.objects.get_for_model(learn_unit.lesson)
+ title = learn_unit.lesson.name
+ try:
+ post = Post.objects.get(
+ target_ct=lesson_ct, target_id=learn_unit.lesson.id,
+ active=True, title=title
+ )
+ except Post.DoesNotExist:
+ post = Post.objects.create(
+ target_ct=lesson_ct, target_id=learn_unit.lesson.id,
+ active=True, title=title, creator=user,
+ description=f'Discussion on {title} lesson',
+ )
+ 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.anonymous = request.POST.get('anonymous', '') == 'on'
+ new_comment.save()
+ return redirect(request.path_info)
+ else:
+ raise Http404(f'Post does not exist for lesson {title}')
+ else:
+ form = CommentForm()
+ comments = post.comment.filter(active=True)
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}
+ 'learning_module': learn_module, 'toc': toc,
+ 'contents_by_time': contents_by_time, 'track_id': track.id,
+ 'comments': comments, 'form': form, 'post': post}
return my_render_to_response(request, 'yaksh/show_video.html', context)
@@ -2838,19 +3004,6 @@ def add_module(request, course_id=None, module_id=None):
@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
@@ -3328,17 +3481,22 @@ def course_forum(request, course_id):
base_template = 'manage.html'
moderator = True
course = get_object_or_404(Course, id=course_id)
+ course_ct = ContentType.objects.get_for_model(course)
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)
+ posts = Post.objects.filter(
+ Q(title__icontains=search_term) |
+ Q(description__icontains=search_term),
+ target_ct=course_ct, target_id=course.id, active=True
+ )
else:
- posts = course.post.get_queryset().filter(
- active=True).order_by('-modified_at')
- paginator = Paginator(posts, 1)
+ posts = Post.objects.filter(
+ target_ct=course_ct, target_id=course.id, active=True
+ ).order_by('-modified_at')
+ paginator = Paginator(posts, 10)
page = request.GET.get('page')
posts = paginator.get_page(page)
if request.method == "POST":
@@ -3346,7 +3504,8 @@ def course_forum(request, course_id):
if form.is_valid():
new_post = form.save(commit=False)
new_post.creator = user
- new_post.course = course
+ new_post.target = course
+ new_post.anonymous = request.POST.get('anonymous', '') == 'on'
new_post.save()
return redirect('yaksh:post_comments',
course_id=course.id, uuid=new_post.uid)
@@ -3365,6 +3524,27 @@ def course_forum(request, course_id):
@login_required
@email_verified
+def lessons_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)
+ course_ct = ContentType.objects.get_for_model(course)
+ lesson_posts = course.get_lesson_posts()
+ return render(request, 'yaksh/lessons_forum.html', {
+ 'user': user,
+ 'base_template': base_template,
+ 'moderator': moderator,
+ 'course': course,
+ 'posts': lesson_posts,
+ })
+
+
+@login_required
+@email_verified
def post_comments(request, course_id, uuid):
user = request.user
base_template = 'user.html'
@@ -3383,6 +3563,7 @@ def post_comments(request, course_id, uuid):
new_comment = form.save(commit=False)
new_comment.creator = request.user
new_comment.post_field = post
+ new_comment.anonymous = request.POST.get('anonymous', '') == 'on'
new_comment.save()
return redirect(request.path_info)
return render(request, 'yaksh/post_comments.html', {
@@ -3390,7 +3571,8 @@ def post_comments(request, course_id, uuid):
'comments': comments,
'base_template': base_template,
'form': form,
- 'user': user
+ 'user': user,
+ 'course': course
})
@@ -3400,7 +3582,9 @@ 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))
+ raise Http404(
+ 'Only a course creator or a teacher can delete the post.'
+ )
post = get_object_or_404(Post, uid=uuid)
post.comment.active = False
post.active = False
@@ -3412,11 +3596,547 @@ def hide_post(request, course_id, uuid):
@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))
+ 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(
+ 'Only a course creator or a teacher can delete the comments'
+ )
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
+ 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')
+ 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
+
+
+def get_toc_contents(request, course_id, lesson_id):
+ contents = TableOfContents.objects.filter(
+ course_id=course_id, lesson_id=lesson_id
+ ).order_by("time")
+ data = loader.render_to_string(
+ "yaksh/show_toc.html", context={
+ 'contents': contents, 'lesson_id': lesson_id
+ },
+ request=request
+ )
+ return data
+
+
+@login_required
+@email_verified
+def allow_special_attempt(request, user_id, course_id, quiz_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')
+
+ quiz = get_object_or_404(Quiz, pk=quiz_id)
+ student = get_object_or_404(User, pk=user_id)
+
+ if not course.is_enrolled(student):
+ raise Http404('The student is not enrolled for this course')
+
+ micromanager, created = MicroManager.objects.get_or_create(
+ course=course, student=student, quiz=quiz
+ )
+ micromanager.manager = user
+ micromanager.save()
+
+ if (not micromanager.is_special_attempt_required() or
+ micromanager.is_last_attempt_inprogress()):
+ name = student.get_full_name()
+ msg = '{} can attempt normally. No special attempt required!'.format(
+ name)
+ elif micromanager.can_student_attempt():
+ msg = '{} already has a special attempt!'.format(
+ student.get_full_name())
+ else:
+ micromanager.allow_special_attempt()
+ msg = 'A special attempt is provided to {}!'.format(
+ student.get_full_name())
+
+ messages.info(request, msg)
+ return redirect('yaksh:monitor', quiz_id, course_id)
+
+
+@login_required
+@email_verified
+def add_topic(request, content_type, course_id, lesson_id, toc_id=None,
+ topic_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')
+ if topic_id:
+ topic = get_object_or_404(Topic, pk=topic_id)
+ else:
+ topic = None
+ if toc_id:
+ toc = get_object_or_404(TableOfContents, pk=toc_id)
+ else:
+ toc = None
+ context = {}
+ if request.method == "POST":
+ form = TopicForm(request.POST, instance=topic)
+ if form.is_valid():
+ form.save()
+ time = request.POST.get("timer")
+ if not topic:
+ TableOfContents.objects.create(
+ content_object=form.instance, course_id=course_id,
+ lesson_id=lesson_id, content=content_type,
+ time=time
+ )
+ context['toc'] = get_toc_contents(request, course_id, lesson_id)
+ if toc:
+ toc.time = time
+ toc.save()
+ status_code = 200
+ context['success'] = True
+ context['message'] = 'Saved topic successfully'
+ else:
+ status_code = 400
+ context['success'] = False
+ context['message'] = form.errors.as_json()
+ else:
+ form = TopicForm(instance=topic, time=toc.time)
+ template_context = {'form': form, 'course_id': course.id,
+ 'lesson_id': lesson_id, 'content_type': content_type,
+ 'topic_id': topic_id, 'toc_id': toc_id}
+ data = loader.render_to_string(
+ "yaksh/add_topic.html", context=template_context, request=request
+ )
+ context['success'] = True
+ context['data'] = data
+ context['content_type'] = content_type
+ context['status'] = 1
+ status_code = 200
+ return JsonResponse(context, status=status_code)
+
+
+@login_required
+@email_verified
+def add_marker_quiz(request, content_type, course_id, lesson_id,
+ toc_id=None, question_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')
+ if question_id:
+ question = get_object_or_404(Question, pk=question_id)
+ else:
+ question = None
+ if toc_id:
+ toc = get_object_or_404(TableOfContents, pk=toc_id)
+ else:
+ toc = None
+ context = {}
+ if request.method == "POST":
+ qform = VideoQuizForm(request.POST, instance=question)
+ if qform.is_valid():
+ if not question_id:
+ 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()
+ time = request.POST.get("timer")
+ if not question:
+ TableOfContents.objects.create(
+ content_object=qform.instance, course_id=course_id,
+ lesson_id=lesson_id, content=content_type,
+ time=time
+ )
+ context['toc'] = get_toc_contents(request, course_id, lesson_id)
+ if toc:
+ toc.time = time
+ toc.save()
+ status_code = 200
+ context['success'] = True
+ context['message'] = 'Saved question successfully'
+ context['content_type'] = content_type
+ else:
+ status_code = 200
+ context['success'] = False
+ context['message'] = "Error in saving."\
+ " Please check the question test cases"
+ else:
+ status_code = 400
+ context['success'] = False
+ context['message'] = qform.errors.as_json()
+ else:
+ form = VideoQuizForm(instance=question, time=toc.time)
+ formset, tc_class = get_tc_formset(question.type, question=question)
+ template_context = {
+ 'form': form, 'course_id': course.id, 'lesson_id': lesson_id,
+ 'formset': formset, 'tc_class': tc_class, 'toc_id': toc_id,
+ 'content_type': content_type, 'question_id': question_id
+ }
+ data = loader.render_to_string(
+ "yaksh/add_video_quiz.html", context=template_context,
+ request=request
+ )
+ context['success'] = True
+ context['data'] = data
+ context['content_type'] = content_type
+ context['status'] = 2
+ status_code = 200
+ return JsonResponse(context, status=status_code)
+
+
+@login_required
+@email_verified
+def revoke_special_attempt(request, micromanager_id):
+ user = request.user
+
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page')
+
+ micromanager = get_object_or_404(MicroManager, pk=micromanager_id)
+ course = micromanager.course
+ if not course.is_creator(user) and not course.is_teacher(user):
+ raise Http404('This course does not belong to you')
+ micromanager.revoke_special_attempt()
+ msg = 'Revoked special attempt for {}'.format(
+ micromanager.student.get_full_name())
+ messages.info(request, msg)
+ return redirect(
+ 'yaksh:monitor', micromanager.quiz.id, course.id)
+
+
+@login_required
+@email_verified
+def delete_toc(request, course_id, toc_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')
+ toc = get_object_or_404(TableOfContents, pk=toc_id)
+ redirect_url = request.POST.get("redirect_url")
+ if toc.content == 1:
+ get_object_or_404(Topic, pk=toc.object_id).delete()
+ else:
+ get_object_or_404(Question, id=toc.object_id).delete()
+ messages.success(request, "Content deleted successfully")
+ return redirect(redirect_url)
+
+
+@login_required
+@email_verified
+def extend_time(request, paper_id):
+ user = request.user
+
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page')
+
+ anspaper = get_object_or_404(AnswerPaper, pk=paper_id)
+ course = anspaper.course
+ 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":
+ extra_time = float(request.POST.get('extra_time', 0))
+ if extra_time is None:
+ msg = 'Please provide time'
+ else:
+ anspaper.set_extra_time(extra_time)
+ msg = 'Extra {0} minutes given to {1}'.format(
+ extra_time, anspaper.user.get_full_name())
+ else:
+ msg = 'Bad Request'
+ messages.info(request, msg)
+ return redirect(
+ 'yaksh:monitor', anspaper.question_paper.quiz.id, course.id
+ )
+
+
+@login_required
+@email_verified
+def get_marker_quiz(request, course_id, toc_id):
+ user = request.user
+ course = get_object_or_404(Course, pk=course_id)
+ if not course.is_student(user):
+ raise Http404("You are not allowed to view this page")
+ toc = get_object_or_404(TableOfContents, pk=toc_id)
+ question = toc.content_object
+ template_context = {
+ "question": question, "course_id": course_id, "toc": toc,
+ "test_cases": question.get_test_cases()
+ }
+ data = loader.render_to_string(
+ "yaksh/show_lesson_quiz.html", context=template_context,
+ request=request
+ )
+ context = {"data": data, "success": True}
+ return JsonResponse(context)
+
+
+@login_required
+@email_verified
+def submit_marker_quiz(request, course_id, toc_id):
+ user = request.user
+ course = get_object_or_404(Course, pk=course_id)
+ if not course.is_student(user):
+ raise Http404("You are not allowed to view this page")
+ toc = get_object_or_404(TableOfContents, pk=toc_id)
+ current_question = toc.content_object
+ 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:
+ user_answer = None
+ elif current_question.type == 'float':
+ try:
+ user_answer = float(request.POST.get('answer'))
+ except ValueError:
+ user_answer = None
+ 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]
+
+ def is_valid_answer(answer):
+ status = True
+ if ((current_question.type == "mcc" or
+ current_question.type == "arrange") and not answer):
+ status = False
+ elif answer is None or not str(answer):
+ status = False
+ return status
+
+ if is_valid_answer(user_answer):
+ success = True
+ # check if graded quiz and already attempted
+ has_attempts = LessonQuizAnswer.objects.filter(
+ toc_id=toc_id, student_id=user.id).exists()
+ if ((toc.content == 2 and not has_attempts) or
+ toc.content == 3 or toc.content == 4):
+ answer = Answer.objects.create(
+ question_id=current_question.id, answer=user_answer,
+ correct=False, error=json.dumps([])
+ )
+ lesson_ans = LessonQuizAnswer.objects.create(
+ toc_id=toc_id, student=user, answer=answer
+ )
+ msg = "Answer saved successfully"
+ # call check answer only for graded quiz and exercise
+ if toc.content == 3 or toc.content == 2:
+ result = lesson_ans.check_answer(user_answer)
+ # if exercise then show custom message
+ if toc.content == 3:
+ if result.get("success"):
+ msg = "You answered the question correctly"
+ else:
+ success = False
+ msg = "You have answered the question incorrectly. "\
+ "Please refer the lesson again"
+ else:
+ msg = "You have already submitted the answer"
+ else:
+ success = False
+ msg = "Please submit a valid answer"
+ context = {"success": success, "message": msg}
+ return JsonResponse(context)
+
+
+@login_required
+@email_verified
+def lesson_statistics(request, course_id, lesson_id, toc_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')
+ context = {}
+ lesson = get_object_or_404(Lesson, id=lesson_id)
+ data = TableOfContents.objects.get_data(course_id, lesson_id)
+ context['data'] = data
+ context['lesson'] = lesson
+ context['course_id'] = course_id
+ if toc_id:
+ per_que_data = TableOfContents.objects.get_question_stats(toc_id)
+ question = per_que_data[0]
+ answers = per_que_data[1]
+ is_percent_reqd = (
+ True if question.type == "mcq" or question.type == "mcc"
+ else False
+ )
+ per_tc_ans, total_count = TableOfContents.objects.get_per_tc_ans(
+ toc_id, question.type, is_percent_reqd
+ )
+ context['per_tc_ans'] = per_tc_ans
+ context['total_count'] = total_count
+ paginator = Paginator(answers, 50)
+ context['question'] = question
+ page = request.GET.get('page')
+ per_que_data = paginator.get_page(page)
+ context['is_que_data'] = True
+ context['objects'] = per_que_data
+ return render(request, 'yaksh/show_lesson_statistics.html', context)
+
+
+@login_required
+@email_verified
+def upload_marks(request, course_id, questionpaper_id):
+ user = request.user
+ course = get_object_or_404(Course, pk=course_id)
+ question_paper = get_object_or_404(QuestionPaper, pk=questionpaper_id)
+ quiz = question_paper.quiz
+
+ 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 redirect('yaksh:monitor', quiz.id, 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 redirect('yaksh:monitor', quiz.id, course_id)
+ try:
+ reader = csv.DictReader(
+ csv_file.read().decode('utf-8').splitlines(),
+ dialect=dialect)
+ except TypeError:
+ messages.warning(request, "Bad CSV file")
+ return redirect('yaksh:monitor', quiz.id, course_id)
+ question_ids = _get_header_info(reader)
+ _read_marks_csv(request, reader, course, question_paper, question_ids)
+ return redirect('yaksh:monitor', quiz.id, course_id)
+
+
+def _get_header_info(reader):
+ user_ids, question_ids = [], []
+ fields = reader.fieldnames
+ for field in fields:
+ if field.startswith('Q') and field.count('-') > 0:
+ qid = int(field.split('-')[1])
+ if qid not in question_ids:
+ question_ids.append(qid)
+ return question_ids
+
+
+def _read_marks_csv(request, reader, course, question_paper, question_ids):
+ messages.info(request, 'Marks Uploaded!')
+ for row in reader:
+ username = row['username']
+ user = User.objects.filter(username=username).first()
+ if user:
+ answerpapers = question_paper.answerpaper_set.filter(course=course,
+ user_id=user.id)
+ else:
+ messages.info(request, '{0} user not found!'.format(username))
+ continue
+ answerpaper = answerpapers.last()
+ if not answerpaper:
+ messages.info(request, '{0} has no answerpaper!'.format(username))
+ continue
+ answers = answerpaper.answers.all()
+ questions = answerpaper.questions.all().values_list('id', flat=True)
+ for qid in question_ids:
+ question = Question.objects.filter(id=qid).first()
+ if not question:
+ messages.info(request,
+ '{0} is an invalid question id!'.format(qid))
+ continue
+ if qid in questions:
+ answer = answers.filter(question_id=qid).last()
+ if not answer:
+ answer = Answer(question_id=qid, marks=0, correct=False,
+ answer='Created During Marks Update!',
+ error=json.dumps([]))
+ answer.save()
+ answerpaper.answers.add(answer)
+ key1 = 'Q-{0}-{1}-{2}-marks'.format(qid, question.summary,
+ question.points)
+ key2 = 'Q-{0}-{1}-comments'.format(qid, question.summary,
+ question.points)
+ if key1 in reader.fieldnames:
+ try:
+ answer.set_marks(float(row[key1]))
+ except ValueError:
+ messages.info(request,
+ '{0} invalid marks!'.format(row[key1]))
+ if key2 in reader.fieldnames:
+ answer.set_comment(row[key2])
+ answer.save()
+ answerpaper.update_marks(state='completed')
+ answerpaper.save()
+ messages.info(request,
+ 'Updated successfully for user: {0}, question: {1}'.format(
+ username, question.summary))