Tạo Nền Tảng Học Trực Tuyến với Django
Trong chương này, chúng ta sẽ xây dựng một dự án mới: một nền tảng học trực tuyến, và tạo hệ thống quản lý nội dung (CMS - Content Management System).
1. Khởi tạo dự án nền tảng học trực tuyến
Dự án cuối cùng của chúng ta là nền tảng học trực tuyến. Trong dự án này, chúng ta sẽ xây dựng một hệ thống CMS linh hoạt cho phép giảng viên tạo và quản lý khóa học.
Tạo môi trường ảo và cài đặt Django:
mkdir env
virtualenv env/educa
source env/educa/bin/activate
pip install Django==2.0.5
pip install Pillow==5.1.0
Tạo dự án `educa` và ứng dụng `courses`:
django-admin startproject educa
cd educa
django-admin startapp courses
Chỉnh sửa `settings.py` để kích hoạt ứng dụng `courses`:
INSTALLED_APPS = [
'courses.apps.CoursesConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
2. Tạo mô hình khóa học
Nền tảng học trực tuyến của chúng ta sẽ cung cấp nhiều khóa học khác nhau, mỗi khóa học được chia thành các mô-đun, và mỗi mô-đun chứa nhiều nội dung khác nhau. Dưới đây là cấu trúc mô hình:
from django.db import models
from django.contrib.auth.models import User
class Subject(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
class Meta:
ordering = ['title']
def __str__(self):
return self.title
class Course(models.Model):
owner = models.ForeignKey(User, related_name='course_created', on_delete=models.CASCADE)
subject = models.ForeignKey(Subject, related_name='courses', on_delete=models.CASCADE)
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
overview = models.TextField()
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created']
def __str__(self):
return self.title
class Module(models.Model):
course = models.ForeignKey(Course, related_name='modules', on_delete=models.CASCADE)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
def __str__(self):
return self.title
2.1 Đăng ký mô hình trong quản trị viên
Chỉnh sửa `admin.py` trong ứng dụng `courses`:
from django.contrib import admin
from .models import Subject, Course, Module
@admin.register(Subject)
class SubjectAdmin(admin.ModelAdmin):
list_display = ['title', 'slug']
prepopulated_fields = {'slug': ('title',)}
class ModuleInline(admin.StackedInline):
model = Module
@admin.register(Course)
class CourseAdmin(admin.ModelAdmin):
list_display = ['title', 'subject', 'created']
list_filter = ['created', 'subject']
search_fields = ['title', 'overview']
prepopulated_fields = {'slug': ('title',)}
inlines = [ModuleInline]
2.2 Sử dụng fixture để cung cấp dữ liệu khởi tạo
Đôi khi, chúng ta cần sử dụng dữ liệu gốc để điền vào cơ sở dữ liệu. Django cung cấp tính năng fixture để giúp việc này. Tạo một fixture cho `Subject`:
python manage.py createsuperuser
python manage.py runserver
python manage.py dumpdata courses --indent=2
mkdir courses/fixtures
python manage.py dumpdata courses --indent=2 --output=courses/fixtures/subjects.json
3. Tạo mô hình cho các loại nội dung khác nhau
Chúng ta cần một mô hình chung để lưu trữ các loại nội dung khác nhau. Chỉnh sửa `models.py` trong ứng dụng `courses`:
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
class Content(models.Model):
module = models.ForeignKey(Module, related_name='contents', on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
item = GenericForeignKey('content_type', 'object_id')
3.1 Kế thừa mô hình
Django hỗ trợ kế thừa mô hình, tương tự như kế thừa lớp trong Python. Có ba cách kế thừa mô hình:
- Mô hình trừu tượng (Abstract model)
- Mô hình đa bảng (Multi-table model inheritance)
- Mô hình đại diện (Proxy models)
3.2 Tạo mô hình nội dung
Chỉnh sửa `models.py` để thêm các mô hình nội dung:
class ItemBase(models.Model):
owner = models.ForeignKey(User, related_name='%(class)s_related', on_delete=models.CASCADE)
title = models.CharField(max_length=250)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
def __str__(self):
return self.title
class Text(ItemBase):
content = models.TextField()
class File(ItemBase):
file = models.FileField(upload_to='files')
class Image(ItemBase):
file = models.FileField(upload_to='images')
class Video(ItemBase):
url = models.URLField()
3.3 Tạo trường tùy chỉnh
Chúng ta cần một trường để lưu trữ thứ tự của khóa học và nội dung. Tạo `fields.py` trong ứng dụng `courses`:
from django.db import models
from django.core.exceptions import ObjectDoesNotExist
class OrderField(models.PositiveIntegerField):
def __init__(self, for_fields=None, *args, **kwargs):
self.for_fields = for_fields
super(OrderField, self).__init__(*args, **kwargs)
def pre_save(self, model_instance, add):
if getattr(model_instance, self.attname) is None:
try:
qs = self.model.objects.all()
if self.for_fields:
query = {field: getattr(model_instance, field) for field in self.for_fields}
qs = qs.filter(query)
last_item = qs.latest(self.attname)
value = last_item.order + 1
except ObjectDoesNotExist:
value = 0
setattr(model_instance, self.attname, value)
return value
else:
return super(OrderField, self).pre_save(model_instance, add)
3.4 Thêm trường tùy chỉnh vào mô hình
Chỉnh sửa `models.py` để thêm trường `OrderField`:
from .fields import OrderField
class Module(models.Model):
# ...
order = OrderField(for_fields=['course'], blank=True)
class Content(models.Model):
# ...
order = OrderField(blank=True, for_fields=['module'])
4. Tạo hệ thống quản lý nội dung (CMS)
Chúng ta cần một CMS để giảng viên có thể tạo và quản lý khóa học. CMS cần có các chức năng sau:
- Đăng nhập
- Liệt kê tất cả khóa học của giảng viên
- Tạo, chỉnh sửa và xóa khóa học
- Thêm mô-đun cho khóa học
- Thêm nội dung cho mô-đun
4.1 Thêm hệ thống xác thực người dùng
Chỉnh sửa `urls.py` trong dự án `educa`:
from django.contrib import admin
from django.urls import path
from django.contrib.auth import views as auth_views
urlpatterns = [
path('accounts/login/', auth_views.LoginView.as_view(), name='login'),
path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'),
path('admin/', admin.site.urls),
]
4.2 Tạo mẫu xác thực người dùng
Tạo các tệp mẫu trong thư mục `templates/`:
templates/
base.html
registration/
login.html
logged_out.html
Chỉnh sửa `base.html`:
{% load static %}
<html>
<head>
<meta charset="utf-8"/>
<title>{% block title %}Educa{% endblock %}</title>
<link href="{% static "css/base.css" %}" rel="stylesheet">
</head>
<body>
<div id="header">
<a href="/" class="logo">Educa</a>
<ul class="menu">
{% if request.user.is_authenticated %}
<li><a href="{% url "logout" %}">Sign out</a></li>
{% else %}
<li><a href="{% url "login" %}">Sign in</a></li>
{% endif %}
</ul>
</div>
<div id="content">
{% block content %}
{% endblock %}
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
{% block domready %}
{% endblock %}
});
</script>
</body>
</html>
4.3 Tạo CBV
Chỉnh sửa `views.py` trong ứng dụng `courses`:
from django.views.generic.list import ListView
from .models import Course
class ManageCourseListView(ListView):
model = Course
template_name = 'courses/manage/course/list.html'
def get_queryset(self):
qs = super(ManageCourseListView, self).get_queryset()
return qs.filter(owner=self.request.user)
4.4 Sử dụng mixin trong CBV
Chỉnh sửa `views.py` để thêm các mixin:
from django.urls import reverse_lazy
from django.views.generic.list import ListView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from .models import Course
class OwnerMixin:
def get_queryset(self):
qs = super(OwnerMixin, self).get_queryset()
return qs.filter(owner=self.request.user)
class OwnerEditMixin:
def form_valid(self, form):
form.instance.owner = self.request.user
return super(OwnerEditMixin, self).form_valid(form)
class OwnerCourseMixin(OwnerMixin):
model = Course
class OwnerCourseEditMixin(OwnerCourseMixin, OwnerEditMixin):
fields = ['subject', 'title', 'slug', 'overview']
success_url = reverse_lazy('manage_course_list')
template_name = 'courses/manage/course/form.html'
class ManageCourseListView(OwnerCourseMixin, ListView):
template_name = 'courses/manage/course/list.html'
class CourseCreateView(OwnerCourseEditMixin, CreateView):
pass
class CourseUpdateView(OwnerCourseEditMixin, UpdateView):
pass
class CourseDeleteView(OwnerCourseMixin, DeleteView):
template_name = 'courses/manage/course/delete.html'
success_url = reverse_lazy('manage_course_list')
4.5 Sử dụng nhóm người dùng và quyền hạn
Chỉnh sửa `views.py` để giới hạn truy cập:
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
class OwnerCourseMixin(OwnerMixin, LoginRequiredMixin):
model = Course
fields = ['subject', 'title', 'slug', 'overview']
success_url = reverse_lazy('manage_course_list')
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'
5. Quản lý mô-đun và nội dung
Chỉnh sửa `views.py` để thêm các view quản lý mô-đun và nội dung:
from django.forms.models import inlineformset_factory
from .models import Course, Module
ModuleFormSet = inlineformset_factory(Course, Module, fields=['title', 'description'], extra=2, can_delete=True)
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(CourseModuleUpdateView, self).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})
5.1 Thêm nội dung cho khóa học
Chỉnh sửa `views.py` để thêm nội dung cho khóa học:
from django.forms.models import modelform_factory
from django.apps import apps
from .models import Module, Content
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:
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})
5.2 Quản lý mô-đun và nội dung
Chỉnh sửa `views.py` để hiển thị danh sách mô-đun và nội dung:
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})
5.3 Sắp xếp lại mô-đun và nội dung
Chỉnh sửa `views.py` để thêm các view sắp xếp lại mô-đun và nội dung:
from braces.views import CsrfExemptMixin, JsonRequestResponseMixin
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'})
Kết luận
Trong chương này, chúng ta đã học cách xây dựng một CMS. Chúng ta đã sử dụng kế thừa mô hình, tạo trường tùy chỉnh, sử dụng CBV và mixin, cũng như sử dụng formset để quản lý nội dung khác nhau.
Trong chương tiếp theo, chúng ta sẽ học cách tạo hệ thống đăng ký sinh viên, hiển thị nội dung khóa học trên trang web, và sử dụng bộ nhớ đệm Django.