summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorankitjavalkar2020-11-27 15:04:30 +0530
committerankitjavalkar2021-01-06 14:21:06 +0530
commit7cf18e744c9260ebd33f6233d0211a3c0aa3a782 (patch)
tree094b128225886a40c328af8d2103675bd85310f0
parent30dd519ba7a5277348960a696f3a7cbd91f3f72f (diff)
downloadonline_test-7cf18e744c9260ebd33f6233d0211a3c0aa3a782.tar.gz
online_test-7cf18e744c9260ebd33f6233d0211a3c0aa3a782.tar.bz2
online_test-7cf18e744c9260ebd33f6233d0211a3c0aa3a782.zip
Add a feature to upload and download
-rw-r--r--online_test/urls.py1
-rw-r--r--upload/models.py0
-rw-r--r--upload/urls.py9
-rw-r--r--upload/utils.py554
-rw-r--r--upload/views.py62
-rw-r--r--yaksh/models.py50
-rw-r--r--yaksh/templates/yaksh/course_detail.html2
-rw-r--r--yaksh/templates/yaksh/course_detail_options.html5
-rw-r--r--yaksh/templates/yaksh/upload_download_course_md.html13
-rw-r--r--yaksh/urls.py2
-rw-r--r--yaksh/views.py41
11 files changed, 735 insertions, 4 deletions
diff --git a/online_test/urls.py b/online_test/urls.py
index 2a53d97..96b8bf1 100644
--- a/online_test/urls.py
+++ b/online_test/urls.py
@@ -18,6 +18,7 @@ urlpatterns = [
url(r'^grades/', include(('grades.urls', 'grades'))),
url(r'^api/', include('api.urls', namespace='api')),
url(r'^stats/', include('stats.urls', namespace='stats')),
+ url(r'^flatfiles/', include(('upload.urls', 'upload'))),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/upload/models.py b/upload/models.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/upload/models.py
diff --git a/upload/urls.py b/upload/urls.py
new file mode 100644
index 0000000..207b9ea
--- /dev/null
+++ b/upload/urls.py
@@ -0,0 +1,9 @@
+from django.conf.urls import url
+from upload import views
+
+app_name = 'upload'
+
+urlpatterns = [
+ url(r'^download_course_md/(?P<course_id>\d+)/$',
+ views.download_course_md, name="download_course_md"),
+] \ No newline at end of file
diff --git a/upload/utils.py b/upload/utils.py
new file mode 100644
index 0000000..8b10eb6
--- /dev/null
+++ b/upload/utils.py
@@ -0,0 +1,554 @@
+import io
+import re
+import os
+import json
+import yaml
+import requests
+import more_itertools
+
+import html2text
+import pypandoc
+import ruamel.yaml
+
+from django.core.exceptions import ObjectDoesNotExist
+
+from yaksh.models import Lesson, Course, LearningUnit, LearningModule, Quiz, TableOfContents
+
+_HEADER_RE = re.compile(r"^---\s*$")
+_BLANK_RE = re.compile(r"^\s*$")
+_JUPYTER_RE = re.compile(r"^meta\s*:\s*$")
+_LEFTSPACE_RE = re.compile(r"^\s")
+DATA_SEP = '~#~#~#'
+
+
+def recursive_update(target, update):
+ """ Update recursively a (nested) dictionary with the content of another.
+ Inspired from https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth
+ """
+ for key in update:
+ value = update[key]
+ if value is None:
+ del target[key]
+ elif isinstance(value, dict):
+ target[key] = recursive_update(target.get(key, {}), value)
+ else:
+ target[key] = value
+ return target
+
+def _metadata_to_dict(lines):
+ metadata= {}
+ ended = False
+ injupyter = False
+ # jupyter = []
+ meta_lines = []
+
+ for i, line in enumerate(lines):
+ if i == 0 and _HEADER_RE.match(line):
+ continue
+
+ if (i > 0) and _HEADER_RE.match(line):
+ ended = True
+
+ if _JUPYTER_RE.match(line):
+ injupyter = True
+ elif line and not _LEFTSPACE_RE.match(line):
+ injupyter = False
+
+ if injupyter:
+ meta_lines.append(line)
+
+ if ended:
+ if meta_lines:
+ recursive_update(metadata, yaml.safe_load("\n".join(meta_lines)))
+ return metadata
+
+def _display_content_to_dict(lines):
+ ended = False
+ display_content = []
+
+ for i, line in enumerate(lines):
+ if (i > 0) and _HEADER_RE.match(line):
+ ended = True
+ continue
+
+ if ended:
+ display_content.append(line)
+
+ return ''.join(display_content)
+
+
+class LessonData():
+ type = "lesson"
+ display_content_field = 'description'
+ def __init__(self, obj, order, course_id):
+ toc_file = TableOfContents.objects.get_all_tocs_as_yaml(
+ course_id, obj.id, '{0}_lesson_toc.yaml'.format(obj.id)
+ )
+ self.data = {
+ "id": obj.id,
+ "name": obj.name,
+ "active": obj.active,
+ "order": order,
+ }
+ if toc_file:
+ self.data.update(
+ {"toc": toc_file,}
+ )
+ self.display_content = obj.description if obj.description else None
+
+
+class QuizData():
+ type = "quiz"
+ display_content_field = 'instructions'
+ def __init__(self, obj, order):
+ self.data = {
+ "id": obj.id,
+ "description": obj.description,
+ "active": obj.active,
+ "start_date_time": obj.start_date_time,
+ "end_date_time": obj.end_date_time,
+ "duration": obj.duration,
+ "pass_criteria": obj.pass_criteria,
+ "attempts_allowed": obj.attempts_allowed,
+ "allow_skip": obj.allow_skip,
+ "view_answerpaper": obj.view_answerpaper,
+ "order": order,
+ }
+ self.display_content = obj.instructions if obj.instructions else None
+
+
+class UnitData():
+
+ @classmethod
+ def get_lesson_or_quiz_data(cls, units, course_id):
+ return [
+ LessonData(unit_obj.lesson, unit_obj.order, course_id)
+ if unit_obj.lesson else QuizData(unit_obj.quiz, unit_obj.order)
+ for unit_obj in units
+ ]
+
+ @classmethod
+ def md_to_dict(cls, lines):
+ all_unit_data = list(
+ more_itertools.split_at(
+ lines,
+ lambda x: x.strip('\n') == DATA_SEP
+ )
+ )
+ learning_units = []
+ module_data = None
+ for unit_lines in all_unit_data:
+ if unit_lines:
+ metadata = _metadata_to_dict(unit_lines)
+ display_content = _display_content_to_dict(unit_lines)
+ clean_display_content = display_content.strip('\n')
+ try:
+ unit_data = metadata.get('meta', {}).get('data', {})
+ order = unit_data.pop('order', 0)
+ unit_type = metadata.get('meta', {}).get('type', None)
+ unit_cls = LessonData if unit_type == 'lesson' else QuizData
+ except KeyError:
+ print("[ERROR] Data not found. Please check the MD file")
+
+ unit_data.update(
+ {getattr(unit_cls,'display_content_field'): clean_display_content,}
+ )
+ if unit_type == 'lesson':
+ learning_unit_data = {
+ "order": order,
+ "type": unit_type,
+ "quiz": None,
+ "lesson": unit_data,
+ }
+ learning_units.append(learning_unit_data)
+ elif unit_type == 'quiz':
+ learning_unit_data = {
+ "order": order,
+ "type": unit_type,
+ "quiz": unit_data,
+ "lesson": None,
+ }
+ learning_units.append(learning_unit_data)
+ elif unit_type == 'module':
+ module_data = ModuleData.md_to_dict(unit_lines)
+
+ return {'module': module_data, 'learning_units': learning_units}
+
+ @classmethod
+ def md_to_dict_from_file(cls, file):
+ with open(file, 'r') as f:
+ lines = f.readlines()
+ return cls.md_to_dict(lines)
+
+
+class ModuleData():
+ type = "module"
+ display_content_field = 'description'
+ def __init__(self, obj, course_id):
+ self.data = {
+ "id": obj.id,
+ "name": obj.name,
+ "order": obj.order,
+ "active":obj.active,
+ }
+ self.display_content = obj.description if obj.description else None
+ self.units = self.set_lessons(obj.get_learning_units(), course_id)
+
+ def set_lessons(self, units, course_id):
+ return UnitData.get_lesson_or_quiz_data(units, course_id)
+
+
+ @classmethod
+ def md_to_dict(cls, lines):
+ metadata = _metadata_to_dict(lines)
+ display_content = _display_content_to_dict(lines)
+ clean_display_content = display_content.strip('\n')
+ try:
+ data = metadata.get('meta', {}).get('data', {})
+ except KeyError:
+ print("[ERROR] Data not found. Please check the MD file")
+
+ data.update({cls.display_content_field: clean_display_content})
+ return data
+
+ @classmethod
+ def md_to_dict_from_file(cls, file):
+ with open(file, 'r') as f:
+ lines = f.readlines()
+ return cls.md_to_dict(lines)
+
+
+class CourseData():
+ type = "course"
+ display_content_field = "instructions"
+ def __init__(self, obj):
+ self.data = {
+ "id": obj.id,
+ "name": obj.name,
+ "enrollment": obj.enrollment,
+ "active": obj.active,
+ "start_enroll_time": obj.start_enroll_time,
+ "end_enroll_time": obj.end_enroll_time,
+ }
+ self.display_content = obj.instructions if obj.instructions else None
+ self.modules = self.set_modules(obj.get_learning_modules(), obj.id)
+
+ def set_modules(self, modules, course_id):
+ return [ModuleData(module_obj, course_id) for module_obj in modules]
+
+ @classmethod
+ def md_to_dict(cls, file):
+ with open(file, 'r') as f:
+ lines = f.readlines()
+ metadata = _metadata_to_dict(lines)
+ display_content = _display_content_to_dict(lines)
+ clean_display_content = display_content.strip('\n')
+ try:
+ data = metadata.get('meta', {}).get('data', {})
+ except KeyError:
+ print("[ERROR] Data not found. Please check the MD file")
+
+ data.update({cls.display_content_field: clean_display_content})
+ return data
+
+
+def create_header(data, dtype):
+ header = []
+ metadata = {
+ "type": dtype,
+ "data": data
+ }
+ yaml=YAML()
+ yaml.default_flow_style = False
+ io_obj = io.StringIO()
+
+ yaml.dump({"meta": metadata}, io_obj)
+
+ if metadata['data'].get('id', None):
+ raw_yaml_data = yaml.load(io_obj.getvalue())
+ raw_yaml_data['meta']['data'].yaml_add_eol_comment('Do Not Change This Value', 'id')
+ io_obj = io.StringIO()
+ yaml.dump(raw_yaml_data, io_obj)
+
+ header.extend(io_obj.getvalue().splitlines())
+ header = ["---"] + header + ["---"]
+
+ return header
+
+
+def get_course_data(course_id):
+ course_obj = Course.objects.get(id=course_id)
+ return CourseData(course_obj)
+
+
+def _create_clean_file_name(name, ext):
+ clean_name = re.sub(r'[^\w\s-]', '', name).strip().lower()
+ return re.sub(r'[-\s]+', '_', clean_name) + '.' + ext
+
+def _get_file_name_from_object(content_object, ext):
+ name = ' '.join(
+ [content_object.type, content_object.data.get('name')]
+ )
+ return _create_clean_file_name(name, ext)
+
+
+
+def create_md(content_object, file_name=None, multiple_obj=False):
+ file_content = []
+ if not file_name:
+ dest_file_name = _get_file_name_from_object(content_object, 'md')
+ else:
+ dest_file_name = file_name
+ data = content_object.data
+ dtype = content_object.type
+ seperator = '\n{0}\n'.format(DATA_SEP) if multiple_obj else ''
+ header = create_header(data, dtype)
+ if content_object.display_content:
+ file_content.append(content_object.display_content)
+
+ file_content = (
+ '\n'.join(header) + '\n\n' + '\n'.join(file_content) +
+ seperator
+ )
+
+ with open(dest_file_name, 'a+') as f:
+ f.write(file_content)
+
+ return dest_file_name
+
+
+def write_course_to_file(course_id):
+ # Create the course md file
+ course = get_course_data(course_id)
+ course_file = create_md(course)
+
+ course_map = {'course': course_file, 'modules': []}
+
+ # Create the modules and lessons md files
+ for module in course.modules:
+ mod_file = create_md(module, multiple_obj=True)
+ for lesson in module.units:
+ create_md(lesson, mod_file, multiple_obj=True)
+
+ course_map['modules'].append(
+ {'file': mod_file,}
+ )
+
+ with open('toc.yml', 'w') as f:
+ f.write(yaml.safe_dump(course_map))
+
+
+def convert_md_to_dict(toc, user):
+ course_file = toc.get('course')
+ course_data = CourseData.md_to_dict(course_file)
+ module_obj_list = []
+ for module in toc.get('modules', []):
+ module_file = module.get('file')
+ module_data = UnitData.md_to_dict_from_file(
+ module_file
+ ).get('module')
+ module_id = module_data.get('id', None)
+ if module_id:
+ mod_created = False
+ module_obj = LearningModule.objects.get(id=module_id)
+ module_obj.__dict__.update(module_data)
+ module_obj.save()
+
+ else:
+ mod_created = True
+ module_data.update({'creator': user})
+ module_obj = LearningModule.objects.create(
+ **module_data
+ )
+
+ unit_file = module.get('units', None)
+ unit_list = UnitData.md_to_dict_from_file(module_file).get('learning_units')
+ unit_obj_list = []
+ for unit in unit_list:
+ unit_type = unit.get('type')
+ lesson_or_quiz_obj = None
+ if unit_type == 'lesson':
+ lq_data = unit.pop('lesson')
+ lq_data.update({'creator': user})
+ if lq_data.get('id'): # Lesson already exists
+ lesson_or_quiz_obj = Lesson.objects.get(id=lq_data.get('id'))
+ lesson_or_quiz_obj.__dict__.update(lq_data)
+ lesson_or_quiz_obj.save()
+
+ toc_file = lq_data.get('toc', None)
+ if toc_file:
+ with open(toc_file, 'r') as tocf:
+ toc_data = ruamel.yaml.safe_load_all(tocf.read())
+ results = TableOfContents.objects.add_contents(
+ course_data.get('id'), lesson_or_quiz_obj.id , user, toc_data)
+ for status, msg in results:
+ if status == False:
+ raise Exception(msg)
+ else:
+ lesson_or_quiz_obj = Lesson.objects.create(
+ **lq_data,
+ )
+ unit['lesson'] = lesson_or_quiz_obj
+
+ toc_file = lq_data.get('toc', None)
+ if toc_file:
+ with open(toc_file, 'r') as tocf:
+ toc_data = ruamel.yaml.safe_load_all(tocf.read())
+ results = TableOfContents.objects.add_contents(
+ course_data.get('id'), lesson_or_quiz_obj.id , user, toc_data)
+ for status, msg in results:
+ if status == False:
+ raise Exception(msg)
+ else:
+ lq_data = unit.pop('quiz')
+ lq_data.update({'creator': user})
+ if lq_data.get('id'): # Quiz already exists
+ lesson_or_quiz_obj = Quiz.objects.get(id=lq_data.get('id'))
+ lesson_or_quiz_obj.__dict__.update(lq_data)
+ lesson_or_quiz_obj.save()
+ else:
+ lesson_or_quiz_obj = Quiz.objects.create(
+ **lq_data,
+ )
+ unit['quiz'] = lesson_or_quiz_obj
+
+ if not lq_data.get('id'):
+ unit_obj, unit_created = LearningUnit.objects.create(
+ **unit
+ ), True
+ else:
+ lesson_or_quiz_class_map = {
+ 'lesson': Lesson,
+ 'quiz': Quiz,
+ }
+ unit_created = False
+ lq_obj_id = lq_data.get('id')
+ lq_obj = lesson_or_quiz_class_map.get(unit_type).objects.get(id=lq_obj_id)
+ m_units = module_obj.learning_unit.values_list('id', flat=True)
+ lq_units = lq_obj.learningunit_set.values_list('id', flat=True)
+ unit_id = list(set(m_units) & set(lq_units))[0]
+
+ unit_obj = LearningUnit.objects.get(id=unit_id)
+ unit_obj.__dict__.update({'order': unit.get('order', 0)})
+ unit_obj.save()
+
+ if unit_created:
+ unit_obj_list.append(unit_obj)
+
+ module_obj.learning_unit.add(*unit_obj_list)
+ if mod_created:
+ module_obj_list.append(module_obj)
+
+ if course_data.get('id', None):
+ course_obj = Course.objects.get(id=course_data.get('id'))
+ course_obj.__dict__.update(course_data)
+ course_obj.save()
+ else:
+ course_data.update({'creator': user})
+ course_obj, course_created = Course.objects.create(
+ **course_data
+ ), True
+
+ course_obj.learning_module.add(*module_obj_list)
+
+ return course_obj
+
+
+def check_data(toc):
+ course_file = toc.get('course')
+ course_data = CourseData.md_to_dict(course_file)
+ course_id = course_data.get('id')
+ module_id_list = []
+ for data_elem in toc.get('modules', []):
+ _file = data_elem.get('file')
+ _data = UnitData.md_to_dict_from_file(_file)
+
+ module_id = _data.get('module', None).get('id', None)
+ if module_id:
+ module_id_list.append(module_id)
+
+ lesson_id_list = []
+ quiz_id_list = []
+ for unit in _data.get('learning_units'):
+ unit_id = unit.get('id', None)
+ unit_type = unit.get('type', None)
+ if unit_id:
+ if unit_type == 'lesson':
+ lesson_id_list.append(unit_id)
+ else:
+ quiz_id_list.append(unit_id)
+
+ try:
+ if not has_relationship(module_id, 'learning_module', lesson_id_list, unit_type):
+ msg = "Lesson IDs used in metadata do not belong to current course, Kindly inspect and reupload"
+ return False, msg
+ if not has_relationship(module_id, 'learning_module', quiz_id_list, unit_type):
+ msg = "Quiz IDs used in metadata do not belong to current course, Kindly inspect and reupload"
+ return False, msg
+ except ObjectDoesNotExist:
+ msg = "Object does not exist in DB"
+ return False, msg
+
+ try:
+ if not has_relationship(course_id, 'course', module_id_list):
+ msg = "Module IDs used in metadata do not belong to current course, Kindly inspect and reupload"
+ return False, msg
+
+ if has_duplicate_id(course_id, 'course', module_id_list):
+ msg = "Modules metadata contains duplicate IDs, Kindly inspect and reupload"
+ return False, msg
+ except ObjectDoesNotExist:
+ msg = "Object does not exist in DB"
+ return False, msg
+ return True, 'File check successful'
+
+
+def get_parent_child_data_from_db(parent_id, parent_type, child_id_list, child_type=None):
+ if parent_type == 'learning_module':
+ mod_obj = LearningModule.objects.get(id=parent_id)
+ relationship_id_list = [ e for e in
+ mod_obj.learning_unit.order_by(
+ "order"
+ ).values_list(
+ child_type + '__id', flat=True,
+ ) if e != None
+ ]
+ return relationship_id_list
+
+ elif parent_type == 'course':
+ course_obj = Course.objects.get(id=parent_id)
+ relationship_id_list = course_obj.get_learning_modules().values_list(
+ 'id', flat=True,
+ )
+ return relationship_id_list
+
+def has_duplicate_id(parent_id, parent_type, child_id_list, child_type=None):
+ relationship_id_list = get_parent_child_data_from_db(
+ parent_id, parent_type, child_id_list, child_type
+ )
+ if len(child_id_list) != len(set(relationship_id_list)):
+ return True # duplicates exist
+ else:
+ return False # duplicates do not exist
+
+def has_relationship(parent_id, parent_type, child_id_list, child_type=None):
+ relationship_id_list = get_parent_child_data_from_db(
+ parent_id, parent_type, child_id_list, child_type
+ )
+
+ for _id in child_id_list:
+ if _id not in relationship_id_list:
+ return False
+ return True
+
+def read_toc(file):
+ with open(file, 'r') as f:
+ toc = yaml.load(f, Loader=yaml.FullLoader)
+ return toc
+
+def upload_course(user):
+ toc = read_toc('toc.yml')
+ status, msg = check_data(toc)
+ course_data = convert_md_to_dict(toc, user)
+ return status, msg
+
diff --git a/upload/views.py b/upload/views.py
new file mode 100644
index 0000000..fb80c07
--- /dev/null
+++ b/upload/views.py
@@ -0,0 +1,62 @@
+import tempfile
+import os
+from zipfile import ZipFile
+from io import BytesIO as string_io
+
+from django.http import HttpResponse
+from django.shortcuts import render
+from django.contrib.auth.decorators import login_required
+from django.contrib import messages
+
+from upload.utils import upload_course, write_course_to_file
+
+
+def upload_course_md(request):
+ if request.method == 'POST':
+ status = False
+ msg = None
+ user = request.user
+ course_upload_file = request.FILES.get('course_upload_md')
+ file_extension = os.path.splitext(course_upload_file.name)[1][1:]
+ if file_extension not in ['zip']:
+ messages.warning(
+ request, "Please upload zip file"
+ )
+ else:
+ curr_dir = os.getcwd()
+ try:
+ with tempfile.TemporaryDirectory() as tmpdirname, ZipFile(course_upload_file, 'r') as zip_file:
+ zip_file.extractall(tmpdirname)
+ os.chdir(tmpdirname)
+ status, msg = upload_course(user)
+ except Exception as e:
+ import traceback
+ traceback.print_exc()
+ messages.warning(request, f"Error parsing file structure: {e}")
+ finally:
+ os.chdir(curr_dir)
+
+ return status, msg
+
+def download_course_md(request, course_id):
+ curr_dir = os.getcwd()
+ zip_file_name = string_io()
+ try:
+ with tempfile.TemporaryDirectory() as tmpdirname, ZipFile(zip_file_name, 'w') as zip_file:
+ os.chdir(tmpdirname)
+ write_course_to_file(course_id)
+
+ for foldername, subfolders, filenames in os.walk(tmpdirname):
+ for filename in filenames:
+ zip_file.write(os.path.join(filename))
+ except Exception as e:
+ messages.warning(request, f"Error while downloading file: {e}")
+ finally:
+ os.chdir(curr_dir)
+
+ zip_file_name.seek(0)
+ response = HttpResponse(content_type='application/zip')
+ response['Content-Disposition'] = 'attachment; filename=course.zip'
+ response.write(zip_file_name.read())
+
+ return response \ No newline at end of file
diff --git a/yaksh/models.py b/yaksh/models.py
index a29e910..bdac927 100644
--- a/yaksh/models.py
+++ b/yaksh/models.py
@@ -1381,7 +1381,7 @@ class Question(models.Model):
# Solution for the question.
solution = models.TextField(blank=True)
- content = GenericRelation("TableOfContents")
+ content = GenericRelation("TableOfContents", related_query_name='questions')
tc_code_types = {
"python": [
@@ -2823,6 +2823,17 @@ class TOCManager(models.Manager):
"student_id", flat=True).distinct().count()
return data
+ def get_all_tocs_as_yaml(self, course_id, lesson_id, file_path):
+ all_tocs = TableOfContents.objects.filter(
+ course_id=course_id, lesson_id=lesson_id,
+ )
+ if not all_tocs.exists():
+ return None
+ for toc in all_tocs:
+ toc.get_toc_as_yaml(file_path)
+ return file_path
+
+
def get_question_stats(self, toc_id):
answers = LessonQuizAnswer.objects.get_queryset().filter(
toc_id=toc_id).order_by('id')
@@ -2929,7 +2940,7 @@ class TOCManager(models.Manager):
else:
que = Question.objects.create(**content)
for test_case in test_cases:
- test_case_type = test_case.pop('test_case_type')
+ test_case_type = test_case.pop('type')
model_class = get_model_class(test_case_type)
model_class.objects.get_or_create(
question=que, **test_case, type=test_case_type
@@ -2971,6 +2982,39 @@ class TableOfContents(models.Model):
content_name = self.content_object.summary
return content_name
+ def get_toc_as_yaml(self, file_path):
+ data = {'content_type': self.content, 'time': self.time}
+ if self.topics.exists():
+ content = self.topics.first()
+ data.update(
+ {
+ 'name': content.name,
+ 'description': content.description,
+ }
+ )
+ elif self.questions.exists():
+ content = self.questions.first()
+ tc_data = []
+ for tc in content.get_test_cases():
+ _tc_as_dict = model_to_dict(
+ tc, exclude=['id', 'testcase_ptr', 'question'],
+ )
+ tc_data.append(_tc_as_dict)
+ data.update(
+ {
+ 'summary': content.summary,
+ 'type': content.type,
+ 'language': content.language,
+ 'description': content.description,
+ 'points': content.points,
+ 'testcase': tc_data,
+ }
+ )
+ yaml_block = dict_to_yaml(data)
+ with open(file_path, "a") as yaml_file:
+ yaml_file.write(yaml_block)
+ return yaml_file
+
def __str__(self):
return f"TOC for {self.lesson.name} with {self.get_content_display()}"
@@ -2978,7 +3022,7 @@ class TableOfContents(models.Model):
class Topic(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
- content = GenericRelation(TableOfContents)
+ content = GenericRelation(TableOfContents, related_query_name='topics')
def __str__(self):
return f"{self.name}"
diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html
index 9f75a68..8661aea 100644
--- a/yaksh/templates/yaksh/course_detail.html
+++ b/yaksh/templates/yaksh/course_detail.html
@@ -55,6 +55,8 @@
{% include "yaksh/addteacher.html" %}
{% elif is_teachers %}
{% include "yaksh/course_teachers.html" %}
+ {% elif is_upload_download_md %}
+ {% include "yaksh/upload_download_course_md.html" %}
{% else %}
<div class="jumbotron">
<h1 class="display-4">Manage Course</h1>
diff --git a/yaksh/templates/yaksh/course_detail_options.html b/yaksh/templates/yaksh/course_detail_options.html
index 84f78ce..f9393ed 100644
--- a/yaksh/templates/yaksh/course_detail_options.html
+++ b/yaksh/templates/yaksh/course_detail_options.html
@@ -43,4 +43,9 @@
Current Teachers/TAs
</a>
</li>
+ <li class="nav-item">
+ <a class="nav-link list-group-item {% if is_upload_download_md %} active {% endif %}" href="{% url 'yaksh:upload_download_course_md' course.id %}" data-toggle="tooltip" title="Upload / Download MD files" data-placement="top">
+ Upload / Download MD
+ </a>
+ </li>
</ul> \ No newline at end of file
diff --git a/yaksh/templates/yaksh/upload_download_course_md.html b/yaksh/templates/yaksh/upload_download_course_md.html
new file mode 100644
index 0000000..072ae4c
--- /dev/null
+++ b/yaksh/templates/yaksh/upload_download_course_md.html
@@ -0,0 +1,13 @@
+<div>
+ <a href="{% url 'upload:download_course_md' course.id %}">
+ <i class="fa fa-download"></i>&nbsp;Download
+ </a>
+ <br><br>
+ <form action="" method="POST" enctype="multipart/form-data">
+ {% csrf_token %}
+ <input type="file" name="course_upload_md" required="">
+ <button class="btn btn-outline-success" id="course_upload_md_btn" name="course_upload_md_btn">
+ <i class="fa fa-upload"></i>&nbsp;Upload
+ </button>
+ </form>
+</div> \ No newline at end of file
diff --git a/yaksh/urls.py b/yaksh/urls.py
index e93d80a..b7d8ff6 100644
--- a/yaksh/urls.py
+++ b/yaksh/urls.py
@@ -271,4 +271,6 @@ urlpatterns = [
views.download_sample_toc, name='download_sample_toc'),
path('manage/upload_marks/<int:course_id>/<int:questionpaper_id>/',
views.upload_marks, name='upload_marks'),
+ path(r'manage/upload_download_course_md/<int:course_id>',
+ views.upload_download_course_md, name="upload_download_course_md"),
]
diff --git a/yaksh/views.py b/yaksh/views.py
index 11a77b8..f3e8668 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -40,7 +40,7 @@ from yaksh.models import (
StdIOBasedTestCase, StringTestCase, TestCase, User,
get_model_class, FIXTURES_DIR_PATH, MOD_GROUP_NAME, Lesson, LessonFile,
LearningUnit, LearningModule, CourseStatus, question_types, Post, Comment,
- Topic, TableOfContents, LessonQuizAnswer, MicroManager
+ Topic, TableOfContents, LessonQuizAnswer, MicroManager, dict_to_yaml
)
from stats.models import TrackLesson
from yaksh.forms import (
@@ -2621,6 +2621,23 @@ def download_sample_toc(request):
@login_required
@email_verified
+def download_toc(request, course_id, lesson_id):
+ user = request.user
+ tmp_file_path = tempfile.mkdtemp()
+ yaml_path = os.path.join(tmp_file_path, "lesson_toc.yaml")
+ TableOfContents.objects.get_all_tocs_as_yaml(course_id, lesson_id, yaml_path)
+
+ with open(yaml_path, 'r') as yml_file:
+ response = HttpResponse(yml_file.read(), content_type='text/yaml')
+ response['Content-Disposition'] = (
+ 'attachment; filename="lesson_toc.yaml"'
+ )
+ return response
+
+
+
+@login_required
+@email_verified
def duplicate_course(request, course_id):
user = request.user
course = Course.objects.get(id=course_id)
@@ -4144,3 +4161,25 @@ def _read_marks_csv(request, reader, course, question_paper, question_ids):
messages.info(request,
'Updated successfully for user: {0}, question: {1}'.format(
username, question.summary))
+
+
+@login_required
+@email_verified
+def upload_download_course_md(request, course_id):
+ course = get_object_or_404(Course, pk=course_id)
+ if request.method == "POST":
+ from upload.views import upload_course_md
+ status, msg = upload_course_md(request)
+ if status:
+ messages.success(request, "MD File Successfully uploaded to course")
+ else:
+ messages.warning(request, "{0}".format(msg))
+ return redirect(
+ 'yaksh:course_detail', course.id
+ )
+ else:
+ context = {
+ 'course': course,
+ 'is_upload_download_md': True,
+ }
+ return my_render_to_response(request, 'yaksh/course_detail.html', context)