Django項目實戰:在線作業管理系統(2)

上篇文章講解了系統的登陸、註冊、查看和編輯個人信息、修改密碼功能,本篇章繼續講解課程信息模塊的相關功能以及代碼實現。本篇章主要使用 Django 中的通用視圖來簡化系統的開發,該項目只有 project 一個 APP,當時在寫項目的時候,發現兩個APP 中的 models 並不能直接相互調用,因此將所有表都寫入到了 models.py 文件中。

模型 models.py

  1. 自定義上傳路徑:爲了防止後來上傳的文件與已上傳的文件重名而發生覆蓋,在 models.py 動態地定義了文件上傳路徑user_directory_path,並對上傳的文件以 uuid 形式重命名,當然也可以在視圖 views.py 中定義路徑。
  2. 由於視圖中使用了 Django 的通用視圖,每個模型裏需要定義 get_abosolute_url。Django 的 CreateView和 UpdateView 在完成對象的創建或編輯後會自動跳轉到這個絕對url。
  3. 抽象屬性 abstract:這個屬性是定義當前的模型類是不是一個抽象類。所謂抽象類是不會對應數據庫表的。一般用它來歸納一些公共屬性字段,然後繼承它的子類可以繼承這些字段。
  4. 富文本編輯器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的自定義

  1. 通過更具體的 get_object() 方法來返回一個更具體的對象
  2. 通過重寫 get_queryset 方法傳遞額外的參數或內容
  3. 通過重寫 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用來展示一個對象的列表。

  1. 提取了需要顯示的對象列表或數據集(queryset): Article.objects.all()
  2. 指定了用來顯示對象列表的模板名稱(template_name): 默認 app_name/model_name_list.html
  3. 指定了內容對象名稱(context_object_name):默認值object_list
# DetailView 視圖不能使用 @login_required 這個裝飾器

class CourseDetail(DetailView):
    model = Course

DetailView 用來展示一個具體對象的詳細信息。

  1. 它需要URL提供訪問某個對象的具體參數(如pk, slug值)
  2. 默認的模板是 app/model_name_detail.html
  3. 默認的內容對象名字 context_object_name 是 model_name
  4. 如指定了 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 的自定義。

  1. 默認的模板是 model_name_form.html
  2. 默認的 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 一般通過某個表單更新現有對象的信息,更新完成後會轉到對象詳細信息頁面。

  1. 它需要 URL 提供訪問某個對象的具體參數(如pk, slug值)
  2. UpdateView 和 CreateView很類似,比如默認模板都是 model_name_form.html
  3. CreateView 顯示的表單是空表單,UpdateView 中的表單會顯示現有對象的數據。
  4. 用戶提交表單後,CreateView 轉向對象列表,UpdateView 轉向對象詳細信息頁面
class CourseDelete(DeleteView):
    model = Course
    # get = DeleteView.post
    success_url = reverse_lazy('project:course_list')

DeleteView一般用來刪除某個具體對象。

  1. 它要求用戶點擊確認後再刪除一個對象。
  2. 需要定義模型的名稱 model 和成功刪除對象後的返回的 URL
  3. 默認模板是 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"  否則文件或圖片無法上傳成功。

其他模板代碼基本上都是一樣的,可直接去我的資源中下載

效果圖如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章