From db8aa57e9a3caf9b2ed080b440703d634abd14a1 Mon Sep 17 00:00:00 2001 From: adityacp Date: Mon, 15 May 2017 15:00:48 +0530 Subject: Add send_bulk_mail function to send mails to students enrolled in a course --- yaksh/send_emails.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/yaksh/send_emails.py b/yaksh/send_emails.py index 37736d5..bcf633f 100644 --- a/yaksh/send_emails.py +++ b/yaksh/send_emails.py @@ -8,11 +8,14 @@ import hashlib from random import randint from textwrap import dedent import smtplib +import os # Django imports from django.utils.crypto import get_random_string from django.conf import settings -from django.core.mail import send_mass_mail, send_mail +from django.core.mail import EmailMultiAlternatives, send_mail +from django.core.files.storage import default_storage +from django.core.files.base import ContentFile def generate_activation_key(username): @@ -56,3 +59,30 @@ def send_user_mail(user_mail, key): success = False return success, msg + +def send_bulk_mail(subject, email_body, recipients, attachments): + try: + text_msg = "Yaksh" + msg = EmailMultiAlternatives(subject, text_msg, settings.SENDER_EMAIL, + recipients + ) + msg.attach_alternative(email_body, "text/html") + if attachments: + for file in attachments: + path = default_storage.save('attachments/'+file.name, + ContentFile(file.read()) + ) + msg.attach_file(os.sep.join((settings.MEDIA_ROOT, path)), + mimetype="text/html" + ) + default_storage.delete(path) + msg.send() + + message = "Email Sent Successfully" + + except Exception as exc_msg: + message = """Error: {0}. Please check email address.\ + If email address is correct then + Please contact {1}.""".format(exc_msg, settings.REPLY_EMAIL) + + return message -- cgit From bd255f751dbff97d92e9c34652af37c67b62b2ff Mon Sep 17 00:00:00 2001 From: adityacp Date: Mon, 15 May 2017 15:01:10 +0530 Subject: Change urls.py and views.py - Change views to send mails to students enrolled in a course - Add reverse resolution in urls.py --- yaksh/urls.py | 2 +- yaksh/views.py | 31 +++++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/yaksh/urls.py b/yaksh/urls.py index e4676d3..825e5f5 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -79,7 +79,7 @@ urlpatterns = [ url(r'manage/enroll/rejected/(?P\d+)/$', views.enroll, {'was_rejected': True}), url(r'manage/enrolled/reject/(?P\d+)/$', - views.reject, {'was_enrolled': True}), + views.reject, {'was_enrolled': True}, name="reject_users"), url(r'^manage/searchteacher/(?P\d+)/$', views.search_teacher), url(r'^manage/addteacher/(?P\d+)/$', views.add_teacher, name='add_teacher'), url(r'^manage/remove_teachers/(?P\d+)/$', views.remove_teachers, name='remove_teacher'), diff --git a/yaksh/views.py b/yaksh/views.py index c7af5cc..41db80e 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -42,7 +42,7 @@ from yaksh.forms import UserRegisterForm, UserLoginForm, QuizForm,\ from .settings import URL_ROOT from yaksh.models import AssignmentUpload from .file_utils import extract_files -from .send_emails import send_user_mail, generate_activation_key +from .send_emails import send_user_mail, generate_activation_key, send_bulk_mail from .decorators import email_verified @@ -771,12 +771,35 @@ def reject(request, course_id, user_id=None, was_enrolled=False): raise Http404('This course does not belong to you') if request.method == 'POST': - reject_ids = request.POST.getlist('check') + user_ids = request.POST.getlist('check') + if not user_ids: + message = "Please select atleast one User" + return my_render_to_response('yaksh/course_detail.html', + {'course': course, "message": message}, + context_instance=ci) + + if request.POST.get('send_mail') == 'send_mail': + users = User.objects.filter(id__in=user_ids) + recipients = [user.email for user 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) + + return my_render_to_response('yaksh/course_detail.html', + {'course': course, "message": message}, + context_instance=ci) + + if request.POST.get('reject') == 'reject': + reject_ids = user_ids else: reject_ids = [user_id] if not reject_ids: - return my_render_to_response('yaksh/course_detail.html', {'course': course}, - context_instance=ci) + message = "Please select atleast one User" + return my_render_to_response('yaksh/course_detail.html', + {'course': course, "message": message}, + context_instance=ci) users = User.objects.filter(id__in=reject_ids) course.reject(was_enrolled, *users) return course_detail(request, course_id) -- cgit From 2d62359adf671d5a75201a2314825039adc7291a Mon Sep 17 00:00:00 2001 From: adityacp Date: Mon, 15 May 2017 15:03:21 +0530 Subject: Change course.js and course_detail.html - Allow moderator to send emails to students enrollded in a course - Add validations to check email data in course.js --- yaksh/static/yaksh/js/course.js | 45 ++++++++++++++++++++++++ yaksh/templates/yaksh/course_detail.html | 59 +++++++++++++++++++++++++------- 2 files changed, 92 insertions(+), 12 deletions(-) diff --git a/yaksh/static/yaksh/js/course.js b/yaksh/static/yaksh/js/course.js index 5b79e68..3e214be 100644 --- a/yaksh/static/yaksh/js/course.js +++ b/yaksh/static/yaksh/js/course.js @@ -35,4 +35,49 @@ $(".reject").change( function(){ }); } }); + +$(function() { + $('textarea#email_body').froalaEditor({ + heightMin: 100, + heightMax: 200 + }) + }); + +var status; +var btn_name; + +$("#send_mail").click(function(){ + btn_name = "send_mail"; + var subject = $("#subject").val(); + var body = $('#email_body').val(); + if (subject == '' || body == ''){ + status = false; + $("#error_msg").html("Please enter email details"); + $("#dialog").dialog(); + } + else{ + status = true; + } +}); + +$('#reject-form').submit(function(eventObj) { + if (btn_name == 'send_mail'){ + if(status == false){ + return false; + } + } + var selected = []; + $('#reject input:checked').each(function() { + selected.push($(this).attr('value')); + }); + if(selected.length > 0){ + return true; + } + else{ + $("#error_msg").html("Please select atleast one user"); + $( "#dialog" ).dialog(); + return false; + } +}); + }); diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html index cd4144f..2cbdf8b 100644 --- a/yaksh/templates/yaksh/course_detail.html +++ b/yaksh/templates/yaksh/course_detail.html @@ -6,6 +6,14 @@ {% block script %} + + +{% endblock %} +{% block css %} + + + + {% endblock %} {% block content %}
@@ -29,11 +37,20 @@
+ {% if message %} + + {% endif %}
Requests

{% if course.get_requests %}  Select all
+
+ {% csrf_token %} @@ -43,9 +60,7 @@ - - {% csrf_token %} - {% for request in course.get_requests %} + {% for request in course.get_requests %} @@ -76,6 +91,8 @@ {% if course.get_enrolled %}  Select all
+ + {% csrf_token %}
Institute Department Enroll/Reject
{{ forloop.counter }}.
@@ -86,9 +103,7 @@ {% for enrolled in course.get_enrolled %} - - {% csrf_token %} - + @@ -99,11 +114,27 @@ - + + {% endfor %}
Department Reject
{{ forloop.counter }}. {{ enrolled.get_full_name|title }} Reject -
- + + + Click Here to send email to students + +
+
+ +

+
+ Attachments: +
+ +
+

+
{% endif %} @@ -114,6 +145,8 @@ {% if course.get_rejected %}  Select all
+
+ {% csrf_token %} @@ -123,9 +156,7 @@ - {% for rejected in course.get_rejected %} - - {% csrf_token %} + {% for rejected in course.get_rejected %} @@ -151,4 +182,8 @@ + +
+

+
{% endblock %} -- cgit From eff546b51b7b0d025b48472b4238325f406f06db Mon Sep 17 00:00:00 2001 From: adityacp Date: Mon, 15 May 2017 15:06:11 +0530 Subject: Add tests to check bulk email sending --- yaksh/test_views.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 37e5ce4..7018bd2 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -8,6 +8,7 @@ from django.test import TestCase from django.test import Client from django.utils import timezone from django.core import mail +from django.core.files.uploadedfile import SimpleUploadedFile from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\ @@ -1072,6 +1073,57 @@ class TestCourseDetail(TestCase): self.assertTemplateUsed(response, 'yaksh/course_detail.html') + def test_send_mail_to_course_students(self): + """ Check if bulk mail is sent to multiple students enrolled in a course + """ + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + self.student2 = User.objects.create_user( + username='demo_student2', + password=self.student_plaintext_pass, + first_name='student_first_name', + last_name='student_last_name', + email='demo_student2@test.com' + ) + self.student3 = User.objects.create_user( + username='demo_student3', + password=self.student_plaintext_pass, + first_name='student_first_name', + last_name='student_last_name', + email='demo_student3@test.com' + ) + self.student4 = User.objects.create_user( + username='demo_student4', + password=self.student_plaintext_pass, + first_name='student_first_name', + last_name='student_last_name', + email='demo_student4@test.com' + ) + user_ids = [self.student.id, self.student2.id, self.student3.id, + self.student4.id] + user_emails = [self.student.email, self.student2.email, + self.student3.email, self.student4.email] + + self.user1_course.students.add(*user_ids) + attachment = SimpleUploadedFile("file.txt", b"Test") + response = self.client.post(reverse('yaksh:reject_users', + kwargs={'course_id': self.user1_course.id}), + data={'send_mail': 'send_mail', 'email_attach': [attachment], + 'subject': 'test_bulk_mail', 'body': 'Test_Mail', + 'check': user_ids} + ) + attachment_file = mail.outbox[0].attachments[0][0] + subject = mail.outbox[0].subject + body = mail.outbox[0].alternatives[0][0] + recipients = mail.outbox[0].recipients() + self.assertEqual(attachment_file, "file.txt") + self.assertEqual(subject, "test_bulk_mail") + self.assertEqual(body, "Test_Mail") + self.assertSequenceEqual(recipients, user_emails) + + class TestEnrollRequest(TestCase): def setUp(self): self.client = Client() -- cgit From 689e777cf21e30b33fd9fccbd6c2cd5dc4d935ea Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Wed, 31 May 2017 19:43:52 +0530 Subject: Validate quiz prerequisite field --- yaksh/forms.py | 16 +++++++++++++--- yaksh/views.py | 28 +++++++++++----------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/yaksh/forms.py b/yaksh/forms.py index 3459be9..14a3db0 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -181,9 +181,13 @@ class QuizForm(forms.ModelForm): user = kwargs.pop('user') course_id = kwargs.pop('course') super(QuizForm, self).__init__(*args, **kwargs) - self.fields['prerequisite'] = forms.ModelChoiceField( - queryset=Quiz.objects.filter(course__id=course_id, - is_trial=False)) + + prerequisite_list = Quiz.objects.filter( + course__id=course_id, + is_trial=False + ).exclude(id=self.instance.id) + + self.fields['prerequisite'] = forms.ModelChoiceField(prerequisite_list) self.fields['prerequisite'].required = False self.fields['course'] = forms.ModelChoiceField( queryset=Course.objects.filter(id=course_id), empty_label=None) @@ -240,6 +244,12 @@ class QuizForm(forms.ModelForm):

