From 20b692ea468a280e3edb4a9e7f97543b5499025d Mon Sep 17 00:00:00 2001 From: adityacp Date: Tue, 26 Jun 2018 13:13:22 +0530 Subject: Changes in views, models, forms, urls, file_utils - Add new view function to download course content - Add new attribute to Lesson model - Add new model methods to add course, module and lesson content - Add validation in forms to check for lesson video file format - Add functions in file_utils to add static files and templates to the zip file --- yaksh/file_utils.py | 26 ++++++++++++++++ yaksh/forms.py | 14 ++++++++- yaksh/models.py | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++-- yaksh/urls.py | 6 +++- yaksh/views.py | 37 ++++++++++++++++++++++- 5 files changed, 165 insertions(+), 5 deletions(-) diff --git a/yaksh/file_utils.py b/yaksh/file_utils.py index 6c3fd5d..6b0340f 100644 --- a/yaksh/file_utils.py +++ b/yaksh/file_utils.py @@ -3,6 +3,7 @@ import os import zipfile import tempfile import csv +from django.template import Context, Template def copy_files(file_paths): @@ -66,3 +67,28 @@ def is_csv(document): except (csv.Error, UnicodeDecodeError): return False, None return True, dialect + + +def write_static_files_to_zip(zipfile, course_name, current_dir): + relative_folders = ["css", "js", "images"] + static_files = {"js": ["bootstrap.js", "bootstrap.min.js", + "jquery-1.9.1.min.js", "video.js"], + "css": ["bootstrap.css", "bootstrap.min.css", + "video-js.css"], + "images": ["yaksh_banner.png"]} + for folder in relative_folders: + folder_path = os.sep.join((current_dir, "static", "yaksh", folder)) + for file in static_files[folder]: + file_path = os.sep.join((folder_path, file)) + zipfile.write(file_path, os.sep.join((course_name, "static", + folder, file))) + + +def write_templates_to_zip(zipfile, template_path, data, filename, filepath): + with open(template_path) as f: + template_data = f.read() + template = Template(template_data) + context = Context(data) + render = template.render(context) + zipfile.writestr(os.sep.join((filepath, "{0}.html".format(filename))), + render) diff --git a/yaksh/forms.py b/yaksh/forms.py index 41c9176..66076b1 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -324,7 +324,19 @@ class LessonForm(forms.ModelForm): class Meta: model = Lesson - exclude = ['completed_lessons', 'creator', 'html_data'] + exclude = ['creator', 'html_data'] + + def clean_video_file(self): + file = self.cleaned_data.get("video_file") + if file: + extension = file.name.split(".")[-1] + actual_extension = ["mp4", "ogv"] + if extension not in actual_extension: + raise forms.ValidationError( + "Please upload video files in {0} format".format( + ", ".join(actual_extension)) + ) + return file class LessonFileForm(forms.Form): diff --git a/yaksh/models.py b/yaksh/models.py index 5d17dba..b97859d 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -26,7 +26,10 @@ import zipfile import tempfile from textwrap import dedent from ast import literal_eval -from .file_utils import extract_files, delete_files +from .file_utils import ( + extract_files, delete_files, write_templates_to_zip, + write_static_files_to_zip +) from yaksh.code_server import ( submit, get_result as get_result_from_code_server ) @@ -124,7 +127,10 @@ def dict_to_yaml(dictionary): def get_file_dir(instance, filename): - upload_dir = instance.lesson.name.replace(" ", "_") + if isinstance(instance, LessonFile): + upload_dir = instance.lesson.name.replace(" ", "_") + else: + upload_dir = instance.name.replace(" ", "_") return os.sep.join((upload_dir, filename)) @@ -159,6 +165,11 @@ class Lesson(models.Model): # Activate/Deactivate Lesson active = models.BooleanField(default=True) + # A video file + video_file = models.FileField(upload_to=get_file_dir, default=None, + null=True, blank=True + ) + def __str__(self): return "{0}".format(self.name) @@ -182,6 +193,37 @@ class Lesson(models.Model): lesson_file_obj.file.save(file_name, django_file, save=True) return new_lesson + def remove_file(self): + if self.video_file: + file_path = self.video_file.path + if os.path.exists(file_path): + os.remove(file_path) + + def _add_lesson_to_zip(self, module, course, zip_file, path): + lesson_name = self.name.replace(" ", "_") + course_name = course.name.replace(" ", "_") + module_name = module.name.replace(" ", "_") + sub_folder_name = os.sep.join(( + course_name, module_name, lesson_name + )) + lesson_files = self.get_files() + if self.video_file: + video_file = os.sep.join((sub_folder_name, os.path.basename( + self.video_file.name))) + zip_file.write(self.video_file.path, video_file) + for lesson_file in lesson_files: + if os.path.exists(lesson_file.file.path): + filename = os.sep.join((sub_folder_name, os.path.basename( + lesson_file.file.name))) + zip_file.write(lesson_file.file.path, filename) + unit_file_path = os.sep.join(( + path, "templates", "yaksh", "unit.html" + )) + lesson_data = {"course": course, "module": module, + "lesson": self, "lesson_files": lesson_files} + write_templates_to_zip(zip_file, unit_file_path, lesson_data, + lesson_name, sub_folder_name) + ############################################################################# class LessonFile(models.Model): @@ -491,6 +533,10 @@ class LearningModule(models.Model): return [unit.quiz for unit in self.learning_unit.filter( type="quiz")] + def get_lesson_units(self): + return [unit.lesson for unit in self.learning_unit.filter( + type="lesson").order_by("order")] + def get_learning_units(self): return self.learning_unit.order_by("order") @@ -579,6 +625,20 @@ class LearningModule(models.Model): new_module.learning_unit.add(new_unit) return new_module + def _add_module_to_zip(self, course, zip_file, path): + module_name = self.name.replace(" ", "_") + course_name = course.name.replace(" ", "_") + folder_name = os.sep.join((course_name, module_name)) + lessons = self.get_lesson_units() + for lesson in lessons: + lesson._add_lesson_to_zip(self, course, zip_file, path) + module_file_path = os.sep.join(( + path, "templates", "yaksh", "module.html" + )) + module_data = {"course": course, "module": self, "lessons": lessons} + write_templates_to_zip(zip_file, module_file_path, module_data, + module_name, folder_name) + def __str__(self): return self.name @@ -819,6 +879,29 @@ class Course(models.Model): percentage = 0 return percentage + def is_student(self, user): + return user in self.students.all() + + def create_zip(self, zip_file, path): + course_name = self.name.replace(" ", "_") + modules = self.get_learning_modules() + file_path = os.sep.join((path, "templates", "yaksh", "index.html")) + write_static_files_to_zip(zip_file, course_name, path) + course_data = {"course": self, "modules": modules} + write_templates_to_zip(zip_file, file_path, course_data, + "index", course_name) + for module in modules: + module._add_module_to_zip(self, zip_file, path) + + def has_lessons(self): + modules = self.get_learning_modules() + status = False + for module in modules: + if module.get_lesson_units(): + status = True + break + return status + def __str__(self): return self.name diff --git a/yaksh/urls.py b/yaksh/urls.py index 1e1def6..6e6b8c1 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -200,5 +200,9 @@ urlpatterns = [ url(r'^manage/preview_questionpaper/(?P\d+)/$', views.preview_questionpaper, name="preview_questionpaper"), url(r'^manage/get_user_status/(?P\d+)/(?P\d+)/$', - views.get_user_data, name="get_user_data") + views.get_user_data, name="get_user_data"), + url(r'^manage/courses/download_course/(?P\d+)/$', + views.download_course, name="download_course"), + url(r'^download_course/(?P\d+)/$', + views.download_course, name="download_course"), ] diff --git a/yaksh/views.py b/yaksh/views.py index 3341aca..c5c5be1 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -2297,9 +2297,16 @@ def edit_lesson(request, lesson_id=None, course_id=None): context = {} if request.method == "POST": if "Save" in request.POST: - lesson_form = LessonForm(request.POST, instance=lesson) + lesson_form = LessonForm(request.POST, request.FILES, + instance=lesson) lesson_file_form = LessonFileForm(request.POST, request.FILES) lessonfiles = request.FILES.getlist('Lesson_files') + clear = request.POST.get("video_file-clear") + video_file = request.FILES.get("video_file") + if (clear or video_file) and lesson: + # Remove previous video file if new file is uploaded or + # if clear is selected + lesson.remove_file() if lesson_form.is_valid(): if lesson is None: lesson_form.instance.creator = user @@ -2314,6 +2321,7 @@ def edit_lesson(request, lesson_id=None, course_id=None): return my_redirect(redirect_url) else: context['lesson_form'] = lesson_form + context['error'] = lesson_form["video_file"].errors context['lesson_file_form'] = lesson_file_form if 'Delete' in request.POST: @@ -2808,3 +2816,30 @@ def get_user_data(request, course_id, student_id): context = Context(data) data = template.render(context) return HttpResponse(json.dumps({"user_data": data}), **response_kwargs) + + +@login_required +@email_verified +def download_course(request, course_id): + user = request.user + course = get_object_or_404(Course, pk=course_id) + if (not course.is_creator(user) and not course.is_teacher(user) and not + course.is_student(user)): + raise Http404("You are not allowed to download {0} course".format( + course.name)) + if not course.has_lessons(): + raise Http404("{0} course does not have any lessons".format( + course.name)) + file_name = string_io() + current_dir = os.path.dirname(__file__) + course_name = course.name.replace(" ", "_") + zip_file = zipfile.ZipFile(file_name, "w") + course.create_zip(zip_file, current_dir) + zip_file.close() + file_name.seek(0) + response = HttpResponse(content_type='application/zip') + response['Content-Disposition'] = 'attachment; filename={0}.zip'.format( + course_name + ) + response.write(file_name.read()) + return response -- cgit