summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--yaksh/migrations/0005_auto_20170410_1024.py26
-rw-r--r--yaksh/models.py31
-rw-r--r--yaksh/static/yaksh/js/add_question.js3
-rw-r--r--yaksh/templates/404.html4
-rw-r--r--yaksh/templates/yaksh/grade_user.html70
-rw-r--r--yaksh/templates/yaksh/view_answerpaper.html8
-rw-r--r--yaksh/test_models.py73
-rw-r--r--yaksh/urls.py6
-rw-r--r--yaksh/views.py111
9 files changed, 270 insertions, 62 deletions
diff --git a/yaksh/migrations/0005_auto_20170410_1024.py b/yaksh/migrations/0005_auto_20170410_1024.py
new file mode 100644
index 0000000..13b4cce
--- /dev/null
+++ b/yaksh/migrations/0005_auto_20170410_1024.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.5 on 2017-04-10 10:24
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('yaksh', '0004_auto_20170331_0632'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='assignmentupload',
+ name='question_paper',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='yaksh.QuestionPaper'),
+ ),
+ migrations.AlterField(
+ model_name='hooktestcase',
+ name='hook_code',
+ field=models.TextField(default='def check_answer(user_answer):\n \'\'\' Evaluates user answer to return -\n success - Boolean, indicating if code was executed correctly\n mark_fraction - Float, indicating fraction of the\n weight to a test case\n error - String, error message if success is false\n\n In case of assignment upload there will be no user answer \'\'\'\n\n success = False\n err = "Incorrect Answer" # Please make this more specific\n mark_fraction = 0.0\n\n # write your code here\n\n return success, err, mark_fraction\n\n'),
+ ),
+ ]
diff --git a/yaksh/models.py b/yaksh/models.py
index 802a1fc..6646615 100644
--- a/yaksh/models.py
+++ b/yaksh/models.py
@@ -79,7 +79,8 @@ test_status = (
def get_assignment_dir(instance, filename):
return os.sep.join((
- instance.user.username, str(instance.assignmentQuestion.id), filename
+ instance.question_paper.quiz.description, instance.user.username,
+ str(instance.assignmentQuestion.id), filename
))
@@ -1305,11 +1306,35 @@ class AnswerPaper(models.Model):
.format(u.first_name, u.last_name, q.description)
-###############################################################################
+################################################################################
+class AssignmentUploadManager(models.Manager):
+
+ def get_assignments(self, qp, que_id=None, user_id=None):
+ if que_id and user_id:
+ assignment_files = AssignmentUpload.objects.filter(
+ assignmentQuestion_id=que_id, user_id=user_id,
+ question_paper=qp
+ )
+ file_name = User.objects.get(id=user_id).get_full_name()
+ else:
+ assignment_files = AssignmentUpload.objects.filter(
+ question_paper=qp
+ )
+
+ file_name = "{0}_Assignment_files".format(
+ assignment_files[0].question_paper.quiz.description
+ )
+
+ return assignment_files, file_name
+
+
+################################################################################
class AssignmentUpload(models.Model):
user = models.ForeignKey(User)
assignmentQuestion = models.ForeignKey(Question)
assignmentFile = models.FileField(upload_to=get_assignment_dir)
+ question_paper = models.ForeignKey(QuestionPaper, blank=True, null=True)
+ objects = AssignmentUploadManager()
###############################################################################
@@ -1372,7 +1397,9 @@ class HookTestCase(TestCase):
mark_fraction - Float, indicating fraction of the
weight to a test case
error - String, error message if success is false
+
In case of assignment upload there will be no user answer '''
+
success = False
err = "Incorrect Answer" # Please make this more specific
mark_fraction = 0.0
diff --git a/yaksh/static/yaksh/js/add_question.js b/yaksh/static/yaksh/js/add_question.js
index 05752b4..5bec8c6 100644
--- a/yaksh/static/yaksh/js/add_question.js
+++ b/yaksh/static/yaksh/js/add_question.js
@@ -122,9 +122,8 @@ function textareaformat()
});
document.getElementById('my').innerHTML = document.getElementById('id_description').value ;
-
if (document.getElementById('id_grade_assignment_upload').checked ||
- document.getElementById('id_type').val() == 'upload'){
+ document.getElementById('id_type').value == 'upload'){
$("#id_grade_assignment_upload").prop("disabled", false);
}
else{
diff --git a/yaksh/templates/404.html b/yaksh/templates/404.html
index 7d33dd3..e9d99de 100644
--- a/yaksh/templates/404.html
+++ b/yaksh/templates/404.html
@@ -1,5 +1,7 @@
{% extends "base.html" %}
{% block content %}
-The requested page does not exist.
+It seems that you have encountered an error
+Type of Error - {{ exception }}
+Please contact your administrator
{% endblock %}
diff --git a/yaksh/templates/yaksh/grade_user.html b/yaksh/templates/yaksh/grade_user.html
index 67f25a4..c93ec10 100644
--- a/yaksh/templates/yaksh/grade_user.html
+++ b/yaksh/templates/yaksh/grade_user.html
@@ -51,6 +51,12 @@
{% endif %}
{% endif %}
+{% if has_quiz_assignments %}
+
+<a href="{{URL_ROOT}}/exam/manage/download/quiz_assignments/{{quiz_id}}/">
+ Download All Assignments</a>
+{% endif %}
+
<div id = "paper" class="col-md-10">
{% if data %}
@@ -84,7 +90,6 @@ Attempt Number: <b>{{paper.attempt_number}} </b>
</option>
{% endfor %}
</select>
-
<br/>Questions correctly answered: {{ paper.get_answered_str }} <br/>
Total attempts at questions: {{ paper.answers.count }} <br/>
Marks obtained: {{ paper.marks_obtained }} <br/>
@@ -99,7 +104,6 @@ Status : <b style="color: red;"> Failed </b><br/>
Status : <b style="color: green;"> Passed </b><br/>
{% endif %}
</p>
-
{% if paper.answers.count %}
<h4> Report </h4><br>
@@ -126,8 +130,8 @@ Status : <b style="color: green;"> Passed </b><br/>
{% endif %}
method="post">
{% csrf_token %}
-{% for question, answers in paper.get_question_answers.items %}
+{% for question, answers in paper.get_question_answers.items %}
<div class="panel panel-info">
<div class="panel-heading">
<strong> Details: {{forloop.counter}}. {{ question.summary }}
@@ -157,7 +161,6 @@ Status : <b style="color: green;"> Passed </b><br/>
<strong>{{ testcase.error_margin|safe }}</strong>
{% endif %}
{% endfor %}
-
{% else %}
<h5> <u>Test cases: </u></h5>
{% for testcase in question.get_test_cases %}
@@ -167,29 +170,50 @@ Status : <b style="color: green;"> Passed </b><br/>
</div>
</div>
<h5>Student answer: </h5>
- {% for ans in answers %}
- {% if ans.answer.correct %}
- <div class="panel panel-success">
- <div class="panel-heading">Correct answer:
+ {% if question.type == "upload" %}
+ {% if has_user_assignments %}
+ <a href="{{URL_ROOT}}/exam/manage/download/user_assignment/{{question.id}}/{{data.user.id}}/{{paper.question_paper.quiz.id}}">
+ <div class="panel">
+ Assignment File for {{ data.user.get_full_name.title }}
+ </div>
+ </a>
+ {% with answers|last as answer%}
+ {% if answer.answer.correct %}
+ <div class="panel panel-success">
+ <div class="panel-heading">Correct answer</div></div>
+ {% else %}
+ <div class="panel panel-danger">
+ <div class="panel-heading">Incorrect Answer</div></div>
+ {% endif %}
+ {% endwith %}
+ {% else %}
+ <h5>No Assignment submitted by {{ data.user.get_full_name.title }}</h5>
+ {% endif %}
+ {% else %}
+ {% for ans in answers %}
+ {% if ans.answer.correct %}
+ <div class="panel panel-success">
+ <div class="panel-heading">Correct answer:
+ {% else %}
+ <div class="panel panel-danger">
+ <div class="panel-heading">Error:
+ {% endif %}
+ {% for err in ans.error_list %}
+ <div><pre>{{ err }}</pre></div>
+ {% endfor %}
+ </div>
+ <div class="panel-body">
+ {% if question.type != "code" %}
+ <div class="well well-sm">
+ {{ ans.answer.answer.strip|safe }}
+ </div>
{% else %}
- <div class="panel panel-danger">
- <div class="panel-heading">Error:
+ <pre><code>{{ ans.answer.answer.strip|safe }}</code></pre>
{% endif %}
- {% for err in ans.error_list %}
- <div><pre>{{ err }}</pre></div>
+ </div>
+ </div>
{% endfor %}
- </div>
- <div class="panel-body">
- {% if question.type != "code" %}
- <div class="well well-sm">
- {{ ans.answer.answer.strip|safe }}
- </div>
- {% else %}
- <pre><code>{{ ans.answer.answer.strip|safe }}</code></pre>
{% endif %}
- </div>
- </div>
- {% endfor %}
{% with answers|last as answer %}
Marks: <input id="q{{ question.id }}" type="text"
name="q{{ question.id }}_marks" size="4"
diff --git a/yaksh/templates/yaksh/view_answerpaper.html b/yaksh/templates/yaksh/view_answerpaper.html
index 4520ac3..f4c8846 100644
--- a/yaksh/templates/yaksh/view_answerpaper.html
+++ b/yaksh/templates/yaksh/view_answerpaper.html
@@ -84,7 +84,13 @@
<h5><u>Student answer:</u></h5>
<div class="well well-sm">
{{ answers.0.answer|safe }}
- </div>
+ {% if question.type == "upload" and has_user_assignment %}
+ <a href="{{URL_ROOT}}/exam/download/user_assignment/{{question.id}}/{{data.user.id}}/{{paper.question_paper.quiz.id}}">
+ <div class="panel">
+ Assignment File for {{ data.user.get_full_name.title }}
+ </div></a>
+ {% endif %}
+ </div>
</div>
</div>
{% else %}
diff --git a/yaksh/test_models.py b/yaksh/test_models.py
index dbd367b..9bd8492 100644
--- a/yaksh/test_models.py
+++ b/yaksh/test_models.py
@@ -1,7 +1,7 @@
import unittest
from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\
QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\
- StdIOBasedTestCase, FileUpload, McqTestCase
+ StdIOBasedTestCase, FileUpload, McqTestCase, AssignmentUpload
import json
from datetime import datetime, timedelta
from django.utils import timezone
@@ -68,12 +68,7 @@ def tearDownModule():
Quiz.objects.all().delete()
Course.objects.all().delete()
QuestionPaper.objects.all().delete()
-
- que_id_list = ["25", "22", "24", "27"]
- for que_id in que_id_list:
- dir_path = os.path.join(os.getcwd(), "yaksh", "data","question_{0}".format(que_id))
- if os.path.exists(dir_path):
- shutil.rmtree(dir_path)
+
###############################################################################
class ProfileTestCases(unittest.TestCase):
@@ -1034,3 +1029,67 @@ class TestCaseTestCases(unittest.TestCase):
exp_data = json.loads(self.answer_data_json)
self.assertEqual(actual_data['metadata']['user_answer'], exp_data['metadata']['user_answer'])
self.assertEqual(actual_data['test_case_data'], exp_data['test_case_data'])
+
+
+class AssignmentUploadTestCases(unittest.TestCase):
+ def setUp(self):
+ self.user1 = User.objects.get(username="demo_user")
+ self.user1.first_name = "demo"
+ self.user1.last_name = "user"
+ self.user1.save()
+ self.user2 = User.objects.get(username="demo_user3")
+ self.user2.first_name = "demo"
+ self.user2.last_name = "user3"
+ self.user2.save()
+ self.quiz = Quiz.objects.get(description="demo quiz 1")
+
+ self.questionpaper = QuestionPaper.objects.create(quiz=self.quiz,
+ total_marks=0.0,
+ shuffle_questions=True
+ )
+ self.question = Question.objects.create(summary='Assignment',
+ language='Python',
+ type='upload',
+ active=True,
+ description='Upload a file',
+ points=1.0,
+ snippet='',
+ user=self.user1
+ )
+ self.questionpaper.fixed_question_order = "{0}".format(self.question.id)
+ self.questionpaper.fixed_questions.add(self.question)
+ file_path1 = os.path.join(tempfile.gettempdir(), "upload1.txt")
+ file_path2 = os.path.join(tempfile.gettempdir(), "upload2.txt")
+ self.assignment1 = AssignmentUpload.objects.create(user=self.user1,
+ assignmentQuestion=self.question, assignmentFile=file_path1,
+ question_paper=self.questionpaper
+ )
+ self.assignment2 = AssignmentUpload.objects.create(user=self.user2,
+ assignmentQuestion=self.question, assignmentFile=file_path2,
+ question_paper=self.questionpaper
+ )
+
+ def test_get_assignments_for_user_files(self):
+ assignment_files, file_name = AssignmentUpload.objects.get_assignments(
+ self.questionpaper, self.question.id,
+ self.user1.id
+ )
+ self.assertIn("upload1.txt", assignment_files[0].assignmentFile.name)
+ self.assertEqual(assignment_files[0].user, self.user1)
+ actual_file_name = self.user1.get_full_name().replace(" ", "_")
+ file_name = file_name.replace(" ", "_")
+ self.assertEqual(file_name, actual_file_name)
+
+ def test_get_assignments_for_quiz_files(self):
+ assignment_files, file_name = AssignmentUpload.objects.get_assignments(
+ self.questionpaper
+ )
+ files = [os.path.basename(file.assignmentFile.name)
+ for file in assignment_files]
+ question_papers = [file.question_paper for file in assignment_files]
+ self.assertIn("upload1.txt", files)
+ self.assertIn("upload2.txt", files)
+ self.assertEqual(question_papers[0].quiz, self.questionpaper.quiz)
+ actual_file_name = self.quiz.description.replace(" ", "_")
+ file_name = file_name.replace(" ", "_")
+ self.assertIn(actual_file_name, file_name)
diff --git a/yaksh/urls.py b/yaksh/urls.py
index 91f4137..8ddfe67 100644
--- a/yaksh/urls.py
+++ b/yaksh/urls.py
@@ -26,6 +26,8 @@ urlpatterns = [
url(r'^enroll_request/(?P<course_id>\d+)/$', views.enroll_request, name='enroll_request'),
url(r'^self_enroll/(?P<course_id>\d+)/$', views.self_enroll, name='self_enroll'),
url(r'^view_answerpaper/(?P<questionpaper_id>\d+)/$', views.view_answerpaper, name='view_answerpaper'),
+ url(r'^download/user_assignment/(?P<question_id>\d+)/(?P<user_id>\d+)/(?P<quiz_id>\d+)$',
+ views.download_assignment_file, name="download_user_assignment"),
url(r'^manage/$', views.prof_manage, name='manage'),
url(r'^manage/addquestion/$', views.add_question),
url(r'^manage/addquestion/(?P<question_id>\d+)/$', views.add_question),
@@ -91,4 +93,8 @@ urlpatterns = [
url(r'^manage/create_demo_course/$', views.create_demo_course),
url(r'^manage/courses/download_course_csv/(?P<course_id>\d+)/$',
views.download_course_csv),
+ url(r'^manage/download/user_assignment/(?P<question_id>\d+)/(?P<user_id>\d+)/(?P<quiz_id>\d+)/$',
+ views.download_assignment_file),
+ url(r'^manage/download/quiz_assignments/(?P<quiz_id>\d+)/$',
+ views.download_assignment_file)
]
diff --git a/yaksh/views.py b/yaksh/views.py
index 27276ae..44f06b1 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -22,6 +22,11 @@ from taggit.models import Tag
from itertools import chain
import json
import six
+import zipfile
+try:
+ from StringIO import StringIO as string_io
+except ImportError:
+ from io import BytesIO as string_io
# Local imports.
from yaksh.models import get_model_class, Quiz, Question, QuestionPaper, QuestionSet, Course
from yaksh.models import Profile, Answer, AnswerPaper, User, TestCase, FileUpload,\
@@ -494,22 +499,33 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None):
elif current_question.type == 'upload':
# if time-up at upload question then the form is submitted without
# validation
- if 'assignment' in request.FILES:
- assignment_filename = request.FILES.getlist('assignment')
+ assignment_filename = request.FILES.getlist('assignment')
+ if not assignment_filename:
+ msg = "Please upload assignment file"
+ return show_question(request, current_question, paper, notification=msg)
+
for fname in assignment_filename:
- if AssignmentUpload.objects.filter(
- assignmentQuestion=current_question,
- assignmentFile__icontains=fname, user=user).exists():
- assign_file = AssignmentUpload.objects.get(
- assignmentQuestion=current_question,
- assignmentFile__icontains=fname, user=user)
+ assignment_files = AssignmentUpload.objects.filter(
+ assignmentQuestion=current_question,
+ assignmentFile__icontains=fname, user=user,
+ question_paper=questionpaper_id)
+ if assignment_files.exists():
+ assign_file = assignment_files.get(
+ assignmentQuestion=current_question,
+ assignmentFile__icontains=fname, user=user,
+ question_paper=questionpaper_id)
os.remove(assign_file.assignmentFile.path)
assign_file.delete()
AssignmentUpload.objects.create(user=user,
- assignmentQuestion=current_question, assignmentFile=fname
+ assignmentQuestion=current_question, 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)
else:
@@ -913,14 +929,15 @@ def design_questionpaper(request, quiz_id, questionpaper_id=None):
if 'remove-fixed' in request.POST:
question_ids = request.POST.getlist('added-questions', None)
- 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()
+ 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)
if 'add-random' in request.POST:
@@ -1106,7 +1123,13 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None):
if not quiz.course.is_creator(current_user) and not \
quiz.course.is_teacher(current_user):
raise Http404('This course does not belong to you')
- context = {"users": user_details, "quiz_id": quiz_id, "quiz": quiz}
+
+ has_quiz_assignments = AssignmentUpload.objects.filter(
+ question_paper_id=questionpaper_id
+ ).exists()
+ context = {"users": user_details, "quiz_id": quiz_id, "quiz":quiz,
+ "has_quiz_assignments": has_quiz_assignments
+ }
if user_id is not None:
attempts = AnswerPaper.objects.get_user_all_attempts\
@@ -1116,23 +1139,27 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None):
attempt_number = attempts[0].attempt_number
except IndexError:
raise Http404('No attempts for paper')
-
+ has_user_assignments = AssignmentUpload.objects.filter(
+ question_paper_id=questionpaper_id,
+ user_id=user_id
+ ).exists()
user = User.objects.get(id=user_id)
data = AnswerPaper.objects.get_user_data(user, questionpaper_id,
attempt_number
)
-
context = {'data': data, "quiz_id": quiz_id, "users": user_details,
- "attempts": attempts, "user_id": user_id
+ "attempts": attempts, "user_id": user_id,
+ "has_user_assignments": has_user_assignments,
+ "has_quiz_assignments": has_quiz_assignments
}
if request.method == "POST":
papers = data['papers']
for paper in papers:
- for question, answers, errors in six.iteritems(paper.get_question_answers()):
+ for question, answers in six.iteritems(paper.get_question_answers()):
marks = float(request.POST.get('q%d_marks' % question.id, 0))
- answers = answers[-1]
- answers.set_marks(marks)
- answers.save()
+ 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')
@@ -1313,7 +1340,10 @@ def view_answerpaper(request, questionpaper_id):
quiz = get_object_or_404(QuestionPaper, pk=questionpaper_id).quiz
if quiz.view_answerpaper and user in quiz.course.students.all():
data = AnswerPaper.objects.get_user_data(user, questionpaper_id)
- context = {'data': data, 'quiz': quiz}
+ has_user_assignment = AssignmentUpload.objects.filter(user=user,
+ question_paper_id=questionpaper_id).exists()
+ context = {'data': data, 'quiz': quiz,
+ "has_user_assignment":has_user_assignment}
return my_render_to_response('yaksh/view_answerpaper.html', context)
else:
return my_redirect('/exam/quizzes/')
@@ -1410,3 +1440,32 @@ def download_course_csv(request, course_id):
for student in students:
writer.writerow(student)
return response
+
+
+@login_required
+def download_assignment_file(request, quiz_id, question_id=None, user_id=None):
+ user = request.user
+ qp = QuestionPaper.objects.get(quiz_id=quiz_id)
+ assignment_files, file_name = AssignmentUpload.objects.get_assignments(qp,
+ question_id,
+ user_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