""") + def clean_prerequisite(self): + prereq = self.cleaned_data['prerequisite'] + if prereq and prereq.prerequisite.id == self.instance.id: + raise forms.ValidationError("Please set another prerequisite quiz") + return None + class Meta: model = Quiz exclude = ["is_trial"] diff --git a/yaksh/views.py b/yaksh/views.py index 7db0366..8746a57 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -256,30 +256,24 @@ def add_quiz(request, course_id, quiz_id=None): if form.is_valid(): form.save() return my_redirect("/exam/manage/courses/") - else: - context["form"] = form - return my_render_to_response('yaksh/add_quiz.html', - context, - context_instance=ci) + else: quiz = Quiz.objects.get(id=quiz_id) form = QuizForm(request.POST, user=user, course=course_id, - instance=quiz) + instance=quiz + ) if form.is_valid(): form.save() - context["quiz_id"] = quiz_id return my_redirect("/exam/manage/courses/") + else: - if quiz_id is None: - form = QuizForm(course=course_id, user=user) - else: - quiz = Quiz.objects.get(id=quiz_id) - form = QuizForm(user=user,course=course_id, instance=quiz) - context["quiz_id"] = quiz_id - context["form"] = form - return my_render_to_response('yaksh/add_quiz.html', - context, - context_instance=ci) + quiz = Quiz.objects.get(id=quiz_id) if quiz_id else None + form = QuizForm(user=user,course=course_id, instance=quiz) + context["quiz_id"] = quiz_id + context["form"] = form + return my_render_to_response('yaksh/add_quiz.html', + context, + context_instance=ci) @login_required -- cgit From c277c1f742d8d984fc7ab066cd3ac513e83f0d89 Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Wed, 31 May 2017 19:44:43 +0530 Subject: Fix conditional logic to pass test --- yaksh/forms.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/yaksh/forms.py b/yaksh/forms.py index 14a3db0..2740497 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -246,9 +246,10 @@ class QuizForm(forms.ModelForm): def clean_prerequisite(self): prereq = self.cleaned_data['prerequisite'] - if prereq and prereq.prerequisite.id == self.instance.id: - raise forms.ValidationError("Please set another prerequisite quiz") - return None + if prereq and prereq.prerequisite: + if prereq.prerequisite.id == self.instance.id: + raise forms.ValidationError("Please set another prerequisite quiz") + return prereq class Meta: model = Quiz -- cgit From 928ebb23f8b2926240fc220791655614cc4192d2 Mon Sep 17 00:00:00 2001 From: Date: Mon, 12 Jun 2017 18:19:21 +0530 Subject: Change views.py and urls.py - Add new url for sending email - Add new view function to send email to students --- yaksh/urls.py | 1 + yaksh/views.py | 27 ++++++++++++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/yaksh/urls.py b/yaksh/urls.py index 825e5f5..068df7a 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -69,6 +69,7 @@ urlpatterns = [ url(r'manage/enroll/rejected/(?P\d+)/(?P\d+)/$', views.enroll, {'was_rejected': True}), url(r'manage/reject/(?P\d+)/(?P\d+)/$', views.reject), + url(r'manage/send_mail/(?P\d+)/$', views.send_mail, name="send_mail"), url(r'manage/enrolled/reject/(?P\d+)/(?P\d+)/$', views.reject, {'was_enrolled': True}), url(r'manage/toggle_status/(?P\d+)/$', views.toggle_course_status), diff --git a/yaksh/views.py b/yaksh/views.py index 41db80e..3319773 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -757,10 +757,9 @@ def enroll(request, course_id, user_id=None, was_rejected=False): course.enroll(was_rejected, *users) return course_detail(request, course_id) - @login_required @email_verified -def reject(request, course_id, user_id=None, was_enrolled=False): +def send_mail(request, course_id, user_id=None): user = request.user ci = RequestContext(request) if not is_moderator(user): @@ -770,6 +769,7 @@ def reject(request, course_id, user_id=None, was_enrolled=False): 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 not user_ids: @@ -787,12 +787,25 @@ def reject(request, course_id, user_id=None, was_enrolled=False): message = send_bulk_mail(subject, email_body, recipients, attachments) - return my_render_to_response('yaksh/course_detail.html', - {'course': course, "message": message}, - context_instance=ci) + return my_render_to_response('yaksh/course_detail.html', + {'course': course, "message": message, + 'msg': 'mail'}, + context_instance=ci) - if request.POST.get('reject') == 'reject': - reject_ids = user_ids +@login_required +@email_verified +def reject(request, course_id, user_id=None, was_enrolled=False): + user = request.user + ci = RequestContext(request) + 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 request.method == 'POST': + reject_ids = request.POST.getlist('check') else: reject_ids = [user_id] if not reject_ids: -- cgit From a7c9591eac66eb2751a2706b5686a997d7c236dd Mon Sep 17 00:00:00 2001 From: adityacp Date: Mon, 12 Jun 2017 18:22:21 +0530 Subject: Add separate form to send emails --- yaksh/static/yaksh/js/course.js | 2 +- yaksh/templates/yaksh/course_detail.html | 67 ++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/yaksh/static/yaksh/js/course.js b/yaksh/static/yaksh/js/course.js index 3e214be..74ad37c 100644 --- a/yaksh/static/yaksh/js/course.js +++ b/yaksh/static/yaksh/js/course.js @@ -60,7 +60,7 @@ $("#send_mail").click(function(){ } }); -$('#reject-form').submit(function(eventObj) { +$('#send_mail_form').submit(function(eventObj) { if (btn_name == 'send_mail'){ if(status == false){ return false; diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html index 2cbdf8b..676d7f3 100644 --- a/yaksh/templates/yaksh/course_detail.html +++ b/yaksh/templates/yaksh/course_detail.html @@ -21,9 +21,13 @@ @@ -44,6 +52,47 @@ {% endif %} + {% if msg == 'mail' %} +
+
Send Mails to Students

+ {% if course.get_enrolled %} +  Select all +
+ + {% csrf_token %} +
Institute Department Enroll
{{ forloop.counter }}.
+ + + + + + + + {% for enrolled in course.get_enrolled %} + + + + + + + + + + {% endfor %} +
Full NameEmailRoll NumberInstituteDepartment
{{ forloop.counter }}. {{ enrolled.get_full_name|title }} {{enrolled.email}} {{enrolled.profile.roll_number}} {{enrolled.profile.institute}} {{enrolled.profile.department}}
+
+ +

+
+ Attachments: +
+ +
+ {% endif %} + +
+ {% else %}
Requests

{% if course.get_requests %} @@ -91,7 +140,7 @@ {% if course.get_enrolled %}  Select all
-
+ {% csrf_token %} @@ -118,21 +167,6 @@ {% endfor %}
- - - Click Here to send email to students - -
-
- -

-
- Attachments: -
- -
-

@@ -180,6 +214,7 @@ {% endif %}
+ {% endif %}
-- cgit From a002ab59dbff856ee3838078cbe4f8fa439fd894 Mon Sep 17 00:00:00 2001 From: adityacp Date: Mon, 12 Jun 2017 18:23:17 +0530 Subject: Change views tests to check send email --- yaksh/test_views.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 7018bd2..caec9d4 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -1108,7 +1108,7 @@ class TestCourseDetail(TestCase): self.user1_course.students.add(*user_ids) attachment = SimpleUploadedFile("file.txt", b"Test") - response = self.client.post(reverse('yaksh:reject_users', + response = self.client.post(reverse('yaksh:send_mail', kwargs={'course_id': self.user1_course.id}), data={'send_mail': 'send_mail', 'email_attach': [attachment], 'subject': 'test_bulk_mail', 'body': 'Test_Mail', @@ -1123,6 +1123,20 @@ class TestCourseDetail(TestCase): self.assertEqual(body, "Test_Mail") self.assertSequenceEqual(recipients, user_emails) + # Test for get request in send mail + get_response = self.client.get(reverse('yaksh:send_mail', + kwargs={'course_id': self.user1_course.id} + )) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['course'], self.user1_course) + + # Test if no users are selected + post_response = self.client.post(reverse('yaksh:send_mail', + kwargs={'course_id': self.user1_course.id}), + data={'check': []} + ) + self.assertIn('select', post_response.context['message']) + class TestEnrollRequest(TestCase): def setUp(self): -- cgit From 57eaf342d16f33b8ea67b89c78907d9c1f7b4632 Mon Sep 17 00:00:00 2001 From: adityacp Date: Wed, 14 Jun 2017 12:33:45 +0530 Subject: Change send_emails.py and course_detail.html - Change variable assignment in send_emails.py - Add proper indentation in course_detail.html --- yaksh/send_emails.py | 2 +- yaksh/templates/yaksh/course_detail.html | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/yaksh/send_emails.py b/yaksh/send_emails.py index 4465ce9..ae49f23 100644 --- a/yaksh/send_emails.py +++ b/yaksh/send_emails.py @@ -63,7 +63,7 @@ def send_user_mail(user_mail, key): def send_bulk_mail(subject, email_body, recipients, attachments): try: - text_msg = "Yaksh" + text_msg = "" msg = EmailMultiAlternatives(subject, text_msg, settings.SENDER_EMAIL, recipients ) diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html index 676d7f3..4ff380e 100644 --- a/yaksh/templates/yaksh/course_detail.html +++ b/yaksh/templates/yaksh/course_detail.html @@ -22,11 +22,15 @@ -- cgit From a116370a56ccfb43cbd97e7588e0d0a6a19453d8 Mon Sep 17 00:00:00 2001 From: mahesh Date: Fri, 11 Aug 2017 15:31:18 +0530 Subject: Literal scalar values for yaml dump --- yaksh/models.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/yaksh/models.py b/yaksh/models.py index fde203c..479a0ec 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -24,6 +24,7 @@ import shutil import zipfile import tempfile from textwrap import dedent +from ast import literal_eval from .file_utils import extract_files, delete_files from yaksh.xmlrpc_clients import code_server from django.conf import settings @@ -388,15 +389,20 @@ class Question(models.Model): questions_dict = [] zip_file_name = string_io() zip_file = zipfile.ZipFile(zip_file_name, "a") + all_keys = [] for question in questions: test_case = question.get_test_cases() - file_names = question._add_and_get_files(zip_file) + file_names = str(question._add_and_get_files(zip_file)) q_dict = model_to_dict(question, exclude=['id', 'user','tags']) - q_dict['testcase']= [case.get_field_value() for case in test_case] + testcases = [] + for case in test_case: + all_keys.extend(list(case.get_field_value().keys())) + testcases.append(case.get_field_value()) + q_dict['testcase'] = testcases q_dict['files'] = file_names - + all_keys.extend(list(q_dict.keys())) questions_dict.append(q_dict) - question._add_yaml_to_zip(zip_file, questions_dict) + question._add_yaml_to_zip(zip_file, questions_dict, all_keys) return zip_file_name def load_questions(self, questions_list, user, file_path=None, @@ -405,7 +411,7 @@ class Question(models.Model): questions = yaml.safe_load_all(questions_list) for question in questions: question['user'] = user - file_names = question.pop('files') + file_names = literal_eval(question.pop('files')) test_cases = question.pop('testcase') que, result = Question.objects.get_or_create(**question) if file_names: @@ -478,11 +484,22 @@ class Question(models.Model): file_upload.extract = extract file_upload.file.save(file_name, django_file, save=True) - def _add_yaml_to_zip(self, zip_file, q_dict): + def _add_yaml_to_zip(self, zip_file, q_dict, all_keys): tmp_file_path = tempfile.mkdtemp() yaml_path = os.path.join(tmp_file_path, "questions_dump.yaml") + + def literal_representer(dumper, data): + data = data.replace("\r\n", "\n") + if not data in all_keys: + return dumper.represent_scalar('tag:yaml.org,2002:str', + data, style='|') + else: + return dumper.represent_scalar('tag:yaml.org,2002:str', data) + + yaml.add_representer(str,literal_representer) with open(yaml_path, "w") as yaml_file: - yaml.safe_dump_all(q_dict, yaml_file, default_flow_style=False) + yaml.dump_all(q_dict, yaml_file, default_flow_style=False, + allow_unicode=True) zip_file.write(yaml_path, os.path.basename(yaml_path)) zip_file.close() shutil.rmtree(tmp_file_path) -- cgit From 67f3119a2e27673694db907d21f501bc8247fce1 Mon Sep 17 00:00:00 2001 From: mahesh Date: Wed, 16 Aug 2017 18:28:26 +0530 Subject: Adds ruamel.yaml instead of pyyaml - Preserves escape characters - Adds ruamel.yaml in requirements --- requirements/requirements-common.txt | 2 +- yaksh/fixtures/demo_questions.zip | Bin 1712 -> 4089 bytes yaksh/models.py | 59 +++++++++++++++++---------------- yaksh/static/yaksh/js/show_question.js | 2 +- yaksh/test_models.py | 2 +- 5 files changed, 34 insertions(+), 31 deletions(-) diff --git a/requirements/requirements-common.txt b/requirements/requirements-common.txt index 6169c9b..100d693 100644 --- a/requirements/requirements-common.txt +++ b/requirements/requirements-common.txt @@ -6,4 +6,4 @@ tornado selenium==2.53.6 coverage psutil -pyyaml \ No newline at end of file +ruamel.yaml==0.15.23 diff --git a/yaksh/fixtures/demo_questions.zip b/yaksh/fixtures/demo_questions.zip index f9c5c2f..577cb86 100644 Binary files a/yaksh/fixtures/demo_questions.zip and b/yaksh/fixtures/demo_questions.zip differ diff --git a/yaksh/models.py b/yaksh/models.py index 479a0ec..7169d37 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -1,7 +1,8 @@ from __future__ import unicode_literals from datetime import datetime, timedelta import json -import yaml +import ruamel.yaml +from ruamel.yaml.scalarstring import PreservedScalarString from random import sample from collections import Counter from django.db import models @@ -383,39 +384,37 @@ class Question(models.Model): return json.dumps(question_data) def dump_questions(self, question_ids, user): - questions = Question.objects.filter( - id__in=question_ids, user_id=user.id, active=True - ) + questions = Question.objects.filter(id__in=question_ids, + user_id=user.id, active=True + ) questions_dict = [] zip_file_name = string_io() zip_file = zipfile.ZipFile(zip_file_name, "a") - all_keys = [] for question in questions: test_case = question.get_test_cases() - file_names = str(question._add_and_get_files(zip_file)) + file_names = question._add_and_get_files(zip_file) q_dict = model_to_dict(question, exclude=['id', 'user','tags']) testcases = [] for case in test_case: - all_keys.extend(list(case.get_field_value().keys())) testcases.append(case.get_field_value()) q_dict['testcase'] = testcases q_dict['files'] = file_names - all_keys.extend(list(q_dict.keys())) questions_dict.append(q_dict) - question._add_yaml_to_zip(zip_file, questions_dict, all_keys) + question._add_yaml_to_zip(zip_file, questions_dict) return zip_file_name def load_questions(self, questions_list, user, file_path=None, files_list=None): try: - questions = yaml.safe_load_all(questions_list) + questions = ruamel.yaml.safe_load_all(questions_list) for question in questions: question['user'] = user - file_names = literal_eval(question.pop('files')) + file_names = question.pop('files') test_cases = question.pop('testcase') que, result = Question.objects.get_or_create(**question) - if file_names: + if file_names!="[]": que._add_files_to_db(file_names, file_path) + for test_case in test_cases: test_case_type = test_case.pop('test_case_type') model_class = get_model_class(test_case_type) @@ -423,10 +422,10 @@ class Question(models.Model): model_class.objects.get_or_create( question=que, **test_case ) - new_test_case.type = test_case_type - new_test_case.save() + new_test_case.type = test_case_type + new_test_case.save() msg = "Questions Uploaded Successfully" - except yaml.scanner.ScannerError as exc_msg: + except ruamel.yaml.scanner.ScannerError as exc_msg: msg = "Error Parsing Yaml: {0}".format(exc_msg) return msg @@ -484,22 +483,26 @@ class Question(models.Model): file_upload.extract = extract file_upload.file.save(file_name, django_file, save=True) - def _add_yaml_to_zip(self, zip_file, q_dict, all_keys): + def _add_yaml_to_zip(self, zip_file, q_dict): tmp_file_path = tempfile.mkdtemp() yaml_path = os.path.join(tmp_file_path, "questions_dump.yaml") - def literal_representer(dumper, data): - data = data.replace("\r\n", "\n") - if not data in all_keys: - return dumper.represent_scalar('tag:yaml.org,2002:str', - data, style='|') - else: - return dumper.represent_scalar('tag:yaml.org,2002:str', data) - - yaml.add_representer(str,literal_representer) - with open(yaml_path, "w") as yaml_file: - yaml.dump_all(q_dict, yaml_file, default_flow_style=False, - allow_unicode=True) + def _dict_walk(question_dict): + for k,v in question_dict.items(): + if isinstance(v, list): + for nested_v in v: + if isinstance(nested_v, dict): + _dict_walk(nested_v) + elif v and isinstance(v,str): + question_dict[k] = PreservedScalarString(v) + for elem in q_dict: + _dict_walk(elem) + with open(yaml_path, "a") as yaml_file: + ruamel.yaml.round_trip_dump(elem, yaml_file, + default_flow_style=False, + explicit_start=True, + allow_unicode=True + ) zip_file.write(yaml_path, os.path.basename(yaml_path)) zip_file.close() shutil.rmtree(tmp_file_path) diff --git a/yaksh/static/yaksh/js/show_question.js b/yaksh/static/yaksh/js/show_question.js index 7cfbf4c..e7cd817 100644 --- a/yaksh/static/yaksh/js/show_question.js +++ b/yaksh/static/yaksh/js/show_question.js @@ -39,5 +39,5 @@ function confirm_edit(frm) } $(document).ready(function() { - $("#questions-table").tablesorter({sortList: [[]]}); + $("#questions-table").tablesorter({sortList: [[0,0], [4,0]]}); }); \ No newline at end of file diff --git a/yaksh/test_models.py b/yaksh/test_models.py index 8dc3244..d5ac394 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -3,7 +3,7 @@ from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\ StdIOBasedTestCase, FileUpload, McqTestCase, AssignmentUpload import json -import yaml +import ruamel.yaml as yaml from datetime import datetime, timedelta from django.utils import timezone import pytz -- cgit From b9bf03024cd29440e57a1a647dab022e6719ac46 Mon Sep 17 00:00:00 2001 From: adityacp Date: Thu, 17 Aug 2017 10:26:44 +0530 Subject: Pep8 change in settings.py --- online_test/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/online_test/settings.py b/online_test/settings.py index 4ca2967..90cce9d 100644 --- a/online_test/settings.py +++ b/online_test/settings.py @@ -113,7 +113,8 @@ EMAIL_HOST_USER = 'email_host_user' EMAIL_HOST_PASSWORD = 'email_host_password' -# Set EMAIL_BACKEND to 'django.core.mail.backends.smtp.EmailBackend' in production +# Set EMAIL_BACKEND to 'django.core.mail.backends.smtp.EmailBackend' +# in production EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend' # SENDER_EMAIL, REPLY_EMAIL, PRODUCTION_URL, IS_DEVELOPMENT are used in email -- cgit From ca630cd1a3464bc665f06ade7c6e78efa27e93cb Mon Sep 17 00:00:00 2001 From: adityacp Date: Thu, 17 Aug 2017 10:40:43 +0530 Subject: Change course.js to validate email form details --- yaksh/static/yaksh/js/course.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/yaksh/static/yaksh/js/course.js b/yaksh/static/yaksh/js/course.js index 74ad37c..0eeb017 100644 --- a/yaksh/static/yaksh/js/course.js +++ b/yaksh/static/yaksh/js/course.js @@ -43,37 +43,30 @@ $(function() { }) }); -var status; -var btn_name; - $("#send_mail").click(function(){ - btn_name = "send_mail"; var subject = $("#subject").val(); var body = $('#email_body').val(); + var status; if (subject == '' || body == ''){ status = false; - $("#error_msg").html("Please enter email details"); + $("#error_msg").html("Please enter mail details"); $("#dialog").dialog(); } else{ status = true; } + return status; }); $('#send_mail_form').submit(function(eventObj) { - if (btn_name == 'send_mail'){ - if(status == false){ - return false; - } - } var selected = []; $('#reject input:checked').each(function() { selected.push($(this).attr('value')); }); - if(selected.length > 0){ + if(selected.length > 0) { return true; } - else{ + else { $("#error_msg").html("Please select atleast one user"); $( "#dialog" ).dialog(); return false; -- cgit From 8c1d2ffb0670998c35bd963d5458d245ac97c5fd Mon Sep 17 00:00:00 2001 From: adityacp Date: Thu, 17 Aug 2017 10:45:38 +0530 Subject: Course Detail Template change - Change input html tag to textarea for email subject - Change confussing variable name from template --- yaksh/templates/yaksh/course_detail.html | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html index 4ff380e..1f0e894 100644 --- a/yaksh/templates/yaksh/course_detail.html +++ b/yaksh/templates/yaksh/course_detail.html @@ -10,8 +10,7 @@ {% endblock %} {% block css %} - - + {% endblock %} @@ -21,7 +20,7 @@
{% endif %} - {% if msg == 'mail' %} + {% if status == 'mail' %}
Send Mails to Students

{% if course.get_enrolled %} @@ -85,7 +84,7 @@ {% endfor %}
- +


Attachments: -- cgit From 4f305d1e2ccce8b2250624838a29d2000d0d9230 Mon Sep 17 00:00:00 2001 From: adityacp Date: Thu, 17 Aug 2017 10:47:50 +0530 Subject: Change views.py - Pep8 changes - Remove redundant check from send mail function - Move validation send mail validation to js --- yaksh/views.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/yaksh/views.py b/yaksh/views.py index ffdcf98..b4ab9bb 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -732,6 +732,7 @@ def enroll(request, course_id, user_id=None, was_rejected=False): course.enroll(was_rejected, *users) return course_detail(request, course_id) + @login_required @email_verified def send_mail(request, course_id, user_id=None): @@ -747,25 +748,23 @@ def send_mail(request, course_id, user_id=None): message = None if request.method == 'POST': user_ids = request.POST.getlist('check') - if not user_ids: - message = "Please select atleast one User" - return my_render_to_response('yaksh/course_detail.html', - {'course': course, "message": message}, - context_instance=ci) - if request.POST.get('send_mail') == 'send_mail': users = User.objects.filter(id__in=user_ids) - recipients = [user.email for user in users] + 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) + message = send_bulk_mail( + subject, email_body, recipients, attachments + ) + context = { + 'course': course, 'message': message, + 'status': 'mail' + } + return my_render_to_response( + 'yaksh/course_detail.html', context, context_instance=ci + ) - return my_render_to_response('yaksh/course_detail.html', - {'course': course, "message": message, - 'msg': 'mail'}, - context_instance=ci) @login_required @email_verified -- cgit From 30bd1c36a4abea6c6afea3a52d3a84d335c5673a Mon Sep 17 00:00:00 2001 From: adityacp Date: Thu, 17 Aug 2017 10:50:52 +0530 Subject: Change test_views.py - Remove validation test case for send mail - Pep8 changes --- yaksh/test_views.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 3c5b0dd..763f5ff 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -1869,16 +1869,19 @@ class TestCourseDetail(TestCase): user_ids = [self.student.id, self.student2.id, self.student3.id, self.student4.id] user_emails = [self.student.email, self.student2.email, - self.student3.email, self.student4.email] + self.student3.email, self.student4.email] self.user1_course.students.add(*user_ids) attachment = SimpleUploadedFile("file.txt", b"Test") - response = self.client.post(reverse('yaksh:send_mail', - kwargs={'course_id': self.user1_course.id}), - data={'send_mail': 'send_mail', 'email_attach': [attachment], - 'subject': 'test_bulk_mail', 'body': 'Test_Mail', - 'check': user_ids} - ) + email_data = { + 'send_mail': 'send_mail', 'email_attach': [attachment], + 'subject': 'test_bulk_mail', 'body': 'Test_Mail', + 'check': user_ids + } + self.client.post(reverse( + 'yaksh:send_mail', kwargs={'course_id': self.user1_course.id}), + data=email_data + ) attachment_file = mail.outbox[0].attachments[0][0] subject = mail.outbox[0].subject body = mail.outbox[0].alternatives[0][0] @@ -1889,18 +1892,12 @@ class TestCourseDetail(TestCase): self.assertSequenceEqual(recipients, user_emails) # Test for get request in send mail - get_response = self.client.get(reverse('yaksh:send_mail', - kwargs={'course_id': self.user1_course.id} - )) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.context['course'], self.user1_course) - - # Test if no users are selected - post_response = self.client.post(reverse('yaksh:send_mail', - kwargs={'course_id': self.user1_course.id}), - data={'check': []} - ) - self.assertIn('select', post_response.context['message']) + get_response = self.client.get(reverse( + 'yaksh:send_mail', kwargs={'course_id': self.user1_course.id}) + ) + self.assertEqual(get_response.status_code, 200) + self.assertEqual(get_response.context['course'], self.user1_course) + self.assertEqual(get_response.context['status'], 'mail') class TestEnrollRequest(TestCase): -- cgit From 66fc59e724a2a92a8f0b372e64514f42695e66bf Mon Sep 17 00:00:00 2001 From: mahesh Date: Thu, 17 Aug 2017 15:56:04 +0530 Subject: Fixes in question template, submit buttons properly rendered in w.r.t. question type --- yaksh/templates/yaksh/question.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yaksh/templates/yaksh/question.html b/yaksh/templates/yaksh/question.html index ee33523..74dd8c3 100644 --- a/yaksh/templates/yaksh/question.html +++ b/yaksh/templates/yaksh/question.html @@ -238,8 +238,8 @@ function call_skip(url) {% endif %}
- {% if question.type == "mcq" or "mcc" or "integer" or "float" or "string" %} -
  
+ {% if question.type == "mcq" or question.type == "mcc" or question.type == "integer" or question.type == "float" or question.type == "string" %} +
   {% elif question.type == "upload" %}
   -- cgit From 98386cf63e6f574b38477564b96a5bcab25f2f8f Mon Sep 17 00:00:00 2001 From: mahesh Date: Thu, 17 Aug 2017 18:52:55 +0530 Subject: Adds yaml file containing all types of questions --- yaksh/demo_templates/yaml_question_template | 429 ++++++++++++++++++++++------ yaksh/fixtures/demo_questions.zip | Bin 4089 -> 8587 bytes yaksh/models.py | 25 +- 3 files changed, 362 insertions(+), 92 deletions(-) diff --git a/yaksh/demo_templates/yaml_question_template b/yaksh/demo_templates/yaml_question_template index 4de9274..065072c 100644 --- a/yaksh/demo_templates/yaml_question_template +++ b/yaksh/demo_templates/yaml_question_template @@ -1,90 +1,357 @@ --- -# Yaml Template for writing questions -# Always keep the name of this file as questions_dump.yaml -# Zip this file with necessary dependent files. - +active: true # question status = true or false +language: |- # bash, scilab, python, c/c++, java + python +partial_grading: false +snippet: '' +summary: |- + Roots of quadratic equation +type: |- + integer +grade_assignment_upload: false +description: |- # Entire question description. + Type in the box below, one of the roots to the following quadratic equation? +
+ + + x + 2 + + + + x + - + 6 + = + 0 + +points: 1.0 +testcase: +- test_case_type: |- + integertestcase + correct: 2 +- test_case_type: |- + integertestcase + correct: -3 +files: [] +--- active: true -# question status = true or false - -description: "Write a function product(a,b) which will take - two integers and return the product of it.
- " -# Entire question description. - +language: |- + python +partial_grading: false +snippet: '' +summary: |- + Print Output +type: |- + string +grade_assignment_upload: false +description: |- + What is the output for the following code in Python 2.x: +
+ + print(2, "Hello"*2, ":" ,"Bye") + +points: 1.0 +testcase: +- string_check: |- #exact or lower + exact + test_case_type: |- + stringtestcase + correct: |- + (2, 'HelloHello', ':', 'Bye') files: [] -# [[file name, zip_extract_status=true or false]] - -language: python -# bash, scilab, python, c/c++, java - +--- +active: true +language: |- + python +partial_grading: false +snippet: '' +summary: |- + Adding decimals +type: |- + float +grade_assignment_upload: false +description: |- + Write down the resultant value of the following - +
3.4566+2.122
+  

points: 1.0 -# marks in float - -snippet: "def sum(a,b):" -# adds in the student's code area - -summary: Product of two numbers -# one line short Summary testcase: -- test_case_type: standardtestcase - test_case: assert sum(3,5) == 15 - test_case_args: "" +- test_case_type: |- + floattestcase + correct: 5.5786 + error_margin: 0.0 +files: [] +--- +active: true +language: |- + bash +partial_grading: false +snippet: |- + #!/bin/bash +summary: |- + Extract columns from files +type: |- + code +grade_assignment_upload: false +description: |- + Write a bash script that takes exactly three file arguments. + + The first argument (file1.csv) would have 3 columns, the second argument (file2.csv) would have 2 columns. The third argument (file3.csv) would have 2 columns. +

+ All files have columns delimited by : (colon). +

+ We need the 2nd column from file1.csv to be removed and concatenated BEFORE file2.csv and this concatenated file should come BESIDE file3.csv. + + Left is file3.csv, and the LATER columns come from file1.csv and file2.csv. +

+ The delimiter while putting the files BESIDE each other should again be : (colon) +

+
+  Note: - Do not hard-code the filenames. They will be passed in as arguments.
+  Assume no headers (to avoid header-non-repetition issues).
+  
+points: 2.0 +testcase: +- test_case: |- + #!/bin/bash + cat $1 | cut -d: -f2 | paste -d: $3 - $2 weight: 1.0 -#testcase 1 - -- test_case_type: standardtestcase - test_case: assert sum(10,10) == 100 - test_case_args: "" + test_case_type: |- + standardtestcase + test_case_args: |- + file1.csv file2.csv file3.csv +files: +- - file1.csv + - false +- - file2.csv + - false +- - file3.csv + - false +--- +active: true +language: |- + python +partial_grading: false +snippet: |- + def is_palindrome(s): +summary: |- + Check Palindrome +type: |- + code +grade_assignment_upload: false +description: |- + Write a function is_palindrome(arg) which will take one string argument. +
+ Return True if the argument is palindrome & False otherwise. +
+ The function should be case sensitive. +

+ For Example:
is_palindrome("Hello") should return False
+points: 2.0 +testcase: +- test_case: |- + assert is_palindrome("hello") == False + weight: 1.0 + test_case_type: |- + standardtestcase + test_case_args: '' +- test_case: |- + assert is_palindrome("nitin") == True + weight: 1.0 + test_case_type: |- + standardtestcase + test_case_args: '' +- test_case: |- + assert is_palindrome("madaM") == False weight: 1.0 -#testcase 2 - -# for standard testcase: - # - test_case_type: standardtestcase - # test_case: test case in the selected language - # test_case_args: command line args (only for bash) - # weight: weightage for each course - -# for stdIO testcase: - # - test_case_type: stdiobasedtestcase - # expected_input: Standard input given to the students' code. (Optional) - # expected_input: Standard output expected from the students' code. - # weight: weightage for each course - -# for MCQ/MCC testcase: - # - test_case_type: mcqtestcase - # options: MCQ/MCC option. - # correct: true or false. - -# for Hook testcase: - # - test_case_type: hooktestcase - # hook_code: Selected language code written by moderator (Optional) - # weight: weightage for each course - -# for Integer testcase: - # - test_case_type: integertestcase - # correct: Correct integer value - -# for Float testcase: - # - test_case_type: floattestcase - # correct: Correct float value - # error_margin: Margin of error allowed - -# for String testcase: - # - test_case_type: stringtestcase - # correct: Exact string to be compared - # string_check: lower or exact.(case insensitive or sensitive) - -type: code -# mcq, Single Correct Choice, -# mcc, Multiple Correct Choices, -# code, Code Question, -# upload, Assignment Upload, -# integer, Answer in Integer, -# string, Answer in String, -# float, Answer in Float - + test_case_type: |- + standardtestcase + test_case_args: '' +files: [] +--- +active: true +language: |- + python +partial_grading: true +snippet: '' +summary: |- + For Loop over String +type: |- + code grade_assignment_upload: false -# Grade uploaded assignment (works with hook)true or false - +description: |- + Write a python script that accepts a string as input +
+ The script must print each character of the string using a for loop. + + For example; +
+  Input:
+  box
+  Output
+  b
+  o
+  x
+  
+points: 1.0 +testcase: +- expected_output: |- + s + t + r + i + n + g + weight: 1 + test_case_type: |- + stdiobasedtestcase + expected_input: |- + string +- expected_output: |- + s + + t + + o + + p + + s + + i + + g + + n + weight: 1 + test_case_type: |- + stdiobasedtestcase + expected_input: |- + s t o p s i g n +- hook_code: |- + def check_answer(user_answer): + ''' Evaluates user answer to return - + success - Boolean, indicating if code was executed correctly + 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 = "You are using while in your code." + mark_fraction = 0.0 + + if not 'while' in user_answer: + success=True + err = "Correct Answer" + mark_fraction = 1.0 + return success, err, mark_fraction + test_case_type: |- + hooktestcase + weight: 1.0 +files: [] +--- +active: true +language: |- + c partial_grading: false -# partial grading with respect to each testcase. +snippet: '' +summary: |- + Add 3 numbers +type: |- + code +grade_assignment_upload: false +description: |- + Write a program to add 3 numbers. +
+ Function Name is to be called add +
+

+
+  Note: You do not have to print anything, neither you have to make the function call. 
+  Just define the function to perform the required operation, return the output & click on check answer.
+  Also, note that the function name should exactly be as mentioned above.
+  
+points: 2.0 +testcase: +- test_case: |- + #include + #include + + extern int add(int, int, int); + + template + void check(T expect,T result) + { + if (expect == result) + { + printf("\nCorrect:\n Expected %d got %d \n",expect,result); + } + else + { + printf("\nIncorrect:\n Expected %d got %d \n",expect,result); + exit (1); + } + } + + int main(void) + { + int result; + result = add(0,0,0); + printf("Input submitted to the function: 0, 0, 0"); + check(0, result); + result = add(2,3,3); + printf("Input submitted to the function: 2, 3, 3"); + check(8,result); + printf("All Correct\n"); + } + weight: 1.0 + test_case_type: |- + standardtestcase + test_case_args: '' +files: [] +--- +active: true +language: |- + python +partial_grading: false +snippet: '' +summary: |- + Hello World in File +type: |- + upload +grade_assignment_upload: true +description: |- + Upload a file called new.txt which contains the string Hello, World! in it. +points: 1.0 +testcase: +- hook_code: |- + def check_answer(user_answer): + ''' Evaluates user answer to return - + success - Boolean, indicating if code was executed correctly + 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 + + try: + with open('new.txt', 'r') as f: + if "Hello, World!" in f.read(): + success = True + err = "Correct Answer" + mark_fraction = 1.0 + else: + err = "Did not found string Hello, World! in file." + except IOError: + err = "File new.txt not found." + return success, err, mark_fraction + test_case_type: |- + hooktestcase + weight: 1.0 +files: [] diff --git a/yaksh/fixtures/demo_questions.zip b/yaksh/fixtures/demo_questions.zip index 577cb86..08b06c4 100644 Binary files a/yaksh/fixtures/demo_questions.zip and b/yaksh/fixtures/demo_questions.zip differ diff --git a/yaksh/models.py b/yaksh/models.py index 7169d37..dfffb53 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -407,25 +407,28 @@ class Question(models.Model): files_list=None): try: questions = ruamel.yaml.safe_load_all(questions_list) + msg = "Questions Uploaded Successfully" for question in questions: question['user'] = user file_names = question.pop('files') test_cases = question.pop('testcase') que, result = Question.objects.get_or_create(**question) - if file_names!="[]": + if file_names: que._add_files_to_db(file_names, file_path) for test_case in test_cases: - test_case_type = test_case.pop('test_case_type') - model_class = get_model_class(test_case_type) - new_test_case, obj_create_status = \ - model_class.objects.get_or_create( - question=que, **test_case - ) - new_test_case.type = test_case_type - new_test_case.save() - msg = "Questions Uploaded Successfully" - except ruamel.yaml.scanner.ScannerError as exc_msg: + try: + test_case_type = test_case.pop('test_case_type') + model_class = get_model_class(test_case_type) + new_test_case, obj_create_status = \ + model_class.objects.get_or_create( + question=que, **test_case + ) + new_test_case.type = test_case_type + new_test_case.save() + except: + msg = "File not correct." + except Exception as exc_msg: msg = "Error Parsing Yaml: {0}".format(exc_msg) return msg -- cgit From 5d37ce12b0fbd01756221abe2a001eb35415bccc Mon Sep 17 00:00:00 2001 From: mahesh Date: Fri, 18 Aug 2017 00:38:53 +0530 Subject: Adds a function dict_to_yaml in models --- yaksh/models.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/yaksh/models.py b/yaksh/models.py index dfffb53..4113a62 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -106,6 +106,22 @@ def get_upload_dir(instance, filename): 'question_%s' % (instance.question.id), filename )) +def dict_to_yaml(dictionary, path_to_file=None): + for k,v in dictionary.items(): + if isinstance(v, list): + for nested_v in v: + if isinstance(nested_v, dict): + dict_to_yaml(nested_v) + elif v and isinstance(v,str): + dictionary[k] = PreservedScalarString(v) + if path_to_file: + with open(path_to_file, "a") as yaml_file: + ruamel.yaml.round_trip_dump(dictionary, yaml_file, + default_flow_style=False, + explicit_start=True, + allow_unicode=True + ) + ############################################################################### class CourseManager(models.Manager): @@ -489,23 +505,8 @@ class Question(models.Model): def _add_yaml_to_zip(self, zip_file, q_dict): tmp_file_path = tempfile.mkdtemp() yaml_path = os.path.join(tmp_file_path, "questions_dump.yaml") - - def _dict_walk(question_dict): - for k,v in question_dict.items(): - if isinstance(v, list): - for nested_v in v: - if isinstance(nested_v, dict): - _dict_walk(nested_v) - elif v and isinstance(v,str): - question_dict[k] = PreservedScalarString(v) for elem in q_dict: - _dict_walk(elem) - with open(yaml_path, "a") as yaml_file: - ruamel.yaml.round_trip_dump(elem, yaml_file, - default_flow_style=False, - explicit_start=True, - allow_unicode=True - ) + dict_to_yaml(elem, yaml_path) zip_file.write(yaml_path, os.path.basename(yaml_path)) zip_file.close() shutil.rmtree(tmp_file_path) -- cgit From a9c4aab85fb9b8edce53212548f8d0c285832dc4 Mon Sep 17 00:00:00 2001 From: mahesh Date: Fri, 18 Aug 2017 02:12:37 +0530 Subject: Adds tags in yaml files --- yaksh/demo_templates/yaml_question_template | 399 ++++++++++++++++++---------- yaksh/fixtures/demo_questions.zip | Bin 8587 -> 3015 bytes yaksh/models.py | 8 +- 3 files changed, 258 insertions(+), 149 deletions(-) diff --git a/yaksh/demo_templates/yaml_question_template b/yaksh/demo_templates/yaml_question_template index 065072c..1309c06 100644 --- a/yaksh/demo_templates/yaml_question_template +++ b/yaksh/demo_templates/yaml_question_template @@ -1,15 +1,26 @@ --- -active: true # question status = true or false +testcase: +- test_case_type: |- + integertestcase + correct: 2 +- test_case_type: |- + integertestcase + correct: -3 +files: [] language: |- # bash, scilab, python, c/c++, java - python + python partial_grading: false +tags: +- python +- quadratic +- demo +- integer +points: 1.0 snippet: '' -summary: |- - Roots of quadratic equation +active: true type: |- integer -grade_assignment_upload: false -description: |- # Entire question description. +description: |- Type in the box below, one of the roots to the following quadratic equation?
@@ -24,75 +35,106 @@ description: |- # Entire question description. = 0 -points: 1.0 +summary: |- + Roots of quadratic equation +grade_assignment_upload: false +--- testcase: - test_case_type: |- - integertestcase - correct: 2 -- test_case_type: |- - integertestcase - correct: -3 + stringtestcase + correct: |- + (2, 'HelloHello', ':', 'Bye') + string_check: |- # exact or lower + exact files: [] ---- -active: true language: |- python partial_grading: false +tags: +- python +- demo +- print +- string +- '1' +- case_sensitive +- casesensitive +- python2 +points: 1.0 snippet: '' -summary: |- - Print Output +active: true type: |- string -grade_assignment_upload: false description: |- What is the output for the following code in Python 2.x:
print(2, "Hello"*2, ":" ,"Bye") -points: 1.0 +summary: |- + Print Output +grade_assignment_upload: false +--- testcase: -- string_check: |- #exact or lower - exact - test_case_type: |- - stringtestcase - correct: |- - (2, 'HelloHello', ':', 'Bye') +- test_case_type: |- + floattestcase + correct: 5.5786 + error_margin: 0.0 files: [] ---- -active: true language: |- python partial_grading: false +tags: +- float +- '1' +- python +- decimal +- demo +points: 1.0 snippet: '' -summary: |- - Adding decimals +active: true type: |- float -grade_assignment_upload: false description: |- Write down the resultant value of the following -
3.4566+2.122
   

-points: 1.0 +summary: |- + Adding decimals +grade_assignment_upload: false +--- testcase: - test_case_type: |- - floattestcase - correct: 5.5786 - error_margin: 0.0 -files: [] ---- -active: true + standardtestcase + test_case_args: |- + file1.csv file2.csv file3.csv + test_case: |- + #!/bin/bash + cat $1 | cut -d: -f2 | paste -d: $3 - $2 + weight: 1.0 +files: +- - file1.csv + - false +- - file2.csv + - false +- - file3.csv + - false language: |- bash partial_grading: false +tags: +- demo +- code +- files +- concatenate +- bash +- delimiter +- '2' +points: 2.0 snippet: |- #!/bin/bash -summary: |- - Extract columns from files +active: true type: |- code -grade_assignment_upload: false description: |- Write a bash script that takes exactly three file arguments. @@ -110,104 +152,78 @@ description: |- Note: - Do not hard-code the filenames. They will be passed in as arguments. Assume no headers (to avoid header-non-repetition issues). -points: 2.0 -testcase: -- test_case: |- - #!/bin/bash - cat $1 | cut -d: -f2 | paste -d: $3 - $2 - weight: 1.0 - test_case_type: |- - standardtestcase - test_case_args: |- - file1.csv file2.csv file3.csv -files: -- - file1.csv - - false -- - file2.csv - - false -- - file3.csv - - false ---- -active: true -language: |- - python -partial_grading: false -snippet: |- - def is_palindrome(s): summary: |- - Check Palindrome -type: |- - code + Extract columns from files grade_assignment_upload: false -description: |- - Write a function is_palindrome(arg) which will take one string argument. -
- Return True if the argument is palindrome & False otherwise. -
- The function should be case sensitive. -

- For Example:
is_palindrome("Hello") should return False
-points: 2.0 +--- testcase: -- test_case: |- +- test_case_type: |- + standardtestcase + test_case_args: '' + test_case: |- assert is_palindrome("hello") == False weight: 1.0 - test_case_type: |- +- test_case_type: |- standardtestcase test_case_args: '' -- test_case: |- + test_case: |- assert is_palindrome("nitin") == True weight: 1.0 - test_case_type: |- +- test_case_type: |- standardtestcase test_case_args: '' -- test_case: |- + test_case: |- assert is_palindrome("madaM") == False weight: 1.0 - test_case_type: |- - standardtestcase - test_case_args: '' files: [] ---- -active: true language: |- python -partial_grading: true -snippet: '' -summary: |- - For Loop over String +partial_grading: false +tags: +- python +- assertion +- palindrome +- reverse +- code +- '2' +- demo +points: 2.0 +snippet: |- + def is_palindrome(s): +active: true type: |- code -grade_assignment_upload: false description: |- - Write a python script that accepts a string as input + Write a function is_palindrome(arg) which will take one string argument.
- The script must print each character of the string using a for loop. - - For example; -
-  Input:
-  box
-  Output
-  b
-  o
-  x
-  
-points: 1.0 + Return True if the argument is palindrome & False otherwise. +
+ The function should be case sensitive. +

+ For Example:
is_palindrome("Hello") should return False
+summary: |- + Check Palindrome +grade_assignment_upload: false +--- testcase: -- expected_output: |- +- test_case_type: |- + stdiobasedtestcase + weight: 1 + expected_input: |- + string + expected_output: |- s t r i n g - weight: 1 - test_case_type: |- +- test_case_type: |- stdiobasedtestcase + weight: 1 expected_input: |- - string -- expected_output: |- + s t o p s i g n + expected_output: |- s t @@ -223,12 +239,9 @@ testcase: g n - weight: 1 - test_case_type: |- - stdiobasedtestcase - expected_input: |- - s t o p s i g n -- hook_code: |- +- test_case_type: |- + hooktestcase + hook_code: |- def check_answer(user_answer): ''' Evaluates user answer to return - success - Boolean, indicating if code was executed correctly @@ -247,35 +260,46 @@ testcase: err = "Correct Answer" mark_fraction = 1.0 return success, err, mark_fraction - test_case_type: |- - hooktestcase weight: 1.0 files: [] ---- -active: true language: |- - c -partial_grading: false + python +partial_grading: true +tags: +- python +- stdio +- demo +- '1' +- code +- for +points: 1.0 snippet: '' -summary: |- - Add 3 numbers +active: true type: |- code -grade_assignment_upload: false description: |- - Write a program to add 3 numbers. -
- Function Name is to be called add + Write a python script that accepts a string as input
-

+ The script must print each character of the string using a for loop. + + For example;
-  Note: You do not have to print anything, neither you have to make the function call. 
-  Just define the function to perform the required operation, return the output & click on check answer.
-  Also, note that the function name should exactly be as mentioned above.
+  Input:
+  box
+  Output
+  b
+  o
+  x
   
-points: 2.0 +summary: |- + For Loop over String +grade_assignment_upload: false +--- testcase: -- test_case: |- +- test_case_type: |- + standardtestcase + test_case_args: '' + test_case: |- #include #include @@ -307,26 +331,43 @@ testcase: printf("All Correct\n"); } weight: 1.0 - test_case_type: |- - standardtestcase - test_case_args: '' files: [] ---- -active: true language: |- - python + c partial_grading: false +tags: +- c++ +- c +- demo +- code +- '2' +- addition +- cpp +- numbers +points: 2.0 snippet: '' -summary: |- - Hello World in File +active: true type: |- - upload -grade_assignment_upload: true + code description: |- - Upload a file called new.txt which contains the string Hello, World! in it. -points: 1.0 + Write a program to add 3 numbers. +
+ Function Name is to be called add +
+

+
+  Note: You do not have to print anything, neither you have to make the function call. 
+  Just define the function to perform the required operation, return the output & click on check answer.
+  Also, note that the function name should exactly be as mentioned above.
+  
+summary: |- + Add 3 numbers +grade_assignment_upload: false +--- testcase: -- hook_code: |- +- test_case_type: |- + hooktestcase + hook_code: |- def check_answer(user_answer): ''' Evaluates user answer to return - success - Boolean, indicating if code was executed correctly @@ -351,7 +392,71 @@ testcase: except IOError: err = "File new.txt not found." return success, err, mark_fraction - test_case_type: |- - hooktestcase weight: 1.0 files: [] +language: |- + python +partial_grading: false +tags: +- python +- '1' +- file +- hook +- hello +- world +- grade +- assignment +- upload +- demo +points: 1.0 +snippet: '' +active: true +type: |- + upload +description: |- + Upload a file called new.txt which contains the string Hello, World! in it. +summary: |- + Hello World in File +grade_assignment_upload: true +--- +testcase: +- test_case_type: |- + mcqtestcase + correct: false + options: |- + s.reverse() +- test_case_type: |- + mcqtestcase + correct: true + options: |- + s[::-1] +- test_case_type: |- + mcqtestcase + correct: false + options: |- + reversed(s) +- test_case_type: |- + mcqtestcase + correct: true + options: |- + "''.join(reversed(s)) +files: [] +language: |- + python +partial_grading: false +tags: +- python +- '2' +- reverse +- mcc +- demo +points: 2.0 +snippet: '' +active: true +type: |- + mcc +description: |- + Which of the following options would reverse the string? +summary: |- + Reverse a string +grade_assignment_upload: false diff --git a/yaksh/fixtures/demo_questions.zip b/yaksh/fixtures/demo_questions.zip index 08b06c4..063d2da 100644 Binary files a/yaksh/fixtures/demo_questions.zip and b/yaksh/fixtures/demo_questions.zip differ diff --git a/yaksh/models.py b/yaksh/models.py index 4113a62..476bc16 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -409,12 +409,13 @@ class Question(models.Model): for question in questions: test_case = question.get_test_cases() file_names = question._add_and_get_files(zip_file) - q_dict = model_to_dict(question, exclude=['id', 'user','tags']) + q_dict = model_to_dict(question, exclude=['id', 'user']) testcases = [] for case in test_case: testcases.append(case.get_field_value()) q_dict['testcase'] = testcases q_dict['files'] = file_names + q_dict['tags'] = [tags.tag.name for tags in q_dict['tags']] questions_dict.append(q_dict) question._add_yaml_to_zip(zip_file, questions_dict) return zip_file_name @@ -428,10 +429,12 @@ class Question(models.Model): question['user'] = user file_names = question.pop('files') test_cases = question.pop('testcase') + tags = question.pop('tags') que, result = Question.objects.get_or_create(**question) if file_names: que._add_files_to_db(file_names, file_path) - + if tags: + que.tags.add(*tags) for test_case in test_cases: try: test_case_type = test_case.pop('test_case_type') @@ -442,6 +445,7 @@ class Question(models.Model): ) new_test_case.type = test_case_type new_test_case.save() + except: msg = "File not correct." except Exception as exc_msg: -- cgit From 0ffc49f91dd9e21a6b9917b7841999bf853c3c9f Mon Sep 17 00:00:00 2001 From: mahesh Date: Fri, 18 Aug 2017 02:13:10 +0530 Subject: Adds test cases for yaml - Fixes selenium test cases, test_models, test_views - Fixes create demo question paper --- yaksh/live_server_tests/selenium_test.py | 6 +++--- yaksh/models.py | 7 ++++++- yaksh/test_models.py | 6 ++++-- yaksh/test_views.py | 10 ++++++++-- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/yaksh/live_server_tests/selenium_test.py b/yaksh/live_server_tests/selenium_test.py index 277f08e..aefb724 100644 --- a/yaksh/live_server_tests/selenium_test.py +++ b/yaksh/live_server_tests/selenium_test.py @@ -111,9 +111,9 @@ class SeleniumTest(): ) start_exam_elem.click() - self.test_c_question(question_label=2) - self.test_python_question(question_label=3) - self.test_bash_question(question_label=1) + self.test_c_question(question_label=7) + self.test_python_question(question_label=5) + self.test_bash_question(question_label=4) def close_quiz(self): quit_link_elem = WebDriverWait(self.driver, 5).until( diff --git a/yaksh/models.py b/yaksh/models.py index 476bc16..b4c665c 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -909,8 +909,13 @@ class QuestionPaper(models.Model): total_marks=6.0, shuffle_questions=True ) + summaries = ['Roots of quadratic equation', 'Print Output', + 'Adding decimals', 'For Loop over String', + 'Hello World in File', 'Extract columns from files', + 'Check Palindrome', 'Add 3 numbers', 'Reverse a string' + ] questions = Question.objects.filter(active=True, - summary="Yaksh Demo Question", + summary__in=summaries, user=user) q_order = [str(que.id) for que in questions] question_paper.fixed_question_order = ",".join(q_order) diff --git a/yaksh/test_models.py b/yaksh/test_models.py index d5ac394..a940c0f 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, AssignmentUpload + StdIOBasedTestCase, FileUpload, McqTestCase, AssignmentUpload import json import ruamel.yaml as yaml from datetime import datetime, timedelta @@ -160,7 +160,9 @@ class QuestionTestCases(unittest.TestCase): "language": "Python", "type": "Code", "testcase": self.test_case_upload_data, "files": [[file1, 0]], - "summary": "Yaml Demo"}] + "summary": "Yaml Demo", + "tags": ['yaml_demo'] + }] self.yaml_questions_data = yaml.safe_dump_all(questions_data) def tearDown(self): diff --git a/yaksh/test_views.py b/yaksh/test_views.py index e5308fc..0060ed8 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -3012,12 +3012,18 @@ class TestShowQuestions(TestCase): data={'file': questions_file, 'upload': 'upload'} ) + summaries = ['Roots of quadratic equation', 'Print Output', + 'Adding decimals', 'For Loop over String', + 'Hello World in File', 'Extract columns from files', + 'Check Palindrome', 'Add 3 numbers', 'Reverse a string' + ] + uploaded_ques = Question.objects.filter(active=True, - summary="Yaksh Demo Question", + summary__in=summaries, user=self.user).count() self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'yaksh/showquestions.html') - self.assertEqual(uploaded_ques, 3) + self.assertEqual(uploaded_ques, 9) f.close() dummy_file = SimpleUploadedFile("test.txt", b"test") response = self.client.post(reverse('yaksh:show_questions'), -- cgit From 6ab383c02085936ecc20ffeb4b9f41ca642256c3 Mon Sep 17 00:00:00 2001 From: mahesh Date: Fri, 18 Aug 2017 17:13:02 +0530 Subject: Adds quit quiz function in selenium --- yaksh/live_server_tests/selenium_test.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/yaksh/live_server_tests/selenium_test.py b/yaksh/live_server_tests/selenium_test.py index aefb724..bc400fd 100644 --- a/yaksh/live_server_tests/selenium_test.py +++ b/yaksh/live_server_tests/selenium_test.py @@ -23,6 +23,7 @@ class SeleniumTest(): self.driver.get(self.url) self.login(username, password) self.open_quiz() + self.quit_quiz() self.close_quiz() self.logout() self.driver.close() @@ -115,6 +116,17 @@ class SeleniumTest(): self.test_python_question(question_label=5) self.test_bash_question(question_label=4) + def quit_quiz(self): + quit_link_elem = WebDriverWait(self.driver, 5).until( + EC.presence_of_element_located((By.NAME, "quit")) + ) + quit_link_elem.click() + + quit_link_elem = WebDriverWait(self.driver, 5).until( + EC.presence_of_element_located((By.NAME, "yes")) + ) + quit_link_elem.click() + def close_quiz(self): quit_link_elem = WebDriverWait(self.driver, 5).until( EC.presence_of_element_located((By.ID, "home")) -- cgit From dade8a89e3e3f882aa05f974feed1b7248fd6de8 Mon Sep 17 00:00:00 2001 From: adityacp Date: Tue, 22 Aug 2017 12:22:32 +0530 Subject: Change course.js, course_detail.html and views.py - Change form submission for send email in js - Change template and view variable for mail status --- yaksh/static/yaksh/js/course.js | 31 ++++++++++++------------------- yaksh/templates/yaksh/course_detail.html | 4 ++-- yaksh/views.py | 2 +- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/yaksh/static/yaksh/js/course.js b/yaksh/static/yaksh/js/course.js index 0eeb017..8fb2773 100644 --- a/yaksh/static/yaksh/js/course.js +++ b/yaksh/static/yaksh/js/course.js @@ -38,7 +38,7 @@ $(".reject").change( function(){ $(function() { $('textarea#email_body').froalaEditor({ - heightMin: 100, + heightMin: 200, heightMax: 200 }) }); @@ -46,31 +46,24 @@ $(function() { $("#send_mail").click(function(){ var subject = $("#subject").val(); var body = $('#email_body').val(); - var status; - if (subject == '' || body == ''){ - status = false; - $("#error_msg").html("Please enter mail details"); - $("#dialog").dialog(); - } - else{ - status = true; - } - return status; -}); - -$('#send_mail_form').submit(function(eventObj) { + var status = false; var selected = []; $('#reject input:checked').each(function() { selected.push($(this).attr('value')); }); - if(selected.length > 0) { - return true; + + if (subject == '' || body == ''){ + $("#error_msg").html("Please enter mail details"); + $("#dialog").dialog(); } - else { + else if (selected.length == 0){ $("#error_msg").html("Please select atleast one user"); - $( "#dialog" ).dialog(); - return false; + $("#dialog").dialog(); } + else { + status = true; + } + return status; }); }); diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html index 1f0e894..bcada42 100644 --- a/yaksh/templates/yaksh/course_detail.html +++ b/yaksh/templates/yaksh/course_detail.html @@ -20,7 +20,7 @@
{% endif %} - {% if status == 'mail' %} + {% if state == 'mail' %}
Send Mails to Students

{% if course.get_enrolled %} diff --git a/yaksh/views.py b/yaksh/views.py index b4ab9bb..db45a89 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -759,7 +759,7 @@ def send_mail(request, course_id, user_id=None): ) context = { 'course': course, 'message': message, - 'status': 'mail' + 'state': 'mail' } return my_render_to_response( 'yaksh/course_detail.html', context, context_instance=ci -- cgit From d6c4d44dca8520926fd781b87e35ecd7843e515c Mon Sep 17 00:00:00 2001 From: adityacp Date: Tue, 22 Aug 2017 12:25:47 +0530 Subject: Change test_views for send mail test --- yaksh/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 763f5ff..f8a7c87 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -1897,7 +1897,7 @@ class TestCourseDetail(TestCase): ) self.assertEqual(get_response.status_code, 200) self.assertEqual(get_response.context['course'], self.user1_course) - self.assertEqual(get_response.context['status'], 'mail') + self.assertEqual(get_response.context['state'], 'mail') class TestEnrollRequest(TestCase): -- cgit From ceb55baf69c2f5f7346855ee5a6e5e9f77456fcb Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Fri, 21 Jul 2017 17:07:24 +0530 Subject: Add a has_profile decorator --- yaksh/decorators.py | 37 ++++++++++++++++++++++++++++++++----- yaksh/models.py | 5 ----- yaksh/test_views.py | 35 ++++++++++++++++++++++++++++++----- yaksh/views.py | 26 +++++++++----------------- 4 files changed, 71 insertions(+), 32 deletions(-) diff --git a/yaksh/decorators.py b/yaksh/decorators.py index f0d354c..8e5f879 100644 --- a/yaksh/decorators.py +++ b/yaksh/decorators.py @@ -1,12 +1,39 @@ -from django.shortcuts import render_to_response +from django.shortcuts import render_to_response, redirect from django.conf import settings from django.template import RequestContext +# Local imports +from yaksh.forms import ProfileForm + + +def user_has_profile(user): + return hasattr(user, 'profile') + + +def has_profile(func): + """ + This decorator is used to check if the user account has a profile. + If the user does not have a profile then redirect the user to + profile edit page. + """ + + def _wrapped_view(request, *args, **kwargs): + if user_has_profile(request.user): + return func(request, *args, **kwargs) + ci = RequestContext(request) + template = 'manage.html' if 'moderator' in request.user.groups.all() else 'user.html' + form = ProfileForm(user=request.user, instance=None) + context = {'template': template, 'form': form} + return render_to_response('yaksh/editprofile.html', context, + context_instance=ci) + return _wrapped_view + def email_verified(func): - """ This decorator is used to check if email is verified. - If email is not verified then redirect user for email - verification + """ + This decorator is used to check if email is verified. + If email is not verified then redirect user for email + verification. """ def is_email_verified(request, *args, **kwargs): @@ -14,7 +41,7 @@ def email_verified(func): user = request.user context = {} if not settings.IS_DEVELOPMENT: - if user.is_authenticated() and hasattr(user, 'profile'): + if user.is_authenticated() and user_has_profile(user): if not user.profile.is_email_verified: context['success'] = False context['msg'] = "Your account is not verified. \ diff --git a/yaksh/models.py b/yaksh/models.py index 87e6260..30ecde0 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -92,11 +92,6 @@ def get_model_class(model): return model_class -def has_profile(user): - """ check if user has profile """ - return True if hasattr(user, 'profile') else False - - def get_upload_dir(instance, filename): return os.sep.join(( 'question_%s' % (instance.question.id), filename diff --git a/yaksh/test_views.py b/yaksh/test_views.py index f8a7c87..fefe9bc 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -21,8 +21,9 @@ from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ - QuestionSet, AnswerPaper, Answer, Course, StandardTestCase, has_profile,\ + QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\ AssignmentUpload, FileUpload +from yaksh.decorators import user_has_profile class TestUserRegistration(TestCase): @@ -90,18 +91,18 @@ class TestProfile(TestCase): self.user2.delete() - def test_has_profile_for_user_without_profile(self): + def test_user_has_profile_for_user_without_profile(self): """ If no profile exists for user passed as argument return False """ - has_profile_status = has_profile(self.user1) + has_profile_status = user_has_profile(self.user1) self.assertFalse(has_profile_status) - def test_has_profile_for_user_with_profile(self): + def test_user_has_profile_for_user_with_profile(self): """ If profile exists for user passed as argument return True """ - has_profile_status = has_profile(self.user2) + has_profile_status = user_has_profile(self.user2) self.assertTrue(has_profile_status) @@ -206,6 +207,30 @@ class TestProfile(TestCase): self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'yaksh/editprofile.html') + def test_edit_profile_get_for_user_without_profile(self): + """ + If no profile exists a blank profile form will be displayed + """ + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + response = self.client.get(reverse('yaksh:edit_profile')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/editprofile.html') + + def test_edit_profile_get_for_user_with_profile(self): + """ + If profile exists a editprofile.html template will be rendered + """ + self.client.login( + username=self.user2.username, + password=self.user2_plaintext_pass + ) + response = self.client.get(reverse('yaksh:edit_profile')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/editprofile.html') + def test_update_email_for_user_post(self): """ POST request to update email if multiple users with same email are found diff --git a/yaksh/views.py b/yaksh/views.py index 248a333..823e506 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -32,7 +32,7 @@ except ImportError: # Local imports. from yaksh.models import get_model_class, Quiz, Question, QuestionPaper, QuestionSet, Course from yaksh.models import Profile, Answer, AnswerPaper, User, TestCase, FileUpload,\ - has_profile, StandardTestCase, McqTestCase,\ + StandardTestCase, McqTestCase,\ StdIOBasedTestCase, HookTestCase, IntegerTestCase,\ FloatTestCase, StringTestCase from yaksh.forms import UserRegisterForm, UserLoginForm, QuizForm,\ @@ -43,7 +43,7 @@ from .settings import URL_ROOT from yaksh.models import AssignmentUpload from .file_utils import extract_files from .send_emails import send_user_mail, generate_activation_key, send_bulk_mail -from .decorators import email_verified +from .decorators import email_verified, has_profile def my_redirect(url): @@ -127,6 +127,7 @@ def user_logout(request): @login_required +@has_profile @email_verified def quizlist_user(request, enrolled=None): """Show All Quizzes that is available to logged-in user.""" @@ -280,6 +281,7 @@ def add_quiz(request, course_id, quiz_id=None): context_instance=ci) @login_required +@has_profile @email_verified def prof_manage(request, msg=None): """Take credentials of the user with professor/moderator @@ -1221,6 +1223,7 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None): @login_required +@has_profile @email_verified def view_profile(request): """ view moderators and users profile """ @@ -1230,20 +1233,12 @@ def view_profile(request): template = 'manage.html' else: template = 'user.html' - context = {'template': template} - if has_profile(user): - context['user'] = user - return my_render_to_response('yaksh/view_profile.html', context) - else: - form = ProfileForm(user=user) - msg = True - context['form'] = form - context['msg'] = msg - return my_render_to_response('yaksh/editprofile.html', context, - context_instance=ci) + context = {'template': template, 'user': user} + return my_render_to_response('yaksh/view_profile.html', context) @login_required +@has_profile @email_verified def edit_profile(request): """ edit profile details facility for moderator and students """ @@ -1255,10 +1250,7 @@ def edit_profile(request): else: template = 'user.html' context = {'template': template} - if has_profile(user): - profile = Profile.objects.get(user_id=user.id) - else: - profile = None + profile = Profile.objects.get(user_id=user.id) if request.method == 'POST': form = ProfileForm(request.POST, user=user, instance=profile) -- cgit From 281e28819d4ab62cc01722d90dd4951e417e16cb Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Thu, 3 Aug 2017 15:44:39 +0530 Subject: Add review changes and more tests --- yaksh/decorators.py | 5 +++- yaksh/test_views.py | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/yaksh/decorators.py b/yaksh/decorators.py index 8e5f879..9e9bc6d 100644 --- a/yaksh/decorators.py +++ b/yaksh/decorators.py @@ -21,7 +21,10 @@ def has_profile(func): if user_has_profile(request.user): return func(request, *args, **kwargs) ci = RequestContext(request) - template = 'manage.html' if 'moderator' in request.user.groups.all() else 'user.html' + if request.user.groups.filter(name='moderator').exists(): + template = 'manage.html' + else: + template = 'user.html' form = ProfileForm(user=request.user, instance=None) context = {'template': template, 'form': form} return render_to_response('yaksh/editprofile.html', context, diff --git a/yaksh/test_views.py b/yaksh/test_views.py index fefe9bc..8025ecf 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -274,6 +274,16 @@ class TestStudentDashboard(TestCase): timezone='UTC' ) + # student without profile + self.student_no_profile_plaintext_pass = 'student2' + self.student_no_profile = User.objects.create_user( + username='student_no_profile', + password=self.student_no_profile_plaintext_pass, + first_name='first_name', + last_name='last_name', + email='student_no_profile@test.com' + ) + # moderator self.user_plaintext_pass = 'demo' self.user = User.objects.create_user( @@ -316,6 +326,30 @@ class TestStudentDashboard(TestCase): redirection_url = '/exam/login/?next=/exam/quizzes/' self.assertRedirects(response, redirection_url) + def test_student_dashboard_get_for_user_without_profile(self): + """ + If no profile exists a blank profile form will be displayed + """ + self.client.login( + username=self.student_no_profile.username, + password=self.student_no_profile_plaintext_pass + ) + response = self.client.get(reverse('yaksh:quizlist_user')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/editprofile.html') + + def test_student_dashboard_get_for_user_with_profile(self): + """ + If profile exists a editprofile.html template will be rendered + """ + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + response = self.client.get(reverse('yaksh:quizlist_user')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/quizzes_user.html') + def test_student_dashboard_all_courses_get(self): """ Check student dashboard for all non hidden courses @@ -2586,6 +2620,16 @@ class TestModeratorDashboard(TestCase): position='Moderator', timezone='UTC' ) + + self.mod_no_profile_plaintext_pass = 'demo2' + self.mod_no_profile = User.objects.create_user( + username='demo_user2', + password=self.mod_no_profile_plaintext_pass, + first_name='user_first_name22', + last_name='user_last_name', + email='demo2@test.com' + ) + self.mod_group.user_set.add(self.user) self.course = Course.objects.create(name="Python Course", enrollment="Enroll Request", creator=self.user) @@ -2679,6 +2723,30 @@ class TestModeratorDashboard(TestCase): self.assertEqual(response.status_code, 200) self.assertRedirects(response, '/exam/quizzes/') + def test_moderator_dashboard_get_for_user_without_profile(self): + """ + If no profile exists a blank profile form will be displayed + """ + self.client.login( + username=self.mod_no_profile.username, + password=self.mod_no_profile_plaintext_pass + ) + response = self.client.get(reverse('yaksh:quizlist_user')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/editprofile.html') + + def test_moderator_dashboard_get_for_user_with_profile(self): + """ + If profile exists a editprofile.html template will be rendered + """ + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + response = self.client.get(reverse('yaksh:quizlist_user')) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/quizzes_user.html') + def test_moderator_dashboard_get_all_quizzes(self): """ Check moderator dashboard to get all the moderator created quizzes -- cgit From 3367cf1bef0b8856972f4e4125322194c699d69e Mon Sep 17 00:00:00 2001 From: maheshgudi Date: Thu, 24 Aug 2017 16:55:18 +0530 Subject: Adds alphabetical order during yaml serializing --- yaksh/models.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/yaksh/models.py b/yaksh/models.py index b4c665c..cd79268 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -3,6 +3,7 @@ from datetime import datetime, timedelta import json import ruamel.yaml from ruamel.yaml.scalarstring import PreservedScalarString +from ruamel.yaml.comments import CommentedMap from random import sample from collections import Counter from django.db import models @@ -119,8 +120,8 @@ def dict_to_yaml(dictionary, path_to_file=None): ruamel.yaml.round_trip_dump(dictionary, yaml_file, default_flow_style=False, explicit_start=True, - allow_unicode=True - ) + allow_unicode=True, + ) ############################################################################### @@ -510,7 +511,8 @@ class Question(models.Model): tmp_file_path = tempfile.mkdtemp() yaml_path = os.path.join(tmp_file_path, "questions_dump.yaml") for elem in q_dict: - dict_to_yaml(elem, yaml_path) + commented_map = CommentedMap(sorted(elem.items(), key=lambda x:x[0])) + dict_to_yaml(commented_map, yaml_path) zip_file.write(yaml_path, os.path.basename(yaml_path)) zip_file.close() shutil.rmtree(tmp_file_path) -- cgit From 30b48c30abebf75ed4b51fd034600e0c7d58c95b Mon Sep 17 00:00:00 2001 From: mahesh Date: Fri, 25 Aug 2017 01:33:08 +0530 Subject: Fixes order in demo_questions.zip - Template yaml is now generated on the fly. - Removes yaml_question_template files. - Fixes order for yaml file inside demo_questions.zip --- yaksh/demo_templates/yaml_question_template | 462 ---------------------------- yaksh/fixtures/demo_questions.zip | Bin 3015 -> 3055 bytes yaksh/models.py | 27 +- yaksh/views.py | 13 +- 4 files changed, 21 insertions(+), 481 deletions(-) delete mode 100644 yaksh/demo_templates/yaml_question_template diff --git a/yaksh/demo_templates/yaml_question_template b/yaksh/demo_templates/yaml_question_template deleted file mode 100644 index 1309c06..0000000 --- a/yaksh/demo_templates/yaml_question_template +++ /dev/null @@ -1,462 +0,0 @@ ---- -testcase: -- test_case_type: |- - integertestcase - correct: 2 -- test_case_type: |- - integertestcase - correct: -3 -files: [] -language: |- # bash, scilab, python, c/c++, java - python -partial_grading: false -tags: -- python -- quadratic -- demo -- integer -points: 1.0 -snippet: '' -active: true -type: |- - integer -description: |- - Type in the box below, one of the roots to the following quadratic equation? -
- - - x - 2 - - + - x - - - 6 - = - 0 - -summary: |- - Roots of quadratic equation -grade_assignment_upload: false ---- -testcase: -- test_case_type: |- - stringtestcase - correct: |- - (2, 'HelloHello', ':', 'Bye') - string_check: |- # exact or lower - exact -files: [] -language: |- - python -partial_grading: false -tags: -- python -- demo -- print -- string -- '1' -- case_sensitive -- casesensitive -- python2 -points: 1.0 -snippet: '' -active: true -type: |- - string -description: |- - What is the output for the following code in Python 2.x: -
- - print(2, "Hello"*2, ":" ,"Bye") - -summary: |- - Print Output -grade_assignment_upload: false ---- -testcase: -- test_case_type: |- - floattestcase - correct: 5.5786 - error_margin: 0.0 -files: [] -language: |- - python -partial_grading: false -tags: -- float -- '1' -- python -- decimal -- demo -points: 1.0 -snippet: '' -active: true -type: |- - float -description: |- - Write down the resultant value of the following - -
3.4566+2.122
-  

