summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorprathamesh2017-11-06 14:50:21 +0530
committerprathamesh2017-11-06 14:50:21 +0530
commitcde2eeb4f15de06def22d3bf07120a0a416e807f (patch)
treef2d1a713796f22339377f066cfad7b9423b9ccb8
parent840c00b9e939d2b33058d236ef4170923e0a018b (diff)
downloadonline_test-cde2eeb4f15de06def22d3bf07120a0a416e807f.tar.gz
online_test-cde2eeb4f15de06def22d3bf07120a0a416e807f.tar.bz2
online_test-cde2eeb4f15de06def22d3bf07120a0a416e807f.zip
Upload user to the course via csv.
The csv takes firstname, lastname and email. User and Profile are created with username and password been same as email. Following cases are handled for csv upload: - wrong csv headders - missing csv values - already existing users - invalid csv
-rw-r--r--yaksh/file_utils.py29
-rw-r--r--yaksh/fixtures/users_correct.csv2
-rw-r--r--yaksh/fixtures/users_some_headers_missing.csv2
-rw-r--r--yaksh/fixtures/users_some_values_missing.csv4
-rw-r--r--yaksh/fixtures/users_with_no_values.csv1
-rw-r--r--yaksh/templates/yaksh/course_detail.html20
-rw-r--r--yaksh/test_views.py137
-rw-r--r--yaksh/urls.py1
-rw-r--r--yaksh/views.py65
9 files changed, 259 insertions, 2 deletions
diff --git a/yaksh/file_utils.py b/yaksh/file_utils.py
index f41c531..361b29c 100644
--- a/yaksh/file_utils.py
+++ b/yaksh/file_utils.py
@@ -2,7 +2,7 @@ import shutil
import os
import zipfile
import tempfile
-
+import csv
def copy_files(file_paths):
""" Copy Files to current directory, takes
@@ -50,3 +50,30 @@ def extract_files(zip_file, path=None):
zip_file.close()
return zfiles, extract_path
+
+def is_csv(document):
+ try:
+ try:
+ content = document.read(1024).decode('utf-8')
+ except AttributeError:
+ document.seek(0)
+ content = document.read(1024)
+ sniffer = csv.Sniffer()
+ dialect = sniffer.sniff(content)
+ document.seek(0)
+ except (csv.Error, UnicodeDecodeError):
+ return False
+ return True
+
+
+def headers_present(dict_reader, headers):
+ fields = dict_reader.fieldnames
+ clist = set()
+ for field in fields:
+ if field.strip() in headers.keys():
+ headers[field.strip()] = field
+ clist.add(field.strip())
+ if clist != headers.keys():
+ return False
+ return True
+
diff --git a/yaksh/fixtures/users_correct.csv b/yaksh/fixtures/users_correct.csv
new file mode 100644
index 0000000..7b2a977
--- /dev/null
+++ b/yaksh/fixtures/users_correct.csv
@@ -0,0 +1,2 @@
+firstname, lastname, email
+abc, abc, abc@xyz.com
diff --git a/yaksh/fixtures/users_some_headers_missing.csv b/yaksh/fixtures/users_some_headers_missing.csv
new file mode 100644
index 0000000..ddcaa88
--- /dev/null
+++ b/yaksh/fixtures/users_some_headers_missing.csv
@@ -0,0 +1,2 @@
+noname, lastname, email
+abc, abc, abc@xyz.com
diff --git a/yaksh/fixtures/users_some_values_missing.csv b/yaksh/fixtures/users_some_values_missing.csv
new file mode 100644
index 0000000..d60ed8d
--- /dev/null
+++ b/yaksh/fixtures/users_some_values_missing.csv
@@ -0,0 +1,4 @@
+firstname, lastname, email
+abc, , abc@xyz.com
+dummy, dummy , dummy@xyz.com
+dummy, dummy , dummy@xyz.com
diff --git a/yaksh/fixtures/users_with_no_values.csv b/yaksh/fixtures/users_with_no_values.csv
new file mode 100644
index 0000000..db08ba2
--- /dev/null
+++ b/yaksh/fixtures/users_with_no_values.csv
@@ -0,0 +1 @@
+firstname, lastname, email
diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html
index d8aeb2e..3f13ea7 100644
--- a/yaksh/templates/yaksh/course_detail.html
+++ b/yaksh/templates/yaksh/course_detail.html
@@ -41,6 +41,18 @@
<a href="{{URL_ROOT}}/exam/manage/send_mail/{{ course.id }}/">
Send Mail</a>
</li>
+ <li>
+ <form id="upload_users" action="{{ URL_ROOT }}/exam/manage/upload_users/{{course.id}}/"
+ method="POST" enctype="multipart/form-data">
+ {% csrf_token %}
+ <input type="file" name="csv_file" />
+ <button class="btn btn-primary" type=submit> Upload Users <span class="glyphicon glyphicon-open"/></button>
+ </form>
+ <div class="alert alert-info" role="alert">
+ The uploaded csv should have headers exactly same as mentioned below:<br>
+ <b>firstname, lastname, email</b>
+ </div>
+ </li>
</ul>
</div>
</div>
@@ -53,6 +65,13 @@
</center>
</div>
{% endif %}
+ {% if upload_details %}
+ <div class="alert alert-info" role="info">
+ {% for detail in upload_details %}
+ <strong> {{ detail }} </strong><br>
+ {% endfor %}
+ </div>
+ {% endif %}
{% if state == 'mail' %}
<div id="enrolled-students">
<center><b><u>Send Mails to Students</u></b></center><br>
@@ -218,6 +237,7 @@
{% endif %}
</div>
</div>
+
<!-- Dialog to display error message -->
<div id="dialog" title="Alert">
<p id="error_msg"></p>
diff --git a/yaksh/test_views.py b/yaksh/test_views.py
index dc06126..e27d5ef 100644
--- a/yaksh/test_views.py
+++ b/yaksh/test_views.py
@@ -1507,6 +1507,8 @@ class TestCourses(TestCase):
self.user1_course.delete()
self.user2_course.delete()
+
+
def test_courses_denies_anonymous(self):
"""
If not logged in redirect to login page
@@ -1758,6 +1760,141 @@ class TestCourseDetail(TestCase):
self.student.delete()
self.user1_course.delete()
+
+ def test_upload_users_with_correct_csv(self):
+ # Given
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ csv_file_path = os.path.join(settings.FIXTURE_DIRS, "users_correct.csv")
+ csv_file = open(csv_file_path, 'rb')
+ upload_file = SimpleUploadedFile(csv_file_path, csv_file.read())
+
+ # When
+ response = self.client.post(reverse('yaksh:upload_users',
+ kwargs={'course_id': self.user1_course.id}),
+ data={'csv_file': upload_file})
+ csv_file.close()
+
+ # Then
+ uploaded_user = User.objects.filter(email="abc@xyz.com")
+ self.assertEqual(uploaded_user.count(), 1)
+ self.assertEqual(response.status_code, 200)
+ self.assertIn('upload_details', response.context)
+ self.assertTemplateUsed(response, 'yaksh/course_detail.html')
+
+
+ def test_upload_users_with_wrong_csv(self):
+ # Given
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ csv_file_path = os.path.join(settings.FIXTURE_DIRS, "demo_questions.zip")
+ csv_file = open(csv_file_path, 'rb')
+ upload_file = SimpleUploadedFile(csv_file_path, csv_file.read())
+ message = "The file uploaded is not a CSV file."
+
+ # When
+ response = self.client.post(reverse('yaksh:upload_users',
+ kwargs={'course_id': self.user1_course.id}),
+ data={'csv_file': upload_file})
+ csv_file.close()
+
+ # Then
+ self.assertEqual(response.status_code, 200)
+ self.assertNotIn('upload_details', response.context)
+ self.assertIn('message', response.context)
+ self.assertEqual(response.context['message'], message)
+ self.assertTemplateUsed(response, 'yaksh/course_detail.html')
+
+
+ def test_upload_users_csv_with_missing_headers(self):
+ # Given
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ csv_file_path = os.path.join(settings.FIXTURE_DIRS, "users_some_headers_missing.csv")
+ csv_file = open(csv_file_path, 'rb')
+ upload_file = SimpleUploadedFile(csv_file_path, csv_file.read())
+ message = "The CSV file does not contain the required headers"
+
+ # When
+ response = self.client.post(reverse('yaksh:upload_users',
+ kwargs={'course_id': self.user1_course.id}),
+ data={'csv_file': upload_file})
+ csv_file.close()
+
+ # Then
+ self.assertEqual(response.status_code, 200)
+ self.assertNotIn('upload_details', response.context)
+ self.assertIn('message', response.context)
+ self.assertEqual(response.context['message'], message)
+ self.assertTemplateUsed(response, 'yaksh/course_detail.html')
+
+ def test_upload_users_csv_with_no_values(self):
+ # Given
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ csv_file_path = os.path.join(settings.FIXTURE_DIRS, "users_with_no_values.csv")
+ csv_file = open(csv_file_path, 'rb')
+ upload_file = SimpleUploadedFile(csv_file_path, csv_file.read())
+
+ # When
+ response = self.client.post(reverse('yaksh:upload_users',
+ kwargs={'course_id': self.user1_course.id}),
+ data={'csv_file': upload_file})
+ csv_file.close()
+
+ # Then
+ self.assertEqual(response.status_code, 200)
+ self.assertIn('upload_details', response.context)
+ self.assertNotIn('message', response.context)
+ self.assertIn("No rows in the CSV file", response.context['upload_details'])
+ self.assertTemplateUsed(response, 'yaksh/course_detail.html')
+
+
+ def test_upload_users_csv_with_missing_values(self):
+ '''
+ This test takes csv with 3 row values.
+ 1st row has a missing row.
+ 2nd has a proper row.
+ 3rd has a same row has 2nd
+
+ Only 2nd user will be added.
+
+ This test proves that:
+ - Row with missing values is ignored and continued with next row.
+ - Duplicate user is not created.
+ '''
+ # Given
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ csv_file_path = os.path.join(settings.FIXTURE_DIRS, "users_some_values_missing.csv")
+ csv_file = open(csv_file_path, 'rb')
+ upload_file = SimpleUploadedFile(csv_file_path, csv_file.read())
+
+ # When
+ response = self.client.post(reverse('yaksh:upload_users',
+ kwargs={'course_id': self.user1_course.id}),
+ data={'csv_file': upload_file})
+ csv_file.close()
+
+ # Then
+ uploaded_user = User.objects.filter(email="dummy@xyz.com")
+ self.assertEqual(uploaded_user.count(), 1)
+ self.assertEqual(response.status_code, 200)
+ self.assertIn('upload_details', response.context)
+ self.assertNotIn('message', response.context)
+ self.assertTemplateUsed(response, 'yaksh/course_detail.html')
+
+
def test_course_detail_denies_anonymous(self):
"""
If not logged in redirect to login page
diff --git a/yaksh/urls.py b/yaksh/urls.py
index 4c5593a..a7aeecc 100644
--- a/yaksh/urls.py
+++ b/yaksh/urls.py
@@ -68,6 +68,7 @@ urlpatterns = [
name="enroll_user"),
url(r'manage/enroll/rejected/(?P<course_id>\d+)/(?P<user_id>\d+)/$',
views.enroll, {'was_rejected': True}),
+ url(r'manage/upload_users/(?P<course_id>\d+)/$', views.upload_users, name="upload_users"),
url(r'manage/send_mail/(?P<course_id>\d+)/$', views.send_mail, name="send_mail"),
url(r'manage/reject/(?P<course_id>\d+)/(?P<user_id>\d+)/$', views.reject,
name="reject_user"),
diff --git a/yaksh/views.py b/yaksh/views.py
index b4cb844..838041e 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -45,7 +45,7 @@ from yaksh.forms import (
UploadFileForm, get_object_form, FileForm, QuestionPaperForm
)
from .settings import URL_ROOT
-from .file_utils import extract_files
+from .file_utils import extract_files, is_csv, headers_present
from .send_emails import send_user_mail, generate_activation_key, send_bulk_mail
from .decorators import email_verified, has_profile
@@ -1796,6 +1796,69 @@ def download_assignment_file(request, quiz_id, question_id=None, user_id=None):
@login_required
@email_verified
+def upload_users(request, course_id):
+ user = request.user
+ ci = RequestContext(request)
+ course = get_object_or_404(Course, pk=course_id)
+ context = {'course': course}
+
+ if not (course.is_teacher(user) or course.is_creator(user)):
+ msg = 'You do not have permissions to this course.'
+ return complete(request, reason=msg)
+ if request.method == 'POST':
+ if 'csv_file' not in request.FILES:
+ context['message'] = "Please upload a CSV file."
+ return my_render_to_response('yaksh/course_detail.html', context,
+ context_instance=ci)
+ csv_file = request.FILES['csv_file']
+ if not is_csv(csv_file):
+ context['message'] = "The file uploaded is not a CSV file."
+ return my_render_to_response('yaksh/course_detail.html', context,
+ context_instance=ci)
+ headers = {'firstname':'', 'lastname':'', 'email':''}
+ reader = csv.DictReader(csv_file.read().decode('utf-8').splitlines())
+ if not headers_present(reader, headers):
+ context['message'] = "The CSV file does not contain the required headers"
+ return my_render_to_response('yaksh/course_detail.html', context,
+ context_instance=ci)
+ context['upload_details'] = _read_user_csv(reader, headers, course)
+ return my_render_to_response('yaksh/course_detail.html', context,
+ context_instance=ci)
+
+
+def _read_user_csv(reader, headers, course):
+ upload_details = ["Upload Summary:"]
+ counter = 0;
+ for row in reader:
+ counter += 1
+ email, first_name, last_name = map(str.strip, [row[headers['email']],
+ row[headers['firstname']], row[headers['lastname']]])
+ if not email or not first_name or not last_name:
+ upload_details.append("{0} -- Missing Values".format(counter))
+ continue
+ user = User.objects.filter(email=email)
+ if user.exists():
+ upload_details.append("{0} -- {1} -- Email Already Exists".format(
+ counter, email))
+ else:
+ try:
+ user = User.objects.create_user(username=email, password=email,
+ email=email, first_name=first_name, last_name=last_name)
+ except IntegrityError:
+ upload_details.append("{0} -- {1} -- User Already Exists".format(
+ counter, email))
+ else:
+ Profile.objects.create(user=user, is_email_verified=True)
+ course.students.add(user)
+ upload_details.append("{0} -- {1} -- User Added Successfully".format(
+ counter, email))
+ if counter == 0:
+ upload_details.append("No rows in the CSV file")
+ return upload_details
+
+
+@login_required
+@email_verified
def duplicate_course(request, course_id):
user = request.user
course = get_object_or_404(Course, pk=course_id)