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 静态文件

 {% 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\d+)/profile/$', views.profile, name='profile'),
    re_path(r'^user/(?P\d+)/profile/update/$', views.profile_update, name='profile_update'),
    re_path(r'^user/(?P\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\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\d+)/$',views.CourseDetail.as_view(), name='course_detail'),
    re_path(r'^course/(?P\d+)/update/$',views.CourseUpdate.as_view(), name='course_update'),
    re_path(r'^course/(?P\d+)/delete$',views.CourseDelete.as_view(), name='course_delete'),
    re_path(r'^course/(?P\d+)/select$', views.course_select, name='course_select'),
    re_path(r'^course/(?P\d+)/cancel$', views.course_cancel, name='course_cancel'),

    # 教师发布作业 增 删 查 改
    re_path(r'^course/(?P\d+)/list$', views.HomeworkList.as_view(), name='homework_list'),
    re_path(r'^course/(?P\d+)/homework/create/$',views.HomeworkCreate.as_view(), name='homework_create'),
    re_path(r'^course/(?P\d+)/homework/(?P\d+)/$',views.HomeworkDetail.as_view(), name='homework_detail'),
    re_path(r'^course/(?P\d+)/homework/(?P\d+)/update/$',views.HomeworkUpdate.as_view(), name='homework_update'),
    re_path(r'^course/(?P\d+)/homework/(?P\d+)/delete$',views.HomeworkDelete.as_view(), name='homework_delete'),
    re_path(r'^course/(?P\d+)/homework/(?P\d+)/publish/$',views.homework_publish, name='homework_publish'),
    re_path(r'^course/(?P\d+)/homework/draft/$', views.HomeworkListDraft.as_view(), name='homework_list_publishing'),
    re_path(r'^course/(?P\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[0-9]+)/$', views.homework_comment, name='homework_comment'),
    # 学生作业统计
    re_path(r'^course/(?P\d+)/homework/(?P\d+)/count$', views.HomeworkHandin.as_view(), name='homework_handin_count'),

    # 学生提交作业 增 删 查 改
    re_path(r'^course/(?P\d+)/homework/(?P\d+)/list$', views.HandinList.as_view(), name='handin_list'),
    re_path(r'^course/(?P\d+)/handin/list$', views.HandinListDone.as_view(), name='handin_list_done'),
    re_path(r'^course/(?P\d+)/homework/(?P\d+)/handin/create/$',views.HandinCreate.as_view(), name='handin_create'),
    re_path(r'^course/(?P\d+)/homework/(?P\d+)/handin/(?P\d+)/$',views.HandinDetail.as_view(), name='handin_detail'),
    re_path(r'^course/(?P\d+)/homework/(?P\d+)/handin/(?P\d+)/update/$',views.HandinUpdate.as_view(), name='handin_update'),
    re_path(r'^course/(?P\d+)/homework/(?P\d+)/handin/(?P\d+)/delete/$',views.HandinDelete.as_view(), name='handin_delete'),

    # 课程分组
    re_path(r'^course/(?P\d+)/student$', views.course_student, name='course_student'),
    re_path(r'^course/(?P\d+)/group$', views.GroupList.as_view(), name='group_list'),
    re_path(r'^course/(?P\d+)/group/(?P\d+)/delete/$', views.GroupDelete.as_view(), name='group_delete'),
    re_path(r'^course/(?P\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,模板代码为:
{% csrf_token %}
    {{ form.as_p }}#将表单渲染成< p >标签
    
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,模板代码如下:


{% csrf_token %}

Are you sure you want to delete "{{ article }}"?

本项目完整视图代码如下:

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


{% for object in object_list %}
image

{{ object.cname }}

班级: {{ object.classes }}

授课教师:{{ object.teacher }}

{% endfor %}



{% csrf_token %}
{{ form.media }} {{ form }}
{% for hidden_field in form.hidden_fields %}
{{ hidden_field }}
{% endfor %} {% if form.non_field_errors %} {% endif %}

表单 form 不要忘记添加 enctype="multipart/form-data"  否则文件或图片无法上传成功。

其他模板代码基本上都是一样的,可直接去我的资源中下载。

效果图如下:

Django项目实战:在线作业管理系统(2)_第1张图片

Django项目实战:在线作业管理系统(2)_第2张图片

你可能感兴趣的:(django)