-summary: |- - Adding decimals -grade_assignment_upload: false ---- -testcase: -- test_case_type: |- - standardtestcase - test_case_args: |- - file1.csv file2.csv file3.csv - test_case: |- - #!/bin/bash - cat $1 | cut -d: -f2 | paste -d: $3 - $2 - weight: 1.0 -files: -- - file1.csv - - false -- - file2.csv - - false -- - file3.csv - - false -language: |- - bash -partial_grading: false -tags: -- demo -- code -- files -- concatenate -- bash -- delimiter -- '2' -points: 2.0 -snippet: |- - #!/bin/bash -active: true -type: |- - code -description: |- - Write a bash script that takes exactly three file arguments. - - The first argument (file1.csv) would have 3 columns, the second argument (file2.csv) would have 2 columns. The third argument (file3.csv) would have 2 columns. -

- All files have columns delimited by : (colon). -

- We need the 2nd column from file1.csv to be removed and concatenated BEFORE file2.csv and this concatenated file should come BESIDE file3.csv. - - Left is file3.csv, and the LATER columns come from file1.csv and file2.csv. -

- The delimiter while putting the files BESIDE each other should again be : (colon) -

-
-  Note: - Do not hard-code the filenames. They will be passed in as arguments.
-  Assume no headers (to avoid header-non-repetition issues).
-  
-summary: |- - Extract columns from files -grade_assignment_upload: false ---- -testcase: -- test_case_type: |- - standardtestcase - test_case_args: '' - test_case: |- - assert is_palindrome("hello") == False - weight: 1.0 -- test_case_type: |- - standardtestcase - test_case_args: '' - test_case: |- - assert is_palindrome("nitin") == True - weight: 1.0 -- test_case_type: |- - standardtestcase - test_case_args: '' - test_case: |- - assert is_palindrome("madaM") == False - weight: 1.0 -files: [] -language: |- - python -partial_grading: false -tags: -- python -- assertion -- palindrome -- reverse -- code -- '2' -- demo -points: 2.0 -snippet: |- - def is_palindrome(s): -active: true -type: |- - code -description: |- - Write a function is_palindrome(arg) which will take one string argument. -
- Return True if the argument is palindrome & False otherwise. -
- The function should be case sensitive. -

