上篇文章講解了系統的登陸、註冊、查看和編輯個人信息、修改密碼功能,本篇章繼續講解課程信息模塊的相關功能以及代碼實現。本篇章主要使用 Django 中的通用視圖來簡化系統的開發,該項目只有 project 一個 APP,當時在寫項目的時候,發現兩個APP 中的 models 並不能直接相互調用,因此將所有表都寫入到了 models.py 文件中。
模型 models.py
- 自定義上傳路徑:爲了防止後來上傳的文件與已上傳的文件重名而發生覆蓋,在 models.py 動態地定義了文件上傳路徑user_directory_path,並對上傳的文件以 uuid 形式重命名,當然也可以在視圖 views.py 中定義路徑。
- 由於視圖中使用了 Django 的通用視圖,每個模型裏需要定義 get_abosolute_url。Django 的 CreateView和 UpdateView 在完成對象的創建或編輯後會自動跳轉到這個絕對url。
- 抽象屬性 abstract:這個屬性是定義當前的模型類是不是一個抽象類。所謂抽象類是不會對應數據庫表的。一般用它來歸納一些公共屬性字段,然後繼承它的子類可以繼承這些字段。
- 富文本編輯器CKEditor:豐富作業正文的編輯,例如添加圖片、字體格式。當然也可以使用其他的編輯器。
# 上傳圖片需要安裝 pillow
pip install pillow
# 安裝 CKEditor
pip install django-ckeditor
# 在項目文件夾下新建 static文件夾, 下載 ckeditor 所需的 js 和 css 文件
python manage.py collectstatic
# 配置 homework/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'project',
'ckeditor',
'ckeditor_uploader',
]
#ckeditor
SITE_ID = 1
# 富文本編輯器
CKEDITOR_UPLOAD_PATH = 'homework_uploads/'
CKEDITOR_JQUERY_URL ='https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js'
CKEDITOR_IMAGE_BACKEND = 'pillow'
CKEDITOR_ALLOW_NONIMAGE_FILES = False
CKEDITOR_BROWSE_SHOW_DIRS = True
CKEDITOR_RESTRICT_BY_USER = True
CKEDITOR_RESTRICT_BY_DATE = True
# 顯示代碼
# 只需要在CKEDITOR_CONFIGS中加入codesnipppet的plugin即可。
# 'extraPlugins': 'codesnippet',
# 同時找到static -> ckeditor -> ckeditor -> config.js把codesnippet註冊一下。
# CKEDITOR.editorConfig = function( config ) {
# // Define changes to default configuration here. For example:
# // config.language = 'fr';
# // config.uiColor = '#AADC6E';
# config.extraPlugins: "codesnippet";
# };
CKEDITOR_CONFIGS = {
'default': {
'toolbar': (['Source', '-', 'Preview', '-', ],
['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Print', 'SpellChecker', ],
['Undo', 'Redo', '-', 'Find', 'Replace', '-', 'SelectAll', 'RemoveFormat', '-',
"CodeSnippet", 'Subscript', 'Superscript'],
['NumberedList', 'BulletedList', '-', 'Blockquote'],
['Link', 'Unlink', ],
['Image', 'Table', 'HorizontalRule', 'Smiley', 'SpecialChar', ],
['Format', 'Font', 'FontSize', 'TextColor', 'BGColor', ],
['Bold', 'Italic', 'Underline', 'Strike', ],
['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
),
'extraPlugins': 'codesnippet',#顯示代碼
'width': 'auto',
}
}
# 模型中使用 ckeditor
from ckeditor_uploader.fields import RichTextUploadingField
TextField 改成 RichTextUploadingField
# 表單中使用 ckeditor
# 表單的輸入 widget 需要改爲 CKEditorUploadingWidget
from ckeditor_uploader.widgets import CKEditorUploadingWidget
# 模板中使用 {{ form.media }} 調入 ckeditor 靜態文件
<form method="POST" class="form-horizontal" role="form" action="" >
{% csrf_token %}
{{ form.media }}
{{ form }}
# project/models.py
from django.urls import reverse
from unidecode import unidecode
from django.template.defaultfilters import slugify
from datetime import datetime
import uuid
import os
from ckeditor_uploader.fields import RichTextUploadingField
'''
自定義上傳文件的存儲路徑
'''
def user_directory_path(instance, filename):
ext = filename.split('.')[-1]
filename = '{}.{}'.format(uuid.uuid4().hex[:10], ext)
sub_folder = 'file'
if ext.lower() in ["jpg", "png", "gif"]:
sub_folder = "avatar"
if ext.lower() in ["pdf", "docx","xlsx"]:
sub_folder = "document"
# instance 將表中的某一屬性相關聯
return os.path.join(str(instance.course.teacher.id), sub_folder, filename)
class Course(models.Model):
OPENED_CHOICES = (
(0,'公開'),
(1,'不公開')
)
cname = models.CharField(verbose_name='課程名稱', max_length=50,null=False)
classes = models.CharField(verbose_name='班級',default='', max_length=50)
description = models.TextField(verbose_name='課程描述',)
opened = models.SmallIntegerField('公開狀態',choices=OPENED_CHOICES,default=0)
teacher = models.ForeignKey(Teacher,related_name="course",on_delete=models.CASCADE)
student = models.ManyToManyField(Student,blank=True)
def __str__(self):
return self.cname
def get_absolute_url(self):
return reverse('project:course_detail', args=[str(self.pk)])
# @property把homework_count僞裝成屬性
@property
def homework_count(self):
return Homework.objects.filter(homework_id=self.id).count()
class Meta:
verbose_name='課程'
verbose_name_plural = verbose_name
class WorkAbstractModel(models.Model):
body = RichTextUploadingField('正文')
created = models.DateTimeField('創建時間', auto_now_add=True)
modified = models.DateTimeField('修改時間', auto_now=True)
file = models.FileField('文件',upload_to=user_directory_path,blank=True)
class Meta:
abstract = True
class Homework(WorkAbstractModel):
STATUS_CHOICES = (
('d', '草稿'),
('p', '發表'),
)
GROUP_CHOICES = (
(0,'個人'),
(1,'小組')
)
title = models.CharField('標題', max_length=200)# unique=True
slug = models.SlugField('摘要', max_length=60, blank=True)
published = models.DateTimeField('發佈時間', null=True)
status = models.CharField('作業狀態', max_length=1, choices=STATUS_CHOICES, default='p')
group = models.SmallIntegerField('組隊狀態',choices=GROUP_CHOICES,default=0)
views = models.PositiveIntegerField('瀏覽量', default=0)
course = models.ForeignKey(Course,related_name="homework",on_delete=models.CASCADE)
def __str__(self):
return self.title
# 快速獲取文件格式
def get_format(self):
return self.file.url.split('.')[-1].upper()
# 利用unidecode對中文解碼,利用slugify方法根據標題手動生成slug
def save(self, *args, **kwargs):
if not self.id or not self.slug:
self.slug = slugify(unidecode(self.title))
super().save(*args, **kwargs)
# Django的 CreateView 和 UpdateView 在完成對象的創建或編輯後會自動跳轉到這個絕對url
def get_absolute_url(self):
return reverse('project:homework_detail', args=[str(self.course.id),str(self.pk)])
def clean(self):
if self.status == 'd' and self.published is not None:
self.published = None
# raise ValidationError('草稿沒有發佈日期. 發佈日期已清空。')
if self.status == 'p' and self.published is None:
self.published = datetime.now()
def viewed(self):
self.views += 1
self.save(update_fields=['views'])
def publish(self):
self.status = 'p'
self.published = datetime.now()
self.save(update_fields=['status', 'published'])
class Meta:
ordering = ['-modified']
verbose_name = "作業"
verbose_name_plural = verbose_name
class Handin(WorkAbstractModel):
author = models.ForeignKey(Student,related_name="handin",on_delete=models.CASCADE)
course = models.ForeignKey(Course,related_name="handin",on_delete=models.CASCADE)
homework = models.ForeignKey(Homework,related_name="handin",on_delete=models.CASCADE)
score = models.IntegerField('分數',null=True)
def __str__(self):
return self.author.name
def get_format(self):
return self.file.url.split('.')[-1].upper()
def get_absolute_url(self):
return reverse('project:handin_detail', args=[str(self.homework.course.id),str(self.homework.pk),str(self.pk)])
class Meta:
verbose_name = "作答"
verbose_name_plural = verbose_name
class Comment(models.Model):
homework = models.ForeignKey(Homework,related_name="comment",on_delete=models.CASCADE)
text = models.TextField('評論內容')
created = models.DateTimeField('評論時間',auto_now_add=True)
username = models.CharField('用戶名稱',max_length=50)
def __str__(self):
return self.text[:20]
class Meta:
verbose_name = "評論"
verbose_name_plural = verbose_name
class Group(models.Model):
EDIT_CHOICES = (
(0,'不可編輯'),
(1,'可編輯')
)
leader = models.ForeignKey(Teacher,related_name="leader",on_delete=models.CASCADE)
course = models.ForeignKey(Course,related_name="group",on_delete=models.CASCADE)
member = models.ManyToManyField(Student,blank=True)
edit = models.SmallIntegerField(choices=EDIT_CHOICES,default=1,verbose_name='編輯狀態')
def __str__(self):
return self.leader.name
def get_absolute_url(self):
return reverse('project:group_list', args=[str(self.course.id)])
class Meta:
verbose_name = "組隊"
verbose_name_plural = verbose_name
記得同步一下數據庫:
python manage.py makemigrations
python manage.py migrate
表單 forms.py
# project/forms.py
from django.forms import ModelForm
from .models import Course,Homework,Handin,Comment,Group
from ckeditor_uploader.widgets import CKEditorUploadingWidget
class CourseForm(forms.ModelForm):
class Meta:
model = Course
# 選擇指定字段的所有數據 field
fields = ['cname','classes','description','opened']
# boostrap表單需要 form-control 這個樣式
widgets = {
'cname': forms.TextInput(attrs={'class': 'form-control'}),
'classes': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control','rows':'3'}),
'opened': forms.Select(attrs={'class': 'form-control'}),
}
class HomeworkForm(ModelForm):
class Meta:
model = Homework
# 剔除指定字段的所有數據 exclude
exclude = ['author', 'views', 'slug','published','course']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'body': CKEditorUploadingWidget(attrs={'class': 'form-control'}),
'status': forms.Select(attrs={'class': 'form-control'}),
'group': forms.Select(attrs={'class': 'form-control'}),
'file': forms.ClearableFileInput(attrs={'class': 'form-control'}),
}
labels = {
'title': '作業標題',
'body': '作業內容',
'status': '作業狀態',
'group': '組隊狀態',
'file': '上傳文件',
}
def clean_file(self):
file = self.cleaned_data['file']
if file:
ext = file.name.split('.')[-1].lower()
if ext not in ["","jpg","png","pdf", "xlsx","docx","zip","doc"]:
raise forms.ValidationError("Only zip jpg, png, pdf, doc, docx, and xlsx files are allowed.")
return file
class HandinForm(ModelForm):
class Meta:
model = Handin
exclude = ['author','homework','course','score']
widgets = {
'body': CKEditorUploadingWidget(attrs={'class': 'form-control'}),
# 'file': forms.FileInput(attrs={'class': 'form-control'}),
'file': forms.ClearableFileInput(attrs={'class': 'form-control'}),
}
def clean_file(self):
file = self.cleaned_data['file']
if file:
ext = file.name.split('.')[-1].lower()
if ext not in ["","jpg","png", "pdf", "xlsx","docx","zip","doc"]:
raise forms.ValidationError("Only zip jpg, png, pdf, doc, docx, and xlsx files are allowed")
return file
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['text']
widgets = {
'text': forms.Textarea(attrs={'class': 'form-control','rows':'3'}),
}
labels = {
'text':'評論內容',
}
class GroupForm(forms.ModelForm):
class Meta:
model = Group
fields = ['member']
widgets = {
'member':forms.CheckboxSelectMultiple(attrs={'class': 'multi-checkbox'}),
}
labels = {
'member': '學生列表',
}
路由 urls.py
項目完整路由代碼如下:
# project/urls.py
from django.urls import re_path,path
from . import views
app_name = 'project'
urlpatterns = [
# 登陸、註冊 以及 信息、密碼修改
path('',views.index,name='index'),
re_path(r'^register/$',views.register,name='register'),
re_path(r'^login/$', views.login, name='login'),
re_path(r'^user/(?P<pk>\d+)/profile/$', views.profile, name='profile'),
re_path(r'^user/(?P<pk>\d+)/profile/update/$', views.profile_update, name='profile_update'),
re_path(r'^user/(?P<pk>\d+)/pwdchange/$', views.pwd_change, name='pwd_change'),
re_path(r'^logout/$', views.logout, name='logout'),
# 教師創建課程 增 刪 查 改
path('course/', views.CourseList.as_view(), name='course_list'),
re_path(r'^user/(?P<pk>\d+)/course/$', views.CourseListSelf.as_view(), name='course_list_self'),
re_path(r'^course/create/$',views.CourseCreate.as_view(), name='course_create'),
re_path(r'^course/(?P<pk>\d+)/$',views.CourseDetail.as_view(), name='course_detail'),
re_path(r'^course/(?P<pk>\d+)/update/$',views.CourseUpdate.as_view(), name='course_update'),
re_path(r'^course/(?P<pk>\d+)/delete$',views.CourseDelete.as_view(), name='course_delete'),
re_path(r'^course/(?P<pk>\d+)/select$', views.course_select, name='course_select'),
re_path(r'^course/(?P<pk>\d+)/cancel$', views.course_cancel, name='course_cancel'),
# 教師發佈作業 增 刪 查 改
re_path(r'^course/(?P<pk>\d+)/list$', views.HomeworkList.as_view(), name='homework_list'),
re_path(r'^course/(?P<pk>\d+)/homework/create/$',views.HomeworkCreate.as_view(), name='homework_create'),
re_path(r'^course/(?P<pkr>\d+)/homework/(?P<pk>\d+)/$',views.HomeworkDetail.as_view(), name='homework_detail'),
re_path(r'^course/(?P<pkr>\d+)/homework/(?P<pk>\d+)/update/$',views.HomeworkUpdate.as_view(), name='homework_update'),
re_path(r'^course/(?P<pkr>\d+)/homework/(?P<pk>\d+)/delete$',views.HomeworkDelete.as_view(), name='homework_delete'),
re_path(r'^course/(?P<pkr>\d+)/homework/(?P<pk>\d+)/publish/$',views.homework_publish, name='homework_publish'),
re_path(r'^course/(?P<pk>\d+)/homework/draft/$', views.HomeworkListDraft.as_view(), name='homework_list_publishing'),
re_path(r'^course/(?P<pk>\d+)/homework/publish/$', views.HomeworkListPublished.as_view(), name='homework_list_published'),
re_path(r'^search/$', views.homework_search, name='homework_search'),
# 課程作業的評論功能
re_path(r'^comment/(?P<pk>[0-9]+)/$', views.homework_comment, name='homework_comment'),
# 學生作業統計
re_path(r'^course/(?P<pkr>\d+)/homework/(?P<pk>\d+)/count$', views.HomeworkHandin.as_view(), name='homework_handin_count'),
# 學生提交作業 增 刪 查 改
re_path(r'^course/(?P<pkr>\d+)/homework/(?P<pk>\d+)/list$', views.HandinList.as_view(), name='handin_list'),
re_path(r'^course/(?P<pk>\d+)/handin/list$', views.HandinListDone.as_view(), name='handin_list_done'),
re_path(r'^course/(?P<pkr>\d+)/homework/(?P<pk>\d+)/handin/create/$',views.HandinCreate.as_view(), name='handin_create'),
re_path(r'^course/(?P<pka>\d+)/homework/(?P<pkr>\d+)/handin/(?P<pk>\d+)/$',views.HandinDetail.as_view(), name='handin_detail'),
re_path(r'^course/(?P<pka>\d+)/homework/(?P<pkr>\d+)/handin/(?P<pk>\d+)/update/$',views.HandinUpdate.as_view(), name='handin_update'),
re_path(r'^course/(?P<pka>\d+)/homework/(?P<pkr>\d+)/handin/(?P<pk>\d+)/delete/$',views.HandinDelete.as_view(), name='handin_delete'),
# 課程分組
re_path(r'^course/(?P<pk>\d+)/student$', views.course_student, name='course_student'),
re_path(r'^course/(?P<pk>\d+)/group$', views.GroupList.as_view(), name='group_list'),
re_path(r'^course/(?P<pkr>\d+)/group/(?P<pk>\d+)/delete/$', views.GroupDelete.as_view(), name='group_delete'),
re_path(r'^course/(?P<pk>\d+)/course/group/create$', views.course_group_create, name='course_group_create'),
]
視圖 views.py
展示對象列表(比如所有用戶,所有文章)- ListView
展示某個對象的詳細信息(比如用戶資料,比如文章詳情) - DetailView
通過表單創建某個對象(比如創建用戶,新建文章)- CreateView
通過表單更新某個對象信息(比如修改密碼,修改文字內容)- UpdateView
用戶填寫表單後轉到某個完成頁面 - FormView
刪除某個對象 - DeleteView
可通過重寫 queryset, template_name 和 context_object_name 來完成ListView的自定義
- 通過更具體的 get_object() 方法來返回一個更具體的對象
- 通過重寫 get_queryset 方法傳遞額外的參數或內容
- 通過重寫 get_context_data 方法傳遞額外的參數或內容
class CourseList(ListView):
model = Course
# 兩段代碼等價
def CourseList(request):
queryset = Course.objects.all()
return render(request, 'project/course_list.html', {"object_list": queryset})
ListView用來展示一個對象的列表。
- 提取了需要顯示的對象列表或數據集(queryset): Article.objects.all()
- 指定了用來顯示對象列表的模板名稱(template_name): 默認 app_name/model_name_list.html
- 指定了內容對象名稱(context_object_name):默認值object_list
# DetailView 視圖不能使用 @login_required 這個裝飾器
class CourseDetail(DetailView):
model = Course
DetailView 用來展示一個具體對象的詳細信息。
- 它需要URL提供訪問某個對象的具體參數(如pk, slug值)
- 默認的模板是 app/model_name_detail.html
- 默認的內容對象名字 context_object_name 是 model_name
- 如指定了 queryset, 那麼返回的 object 是 queryset.get(pk = id), 而不是model.objects.get(pk = id)。
class CourseCreate(CreateView):
model = Course
form_class = CourseForm
template_name = 'project/course_form.html'
CreateView 一般通過某個表單創建某個對象,通常完成後會轉到對象列表。可通過重寫 template_name 和 form_class 來完成 CreateView 的自定義。
- 默認的模板是 model_name_form.html
- 默認的 context_object_name 是 form,模板代碼爲:
<form method="post">{% csrf_token %}
{{ form.as_p }}#將表單渲染成< p >標籤
<input type="submit" value="Save" />
</form>
class CourseUpdate(UpdateView):
model = Course
form_class = CourseForm
UpdateView 一般通過某個表單更新現有對象的信息,更新完成後會轉到對象詳細信息頁面。
- 它需要 URL 提供訪問某個對象的具體參數(如pk, slug值)
- UpdateView 和 CreateView很類似,比如默認模板都是 model_name_form.html
- CreateView 顯示的表單是空表單,UpdateView 中的表單會顯示現有對象的數據。
- 用戶提交表單後,CreateView 轉向對象列表,UpdateView 轉向對象詳細信息頁面
class CourseDelete(DeleteView):
model = Course
# get = DeleteView.post
success_url = reverse_lazy('project:course_list')
DeleteView一般用來刪除某個具體對象。
- 它要求用戶點擊確認後再刪除一個對象。
- 需要定義模型的名稱 model 和成功刪除對象後的返回的 URL
- 默認模板是 myapp/model_confirm_delete.html,模板代碼如下:
<!-- project/course_confirm_delete.html -->
<form method="post">{% csrf_token %}
<p>Are you sure you want to delete "{{ article }}"?</p>
<input type="submit" value="Confirm" />
</form>
本項目完整視圖代碼如下:
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import auth
from django.contrib.auth.models import User
from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, UpdateView, DeleteView,FormView
from .models import Teacher,Student,Role,Course,Homework,Handin,Comment,Group,Role
from .forms import RegistrationForm, LoginForm, ProfileForm, PwdChangeForm,CourseForm,HomeworkForm,HandinForm,CommentForm,GroupForm
from django.http import HttpResponseRedirect,Http404
from django.urls import reverse
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.core.paginator import Paginator
from django.urls import reverse, reverse_lazy
class CourseList(ListView):
paginate_by = 5
template_name = 'project/course_list.html'
def get_queryset(self):
return Course.objects.filter(opened=0).order_by("id")
@method_decorator(login_required, name='dispatch')
class CourseListSelf(ListView):
paginate_by = 5
template_name = 'project/course_list_self.html'
def get_queryset(self):
# 判斷用戶角色返回相應的 queryset
if self.request.user.role.role == 1:
return Course.objects.filter(teacher=self.request.user.role.teacher).order_by("id")
else:
return Course.objects.filter(student=self.request.user.role.student).order_by("id")
class CourseDetail(DetailView):
model = Course
template_name = 'project/course_detail.html'
@method_decorator(login_required, name='dispatch')
class CourseCreate(CreateView):
model = Course
form_class = CourseForm
template_name = 'project/course_form.html'
# 將創建對象的用戶與 model 裏的 user 結合
def form_valid(self,form):
form.instance.teacher = self.request.user.role.teacher
return super().form_valid(form)
@method_decorator(login_required, name='dispatch')
class CourseUpdate(UpdateView):
model = Course
form_class = CourseForm
template_name = 'project/course_form.html'
# 用戶只能修改自己的課程,反之返回 Http404 錯誤
def get_object(self,queryset=None):
obj = super().get_object(queryset=queryset)
if obj.teacher != self.request.user.role.teacher:
raise Http404()
return obj
# 用戶提交表單後可以做一些事情
# def form_valid(self, form):
# form.do_sth()
# return super().form_valid(form)
@method_decorator(login_required, name='dispatch')
class CourseDelete(DeleteView):
model = Course
# 通過這行代碼,每次刪除時候就不用彈出確認界面
# get = DeleteView.post
success_url = reverse_lazy('project:course_list')
def get_object(self, queryset=None):
obj = super().get_object(queryset=queryset)
if obj.teacher != self.request.user.role.teacher:
raise Http404()
return obj
@method_decorator(login_required, name='dispatch')
class HomeworkList(ListView):
def get_queryset(self):
return Homework.objects.filter(course__id=self.kwargs['pk']).filter(status='p').order_by('-published')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
course = Course.objects.get(id=self.kwargs['pk'])
homework = [x for x in self.get_queryset()]
judge = [0 for i in range(len(homework))]
handin = Handin.objects.filter(course__id=self.kwargs['pk']).filter(author=self.request.user.role.student)
for handin in handin:
if handin.homework in homework:
judge[homework.index(handin.homework)] = 1
# zip 函數將兩個列表合併,返回一個 tuple
info = list(zip(homework,judge))
if Group.objects.filter(course=course,member=self.request.user.role.student):
group = Group.objects.get(course=course,member=self.request.user.role.student).member.all()
else:
group = ''
context.update({
'course':course,
'judge':judge,
'info':info,
'group':group,
})
return context
@method_decorator(login_required, name='dispatch')
class HomeworkListPublished(ListView):
template_name = 'project/homework_list_published.html'
paginate_by = 5
def get_object(self, queryset=None):
obj = super().get_object(queryset=queryset)
if obj.course.teacher != self.request.user.role.teacher:
raise Http404()
return obj
def get_queryset(self):
return Homework.objects.filter(course__id=self.kwargs['pk']).filter(status='p').order_by('-published')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
course = Course.objects.get(id=self.kwargs['pk'])
context.update({
'course':course,
})
return context
@method_decorator(login_required, name='dispatch')
class HomeworkListDraft(ListView):
paginate_by = 5
template_name = 'project/homework_list_publishing.html'
# 用戶只能看到自己的文章草稿。當用戶查看別人的文章草稿時,返回http 404錯誤
def get_object(self, queryset=None):
obj = super().get_object(queryset=queryset)
if obj.course.teacher != self.request.user.role.teacher:
raise Http404()
return obj
def get_queryset(self):
return Homework.objects.filter(course__id=self.kwargs['pk']).filter(status='d').order_by('-published')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
course = Course.objects.get(id=self.kwargs['pk'])
context.update({
'course':course,
})
return context
class HomeworkDetail(DetailView):
model = Homework
template_name = 'project/homework_detail.html'
def get_object(self, queryset=None):
obj = super().get_object(queryset=queryset)
obj.viewed()
return obj
# 覆寫 get_context_data 的目的是因爲除了將 homework 傳遞給模板外(DetailView 已經幫我們完成),
# 還要把評論表單、homework 下的評論列表傳遞給模板。
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
course = Course.objects.get(id=self.kwargs['pkr'])
form = CommentForm()
comment_list = self.object.comment.filter(homework=self.object)
context.update({
'course':course,
'form': form,
'comment_list': comment_list
})
return context
@method_decorator(login_required, name='dispatch')
class HomeworkCreate(CreateView):
model = Homework
form_class = HomeworkForm
template_name = 'project/homework_form.html'
def form_valid(self, form):
form.instance.course = Course.objects.get(id=self.kwargs['pk'])
return super().form_valid(form)
@method_decorator(login_required, name='dispatch')
class HomeworkUpdate(UpdateView):
model = Homework
form_class = HomeworkForm
template_name = 'project/homework_form.html'
@method_decorator(login_required, name='dispatch')
class HomeworkDelete(DeleteView):
model = Homework
get = DeleteView.post
def get_object(self, queryset=None):
obj = super().get_object(queryset=queryset)
if obj.course.teacher != self.request.user.role.teacher:
raise Http404()
return obj
def get_success_url(self):
return reverse_lazy('project:homework_list_published',args=[str(self.kwargs['pkr'])])
# 不需要確認模板直接刪除
# def get(self,request,*args,**kwargs):
# return self.post(request,*args,**kwargs)
@login_required()
def homework_publish(request, pk,pkr):
homework = get_object_or_404(Homework, pk=pk)
homework.publish()
return redirect(reverse('project:homework_detail', args=[str(pkr),str(pk)]))
@login_required()
def homework_search(request):
pass
@login_required
def homework_comment(request, pk):
homework = get_object_or_404(Homework, pk=pk)
comment_list = homework.comment.filter(homework=homework)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
"""
commit=False 的作用是僅僅利用表單的數據生成 Comment 模型類的實例,但還不保存評論數據到數據庫。
將評論和被評論的文章關聯起來。
"""
comment.homework = homework
if request.user.role.role == 1:
comment.username = request.user.role.teacher.name
else:
comment.username = request.user.role.student.name
comment.save()
"""
重定向到 homework 的詳情頁,實際上當 redirect 函數接收一個模型的實例時,它會調用這個模型實例的 get_absolute_url 方法,
然後重定向到 get_absolute_url 方法返回的 URL。
"""
return redirect(homework)
else:
"""
檢查到數據不合法,重新渲染詳情頁,並且渲染表單的錯誤。
因此傳三個模板變量給 detail.html,
一個是作業(Homework),一個是評論列表,一個是表單 form
注意這裏沒有用到homework.comment_set.all() 方法:homework.comment_set.all() 反向查詢全部評論。
而用到了related_name的反向查詢
"""
context = {'homework': homework,
'form': form,
'comment_list': comment_list
}
return render(request, 'homework/homework_detail.html', context=context)
"""
不是 homework 請求,說明用戶沒有提交數據,重定向到文章詳情頁。
"""
return redirect(homework)
def course_select(request,pk):
course = get_object_or_404(Course,pk=pk)
user = get_object_or_404(User,pk=request.user.id)
course.student.add(user.role.student)
return HttpResponseRedirect(reverse('project:course_list_self', args=[user.id]))
def course_cancel(request,pk):
course = get_object_or_404(Course,pk=pk)
user = get_object_or_404(User,pk=request.user.id)
course.student.remove(user.role.student)
return HttpResponseRedirect(reverse('project:course_list_self', args=[user.id]))
@method_decorator(login_required, name='dispatch')
class HomeworkHandin(ListView):
model = Handin
template_name = 'project/homework_handin_count.html'
def get_queryset(self):
return Handin.objects.filter(course__id=self.kwargs['pkr']).filter(homework__id=self.kwargs['pk'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
homework = Homework.objects.get(id=self.kwargs['pk'])
context.update({
'homework':homework,
})
return context
@method_decorator(login_required, name='dispatch')
class HandinList(ListView):
paginate_by = 5
template_name = 'project/handin_list.html'
def get_queryset(self):
return Homework.objects.get(id=self.kwargs['pk']).handin.all()
@method_decorator(login_required, name='dispatch')
class HandinListDone(ListView):
template_name = 'project/handin_list_done.html'
paginate_by = 5
def get_queryset(self):
return Handin.objects.filter(course__id=self.kwargs['pk']).filter(author=self.request.user.role.student).order_by('-id')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
course = Course.objects.get(id=self.kwargs['pk'])
if Group.objects.filter(course=course,member=self.request.user.role.student):
group = Group.objects.get(course=course,member=self.request.user.role.student).member.all()
else:
group = ''
context.update({
'course':course,
'group':group,
})
return context
@method_decorator(login_required, name='dispatch')
class HandinCreate(CreateView):
model = Handin
form_class = HandinForm
template_name = 'project/handin_form.html'
def form_valid(self, form):
homework = Homework.objects.get(id=self.kwargs['pk'])
course = Course.objects.get(id=self.kwargs['pkr'])
if homework.group == 0:
form.instance.course = course
form.instance.homework = homework
form.instance.author = self.request.user.role.student
return super().form_valid(form)
else:
# 小組作業,一人提交則全部提交
form.instance.course = course
form.instance.homework = homework
form.instance.author = self.request.user.role.student
if Group.objects.filter(course=course,member=self.request.user.role.student):
group = Group.objects.get(course=course,member=self.request.user.role.student).member.all()
for each in group:
if each != self.request.user.role.student:
Handin.objects.get_or_create(course=course,homework=homework,author=each)
return super().form_valid(form)
else:
form.instance.course = course
form.instance.homework = homework
form.instance.author = self.request.user.role.student
return super().form_valid(form)
class HandinDetail(DetailView):
model = Handin
template_name = "project/handin_detail.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
homework = Homework.objects.get(id=self.kwargs['pkr'])
context.update({
'homework':homework,
})
return context
@method_decorator(login_required, name='dispatch')
class HandinUpdate(UpdateView):
model = Handin
form_class = HandinForm
template_name = 'project/handin_form.html'
@method_decorator(login_required, name='dispatch')
class HandinDelete(DeleteView):
model = Handin
def get_success_url(self):
return reverse_lazy('project:homework_list',args=[str(self.kwargs['pka'])])
def get_object(self, queryset=None):
obj = super().get_object(queryset=queryset)
if obj.author != self.request.user.role.student:
raise Http404()
return obj
def get(self,request,*args,**kwargs):
return self.post(request,*args,**kwargs)
@method_decorator(login_required, name='dispatch')
class GroupList(ListView):
paginate_by = 5
template_name = 'project/group.html'
def get_queryset(self):
return Group.objects.filter(course__id=self.kwargs['pk'])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
course = Course.objects.get(id=self.kwargs['pk'])
group = [x for x in self.get_queryset()]
group_list = []
for each in group:
group_list.append(each.member.all())
info = list(zip(group,group_list))
context.update({
'course':course,
'info':info,
})
return context
@method_decorator(login_required, name='dispatch')
class GroupCreate(CreateView):
model = Group
form_class = GroupForm
template_name = 'project/group_form.html'
def form_valid(self, form):
form.instance.course = Course.objects.get(id=self.kwargs['pk'])
form.instance.leader = self.request.user.role.teacher
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
course = Course.objects.get(id=self.kwargs['pk'])
context.update({
'course':course,
})
return context
def course_group_create(request,pk):
course = Course.objects.get(pk=pk)
if request.method == 'GET':
course_students = course.student.all()
groups = Group.objects.filter(course=course)
members = []
for group in groups:
for member in group.member.all():
members.append(member)
students = [x for x in course_students if x not in members]
print(students)
return render(request,'project/group_course_form.html',{'students':students,'course':course})
elif request.method == 'POST':
students = request.POST.getlist('students')
new_group = Group.objects.create(leader=course.teacher,course=course)
for student in students:
new_group.member.add(Student.objects.get(id=student))
group = [x for x in Group.objects.filter(course=course)]
group_list = []
for each in group:
group_list.append(each.member.all())
info = list(zip(group,group_list))
context = {
'course':course,
'info':info,
}
return render(request,'project/group.html',context=context)
@method_decorator(login_required, name='dispatch')
class GroupDelete(DeleteView):
model = Group
get = DeleteView.post
def get_success_url(self):
return reverse_lazy('project:group_list',args=[str(self.kwargs['pkr'])])
def course_student(request,pk):
course = Course.objects.get(pk=pk)
student = course.student.all()
return render(request,'project/course_student.html',context={'student':student,'course':course})
模板 Templates
<!-- project/course_lsit.html -->
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
{% for object in object_list %}
<div class="col-sm-4">
<div class="contact-box">
<div class="col-sm-4">
<div class="text-center">
<img alt="image" class="img-circle m-t-xs img-responsive" src="{% static 'img/python.jpg' %}">
<div class="m-t-xs font-bold"></div>
</div>
</div>
<div class="col-sm-8">
<h3><strong>{{ object.cname }}</strong></h3>
<p><i class="glyphicon glyphicon-book"></i> 班級: {{ object.classes }}</p>
<p><i class="glyphicon glyphicon-user"></i> 授課教師:{{ object.teacher }}</p>
<address>
</address>
<a class="J_menuItem" href="{% url 'project:course_detail' object.id %}">
<button class="btn btn-w-m btn-primary">課程信息</button>
</a>
</div>
<div class="clearfix"></div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- project/course_form.html -->
<!-- 不要忘記 enctype="multipart/form-data" 否則文件或圖片無法上傳成功-->
<form method="POST" class="form-horizontal" role="form" action="" enctype="multipart/form-data">
<div class="form-group">
{% csrf_token %}
<div class="col-sm-12">
<!-- 調入ckeditor靜態文件(js,css和圖片) -->
{{ form.media }}
{{ form }}
</div>
{% for hidden_field in form.hidden_fields %}
<div> {{ hidden_field }} </div>
{% endfor %}
{% if form.non_field_errors %}
<div class="alert alert-danger col-md-12" role="alert">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="form-group">
<div class="col-md-4"></div>
<div class="col-md-4">
<input type="submit" class="btn btn-block btn-primary compose-mail" value="提交">
</div>
</div>
</form>
表單 form 不要忘記添加 enctype="multipart/form-data" 否則文件或圖片無法上傳成功。
其他模板代碼基本上都是一樣的,可直接去我的資源中下載。
效果圖如下: