text: from django.forms.models import modelform_factory
  from django.apps import apps
  from django.db.models import Count
  from django.shortcuts import render
  from django.http import HttpResponse
  from django.urls import reverse_lazy
  from django.shortcuts import redirect, get_object_or_404
  from django.views.generic.base import TemplateResponseMixin, View
  from django.views.generic.list import ListView
  from django.views.generic.detail import DetailView
  from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
  from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
  from django.contrib.auth.models import User
  from braces.views import CsrfExemptMixin, JsonRequestResponseMixin
  from .models import Course, Module, Content, Subject
  from .forms import ModuleFormSet
  from students.forms import CourseEnrollForm
  
  
  class OwnerMixin:
      def get_queryset(self):
          qs = super().get_queryset()
          return qs.filter(owner=self.request.user)
  
  
  class OwnerEditMixin:
      def form_valid(self, form):
          form.instance.owner = self.request.user
          return super().form_valid(form)
  
  
  class OwnerCourseMixin(OwnerMixin, LoginRequiredMixin):
      model = Course
      fields = ['subject', 'title', 'slug', 'banner', 'overview']
      success_url = reverse_lazy('manage_course_list')
  
  
  class OwnerCourseEditMixin(OwnerCourseMixin, OwnerEditMixin):
      fields = ['subject', 'title', 'slug', 'banner', 'overview']
      success_url = reverse_lazy('manage_course_list')
      template_name = 'courses/manage/course/form.html'
  
  
  class ManageCourseListView(OwnerCourseMixin, ListView):
      model = Course
      template_name = 'courses/manage/course/list.html'
  
  
  class CourseCreateView(PermissionRequiredMixin, OwnerCourseEditMixin, CreateView):
      permission_required = 'courses.add_course'
  
  
  class CourseUpdateView(PermissionRequiredMixin, OwnerCourseEditMixin, UpdateView):
      permission_required = 'courses.change_course'
  
  
  class CourseDeleteView(PermissionRequiredMixin, OwnerCourseMixin, DeleteView):
      template_name = 'courses/manage/course/delete.html'
      success_url = reverse_lazy('manage_course_list')
      permission_required = 'courses.delete_course'
  
  
  class CourseModuleUpdateView(TemplateResponseMixin, View):
      template_name = 'courses/manage/module/formset.html'
      course = None
  
      def get_formset(self, data=None):
          return ModuleFormSet(instance=self.course, data=data)
  
      def dispatch(self, request, pk):
          self.course = get_object_or_404(Course, id=pk, owner=request.user)
          return super().dispatch(request, pk)
  
      def get(self, request, *args, **kwargs):
          formset = self.get_formset()
          return self.render_to_response({'course': self.course, 'formset': formset})
  
      def post(self, request, *args, **kwargs):
          formset = self.get_formset(data=request.POST)
          if formset.is_valid():
              formset.save()
              return redirect('manage_course_list')
          return self.render_to_response({'course': self.course,
                                          'formset': formset})
  
  
  class ContentCreateUpdateView(TemplateResponseMixin, View):
      module = None
      model = None
      obj = None
      template_name = 'courses/manage/content/form.html'
  
      def get_model(self, model_name):
          if model_name in ['text', 'video', 'image', 'file']:
              return apps.get_model(app_label='courses', model_name=model_name)
          return None
  
      def get_form(self, model, *args, **kwargs):
          Form = modelform_factory(model, exclude=['owner',
                                                   'order',
                                                   'created',
                                                   'updated'])
          return Form(*args, **kwargs)
  
      def dispatch(self, request, module_id, model_name, id=None):
          self.module = get_object_or_404(Module,
                                          id=module_id,
                                          course__owner=request.user)
          self.model = self.get_model(model_name)
          if id:
              self.obj = get_object_or_404(self.model, id=id, owner=request.user)
          return super(ContentCreateUpdateView, self).dispatch(request, module_id,
                                                               model_name, id)
  
      def get(self, request, module_id, model_name, id=None):
          form = self.get_form(self.model, instance=self.obj)
          return self.render_to_response({'form': form, 'object': self.obj})
  
      def post(self, request, module_id, model_name, id=None):
          form = self.get_form(self.model,
                               instance=self.obj,
                               data=request.POST,
                               files=request.FILES)
          if form.is_valid():
              obj = form.save(commit=False)
              obj.owner = request.user
              obj.save()
              if not id:
                  # New content
                  Content.objects.create(module=self.module,
                                         item=obj)
              return redirect('module_content_list', self.module.id)
          return self.render_to_response({'form': form, 'object': self.obj})
  
  
  class ContentDeleteView(View):
  
      def post(self, request, id):
          content = get_object_or_404(Content,
                                      id=id,
                                      module__course__owner=request.user)
          module = content.module
          content.item.delete()
          content.delete()
          return redirect('module_content_list', module.id)
  
  
  class ModuleContentListView(TemplateResponseMixin, View):
      template_name = 'courses/manage/module/content_list.html'
  
      def get(self, request, module_id):
          module = get_object_or_404(Module,
                                     id=module_id,
                                     course__owner=request.user)
          return self.render_to_response({'module': module})
  
  
  class ModuleOrderView(CsrfExemptMixin, JsonRequestResponseMixin, View):
  
      def post(self, request):
          for id, order in self.request_json.items():
              Module.objects.filter(id=id,
                                    course__owner=request.user).update(order=order)
          return self.render_json_response({'saved': 'OK'})
  
  
  class ContentOrderView(CsrfExemptMixin, JsonRequestResponseMixin, View):
  
      def post(self, request):
          for id, order in self.request_json.items():
              Content.objects.filter(id=id,
                                     module__course__owner=request.user) \
                  .update(order=order)
          return self.render_json_response({'saved': 'OK'})
  
  
  class CourseListView(TemplateResponseMixin, View):
      model = Course
      template_name = 'courses/course/list.html'
  
      def get(self, request, subject=None):
          ordered_subjects = Subject.ordered_active_subjects()
          ordered_subjects = ordered_subjects.annotate(
              total_courses=Count('courses'))
          for s in ordered_subjects:
              print(s.courses.count())
              print('----')
              print(dir(s))
          courses = Course.objects.annotate(
              total_modules=Count('modules'))
          if subject:
              subject = get_object_or_404(Subject, slug=subject)
              courses = courses.filter(subject=subject)
          
          ctx = {
              'ordered_subjects': ordered_subjects,
              'subject': subject,
              'courses': courses}
          return self.render_to_response(ctx)
  
  
  class CourseByOwnerList(TemplateResponseMixin, View):
      """Shows courses created by author."""
      model = Course
      template_name = 'courses/course/owner_list.html'
  
      def get(self, request, owner=None):
          if owner:
              owner = get_object_or_404(User, username=owner)
              courses = Course.objects.filter(owner=owner)
              return self.render_to_response({'courses': courses,
                                              'owner': owner})
          return None
  
  
  class CourseDetailView(DetailView):
      model = Course
      template_name = 'courses/course/detail.html'
  
      def get_context_data(self, **kwargs):
          context = super().get_context_data(**kwargs)
          context['enroll_form'] = CourseEnrollForm(
              initial={'course': self.object}
          )
          return context
  
  
  def search_courses(request):
      if request.method == 'GET':
          query = request.GET.get('q')
          owners_qs = list(Course.objects.filter(
              owner__username__icontains=query
          ))
          subject_qs = list(Course.objects.filter(
              subject__title__icontains=query
          ))
          title_qs = list(Course.objects.filter(
              title__icontains=query
          ))
          results = title_qs + subject_qs + owners_qs
          ctx = {
              'result_number': len(results),
              'results': results,
              'search_query': query,
          }
          return render(request, 'courses/search_result.html', ctx)
      else:
          return HttpResponse('Method not allowed.')
  