- For Example:
is_palindrome("Hello") should return False
-summary: |- - Check Palindrome -grade_assignment_upload: false ---- -testcase: -- test_case_type: |- - stdiobasedtestcase - weight: 1 - expected_input: |- - string - expected_output: |- - s - t - r - i - n - g -- test_case_type: |- - stdiobasedtestcase - weight: 1 - expected_input: |- - s t o p s i g n - expected_output: |- - s - - t - - o - - p - - s - - i - - g - - n -- test_case_type: |- - hooktestcase - hook_code: |- - def check_answer(user_answer): - ''' Evaluates user answer to return - - success - Boolean, indicating if code was executed correctly - 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 = "You are using while in your code." - mark_fraction = 0.0 - - if not 'while' in user_answer: - success=True - err = "Correct Answer" - mark_fraction = 1.0 - return success, err, mark_fraction - weight: 1.0 -files: [] -language: |- - python -partial_grading: true -tags: -- python -- stdio -- demo -- '1' -- code -- for -points: 1.0 -snippet: '' -active: true -type: |- - code -description: |- - Write a python script that accepts a string as input -
- The script must print each character of the string using a for loop. - - For example; -
-  Input:
-  box
-  Output
-  b
-  o
-  x
-  
-summary: |- - For Loop over String -grade_assignment_upload: false ---- -testcase: -- test_case_type: |- - standardtestcase - test_case_args: '' - test_case: |- - #include - #include - - extern int add(int, int, int); - - template - void check(T expect,T result) - { - if (expect == result) - { - printf("\nCorrect:\n Expected %d got %d \n",expect,result); - } - else - { - printf("\nIncorrect:\n Expected %d got %d \n",expect,result); - exit (1); - } - } - - int main(void) - { - int result; - result = add(0,0,0); - printf("Input submitted to the function: 0, 0, 0"); - check(0, result); - result = add(2,3,3); - printf("Input submitted to the function: 2, 3, 3"); - check(8,result); - printf("All Correct\n"); - } - weight: 1.0 -files: [] -language: |- - c -partial_grading: false -tags: -- c++ -- c -- demo -- code -- '2' -- addition -- cpp -- numbers -points: 2.0 -snippet: '' -active: true -type: |- - code -description: |- - Write a program to add 3 numbers. -
- Function Name is to be called add -
-

-
-  Note: You do not have to print anything, neither you have to make the function call. 
-  Just define the function to perform the required operation, return the output & click on check answer.
-  Also, note that the function name should exactly be as mentioned above.
-  
-summary: |- - Add 3 numbers -grade_assignment_upload: false ---- -testcase: -- test_case_type: |- - hooktestcase - hook_code: |- - def check_answer(user_answer): - ''' Evaluates user answer to return - - success - Boolean, indicating if code was executed correctly - 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 - - try: - with open('new.txt', 'r') as f: - if "Hello, World!" in f.read(): - success = True - err = "Correct Answer" - mark_fraction = 1.0 - else: - err = "Did not found string Hello, World! in file." - except IOError: - err = "File new.txt not found." - return success, err, mark_fraction - weight: 1.0 -files: [] -language: |- - python -partial_grading: false -tags: -- python -- '1' -- file -- hook -- hello -- world -- grade -- assignment -- upload -- demo -points: 1.0 -snippet: '' -active: true -type: |- - upload -description: |- - Upload a file called new.txt which contains the string Hello, World! in it. -summary: |- - Hello World in File -grade_assignment_upload: true ---- -testcase: -- test_case_type: |- - mcqtestcase - correct: false - options: |- - s.reverse() -- test_case_type: |- - mcqtestcase - correct: true - options: |- - s[::-1] -- test_case_type: |- - mcqtestcase - correct: false - options: |- - reversed(s) -- test_case_type: |- - mcqtestcase - correct: true - options: |- - "''.join(reversed(s)) -files: [] -language: |- - python -partial_grading: false -tags: -- python -- '2' -- reverse -- mcc -- demo -points: 2.0 -snippet: '' -active: true -type: |- - mcc -description: |- - Which of the following options would reverse the string? -summary: |- - Reverse a string -grade_assignment_upload: false diff --git a/yaksh/fixtures/demo_questions.zip b/yaksh/fixtures/demo_questions.zip index 063d2da..4e86485 100644 Binary files a/yaksh/fixtures/demo_questions.zip and b/yaksh/fixtures/demo_questions.zip differ diff --git a/yaksh/models.py b/yaksh/models.py index 044a164..979740d 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -102,7 +102,7 @@ def get_upload_dir(instance, filename): 'question_%s' % (instance.question.id), filename )) -def dict_to_yaml(dictionary, path_to_file=None): +def dict_to_yaml(dictionary): for k,v in dictionary.items(): if isinstance(v, list): for nested_v in v: @@ -110,14 +110,10 @@ def dict_to_yaml(dictionary, path_to_file=None): dict_to_yaml(nested_v) elif v and isinstance(v,str): dictionary[k] = PreservedScalarString(v) - if path_to_file: - with open(path_to_file, "a") as yaml_file: - ruamel.yaml.round_trip_dump(dictionary, yaml_file, - default_flow_style=False, - explicit_start=True, - allow_unicode=True, - ) - + return ruamel.yaml.round_trip_dump(dictionary, explicit_start=True, + default_flow_style=False, + allow_unicode=True, + ) ############################################################################### class CourseManager(models.Manager): @@ -502,12 +498,15 @@ class Question(models.Model): file_upload.extract = extract file_upload.file.save(file_name, django_file, save=True) - def _add_yaml_to_zip(self, zip_file, q_dict): + def _add_yaml_to_zip(self, zip_file, q_dict,path_to_file=None): + tmp_file_path = tempfile.mkdtemp() yaml_path = os.path.join(tmp_file_path, "questions_dump.yaml") for elem in q_dict: - commented_map = CommentedMap(sorted(elem.items(), key=lambda x:x[0])) - dict_to_yaml(commented_map, yaml_path) + sorted_dict = CommentedMap(sorted(elem.items(), key=lambda x:x[0])) + yaml_block = dict_to_yaml(sorted_dict) + with open(yaml_path, "a") as yaml_file: + yaml_file.write(yaml_block) zip_file.write(yaml_path, os.path.basename(yaml_path)) zip_file.close() shutil.rmtree(tmp_file_path) @@ -518,7 +517,9 @@ class Question(models.Model): if os.path.exists(yaml_file): with open(yaml_file, 'r') as q_file: questions_list = q_file.read() - msg = self.load_questions(questions_list, user, file_path, files) + msg = self.load_questions(questions_list, user, + file_path, files + ) else: msg = "Please upload zip file with questions_dump.yaml in it." diff --git a/yaksh/views.py b/yaksh/views.py index 81d180b..3c7df4d 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -1622,13 +1622,14 @@ 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__), "demo_templates", - "yaml_question_template" + template_path = os.path.join(os.path.dirname(__file__), "fixtures", + "demo_questions.zip" ) - with open(template_path, 'r') as f: - yaml_str = f.read() - response = HttpResponse(yaml_str, content_type='text/yaml') - response['Content-Disposition'] = 'attachment; filename="questions_dump.yaml"' + 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 -- cgit