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"  否则文件或图片无法上传成功。

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

效果图如下:

基于Django的学生作业管理系统是一套高效、稳定、易用的学生作业管理系统。学生作业管理系统为学校、学生、老师三者之间建立了信息及数据交流的平台,使全体师生可以利用互联网方便地进行作业、资料的管理和交流。下面,我们来谈谈其设计与实现。 设计: 1. 需求分析 首先,我们需要进行需求分析,确定学生作业管理系统所需的功能需求和用户需求。该系统需要实现以下功能: (1)学生和教师登录和注册。 (2)教师能够发布作业。 (3)学生能够查看并提交作业。 (4)教师能够批改学生提交的作业。 (5)学生能够查看自己的作业提交情况及成绩。 (6)教师能够管理学生信息。 (7)系统具有一定的权限管理功能。 2. 数据库设计 根据需求分析,我们可以确定学生作业管理系统需要的数据表有:用户表、作业表、提交情况表、成绩表、权限表等。 3. 需求设计 在确定了数据表结构后,我们就可以根据需求分析来设计学生作业管理系统的功能和页面设计。 实现: 1. 环境搭建 首先,安装Django和MySQL等软件,创建Django项目和应用,在应用的models.py中定义数据库表结构。 2. 编写视图 在views.py中编写对应的视图函数,实现对用户请求的处理。编写URL处理函数,将不同请求分配到不同的视图函数。 3. 编写模板 在templates文件夹中编写HTML模板文件,用于呈现页面。调用Django基础模板语言(Template Language)来简化模板中的逻辑处理。 4. 安全机制 开发中应考虑安全机制,可通过设置Access Token、HTTPS等来保证系统的安全性。 总之,该学生作业管理系统基于Django框架,具有良好的用户体验和安全性,有望提高学生、老师和学校之间的交流效率。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值