We need a view for displaying the courses the students are enrolled in, and a view for accessing the actual course contents. Edit the views.py
file of the students application and add the following code to it:
from django.views.generic.list import ListView from courses.models import Course class StudentCourseListView(LoginRequiredMixin, ListView): model = Course template_name = 'students/course/list.html' def get_queryset(self): qs = super(StudentCourseListView, self).get_queryset() return qs.filter(students__in=[self.request.user])
This is the view for students to list the courses they are enrolled in. It inherits from LoginRequiredMixin
to make sure that only logged in users can access the view. It also inherits from the generic ListView
for displaying a list of Course
objects. We override the get_queryset()
method for retrieving only the courses the user is enrolled in: We filter the querysetk by the students ManyToManyField
field for doing so.
Then add the following code to the views.py
file:
from django.views.generic.detail import DetailView class StudentCourseDetailView(DetailView): model = Course template_name = 'students/course/detail.html' def get_queryset(self): qs = super(StudentCourseDetailView, self).get_queryset() return qs.filter(students__in=[self.request.user]) def get_context_data(self, **kwargs): context = super(StudentCourseDetailView, self).get_context_data(**kwargs) # get course object course = self.get_object() if 'module_id' in self.kwargs: # get current module context['module'] = course.modules.get( id=self.kwargs['module_id']) else: # get first module context['module'] = course.modules.all()[0] return context
This is the StudentCourseDetailView
. We override the get_queryset()
method to limit the base queryset to courses in which the user is enrolled. We also override the get_context_data()
method to set a course module in the context if the module_id
URL parameter is given. Otherwise, we set the first module of the course. This way, students will be able to navigate through modules inside a course.
Edit the the urls.py
file of the students application and add the following URL patterns to it:
url(r'^courses/$', views.StudentCourseListView.as_view(), name='student_course_list'), url(r'^course/(?P<pk>d+)/$', views.StudentCourseDetailView.as_view(), name='student_course_detail'), url(r'^course/(?P<pk>d+)/(?P<module_id>d+)/$', views.StudentCourseDetailView.as_view(), name='student_course_detail_module'),
Create the following file structure inside the templates/students/
directory of the students
application:
course/ detail.html list.html
Edit the students/course/list.html
template and add the following code to it:
{% extends "base.html" %} {% block title %}My courses{% endblock %} {% block content %} <h1>My courses</h1> <div class="module"> {% for course in object_list %} <div class="course-info"> <h3>{{ course.title }}</h3> <p><a href="{% url "student_course_detail" course.id %}">Access contents</a></p> </div> {% empty %} <p> You are not enrolled in any courses yet. <a href="{% url "course_list" %}">Browse courses</a> to enroll in a course. </p> {% endfor %} </div> {% endblock %}
This template displays the courses the user is enrolled in. Edit the students/course/detail.html
template and add the following code to it:
{% extends "base.html" %} {% block title %} {{ object.title }} {% endblock %} {% block content %} <h1> {{ module.title }} </h1> <div class="contents"> <h3>Modules</h3> <ul id="modules"> {% for m in object.modules.all %} <li data-id="{{ m.id }}" {% if m == module %}class="selected"{% endif %}> <a href="{% url "student_course_detail_module" object.id m.id %}"> <span> Module <span class="order">{{ m.order|add:1 }}</span> </span> <br> {{ m.title }} </a> </li> {% empty %} <li>No modules yet.</li> {% endfor %} </ul> </div> <div class="module"> {% for content in module.contents.all %} {% with item=content.item %} <h2>{{ item.title }}</h2> {{ item.render }} {% endwith %} {% endfor %} </div> {% endblock %}
This is the template for enrolled students to access a course contents. First we build an HTML list including all course modules and highlighting the current module. Then we iterate over the current module contents, and access each content item to display it using {{ item.render }}
. We are going to add the render()
method to the content models next. This method will take care of rendering the content properly.
We need to provide a way to render each type of content. Edit the models.py
file of the courses application directory and add the following render()
method to the ItemBase
model as follows:
from django.template.loader import render_to_string from django.utils.safestring import mark_safe class ItemBase(models.Model): # ... def render(self): return render_to_string('courses/content/{}.html'.format( self._meta.model_name), {'item': self})
This method uses the render_to_string()
function for rendering a template and returning the rendered content as a string. Each kind of content is rendered using a template named after the content model. We use self._meta.model_name
to build the appropriate template name for la
. The render()
methods provides a common interface for rendering diverse content.
Create the following file structure inside the templates/courses/
directory of the courses application:
content/ text.html file.html image.html video.html
Edit the courses/content/text.html
template and write this code:
{{ item.content|linebreaks|safe }}
Edit the courses/content/file.html
template and add the following:
<p><a href="{{ item.file.url }}" class="button">Download file</a></p>
Edit the courses/content/image.html
template and write:
<p><img src="{{ item.file.url }}"></p>
For files uploaded with ImageField
and FileField
to work, we need to set up our project to serve media files with the development server. Edit the settings.py
file of your project and add the following code to it:
MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
Remember that MEDIA_URL
is the base URL to serve uploaded media files and MEDIA_ROOT
is the local path where the files are located.
Edit the main urls.py
file of your project and add the following imports:
from django.conf import settings from django.conf.urls.static import static
Then, write the following lines at the end of the file:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Your project is now ready for uploading and serving media files using the development server. Remember that the development server is not suitable for production use. We will cover configuring a production environment in the next chapter.
We also have to create a template for rendering Video
objects. We will use django-embed-video for embedding video content. Django-embed-video is a third-party Django application that allows you to embed videos in your templates, from sources like YouTube or Vimeo, by simply providing the video public URL.
Install the package with the following command:
pip install django-embed-video==1.0.0
Then, edit the settings.py
file of your project and add 'embed_video'
to the INSTALLED_APPS
setting. You can find django-embed-video's documentation at http://django-embed-video.readthedocs.org/en/v1.0.0/.
Edit the courses/content/video.html
template and write the following code:
{% load embed_video_tags %} {% video item.url 'small' %}
Now run the development server and access http://127.0.0.1:8000/course/mine/
in your browser. Access the site with a user that belongs the Instructors group or a superuser, and add multiple contents to a course. For including video content, you can just copy any YouTube URL, for example https://www.youtube.com/watch?v=bgV39DlmZ2U, and include it it in the url
field of the form. After adding contents to the course open http://127.0.0.1:8000/
, click the course and click the ENROLL NOW button. You should get enrolled in the course and be redirected to the student_course_detail
URL. The following image shows a sample course contents:
Great! You have created a common interface for rendering course contents, each of them being rendered in a particular way.