Fork me on GitHub

Django+Xadmin打造在线教育系统

系统概括:

  • 系统具有完整的用户登录注册以及找回密码功能,拥有完整个人中心.
  • 个人中心: 修改头像,修改密码,修改邮箱,可以看到我的课程以及我的收藏.可以删除收藏,我的消息.
  • 导航栏: 公开课,授课讲师,授课机构,全局搜索.
  • 点击公开课–> 课程列表,排序-搜索.热门课程推荐,课程的分页.
  • 点击课程–> 课程详情页中对课程进行收藏,取消收藏.富文本展示课程内容.
  • 点击开始学习–> 课程的章节信息,课程的评论信息.课程资源的下载链接.
  • 点击授课讲师–>授课讲师列表页,对讲师进行人气排序以及分页,右边有讲师排行榜.
  • 点击讲师的详情页面–> 对讲师进行收藏和分享,以及讲师的全部课程.
  • 导航栏: 授课机构有分页,排序筛选功能.
  • 机构列表页右侧有快速提交我要学习的表单.
  • 点击机构–> 左侧:机构首页,机构课程,机构介绍,机构讲师.
  • 后台管理系统可以切换主题.左侧每一个功能都有列表显示, 增删改查,筛选功能.
  • 课程列表页可以对不同字段进行排序.选择多条记录进行删除操作.
  • 课程列表页:过滤器->选择字段范围等,搜索,导出csv,xml,json.
  • 课程新增页面上传图片,富文本的编辑.时间选择,添加章节,添加课程资源.
  • 日志记录:记录后台人员的操作

环境

python == 3.6

django == 2.0

创建工程

1
django-admin startproject MxOnline

配置settings.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 数据库

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'imooc', #数据库名字
'USER': 'root', #账号
'PASSWORD': '123456', #密码
'HOST': '127.0.0.1', #IP
'PORT': '3306', #端口
}
}


# 使用中文
LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = False


# 静态文件路径(须在根目录下创建static目录)

STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)

数据库设计

创建四个app,并将其添加到INSTALLED_APPS

1
2
3
4
5
6
7
python manage.py startapp users

python manage.py startapp course

python manage.py startapp organization

python manage.py startapp operation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#  settings.py

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'users',
'course',
'operation',
'organization',
]

在根目录下新建一个apps文件夹,将创建的四个app文件拖到apps文件夹下,大致结构

将apps目录添加到项目路径中

1
2
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0,os.path.join(BASE_DIR,'apps'))

重载UserProfile

1
2
# 使用自己写的UserProfile,而不是系统的
AUTH_USER_MODEL = 'users.UserProfile'

users modesl.py设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
from datetime import datetime

from django.contrib.auth.models import AbstractUser
from django.db import models


class UserProfile(AbstractUser):
# 自定义的性别选择规则
GENDER_CHOICES = (
("male", u"男"),
("female", u"女")
)
# 昵称
nick_name = models.CharField(max_length=50, verbose_name=u"昵称", default="")
# 生日,可以为空
birthday = models.DateField(verbose_name=u"生日", null=True, blank=True)
# 性别 只能男或女,默认女
gender = models.CharField(
max_length=6,
verbose_name=u"性别",
choices=GENDER_CHOICES,
default="female")
# 地址
address = models.CharField(max_length=100, verbose_name="地址", default="")
# 电话
mobile = models.CharField(max_length=11, null=True, blank=True)
# 头像 默认使用default.png
image = models.ImageField(
upload_to="image/%Y/%m",
default=u"image/default.png",
max_length=100
)

# meta信息,即后台栏目名
class Meta:
verbose_name = "用户信息"
verbose_name_plural = verbose_name

# 重载str方法,打印实例会打印username,username为继承自abstractuser
def __str__(self):
return self.username


# 邮箱验证码model
class EmailVerifyRecord(models.Model):
SEND_CHOICES = (
("register", u"注册"),
("forget", u"找回密码")
)
code = models.CharField(max_length=20, verbose_name=u"验证码")
# 未设置null = true blank = true 默认不可为空
email = models.EmailField(max_length=50, verbose_name=u"邮箱")
send_type = models.CharField(choices=SEND_CHOICES, max_length=10)
# 这里的now得去掉(),不去掉会根据编译时间.而不是根据实例化时间.
send_time = models.DateTimeField(default=datetime.now)

class Meta:
verbose_name = "邮箱验证码"
verbose_name_plural = verbose_name


# 轮播图model
class Banner(models.Model):
title = models.CharField(max_length=100, verbose_name=u"标题")
image = models.ImageField(
upload_to="banner/%Y/%m",
verbose_name=u"轮播图",
max_length=100)
url = models.URLField(max_length=200, verbose_name=u"访问地址")
# 默认index很大靠后.想要靠前修改index值.
index = models.IntegerField(default=100, verbose_name=u"顺序")
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")

class Meta:
verbose_name = u"轮播图"
verbose_name_plural = verbose_name

course models.py编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
from datetime import datetime

# 课程信息表
from django.db import models


class Course(models.Model):
DEGREE_CHOICES = (
("cj", u"初级"),
("zj", u"中级"),
("gj", u"高级")
)
name = models.CharField(max_length=50, verbose_name=u"课程名")
desc = models.CharField(max_length=300, verbose_name=u"课程描述")
# TextField允许我们不输入长度.可以输入到无限大.暂时定义为TextFiled,之后更新为富文本
detail = models.TextField(verbose_name=u"课程详情")
degree = models.CharField(choices=DEGREE_CHOICES, max_length=2)
# 使用分钟做后台记录(存储最小单位)前台转换
learn_times = models.IntegerField(default=0, verbose_name=u"学习时长(分钟数)")
# 保存学习人数:点击开始学习才算
students = models.IntegerField(default=0, verbose_name=u"学习人数")
fav_nums = models.IntegerField(default=0, verbose_name=u"收藏人数")
image = models.ImageField(
upload_to="courses/%Y/%m",
verbose_name=u"封面图",
max_length=100)
# 保存点击量,点进页面就算
click_nums = models.IntegerField(default=0, verbose_name=u"点击数")
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")

class Meta:
verbose_name = u"课程"
verbose_name_plural = verbose_name


# 章节
class Lesson(models.Model):
# 因为一个课程对应很多章节.所以在章节表中将课程设置为外键.
# 作为一个字段来让我们可以知道这个章节对应那个课程
course = models.ForeignKey(Course, verbose_name=u"课程", on_delete=models.CASCADE)
name = models.CharField(max_length=100, verbose_name=u"章节名")
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")

class Meta:
verbose_name = u"章节"
verbose_name_plural = verbose_name


# 每章视频
class Video(models.Model):
# 因为一个章节对应很多视频.所以在视频表中将章节设置为外键.
# 作为一个字段来存储让我们可以知道这个视频对应哪个章节.
lesson = models.ForeignKey(Lesson, verbose_name=u"章节", on_delete=models.CASCADE)
name = models.CharField(max_length=100, verbose_name=u"视频名")
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")

class Meta:
verbose_name = u"视频"
verbose_name_plural = verbose_name


# 课程资源
class CourseResource(models.Model):
# 因为一个课程对应很多资源.所以在课程资源表中将课程设置为外键.
# 作为一个字段来让我们可以知道这个资源对应那个课程
course = models.ForeignKey(Course, verbose_name=u"课程", on_delete=models.CASCADE)
name = models.CharField(max_length=100, verbose_name=u"名称")
# 这里定义成文件类型的field,后台管理系统中会直接有上传的按钮.
# FileField也是一个字符串类型,要指定最大长度.
download = models.FileField(
upload_to="course/resource/%Y/%m",
verbose_name=u"资源文件",
max_length=100)
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")

class Meta:
verbose_name = u"课程资源"
verbose_name_plural = verbose_name

organization modesl.py设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from datetime import datetime

from django.db import models

# Create your models here.


# 城市字典
class CityDict(models.Model):
name = models.CharField(max_length=20, verbose_name=u"城市")
# 城市描述:备用不一定展示出来
desc = models.CharField(max_length=200, verbose_name=u"描述")
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")

class Meta:
verbose_name = u"城市"
verbose_name_plural = verbose_name


# 课程机构
class CourseOrg(models.Model):
name = models.CharField(max_length=50, verbose_name=u"机构名称")
# 机构描述,后面会替换为富文本展示
desc = models.TextField(verbose_name=u"机构描述")
click_nums = models.IntegerField(default=0, verbose_name=u"点击数")
fav_nums = models.IntegerField(default=0, verbose_name=u"收藏数")
image = models.ImageField(
upload_to="org/%Y/%m",
verbose_name=u"封面图",
max_length=100)
address = models.CharField(max_length=150, verbose_name=u"机构地址")
# 一个城市可以有很多课程机构,通过将city设置外键,变成课程机构的一个字段
# 可以让我们通过机构找到城市
city = models.ForeignKey(CityDict, verbose_name=u"所在城市", on_delete=models.CASCADE)
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")

class Meta:
verbose_name = u"课程机构"
verbose_name_plural = verbose_name


# 讲师
class Teacher(models.Model):
# 一个机构会有很多老师,所以我们在讲师表添加外键并把课程机构名称保存下来
# 可以使我们通过讲师找到对应的机构
org = models.ForeignKey(CourseOrg, verbose_name=u"所属机构", on_delete=models.CASCADE)
name = models.CharField(max_length=50, verbose_name=u"教师名称")
work_years = models.IntegerField(default=0, verbose_name=u"工作年限")
work_company = models.CharField(max_length=50, verbose_name=u"就职公司")
work_position = models.CharField(max_length=50, verbose_name=u"公司职位")
points = models.CharField(max_length=50, verbose_name=u"教学特点")
click_nums = models.IntegerField(default=0, verbose_name=u"点击数")
fav_nums = models.IntegerField(default=0, verbose_name=u"收藏数")
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")

class Meta:
verbose_name = u"教师"
verbose_name_plural = verbose_name

operation models.py设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
from datetime import datetime

# 引入我们CourseComments所需要的外键models
from django.db import models

from users.models import UserProfile
from course.models import Course

# 用户我要学习表单
class UserAsk(models.Model):
name = models.CharField(max_length=20, verbose_name=u"姓名")
mobile = models.CharField(max_length=11, verbose_name=u"手机")
course_name = models.CharField(max_length=50, verbose_name=u"课程名")
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")

class Meta:
verbose_name = u"用户咨询"
verbose_name_plural = verbose_name


# 用户对于课程评论
class CourseComments(models.Model):

# 会涉及两个外键: 1. 用户, 2. 课程.import进来
course = models.ForeignKey(Course, verbose_name=u"课程", on_delete=models.CASCADE)
user = models.ForeignKey(UserProfile, verbose_name=u"用户", on_delete=models.CASCADE)
comments = models.CharField(max_length=250, verbose_name=u"评论")
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"评论时间")

class Meta:
verbose_name = u"课程评论"
verbose_name_plural = verbose_name


# 用户对于课程,机构,讲师的收藏
class UserFavorite(models.Model):
# 会涉及四个外键.用户,课程,机构,讲师import
TYPE_CHOICES = (
(1, u"课程"),
(2, u"课程机构"),
(3, u"讲师")
)

user = models.ForeignKey(UserProfile, verbose_name=u"用户", on_delete=models.CASCADE)
# course = models.ForeignKey(Course, verbose_name=u"课程")
# teacher = models.ForeignKey()
# org = models.ForeignKey()
# fav_type =

# 机智版
# 直接保存用户的id.
fav_id = models.IntegerField(default=0)
# 表明收藏的是哪种类型.
fav_type = models.IntegerField(
choices=TYPE_CHOICES,
default=1,
verbose_name=u"收藏类型")
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"评论时间")

class Meta:
verbose_name = u"用户收藏"
verbose_name_plural = verbose_name


# 用户消息表
class UserMessage(models.Model):
# 因为我们的消息有两种:发给全员和发给某一个用户.
# 所以如果使用外键,每个消息会对应要有用户.很难实现全员消息.

# 机智版 为0发给所有用户,不为0就是发给用户的id
user = models.IntegerField(default=0, verbose_name=u"接收用户")
message = models.CharField(max_length=500, verbose_name=u"消息内容")

# 是否已读: 布尔类型 BooleanField False未读,True表示已读
has_read = models.BooleanField(default=False, verbose_name=u"是否已读")
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")

class Meta:
verbose_name = u"用户消息"
verbose_name_plural = verbose_name


# 用户课程表
class UserCourse(models.Model):
# 会涉及两个外键: 1. 用户, 2. 课程.import进来
course = models.ForeignKey(Course, verbose_name=u"课程", on_delete=models.CASCADE)
user = models.ForeignKey(UserProfile, verbose_name=u"用户", on_delete=models.CASCADE)
add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")

class Meta:
verbose_name = u"用户课程"
verbose_name_plural = verbose_name

将models映射到数据库中

1
2
python manage.py makemigrations
python manage.py migrate

基于xadmin的后台管理

先使用pip进行安装xadmin及其依赖包

1
pip install django-adminx

安装完成后卸载xadmin,保留依赖包即可

1
pip uninstall django-adminx

需要对源码进行修改,将xadmin的源码下载下来

1
https://github.com/sshwsfc/xadmin/tree/django2

在项目根目录新建Python Package “extra_apps”,把源码xadmin文件夹放到extra_apps文件夹下面,此时目录结构如下:


extra_apps加入系统路径

1
2
3
# settings.py

sys.path.insert(0,os.path.join(BASE_DIR, 'extra_apps'))

配置路由

1
2
3
4
5
6
7
from django.urls import path

from extra_apps import xadmin

urlpatterns = [
path('admin/', xadmin.site.urls),
]

将xadmin添加到INSTALLED_APPS

1
2
'xadmin',
'crispy_forms',

数据库映射

1
2
3
python manage.py makemigrations

python manage.py migrate

创建管理员

1
python manage.py createsuperuser

运行,打开http://127.0.0.1:8000/admin进行访问
输入刚才创建的账号和密码即可进入后台管理系统


后台完善

在创建的四个app目录下新建adminx.py的文件,里面存放后台管理的一些东西,和admin.py中的写法类似,作用一样

course/adminx.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# course/adminx.py

import xadmin

from .models import Course, Lesson, Video, CourseResource


# Course的admin管理器
class CourseAdmin(object):
'''课程'''

list_display = [ 'name','desc','detail','degree','learn_times','students']
search_fields = ['name', 'desc', 'detail', 'degree', 'students']
list_filter = [ 'name','desc','detail','degree','learn_times','students']


class LessonAdmin(object):
'''章节'''

list_display = ['course', 'name', 'add_time']
search_fields = ['course', 'name']
#这里course__name是根据课程名称过滤
list_filter = ['course__name', 'name', 'add_time']


class VideoAdmin(object):
'''视频'''

list_display = ['lesson', 'name', 'add_time']
search_fields = ['lesson', 'name']
list_filter = ['lesson', 'name', 'add_time']


class CourseResourceAdmin(object):
'''课程资源'''

list_display = ['course', 'name', 'download', 'add_time']
search_fields = ['course', 'name', 'download']
list_filter = ['course__name', 'name', 'download', 'add_time']


# 将管理器与model进行注册关联
xadmin.site.register(Course, CourseAdmin)
xadmin.site.register(Lesson, LessonAdmin)
xadmin.site.register(Video, VideoAdmin)
xadmin.site.register(CourseResource, CourseResourceAdmin)

operation/adminx.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# operation/adminx.py

import xadmin

from .models import UserAsk, UserCourse, UserMessage, CourseComments, UserFavorite


class UserAskAdmin(object):
'''用户表单我要学习'''

list_display = ['name', 'mobile', 'course_name', 'add_time']
search_fields = ['name', 'mobile', 'course_name']
list_filter = ['name', 'mobile', 'course_name', 'add_time']


class UserCourseAdmin(object):
'''用户课程学习'''

list_display = ['user', 'course', 'add_time']
search_fields = ['user', 'course']
list_filter = ['user', 'course', 'add_time']


class UserMessageAdmin(object):
'''用户消息后台'''

list_display = ['user', 'message', 'has_read', 'add_time']
search_fields = ['user', 'message', 'has_read']
list_filter = ['user', 'message', 'has_read', 'add_time']


class CourseCommentsAdmin(object):
'''用户评论后台'''

list_display = ['user', 'course', 'comments', 'add_time']
search_fields = ['user', 'course', 'comments']
list_filter = ['user', 'course', 'comments', 'add_time']


class UserFavoriteAdmin(object):
'''用户收藏后台'''

list_display = ['user', 'fav_id', 'fav_type', 'add_time']
search_fields = ['user', 'fav_id', 'fav_type']
list_filter = ['user', 'fav_id', 'fav_type', 'add_time']


# 将后台管理器与models进行关联注册.
xadmin.site.register(UserAsk, UserAskAdmin)
xadmin.site.register(UserCourse, UserCourseAdmin)
xadmin.site.register(UserMessage, UserMessageAdmin)
xadmin.site.register(CourseComments, CourseCommentsAdmin)
xadmin.site.register(UserFavorite, UserFavoriteAdmin)

organization/adminx.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# organization/adminx.py
import xadmin

from .models import CityDict, CourseOrg, Teacher


class CityDictAdmin(object):
'''城市'''

list_display = ['name', 'desc', 'add_time']
search_fields = ['name', 'desc']
list_filter = ['name', 'desc', 'add_time']


class CourseOrgAdmin(object):
'''机构'''

list_display = ['name', 'desc', 'click_nums', 'fav_nums','add_time' ]
search_fields = ['name', 'desc', 'click_nums', 'fav_nums']
list_filter = ['name', 'desc', 'click_nums', 'fav_nums','city__name','address','add_time']


class TeacherAdmin(object):
'''老师'''

list_display = [ 'name','org', 'work_years', 'work_company','add_time']
search_fields = ['org', 'name', 'work_years', 'work_company']
list_filter = ['org__name', 'name', 'work_years', 'work_company','click_nums', 'fav_nums', 'add_time']


xadmin.site.register(CityDict, CityDictAdmin)
xadmin.site.register(CourseOrg, CourseOrgAdmin)
xadmin.site.register(Teacher, TeacherAdmin)

users/adminx.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# users/adminx.py
import xadmin

from .models import EmailVerifyRecord,Banner

#xadmin中这里是继承object,不再是继承admin
class EmailVerifyRecordAdmin(object):
# 显示的列
list_display = ['code', 'email', 'send_type', 'send_time']
# 搜索的字段
search_fields = ['code', 'email', 'send_type']
# 过滤
list_filter = ['code', 'email', 'send_type', 'send_time']


class BannerAdmin(object):
list_display = ['title', 'image', 'url','index', 'add_time']
search_fields = ['title', 'image', 'url','index']
list_filter = ['title', 'image', 'url','index', 'add_time']


xadmin.site.register(EmailVerifyRecord,EmailVerifyRecordAdmin)
xadmin.site.register(Banner,BannerAdmin)

xadmin导出csv乱码

将UTF-8改成gbk即可(xadmin/plugins/export.py)


xadmin全局配置

把全站的配置放在users\adminx.py

1
2
3
4
5
6
7
8
9
from xadmin import views
# 创建X admin的全局管理器并与view绑定.
class BaseSetting(object):
# 开启主题功能
enable_themes = True
use_bootswatch = True

# 将全局配置管理与view绑定注册
xadmin.site.register(views.BaseAdminView, BaseSetting)

1
2
3
4
5
6
7
8
# x admin 全局配置参数信息设置
class GlobalSettings(object):
site_title = "慕课后台管理站"
site_footer = "imooc"
# 收起菜单
menu_style = "accordion"
# 将头部与脚部信息进行注册:
xadmin.site.register(views.CommAdminView, GlobalSettings)

左侧菜单英文变中文
修改每个apps.py文件

1
2
3
4
5
6
7
# users/apps.py
from django.apps import AppConfig


class UsersConfig(AppConfig):
name = 'users'
verbose_name = '用户信息'

修改__init__.py文件

1
default_app_config = 'users.apps.UsersConfig'

将其他三个也进行修改
效果

自定义菜单顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# x admin 全局配置参数信息设置
class GlobalSettings(object):
site_title = "慕课后台管理站"
site_footer = "imooc"
# 收起菜单
menu_style = "accordion"

def get_site_menu(self):
return (
{'title': '课程管理', 'menus': (
{'title': '课程信息', 'url': self.get_model_url(Course, 'changelist')},
{'title': '章节信息', 'url': self.get_model_url(Lesson, 'changelist')},
{'title': '视频信息', 'url': self.get_model_url(Video, 'changelist')},
{'title': '课程资源', 'url': self.get_model_url(CourseResource, 'changelist')},
{'title': '课程评论', 'url': self.get_model_url(CourseComments, 'changelist')},
)},
{'title': '机构管理', 'menus': (
{'title': '所在城市', 'url': self.get_model_url(CityDict, 'changelist')},
{'title': '机构讲师', 'url': self.get_model_url(Teacher, 'changelist')},
{'title': '机构信息', 'url': self.get_model_url(CourseOrg, 'changelist')},
)},
{'title': '用户管理', 'menus': (
{'title': '用户信息', 'url': self.get_model_url(UserProfile, 'changelist')},
{'title': '用户验证', 'url': self.get_model_url(EmailVerifyRecord, 'changelist')},
{'title': '用户课程', 'url': self.get_model_url(UserCourse, 'changelist')},
{'title': '用户收藏', 'url': self.get_model_url(UserFavorite, 'changelist')},
{'title': '用户消息', 'url': self.get_model_url(UserMessage, 'changelist')},
)},

{'title': '系统管理', 'menus': (
{'title': '用户咨询', 'url': self.get_model_url(UserAsk, 'changelist')},
{'title': '首页轮播', 'url': self.get_model_url(Banner, 'changelist')},
{'title': '用户分组', 'url': self.get_model_url(Group, 'changelist')},
{'title': '用户权限', 'url': self.get_model_url(Permission, 'changelist')},
{'title': '日志记录', 'url': self.get_model_url(Log, 'changelist')},
)},)

完成登录 注册 找回密码 激活 验证码集成

将HTML文件拷贝到templates目录下,css,js,img,media,images文件夹拷贝到static文件夹下


修改index.html和login.html文件中的静态文件路径,全部替换为如下格式

1
2
3
<link rel="stylesheet" type="text/css" href="{% static "css/reset.css" %}">
<link rel="stylesheet" type="text/css" href="{% static "css/animate.css" %}">
<link rel="stylesheet" type="text/css" href="{% static "css/style.css" %}">

在文件最上方加入

1
{% load staticfiles %}

配置主页面的路由

1
2
3
4
urlpatterns = [
path('admin/', xadmin.site.urls),
path('',TemplateView.as_view(template_name='index.html'), name='index'),
]

配置登陆页面的路由

1
path('login/',LoginView.as_view(),name = 'login'),

编写登陆的逻辑代码

users/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 实现用户名邮箱均可登录
# 继承ModelBackend类,因为它有方法authenticate,可点进源码查看
from users.models import UserProfile


class CustomBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
try:
# 不希望用户存在两个,get只能有一个.两个是get失败的一种原因 Q为使用并集查询

user = UserProfile.objects.get(Q(username=username)|Q(email=username))

# django的后台中密码加密:所以不能password==password
# UserProfile继承的AbstractUser中有def check_password(self, raw_password):

if user.check_password(password):
return user
except Exception as e:
return None


class LoginView(View):
def get(self,request):
return render(request, 'login.html')

def post(self,request):
# 获取用户提交的用户名和密码
user_name = request.POST.get('username', None)
pass_word = request.POST.get('password', None)
# 成功返回user对象,失败None
user = authenticate(username=user_name, password=pass_word)
# 如果不是null说明验证成功
if user is not None:
# 登录
login(request, user)
return render(request, 'index.html')
else:
return render(request, 'login.html', {'msg': '用户名或密码错误'})

settings.py中启用自定义验证

1
2
3
4
# 自定义登陆验证
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend',
)

修改login文件中的form表单


访问http://127.0.0.1:8000/login/即可进行登陆,成功会跳转到主页面,失败则会显示错误信息

form实现登陆

users目录下新建一个forms.py的文件

1
2
3
4
5
6
7
from django import forms

# 登录表单验证
class LoginForm(forms.Form):
# 用户名密码不能为空
username = forms.CharField(required=True)
password = forms.CharField(required=True, min_length=6)

修改LoginView的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def post(self, request):
# 类实例化需要一个字典参数dict:request.POST就是一个QueryDict所以直接传入
# POST中的username,password,会对应到form中
login_form = LoginForm(request.POST)
# is_valid判断我们字段是否有错执行我们原有逻辑,验证失败跳回login页面
if login_form.is_valid():
# 取不到时为空,username,password为前端页面name值
user_name = request.POST.get("username", "")
pass_word = request.POST.get("password", "")

# 成功返回user对象,失败返回null
user = authenticate(username=user_name, password=pass_word)

# 如果不是null说明验证成功
if user is not None:
# 账号是否激活
if user.is_active:
# login 两参数:request, user
# 实际是对request写了一部分东西进去,然后在render的时候:
# request是要render回去的.这些信息也就随着返回浏览器.完成登录
login(request, user)
# 跳转到首页 user request会被带回到首页
return render(request, "index.html")
else:
return render(request, "login.html", {"msg": "用户未激活!"})
# 验证不成功跳回登录页面
# 没有成功说明里面的值是None,并再次跳转回主页面
else:
return render(request, "login.html", {"msg": "用户名或密码错误! "})
# form.is_valid()已经判断不合法了,所以这里不需要再返回错误信息到前端了,错误信息包含在form.errors中
else:
return render(request,'login.html',{'login_form':login_form})

修改login.html文件内容


1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="form-group marb20 {% if login_form.errors.username %}errorput{% endif %}">
<label>用&nbsp;户&nbsp;名</label>
<input name="username" id="account_l" type="text" placeholder="手机号/邮箱" />
</div>
<div class="form-group marb8 {% if login_form.errors.username %}errorput{% endif %}">
<label>密&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;码</label>
<input name="password" id="password_l" type="password" placeholder="请输入您的密码" />
</div>
<div class="error btns login-form-tips" id="jsLoginTips">
{% for key,error in login_form.errors.items %}
{{ error }}
{% endfor %}
{{ msg }}
</div>

使用admin账号进行登陆测试


邮箱注册

注册时需要验证码
这里使用插件 django-simple-captcha

1
pip install  django-simple-captcha

配置到INSTALLED_APPS

1
2
3
4
INSTALLED_APPS = [
...,
'captcha',
]

配置路由

1
path('captcha/',include('captcha.urls')),

映射到数据库

1
2
3
python manage.py makemigrations

python manage.py migrate

创建一个注册的form

1
2
3
4
5
6
7
8
9
10
11
# 引入验证码field
from captcha.fields import CaptchaField

# 验证码form & 注册表单form
class RegisterForm(forms.Form):
# 此处email与前端name需保持一致.
email = forms.EmailField(required=True)
# 密码不能小于5位
password = forms.CharField(required=True, min_length=5)
# 应用验证码
captcha = CaptchaField(error_messages={'invalid': '验证码错误'})

编写注册的逻辑代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class RegisterView(View):
'''用户注册'''
def get(self,request):
register_form = RegisterForm()
return render(request,'register.html',{'register_form':register_form})

def post(self,request):
register_form = RegisterForm(request.POST)
if register_form.is_valid():
user_name = request.POST.get('email', None)
# 如果用户已存在,则提示错误信息
if UserProfile.objects.filter(email = user_name):
return render(request, 'register.html', {'register_form':register_form,'msg': '用户已存在'})

pass_word = request.POST.get('password', None)
# 实例化一个user_profile对象
user_profile = UserProfile()
user_profile.username = user_name
user_profile.email = user_name
user_profile.is_active = False
# 对保存到数据库的密码加密
user_profile.password = make_password(pass_word)
user_profile.save()
send_register_eamil(user_name,'register')
return render(request,'login.html',{"msg":"邮件已发送,进入邮箱激活"})
else:
return render(request,'register.html',{'register_form':register_form})

修改register.html文件

首先修改静态文件路径,接着修改表单配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

<form id="email_register_form" method="post" action="/register/" autocomplete="off">
<div class="form-group marb20 {% if register_form.errors.email %}errorput{% endif %}">
<label>邮&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;箱</label>
<input type="text" id="id_email" name="email" value="{{ register_form.email.value|default:"" }}" placeholder="请输入您的邮箱地址" />
</div>
<div class="form-group marb8 {% if register_form.errors.password %}errorput{% endif %}">
<label>密&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;码</label>
<input type="password" id="id_password" name="password" placeholder="请输入6-20位非中文字符密码" />
</div>
<div class="form-group marb8 captcha1 {% if register_form.errors.captcha %}errorput{% endif %}">
<label>验&nbsp;证&nbsp;码</label>
{{ register_form.captcha }}
</div>
<div class="error btns" id="jsEmailTips">
{% for key,error in register_form.errors.items %}
{{ error }}
{% endfor %}
{{ msg }}
</div>
<div class="auto-box marb8">
</div>
<input class="btn btn-green" id="jsEmailRegBtn" type="submit" value="注册并登录" />
{% csrf_token %}
</form>

刷新验证码

刷新验证码的功能是前端完成的

1
2
3
4
5
6
7
8
//刷新验证码
function refresh_captcha(event){
$.get("/captcha/refresh/?"+Math.random(), function(result){
$('#'+event.data.form_id+' .captcha').attr("src",result.image_url);
$('#id_captcha_0').attr("value",result.key);
});
return false;
}


发送邮件

这里使用新浪邮箱,首先注册一个新浪邮箱,开启SMTP服务


在apps目录下新建一个utils包,在这个包里面新建一个email_send.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/9/7 下午 08:24
# @Author : gao
# @File : email_send.py

from random import Random

from django.core.mail import send_mail

from MxOnline.settings import EMAIL_FROM
from users.models import EmailVerifyRecord


# 生成随机字符串
def random_str(random_length=8):
str = ''
# 生成字符串的可选字符串
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(random_length):
str += chars[random.randint(0, length)]
return str

# 发送注册邮件
def send_register_eamil(email, send_type="register"):
# 发送之前先保存到数据库,到时候查询链接是否存在
# 实例化一个EmailVerifyRecord对象
email_record = EmailVerifyRecord()
# 生成随机的code放入链接
code = random_str(16)
email_record.code = code
email_record.email = email
email_record.send_type = send_type

email_record.save()

# 定义邮件内容:
email_title = ""
email_body = ""

if send_type == "register":
email_title = "慕课在线 注册激活链接"
email_body = "请点击下面的链接激活你的账号: http://127.0.0.1:8000/active/{0}".format(code)

# 使用Django内置函数完成邮件发送.四个参数:主题,邮件内容,发件人邮箱地址,收件人(是一个字符串列表)
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
# 如果发送成功
if send_status:
pass

if send_type == "forget":
email_title = "慕课在线 找回密码链接"
email_body = "请点击下面的链接找回你的密码: http://127.0.0.1:8000/reset/{0}".format(code)

# 使用Django内置函数完成邮件发送.四个参数:主题,邮件内容,从哪里发,接受者list
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
# 如果发送成功
if send_status:
pass

邮箱激活

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 激活用户的view
class ActiveUserView(View):
def get(self, request, active_code):
# 查询邮箱验证记录是否存在
all_record = EmailVerifyRecord.objects.filter(code = active_code)
# 如果不为空也就是有用户
if all_record:
for record in all_record:
# 获取到对应的邮箱
email = record.email
# 查找到邮箱对应的user
user = UserProfile.objects.get(email=email)
user.is_active = True
user.save()
# 激活成功跳转到登录页面
return render(request, "login.html", )
# 自己瞎输的验证码
else:
return render(request, "active_fail.html")

在templates目录下创建 active_fail.html,代码如下:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p style="color: red;">链接失效</p>
</body>
</html>

添加激活邮箱的路由

1
re_path('active/(?P<active_code>.*)/',ActiveUserView.as_view(),name='user_active'),

忘记密码

点击忘记密码,跳转到忘记密码界面,输入邮箱和验证码后给该邮箱发送重置密码链接
邮箱收到链接后,点击链接跳转到重置密码界面,输入新密码点击提交,成功后跳转到登陆界面

路由设置

1
path('forget/',ForgetPwdView.as_view(),name='forget_pwd'),

新建一个form

1
2
3
4
class ForgetPwdForm(forms.Form):
'''忘记密码'''
email = forms.EmailField(required=True)
captcha = CaptchaField(error_messages={'invalid': '验证码错误'})

逻辑代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ForgetPwdView(View):
'''找回密码'''
def get(self,request):
forget_form = ForgetPwdForm()
return render(request,'forgetpwd.html',{'forget_form':forget_form})

# post方法实现
def post(self, request):
forget_form = ForgetPwdForm(request.POST)
# form验证合法情况下取出email
if forget_form.is_valid():
email = request.POST.get("email", "")
# 发送找回密码邮件
send_register_eamil(email, "forget")
# 发送完毕返回登录页面并显示发送邮件成功.
return render(request, "login.html", {"msg": "重置密码邮件已发送,请注意查收"})
# 如果表单验证失败也就是他验证码输错等.
else:
return render(request, "forgetpwd.html", {"forget_from": forget_form})

修改forgetpwd.html表单提交内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<form id="jsFindPwdForm" method="post" action="/forget/" autocomplete="off">

<div class="form-group marb20 ">
<label>帐&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;号</label>
<input type="text" id="account" name="email" placeholder="邮箱" />
</div>
<div class="form-group captcha1 marb38">
<label>验&nbsp;证&nbsp;码</label>
{{ forget_form.captcha }}
</div>
<div class="error btns" id="jsForgetTips">
{% for key,error in forget_from.errors.items %}
{{ error }}
{% endfor %}
{{ msg }}
</div>
<input type="hidden" name="sms_type" value="1">
<input class="btn btn-green" id="jsFindPwdBtn" type="submit" value="提交" />
<p class="form-p" style="bottom:40px;">您还可以<a href="/login/"> [直接登录]</a></p>
{% csrf_token %}
</form>

密码重置

配置路由

1
re_path('reset/(?P<active_code>.*)/', ResetView.as_view(), name='reset_pwd'),

逻辑代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 重置密码的view
class ResetView(View):
def get(self, request, active_code):
# 查询邮箱验证记录是否存在
all_record = EmailVerifyRecord.objects.filter(code=active_code)
# 如果不为空也就是有用户
if all_record:
for record in all_record:
# 获取到对应的邮箱
email = record.email
# 将email传回来
return render(request, "password_reset.html", {"email":email})
# 自己瞎输的验证码
else:
return render(
request, "forgetpwd.html", {
"msg": "您的重置密码链接无效,请重新请求"})

创建修改密码的form表单

1
2
3
4
5
6
# 重置密码form实现
class ModifyPwdForm(forms.Form):
# 密码不能小于5位
password1 = forms.CharField(required=True, min_length=5)
# 密码不能小于5位
password2 = forms.CharField(required=True, min_length=5)

配置路由

1
path('modify_pwd/', ModifyPwdView.as_view(), name='modify_pwd'),

逻辑代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 # 修改密码
class ModifyPwdView(View):
def post(self, request):
modiypwd_form = ModifyPwdForm(request.POST)
if modiypwd_form.is_valid():
pwd1 = request.POST.get("password1", "")
pwd2 = request.POST.get("password2", "")
email = request.POST.get("email", "")
# 如果两次密码不相等,返回错误信息
if pwd1 != pwd2:
return render(request, "password_reset.html", {"email": email, "msg": "密码不一致"})
# 如果密码一致
user = UserProfile.objects.get(email=email)
# 加密成密文
user.password = make_password(pwd2)
# save保存到数据库
user.save()
return render(request, "login.html", {"msg": "密码修改成功,请登录"})
# 验证失败说明密码位数不够.
else:
email = request.POST.get("email", "")
return render(request, "password_reset.html", {"email": email, "modiypwd_form":modiypwd_form})

需要两个类是因为get请求和post请求参数不同
html文件根据以前的方法进行配置即可

完成授课机构的功能

模板继承

templates目录下,新建base.html,剪切org-list.html内容到里面


编写org-list.html内容
继承base.html,将里面的面包屑和内容拷贝到org-list.html

配置路由

1
2
# 课程机构首页url
path('org_list/', OrgView.as_view(), name="org_list"),

这里需要修改一下models

1
2
3
4
5
6
7
8
9
10
# organization/models.py
class CourseOrg(models.Model):
ORG_CHOICES = (
("pxjg", u"培训机构"),
("gx", u"高校"),
("gr", u"个人"),
)

# 添加字段
category = models.CharField(max_length=20, choices=ORG_CHOICES, verbose_name="机构类别", default="pxjg")

修改了models之后做数据库的变动:

1
2
makemigrations organization
migrate organization

在项目目录下面新建一个目录media,用来存放上传的图片
setting中要配置我们把文件存放在哪个根目录之下

1
2
3
4
# 设置我们上传文件的路径

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

在后台自行添加教育机构和城市

添加完数据后进行逻辑代码的编写

1
2
3
4
5
6
7
8
9
10
11
12
13
class OrgView(View):
'''课程机构'''
def get(self,request):
# 取出所有课程机构
all_orgs = CourseOrg.objects.all()
org_onums = all_orgs.count()
# 取出所有城市
all_citys = CityDict.objects.all()
return render(request, "org-list.html", {
"all_orgs": all_orgs,
"all_citys": all_citys,
'org_onums':org_onums,
})

数据填充

打开org-list.html进行数据的填充



这里只保留一个dl标签即可,其他的数据自行填充
这里说一下MEDIA_URL
数据库中存储的图片路径时没有/media/前缀的

如果想要使用MEDIA_RUL需要在设置中添加media处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
#添加图片处理器,为了在课程列表中前面加上MEDIA_URL
'django.template.context_processors.media',
],
},
},
]

然后还需要配url

1
2
3
4
5
6
from django.views.static import serve

from MxOnline.settings import MEDIA_ROOT

# 处理图片显示的url,使用Django自带serve,传入参数告诉它去哪个路径找,我们有配置好的路径MEDIAROOT
re_path(r'^media/(?P<path>.*)', serve, {"document_root": MEDIA_ROOT })

列表分页功能

这里不使用Django内置的由分页模块,而是使用插件django-pure-pagination

1
pip install django-pure-pagination

进行settings的配置

1
2
3
4
5
6
7
8
9
10
INSTALLED_APPS = (
...
'pure_pagination',
)

PAGINATION_SETTINGS = {
'PAGE_RANGE_DISPLAYED': 10,
'MARGIN_PAGES_DISPLAYED': 2,
'SHOW_FIRST_PAGE_WHEN_INVALID': True,
}

这里参考官方的例子进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pure_pagination import Paginator, EmptyPage, PageNotAnInteger
class OrgView(View):
def get(self,request):
# 查找到所有的课程机构
all_orgs = CourseOrg.objects.all()
# 总共有多少家机构使用count进行统计
org_nums = all_orgs.count()
# 取出所有的城市
all_city = CityDict.objects.all()
# 对课程机构进行分页
# 尝试获取前台get请求传递过来的page参数
# 如果是不合法的配置参数默认返回第一页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# 这里指从allorg中取五个出来,每页显示5个
p = Paginator(all_orgs, 5, request=request)
orgs = p.page(page)

return render(request, "org-list.html", {
"all_orgs":orgs,
"all_city": all_city,
"org_nums": org_nums,
})

在html文件中进行分页
这里for循环需要使用object_list

1
{% for org in all_orgs.object_list %}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <div class="pageturn">
<ul class="pagelist">
{% if all_orgs.has_previous %}
<li class="long"><a href="?{{ all_orgs.previous_page_number.querystring }}">上一页</a></li>
{% endif %}

{% for page in all_orgs.pages %}
{% if page %}
{% ifequal page all_orgs.number %}
<li class="active"><a href="?{{ page.querystring }}">{{ page }}</a></li>
{% else %}
<li><a href="?{{ page.querystring }}" class="page">{{ page }}</a></li>
{% endifequal %}
{% else %}
<li class="none"><a href="">...</a></li>
{% endif %}
{% endfor %}
{% if all_orgs.has_next %}
<li class="long"><a href="?{{ all_orgs.next_page_number.querystring }}">下一页</a></li>
{% endif %}
</ul>
</div>

分类筛选功能

点击某个城市时,该城市处于选中状态,下面显示的数据是当前城市的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class OrgView(View):
def get(self,request):
# 查找到所有的课程机构
all_orgs = CourseOrg.objects.all()

# 取出所有的城市
all_city = CityDict.objects.all()

city_id = request.GET.get('city', '')
if city_id:
all_orgs = all_orgs.filter(city_id=int(city_id))

# 总共有多少家机构使用count进行统计
org_nums = all_orgs.count()

# 对课程机构进行分页
# 尝试获取前台get请求传递过来的page参数
# 如果是不合法的配置参数默认返回第一页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# 这里指从allorg中取五个出来,每页显示5个
p = Paginator(all_orgs, 5, request=request)
orgs = p.page(page)

return render(request, "org-list.html", {
"all_orgs":orgs,
"all_city": all_city,
"org_nums": org_nums,
'city_id': city_id,
})

后台逻辑中给前端传递了一个city_id用来城市的标记

1
2
3
4
5
6
<div class="cont">
<a href="?ct="><span class="{% ifequal city_id '' %}active2{% endifequal %}">全部</span></a>
{% for city in all_city %}
<a href="?city={{ city.id }}"><span class="{% ifequal city.id|stringformat:'i' city_id %}active2{% endifequal %}">{{ city.name }}</span></a>
{% endfor %}
</div>

因为city.id是一个int类型,要转换成字符串,再作比较.

1
{% ifequal city_id '' %}

如果为空,说明没有city选中,则“全部”是“active”

同理,添加类别筛选

1
2
3
4
# 类别筛选
category = request.GET.get('ct','')
if category:
all_orgs = all_orgs.filter(category=category)

1
2
3
4
5
6
7
8
9
10
11
<h2>机构类别</h2>
<div class="cont">
<a href="?city={{ city_id }}"><span
class="{% ifequal category '' %}active2{% endifequal %}">全部</span></a>
<a href="?ct=pxjg&city={{ city_id }}"><span
class="{% ifequal category 'pxjg' %}active2{% endifequal %}">培训机构</span></a>
<a href="?ct=gx&city={{ city_id }}"><span
class="{% ifequal category 'gx' %}active2{% endifequal %}">高校</span></a>
<a href="?ct=gr&city={{ city_id }}"><span
class="{% ifequal category 'gr' %}active2{% endifequal %}">个人</span></a>
</div>
1
2
3
4
5
6
7
8
9
10
<h2>所在地区</h2>
<div class="more">更多</div>
<div class="cont">
<a href="?ct={{ category }}"><span
class="{% ifequal city_id '' %}active2{% endifequal %}">全部</span></a>
{% for city in all_city %}
<a href="?city={{ city.id }}&ct={{ category }}"><span
class="{% ifequal city_id city.id|stringformat:"i" %}active2{% endifequal %}">{{ city.name }}</span></a>
{% endfor %}
</div>

进行城市与分类的联动:

  • 当选择全部类别的时候,就只通过当前城市id.
  • 当选择全部城市的时候,就只通过当前目录id.
  • 当两者都选的时候使用&连接.

课程机构排名

1
2
# 热门机构,如果不加负号会是有小到大.
hot_orgs = all_orgs.order_by("-click_nums")[:3]
1
2
3
4
5
6
7
8
9
10
11
12
<div class="right companyrank layout">
<div class="head">授课机构排名</div>
{% for curent_org in hot_orgs %}
<dl class="des">
<dt class="num fl">{{ foorloop.counter }}</dt>
<dd>
<a href="/company/2/"><h1>{{ curent_org.name }}</h1></a>
<p>{{ curent_org.address }}</p>
</dd>
</dl>
{% endfor %}
</div>

循环时内置变量forloop.counter取当前循环到第几次

学习人数和课程的筛选

在models中添加学习人数和课程数两个字段

1
2
3
4
# 当学生点击学习课程,找到所属机构,学习人数加1
students = models.IntegerField(default=0, verbose_name=u"学习人数")
# 当发布课程就加1
course_nums = models.IntegerField(default=0, verbose_name=u"课程数")

1
2
makemigrations
migrate
1
2
3
4
5
6
7
# 进行排序
sort = request.GET.get('sort', "")
if sort:
if sort == "students":
all_orgs = all_orgs.order_by("-students")
elif sort == "courses":
all_orgs = all_orgs.order_by("-course_nums")
1
2
3
4
5
6
7
<div class="head">
<ul class="tab_header">
<li class="{% if sort == '' %}active{% endif %}"><a href="?sort=students&ct={{ category }}&city={{ city_id }}">全部</a></li>
<li class="{% if sort == 'students' %}active{% endif %}"><a href="?sort=students&ct={{ category }}&city={{ city_id }}">学习人数 &#8595;</a></li>
<li class="{% if sort == 'courses' %}active{% endif %}"><a href="?sort=courses&ct={{ category }}&city={{ city_id }}">课程数 &#8595;</a></li>
</ul>
</div>

modelform 提交我要学习咨询

在organazition目录下创建forms.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 普通版本的form
# class UserAskForm(forms.Form):
# name = forms.CharField(required=True, min_length=2, max_length=20)
# phone = forms.CharField(required=True, max_length=11, min_length=11)
# course_name = forms.CharField(required=True, min_length=5, max_length=50)

# 进阶版本的modelform:它可以向model一样save
from django import forms

from operation.models import UserAsk


class AnotherUserForm(forms.ModelForm):
# 继承之余还可以新增字段

# 是由哪个model转换的
class Meta:
model = UserAsk
# 我需要验证的字段
fields = ['name','mobile','course_name']

使用include进行路由分发
organization目录下新建urls.py

1
2
3
4
5
6
7
8
9
10
from django.urls import path

from organization.views import OrgView, AddUserAskView

app_name = "organization"

urlpatterns = [
# 课程机构首页url
path('list/', OrgView.as_view(), name="org_list"),
]

根目录下的urls.py
删掉org_list,新增include

1
2
# 课程机构app的url配置
path("org/", include('organization.urls',namespace="org")),

修改base.html中“课程机构的链接”

1
<li class="active" ><a href="{% url 'org:org_list' %}">授课机构</a></li>

后台逻辑代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 用户添加我要学习
class AddUserAskView(View):
# 处理表单提交当然post
def post(self,request):
userask_form = UserAskForm(request.POST)
# 判断该form是否有效
if userask_form.is_valid():
# 这里是modelform和form的区别
# 它有model的属性
# 当commit为true进行真正保存
user_ask = userask_form.save(commit=True)
# 这样就不需要把一个一个字段取出来然后存到model的对象中之后save

# 如果保存成功,返回json字符串,后面content type是告诉浏览器的,
return HttpResponse('{"status": "success"}', content_type='application/json')
else:
# 如果保存失败,返回json字符串,并将form的报错信息通过msg传递到前端
return HttpResponse('{"status": "fail", "msg":{0}}'.format(userask_form.errors), content_type='application/json')

路由

1
path('add_ask/', AddUserAskView.as_view(), name="add_ask"),

前端页面采用Ajax方式请求
form表单添加crsf_token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
$(function(){
$('#jsStayBtn').on('click', function(){
$.ajax({
cache: false,
type: "POST",
url:"{% url "org:add_ask" %}",
data:$('#jsStayForm').serialize(),
async: true,
success: function(data) {
if(data.status == 'success'){
$('#jsStayForm')[0].reset();
alert("提交成功")
}else if(data.status == 'fail'){
$('#jsCompanyTips').html(data.msg)
}
},
});
});
})

</script>

在ModelForm中自定义一个手机号验证的方法

1
2
3
4
5
6
7
8
9
10
11
def clean_mobile(self):
"""
验证手机号码是否合法
"""
mobile = self.cleaned_data['mobile']
REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|176\d{8}$"
p = re.compile(REGEX_MOBILE)
if p.match(mobile):
return mobile
else:
raise forms.ValidationError(u"手机号码非法", code="mobile_invalid")

机构详情

  • 机构首页
  • 机构课程
  • 机构介绍
  • 机构讲师

课程中应该有一个外键指向它是哪个机构的

1
2
3
# courses/models.py

course_org = models.ForeignKey(CourseOrg,on_delete=models.CASCADE, verbose_name="所属机构",null=True,blank=True)

1
2
makemigration 
migrate

登录xadmin添加基础的必要数据.添加课程与讲师.
新建一个模板,命名为“org_base.html”,复制org-detail-homepage.html的内容到里面,
添加block,修改静态文件路径



把org_base中的三个“right”剪切到home里面
路由配置

1
re_path('home/(?P<org_id>\d+)/', OrgHomeView.as_view(), name="org_home"),

视图函数

1
2
3
4
5
6
7
8
9
10
11
12
13
class OrgHomeView(View):
'''机构首页'''
def get(self,request,org_id):
# 根据id找到课程机构
course_org = CourseOrg.objects.get(id=int(org_id))
# 反向查询到课程机构的所有课程和老师
all_courses = course_org.course_set.all()[:4]
all_teacher = course_org.teacher_set.all()[:2]
return render(request,'org-detail-homepage.html',{
'course_org':course_org,
'all_courses':all_courses,
'all_teacher':all_teacher,
})

显示课程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="brief group_list">
{% for course in all_courses %}
<div class="module1_5 box">
<a href="course-detail.html"><img width="214" src="{{ MEDIA_URL }}{{ course.image }}"/></a>
<div class="des">
<a href="course-detail.html"><h2>{{ course.name }}</h2></a>
<span class="fl">课时:<i class="key">{{ course.learn_times }}</i></span>
<span class="fr">参加人数:{{ course.students }}</span>
</div>
<div class="bottom">
<span class="fl">{{ course.course_org.name }}</span>
<span class="star fr notlogin
" data-favid="13" data-fav-type="4">
{{ course.fav_nums }}
</span>
</div>
</div>
{% endfor %}
</div>

修改org-base.html

为讲师增加头像字段

1
2
3
4
5
image = models.ImageField(
default= '',
upload_to="teacher/%Y/%m",
verbose_name=u"头像",
max_length=100)

1
2
makemgration 
migrate

显示机构教师

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div class="head">
<h1>机构教师</h1>
<a class="green fr more" href="org-detail-teachers.html">查看更多</a>
</div>

{% for teacher in all_teacher %}
<div class="diarys">
<div class="module5 share company-diary-box" style="padding:10px 0;">
<div class="left">
<img class="pic" src="{{ MEDIA_URL }}{{ teacher.image }}"/>
<p>昵称:{{ teacher.name }}</p>
</div>
<div class="right">
<div class="top">
<div class="fl">
<a href=""><h1>java开发教程</h1></a>
<span>发表于:2015-10-12</span>
</div>
</div>
<div class="middle" style="border-bottom:0;">课程介绍</div>
</div>
</div>
</div>
{% endfor %}

显示机构详情

1
2
3
4
5
6
7
<div class="right companycenter layout" >
<div class="head">
<h1>机构介绍</h1>
<a class="green fr more" href="org-detail-desc.html">查看更多 > </a>
</div>
<div class="cont">{{ course_org.desc }}</div>
</div>

机构课程

配置路由

1
re_path('course/(?P<org_id>\d+)/', OrgCourseView.as_view(), name="org_course"),

视图函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class OrgCourseView(View):
"""
机构课程列表页
"""
def get(self, request, org_id):
# 根据id取到课程机构
course_org = CourseOrg.objects.get(id= int(org_id))
# 通过课程机构找到课程.内建的变量,找到指向这个字段的外键引用
all_courses = course_org.course_set.all()

return render(request, 'org-detail-course.html',{
'all_courses':all_courses,
'course_org': course_org,
})

修改org-base.html中left的链接

1
2
3
4
5
6
7
8
<div class="left">
<ul>
<li class="{% ifequal current_page 'home' %}active2{% endifequal %}"><a href="{% url 'org:org_home' course_org.id %}">机构首页</a></li>
<li class="{% ifequal current_page 'course' %}active2{% endifequal %}"><a href="{% url 'org:org_course' course_org.id %}">机构课程</a></li>
<li class="{% ifequal current_page 'desc' %}active2{% endifequal %}"><a href="{% url 'org:org_desc' course_org.id %}">机构介绍</a></li>
<li class="{% ifequal current_page 'teacher' %}active2{% endifequal %}"><a href="{% url 'org:org_teacher' course_org.id %}">机构讲师</a></li>
</ul>
</div>

显示机构课程,修改org-detail-course.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{% block right_form %}
<div class="right companycenter layout" >
<div class="head">
<h1>机构课程</h1>
</div>
<div class="brief group_list">
{% for course in all_courses %}
<div class="module1_5 box">
<a class="comp-img-box" href="course-detail.html">

<img width="214" height="195" src="{{ MEDIA_URL }}{{ course.image }}"/>
</a>
<div class="des">
<a href="course-detail.html"><h2>{{ course.name }}</h2></a>
<span class="fl">课时:<i class="key">{{ course.learn_times }}</i></span>
<span class="fr">学习人数{{ course.students }}</span>
</div>
<div class="bottom">
<span class="fl">{{ course.course_org.name }}</span>
<span class="star fr notlogin
" data-favid="13" data-fav-type="4">
{{ course.fav_nums }}
</span>
</div>
</div>
{% endfor %}
<div class="pageturn">
<ul class="pagelist">
<li class="active"><a href="?page=1">1</a></li>
</ul>
</div>
</div>
{% endblock %}

左侧active修改

因为现在没有值能判断当前是哪个页面.所以在orghomeview中传值回来current page
修改views,传个current_page参数到前端,可以知道当前是哪个被激活状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class OrgHomeView(View):
'''机构首页'''

def get(self,request,org_id):
current_page = 'home'

# 根据id找到课程机构
course_org = CourseOrg.objects.get(id=int(org_id))
# 反向查询到课程机构的所有课程和老师
all_courses = course_org.course_set.all()[:4]
all_teacher = course_org.teacher_set.all()[:2]
return render(request,'org-detail-homepage.html',{
'course_org':course_org,
'all_courses':all_courses,
'all_teacher':all_teacher,
'current_page': current_page,
})


class OrgCourseView(View):
"""
机构课程列表页
"""
def get(self, request, org_id):
current_page = 'course'

# 根据id取到课程机构
course_org = CourseOrg.objects.get(id= int(org_id))
# 通过课程机构找到课程.内建的变量,找到指向这个字段的外键引用
all_courses = course_org.course_set.all()

return render(request, 'org-detail-course.html',{
'all_courses':all_courses,
'course_org': course_org,
'current_page': current_page,
})

机构介绍

1
re_path('desc/(?P<org_id>\d+)/', OrgDescView.as_view(), name="org_desc"),
1
2
3
4
5
6
7
8
9
10
class OrgDescView(View):
'''机构介绍页'''
def get(self, request, org_id):
current_page = 'desc'
# 根据id取到课程机构
course_org = CourseOrg.objects.get(id= int(org_id))
return render(request, 'org-detail-desc.html',{
'course_org': course_org,
'current_page':current_page,
})
1
2
3
4
5
6
7
8
9
10
11
12
{% block right_form %}
<div class="right companycenter layout" >
<div class="head">
<h1>机构介绍</h1>
</div>
<div class="des">

{{ course_org.desc }}

</div>
</div>
{% endblock %}

机构讲师

1
re_path('teacher/(?P<org_id>\d+)/', OrgTeacherView.as_view(), name="org_teacher"),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class OrgTeacherView(View):
"""
机构教师页
"""
def get(self, request, org_id):
current_page = 'teacher'
course_org = CourseOrg.objects.get(id= int(org_id))
all_teacher = course_org.teacher_set.all()

return render(request, 'org-detail-teachers.html',{
'all_teacher':all_teacher,
'course_org': course_org,
'current_page':current_page,
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{% block right_form %}
<div class="right companycenter layout" >
<div class="head">
<h1>机构讲师</h1>
</div>
<div class="messagelist">
<div class=" butler_list butler-fav-box">
{% for teacher in all_teacher %}
<dl class="des users">
<dt>
<a href="">
<img width="100" height="100" class="scrollLoading" data-url="{{ MEDIA_URL }}{{ teacher.image }}" src="{{ MEDIA_URL }}{{ teacher.image }}"/>
</a>
</dt>
<dd>
<h1>
<a href="">
{{ teacher.name }}<span class="key">已认证</span>
</a>
</h1>
<ul class="cont clearfix">
<li class="time">工作年限:<span>{{ teacher.work_years }}</span></li>
<li class="c7">课程数:<span>3</span></li>
</ul>
</dd>
</dl>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

重载我们的pagepath和title,使数据动态显示

1
2
3
4
5
{% block title %}机构教师{% endblock %}

{% block page_path %}
机构教师
{% endblock %}

课程机构收藏功能

1
path('add_fav/', AddFavView.as_view(), name="add_fav"),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class AddFavView(View):
"""
用户收藏与取消收藏功能
"""
def post(self, request):
# 表明你收藏的不管是课程,讲师,还是机构.他们的id
# 默认值取0是因为空串转int报错
id = request.POST.get('fav_id', 0)
# 取到你收藏的类别,从前台提交的ajax请求中取
type = request.POST.get('fav_type', 0)

# 收藏与已收藏取消收藏
# 判断用户是否登录:即使没登录会有一个匿名的user
if not request.user.is_authenticated:
# 未登录时返回json提示未登录,跳转到登录页面是在ajax中做的
return HttpResponse('{"status":"fail", "msg":"用户未登录"}', content_type='application/json')
exist_records = UserFavorite.objects.filter(user=request.user, fav_id=int(id), fav_type=int(type))
if exist_records:
# 如果记录已经存在, 则表示用户取消收藏
exist_records.delete()
return HttpResponse('{"status":"success", "msg":"收藏"}', content_type='application/json')
else:
user_fav = UserFavorite()
# 过滤掉未取到fav_id type的默认情况
if int(type) >0 and int(id) >0:
user_fav.fav_id = int(id)
user_fav.fav_type = int(type)
user_fav.user = request.user
user_fav.save()
return HttpResponse('{"status":"success", "msg":"已收藏"}', content_type='application/json')
else:
return HttpResponse('{"status":"fail", "msg":"收藏出错"}', content_type='application/json')

Ajax放在org_base.html里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<script type="text/javascript">
//收藏分享
function add_fav(current_elem, fav_id, fav_type){
$.ajax({
cache: false,
type: "POST",
url:"{% url 'org:add_fav' %}",
data:{'fav_id':fav_id, 'fav_type':fav_type},
async: true,
beforeSend:function(xhr, settings){
xhr.setRequestHeader("X-CSRFToken", "{{ csrf_token }}");
},
success: function(data) {
if(data.status == 'fail'){
if(data.msg == '用户未登录'){
window.location.href="/login/";
}else{
alert(data.msg)
}

}else if(data.status == 'success'){
current_elem.text(data.msg)
}
},
});
}

$('.collectionbtn').on('click', function(){
add_fav($(this), {{ course_org.id }}, 2);
});

还有个问题就是,刷新页面后,“已收藏”就变成“收藏”,是因为在返回页面的时候,没有判断收藏状态, 所有要在views里面加个判断

1
2
3
4
5
6
7
8
 # 判断收藏状态
has_fav = False
if request.user.is_authenticated:
if UserFavorite.objects.filter(user=request.user, fav_id=course_org.id, fav_type=2):
has_fav = True

# return redener加上值
"has_fav": has_fav

四个view都要添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class OrgDescView(View):
'''机构介绍页'''
def get(self, request, org_id):
current_page = 'desc'
# 根据id取到课程机构
course_org = CourseOrg.objects.get(id= int(org_id))

has_fav = False
# 必须是用户已登录我们才需要判断.
if request.user.is_authenticated:
if UserFavorite.objects.filter(user=request.user, fav_id=course_org.id, fav_type=2):
has_fav = True
return render(request, 'org-detail-desc.html',{
'course_org': course_org,
'current_page':current_page,
'has_fav': has_fav,
})

前台 org_base.html

1
2
3
4
<div class="btn fr collectionbtn  notlogin 
"data-favid="22" data-fav-type="1">
{% if has_fav %}已收藏{% else %}收藏{% endif %}
</div>

课程相关功能实现

课程列表

创建课程相关的urls.py

1
path("course/", include('course.urls', namespace="course")),

course里面新建urls.py

1
2
3
4
5
6
7
8
9
10
from django.urls import path

from course.views import CourseListView

app_name = "courses"
urlpatterns = [
# 课程列表url
path('list/', CourseListView.as_view(), name="list"),

]

视图函数

1
2
3
4
5
6
from django.shortcuts import render
from django.views.generic import View

class CourseListView(View):
def get(self, request):
return render(request, "course-list.html")

course-list.html继承base.html
修改title,修改bread里面,content里面放course-list独有的
去后台添加一些课程
修改视图函数

1
2
3
4
5
6
7
class CourseListView(View):
def get(self, request):
all_course = Course.objects.all()
return render(request, "course-list.html", {
"all_course": all_course,

})

course-list.html文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{% for course in all_course %}
<div class="box">
<a href="course-detail.html">
<img width="280" height="350" class="scrollLoading" src="{{ MEDIA_URL }}{{ course.image }}"/>
</a>
<div class="des">
<a href="course-detail.html">
<h2>{{ course.name }}</h2>
</a>
<span class="fl">时长:<i class="key">{{ course.learn_times }}</i></span>
<span class="fr">学习人数:{{ course.students }}&nbsp;&nbsp;</span>
</div>
<div class="bottom">
<a href="course-detail.html"><span class="fl">来自{{ course.course_org.name }}</span></a>
<span class="star fr notlogin
" data-favid="15">
{{ course.fav_nums }}
</span>
</div>
</div>
{% endfor %}

分页功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CourseListView(View):
def get(self, request):
all_course = Course.objects.all()
# 对课程进行分页
# 尝试获取前台get请求传递过来的page参数
# 如果是不合法的配置参数默认返回第一页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# 这里指从allorg中取五个出来,每页显示5个
p = Paginator(all_course,6 , request=request)
courses = p.page(page)
return render(request, "course-list.html", {
"all_course":courses,

})

在html中使用时注意object_list,此时的all_course已经不是一个queryset,而是一个purepage对象
页码的替换参考之前的分页,拿来用即可

排序功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class CourseListView(View):
def get(self, request):
all_course = Course.objects.all()

# 进行排序
sort = request.GET.get('sort', "")
if sort:
if sort == "students":
all_course = all_course.order_by("-students")
elif sort == "hot":
all_course = all_course.order_by("-click_nums")

# 热门课程推荐
hot_courses = Course.objects.all().order_by("-students")[:3]


# 对课程进行分页
# 尝试获取前台get请求传递过来的page参数
# 如果是不合法的配置参数默认返回第一页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# 这里指从allorg中取五个出来,每页显示5个
p = Paginator(all_course,6 , request=request)
courses = p.page(page)
return render(request, "course-list.html", {
"all_course":courses,
"sort": sort,
"hot_courses": hot_courses
})
1
2
3
4
5
6
7
<div class="head">
<ul class="tab_header">
<li class="{% ifequal sort '' %}active{% endifequal %}"><a href="?sort=" >最新 </a></li>
<li class="{% ifequal sort 'hot' %}active{% endifequal %}"><a href="?sort=hot" >最热门 </a></li>
<li class="{% ifequal sort 'students' %}active{% endifequal %}"><a href="?sort=students" >参与人数 </a></li>
</ul>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="head">热门课程推荐</div>
<div class="group_recommend">
{% for hot_course in hot_courses %}
<dl>
<dt>
<a target="_blank" href="">
<img width="240" height="220" class="scrollLoading" src="{{ MEDIA_URL }}{{ hot_course.image }}"/>
</a>
</dt>
<dd>
<a target="_blank" href=""><h2> {{ hot_course.name }}</h2></a>
<span class="fl">难度:<i class="key">{{ hot_course.get_degree_display }}</i></span>
</dd>
</dl>
{% endfor %}

课程详情

模板替换,
路由配置

1
2
# 课程详情页
re_path('course/(?P<course_id>\d+)/', CourseDetailView.as_view(), name="course_detail"),

1
2
3
4
5
6
7
8
9
10
11
class CourseDetailView(View):
'''课程详情'''
def get(self, request, course_id):
course = Course.objects.get(id=int(course_id))
# 课程的点击数加1
course.click_nums += 1
course.save()
return render(request, "course-detail.html", {
'course':course,

})

课程的章节数如何实现?

models.py中自定义方法

1
2
3
4
5
6
7
8
def get_zj_nums(self):
# 获取课程章节数的方法
return self.lesson_set.all().count()

# 获取学习这门课程的用户
def get_learn_users(self):
# 谁的里面添加了它做外键,他都可以取出来
return self.usercourse_set.all()[:5]

添加课程类别字段

1
category = models.CharField(max_length=20, default="", verbose_name="课程类别")

修改course-detail.html内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div class="picbox">
<div class="tb-booth tb-pic">
<img width="440" height="445" src="{{ MEDIA_URL }}{{ course.image }}" class="jqzoom" />
</div>

</div>
<div class="des">
<h1 title="{{ course.name }}">{{ course.name }}</h1>
<span class="key">{{ course.desc }}</span>
<div class="prize">
<span class="fl">难度:<i class="key">{{ course.get_degree_display }}</i></span>
<span class="fr">学习人数:{{ course.students }}</span>
</div>
<ul class="parameter">
<li><span class="pram word3">时&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;长:</span><span>{{ course.learn_times }}</span></li>
<li><span class="pram word3">章&nbsp;节&nbsp;数:</span><span>{{ course.get_zj_nums }}</span></li>
<li><span class="pram word3">课程类别:</span><span title="">{{ course.category }}</span></li>
<li class="piclist"><span class="pram word4">学习用户:</span>
{% for user_course in course.get_learn_users %}
<span class="pic"><img width="40" height="40" src="{{ MEDIA_URL }}{{ user_course.user.image }}"/></span>
{% endfor %}
</li>
</ul>
<div class="btns">

1
2
3
4
5
6
7
8
<div class="head">
<ul class="tab_header">
<li class="active">课程详情</li>
</ul>
</div>
<div class="tab_cont tab_cont1">
{{ course.detail }}
</div>

授课机构

CourseOrg model添加一个获取教师数的方法

1
2
3
def get_teacher_nums(self):
#获取机构的教师数
return self.teacher_set.all().count()

授课机构显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div class="head">
<h1>授课机构</h1>
<p>世界名校,课程权威</p>
</div>
<div class="pic">
<a href="/company/14/">
<img width="150" height="80" src="{{ MEDIA_URL }}{{ course.course_org.image }}"/>
</a>
</div>
<a href="/company/14/">
<h2 class="center" title="清华大学">{{ course.course_org.name }}</h2>
</a>
<div class="btn notlogin
"data-favid="14" id="jsRightBtn">
已收藏
</div>
<div class="clear">
<ul>
<li>
<span>课 &nbsp;程&nbsp; 数:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {{ course.course_org.course_nums }}</span>
</li>
<li>
<span>教 &nbsp;师&nbsp; 数:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {{ course.course_org.get_teacher_nums }}</span>
</li>
<li>所在地区:&nbsp;&nbsp;{{ course.course_org.address }}</li>
<li>认&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;证&nbsp;:
&nbsp;&nbsp;
<img title="金牌机构", src="{% static 'images/gold.png' %}"/>
</li>
</ul>
</div>

相关课程推荐

定义课程的tag ,如果tag相同,那么是相关课程.

courses/models.py:

1
tag = models.CharField(max_length=15, verbose_name="课程标签", default="")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class CourseDetailView(View):
'''课程详情'''
def get(self, request, course_id):
course = Course.objects.get(id=int(course_id))
# 课程的点击数加1
course.click_nums += 1
course.save()
# 课程标签
# 通过当前标签,查找数据库中的课程
tag = course.tag
if tag:
# 需要从1开始不然会推荐自己
relate_courses = Course.objects.filter(tag=tag)[:3]
else:
relate_courses = []
return render(request, "course-detail.html", {
'course':course,
'relate_courses':relate_courses,
})

前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class="right layout">
<div class="head">相关课程推荐</div>
<div class="group_recommend">
{% for relate_course in relate_courses %}
<dl>
<dt>
<a target="_blank" href="">
<img width="240" height="220" class="scrollLoading" src="{{ MEDIA_URL }}{{ relate_course.image }}"/>
</a>
</dt>
<dd>
<a target="_blank" href=""><h2> {{ relate_course.name }}</h2></a>
<span class="fl">学习时长:<i class="key">{{ relate_course.learn_times }}</i></span>
</dd>
</dl>
{% endfor %}
</div>
</div>

收藏功能

ajax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<script type="text/javascript">
//收藏分享
function add_fav(current_elem, fav_id, fav_type){
$.ajax({
cache: false,
type: "POST",
url:"{% url "org:add_fav" %}",
data:{'fav_id':fav_id, 'fav_type':fav_type},
async: true,
beforeSend:function(xhr, settings){
xhr.setRequestHeader("X-CSRFToken", "{{ csrf_token }}");
},
success: function(data) {
if(data.status == 'fail'){
if(data.msg == '用户未登录'){
window.location.href="/login/";
}else{
alert(data.msg)
}

}else if(data.status == 'success'){
current_elem.text(data.msg)
}
},
});
}

$('#jsLeftBtn').on('click', function(){
add_fav($(this), {{ course.id }}, 1);
});

$('#jsRightBtn').on('click', function(){
add_fav($(this), {{ course.course_org.id }}, 2);
});


</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class CourseDetailView(View):
'''课程详情'''
def get(self, request, course_id):
course = Course.objects.get(id=int(course_id))
# 课程的点击数加1
course.click_nums += 1
course.save()
# 课程标签
# 通过当前标签,查找数据库中的课程
has_fav_course = False
has_fav_org = False

# 必须是用户已登录我们才需要判断.
if request.user.is_authenticated:
if UserFavorite.objects.filter(user=request.user, fav_id=course.id, fav_type=1):
has_fav_course = True
if UserFavorite.objects.filter(user=request.user, fav_id=course.course_org.id, fav_type=2):
has_fav_org = True
tag = course.tag
if tag:
# 需要从1开始不然会推荐自己
relate_courses = Course.objects.filter(tag=tag)[:2]
else:
relate_courses = []
return render(request, "course-detail.html", {
'course':course,
'relate_courses':relate_courses,
"has_fav_course": has_fav_course,
"has_fav_org": has_fav_org,
})

修改前端代码

1
2
3
4
5
{% if has_fav_org %}
已收藏
{% else %}
收藏
{% endif %}

课程章节信息

章节信息,评论信息
模板替换

1
2
# 课程章节信息页
re_path('info/(?P<course_id>\d+)/', CourseInfoView.as_view(), name="course_info"),

1
2
3
4
5
6
7
class CourseInfoView(View):
'''课程章节信息'''
def get(self, request, course_id):
course = Course.objects.get(id=int(course_id))
return render(request, "course-video.html", {
"course": course,
})

修改开始学习链接

1
<div class="buy btn"><a style="color: white" href="{% url 'course:course_info' course.id %}">开始学习</a></div>

章节视频信息

为video表添加视频对应的url信息

1
url = models.CharField(max_length=200, default="" ,verbose_name="访问地址")

给Vedio添加一个学习时长的字段

1
2
# 使用分钟做后台记录(存储最小单位)前台转换
learn_times = models.IntegerField(default=0, verbose_name="学习时长(分钟数)")

给Course添加一个获取章节的方法

1
2
3
def get_course_lesson(self):
#获取课程的章节
return self.lesson_set.all()

给Lesson添加一个获取所有视频的方法

1
2
3
def get_lesson_vedio(self):
#获取章节所有视频
return self.video_set.all()

后台添加视频信息
前端页面展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class="mod-chapters">
{% for lesson in course.get_course_lesson %}
<div class="chapter chapter-active" >
<h3>
<strong><i class="state-expand"></i>{{ lesson.name }}</strong>
</h3>
<ul class="video">
{% for vedio in lesson.get_lesson_vedio %}
<li>
<a target="_blank" href='{{ vedio.url }}' class="J-media-item studyvideo">{{ vedio.name }} ({{ vedio.learn_times }})
<i class="study-state"></i>
</a>
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>

资料下载

后台自行上传点文件

1
2
3
4
5
class CourseInfoView(View):
def get(self,request,course_id):
course = Course.objects.get(id=int(course_id))
all_resources = CourseResource.objects.filter(course=course)
return render(request,'course-video.html',{'course':course,'all_resources':all_resources})

1
2
3
4
5
6
7
8
9
10
11
<div class="box mb40">
<h4>资料下载</h4>
<ul class="downlist">
{% for course_resource in all_resources %}
<li>
<span ><i class="aui-iconfont aui-icon-file"></i>&nbsp;&nbsp;{{ course_resource.name }}</span>
<a href="{{ MEDIA_URL }}{{ course_resource.download }}" class="downcode" target="_blank" download="" data-id="274" title="">下载</a>
</li>
{% endfor %}
</ul>
</div>

讲师提示

创建课程与讲师之间的关联,给Course添加一个Teacher外键

1
2
# Course
teacher = models.ForeignKey(Teacher,verbose_name='讲师',null=True,blank=True,on_delete=models.CASCADE)

给Course再添加两个字段 “课程须知”和“老师告诉你能学到什么”

1
2
you_need_know = models.CharField(max_length=300, default="一颗勤学的心是本课程必要前提", verbose_name="课程须知")
teacher_tell = models.CharField(max_length=300, default="按时交作业,不然叫家长", verbose_name="老师告诉你")

修改前端显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="box mb40">
<h4>讲师提示</h4>
<div class="teacher-info">
<a href="/u/315464/courses?sort=publish" target="_blank">
<img src='{{ MEDIA_URL }}{{ course.teacher.image }}' width='80' height='80' />
</a>
<span class="tit">
<a href="/u/315464/courses?sort=publish" target="_blank">{{ course.teacher.name }}</a>
</span>
<span class="job">{{ course.teacher.work_position }}</span>
</div>
<div class="course-info-tip">
<dl class="first">
<dt>课程须知</dt>
<dd class="autowrap">{{ course.you_need_know }}</dd>
</dl>
<dl>
<dt>老师告诉你能学到什么?</dt>
<dd class="autowrap">{{ course.teacher_tell }}</dd>
</dl>
</div>
</div>

课程评论页面

修改模板
配置url

1
2
#课程评论
re_path('comment/(?P<course_id>\d+)/', CommentsView.as_view(), name="course_comments"),

1
2
3
4
5
6
7
8
9
class CommentsView(View):
def get(self, request, course_id):
# 此处的id为表默认为我们添加的值.
course = Course.objects.get(id=int(course_id))
all_resources = CourseResource.objects.filter(course=course)
return render(request, "course-comment.html", {
"course": course,
"all_resources": all_resources,
})

在course-vedio.html中修改 “章节”和“评论”的链接

1
2
3
4
5
6
<div class="mod-tab-menu">
<ul class="course-menu clearfix">
<li><a class="ui-tabs-active active" id="learnOn" href="{% url 'course:course_info' course.id %}"><span>章节</span></a></li>
<li><a id="commentOn" class="" href="{% url 'course:course_comments' course.id %}"><span>评论</span></a></li>
</ul>
</div>

发表评论功能
Ajax操作.如果发布成功就会刷新页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# ajax方式添加评论
class AddCommentsView(View):
def post(self, request):
if not request.user.is_authenticated:
# 未登录时返回json提示未登录,跳转到登录页面是在ajax中做的
return HttpResponse('{"status":"fail", "msg":"用户未登录"}', content_type='application/json')
course_id = request.POST.get("course_id", 0)
comments = request.POST.get("comments", "")
if int(course_id) > 0 and comments:
course_comments = CourseComments()
# get只能取出一条数据,如果有多条抛出异常.没有数据也抛异常
# filter取一个列表出来,queryset.没有数据返回空的queryset不会抛异常
course = Course.objects.get(id = int(course_id))
# 外键存入要存入对象
course_comments.course = course
course_comments.comments = comments
course_comments.user = request.user
course_comments.save()
return HttpResponse('{"status":"success", "msg":"评论成功"}', content_type='application/json')
else:
return HttpResponse('{"status":"fail", "msg":"评论失败"}', content_type='application/json')

1
2
# 添加课程评论,已经把参数放到post当中了
path('add_comment/', AddCommentsView.as_view(), name="add_comment"),

Ajax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<script type="text/javascript">
//添加评论
$('#js-pl-submit').on('click', function(){
var comments = $("#js-pl-textarea").val()
if(comments == ""){
alert("评论不能为空")
return
}
$.ajax({
cache: false,
type: "POST",
url:"{% url 'course:add_comment' %}",
data:{'course_id':{{ course.id }}, 'comments':comments},
async: true,
beforeSend:function(xhr, settings){
xhr.setRequestHeader("X-CSRFToken", "{{ csrf_token }}");
},
success: function(data) {
if(data.status == 'fail'){
if(data.msg == '用户未登录'){
window.location.href="/login/";
}else{
alert(data.msg)
}

}else if(data.status == 'success'){
window.location.reload();//刷新当前页面.
}
},
});
});

</script>

相关课程推荐

CourseInfoView添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 选出学了这门课的学生关系
user_courses = UserCourse.objects.filter(course= course)
# 从关系中取出user_id
user_ids = [user_course.user_id for user_course in user_courses]
# 这些用户学了的课程,外键会自动有id,取到字段
all_user_courses = UserCourse.objects.filter(user_id__in=user_ids)
# 取出所有课程id
course_ids = [all_user_course.course_id for all_user_course in all_user_courses]
# 获取学过该课程用户学过的其他课程
relate_courses = Course.objects.filter(id__in=course_ids).order_by("-click_nums")[:5]

return render(request, "course-video.html", {
"course": course,
"all_resources": all_resources,
"relate_courses":relate_courses,
})

修改前端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="cp-other-learned  js-comp-tabs">
<div class="cp-header clearfix">
<h2 class="cp-tit l">该课的同学还学过</h2>
</div>
<div class="cp-body">
<div class="cp-tab-pannel js-comp-tab-pannel" data-pannel="course" style="display: block">
<!-- img 200 x 112 -->
<ul class="other-list">
{% for relate_course in relate_courses %}
<li class="curr">
<a href="{% url 'course:course_detail' relate_course.id %}" target="_blank">
<img src="{{ MEDIA_URL }}{{ relate_course.image }}" alt="{{ relate_course.name }}">
<span class="name autowrap">{{ relate_course.name }}</span>
</a>
</li>
{% endfor %}

</ul>
</div>

</div>
</div>

把课程与用户关联起来

当用户点了“开始学习”之后,应该把这门课程与用户关联起来,在这之前应该需要做个判断,如果没有登录,则让用户先登录才可以.
如果是用函数方式写的话直接加个装饰器(@login_required)就可以,但是我们是用类的方式写的,必须用继承的方式

在utils目录下创建文件 mixin_utils.py(最基本的类都放在mixin_utils.py里面),代码如下:

1
2
3
4
5
6
7
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator

class LoginRequiredMixin(object):
@method_decorator(login_required(login_url='/login/'))
def dispatch(self,request,*args,**kwargs):
return super(LoginRequiredMixin, self).dispatch(request,*args,**kwargs)

然后让CourseInfoViewCommentsView都继承LoginRequiredMixin
继承后,没有登录的用户点“开始学习”,自动跳到login界面
把用户与课程关联起来

1
2
3
4
5
6
7
# CourseInfoView
# 查询用户是否已经学习了该课程
user_courses = UserCourse.objects.filter(user=request.user,course=course)
if not user_courses:
# 如果没有学习该门课程就关联起来
user_course = UserCourse(user=request.user,course=course)
user_course.save()

视频播放页面

把course-paly.html拷贝到templates目录下
使用开源库video js,这里使用CDN

1
2
<link href="https://vjs.zencdn.net/7.1.0/video-js.css" rel="stylesheet">
<script src="https://vjs.zencdn.net/7.1.0/video.js"></script>

1
2
# 课程视频播放页
path('video/(?P<video_id>\d+)/', VideoPlayView.as_view(), name="video_play"),
1
2
3
4
5
<li>
<a target="_blank" href='{% url 'course:video_play' vedio.id %}' class="J-media-item studyvideo">{{ vedio.name }} ({{ vedio.learn_times }})
<i class="study-state"></i>
</a>
</li>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class VideoPlayView(LoginRequiredMixin, View):
'''课程章节视频播放页面'''
def get(self,request,video_id):
video = Video.objects.get(id=int(video_id))
#通过外键找到章节再找到视频对应的课程
course = video.lesson.course

course.students += 1
course.save()

# 查询用户是否已经学习了该课程
user_courses = UserCourse.objects.filter(user=request.user,course=course)
if not user_courses:
# 如果没有学习该门课程就关联起来
user_course = UserCourse(user=request.user,course=course)
user_course.save()

#相关课程推荐
# 找到学习这门课的所有用户
user_courses = UserCourse.objects.filter(course=course)
# 找到学习这门课的所有用户的id
user_ids = [user_course.user_id for user_course in user_courses]
# 通过所有用户的id,找到所有用户学习过的所有过程
all_user_courses = UserCourse.objects.filter(user_id__in=user_ids)
# 取出所有课程id
course_ids = [all_user_course.course_id for all_user_course in all_user_courses]
# 通过所有课程的id,找到所有的课程,按点击量去五个
relate_courses = Course.objects.filter(id__in=course_ids).order_by("-click_nums")[:5]

# 资源
all_resources = CourseResource.objects.filter(course=course)
return render(request,'course-play.html',{
'course':course,
'all_resources':all_resources,
'relate_courses':relate_courses,
'video':video,
})

讲师相关功能实现

拷贝并修改teacher-list.htmlteacher-detail.html,
继承base模板

1
2
# 讲师列表
path('teacher_list/', TeacherListView.as_view(), name="teacher_list"),

添加讲师的年龄字段

1
age = models.IntegerField(default=18, verbose_name=u"年龄")

分页仿照org_list, 注意object_list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 课程讲师列表页
class TeacherListView(View):
def get(self, request):
all_teacher = Teacher.objects.all()
# 总共有多少老师使用count进行统计
teacher_nums = all_teacher.count()
# 对讲师进行分页
# 尝试获取前台get请求传递过来的page参数
# 如果是不合法的配置参数默认返回第一页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# 这里指从allorg中取五个出来,每页显示5个
p = Paginator(all_teacher, 4, request=request)
teachers = p.page(page)
return render(request, "teachers-list.html", {
"all_teacher":teachers,
"teacher_nums":teacher_nums
})

排序 & 讲师排行榜

1
2
3
4
sort = request.GET.get("sort", "")
if sort:
if sort == "hot":
all_teacher = all_teacher.order_by("-click_nums")

将sort return到前端.实现active
排行榜讲师

1
2
# 排行榜讲师
rank_teacher = Teacher.objects.all().order_by("-fav_nums")[:5]

完善前端代码

讲师详情页

1
2
# 访问机构讲师
re_path('teacher/detail/(?P<teacher_id>\d+)/', TeacherDetailView.as_view(), name="teacher_detail"),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 教师详情页面

class TeacherDetailView(View):
def get(self, request, teacher_id):
teacher = Teacher.objects.get(id = int(teacher_id))
all_course = teacher.course_set.all()
# 排行榜讲师
rank_teacher = Teacher.objects.all().order_by("-fav_nums")[:5]

has_fav_teacher = False
if UserFavorite.objects.filter(user=request.user, fav_type=3, fav_id= teacher.id):
has_fav_teacher = True
has_fav_org = False
if UserFavorite.objects.filter(user=request.user, fav_type=2, fav_id= teacher.org.id):
has_fav_org = True
return render(request, "teacher-detail.html", {
"teacher":teacher,
"all_course":all_course,
"rank_teacher":rank_teacher,
"has_fav_teacher":has_fav_teacher,
"has_fav_org":has_fav_org,
})

收藏功能

1
2
3
4
5
6
7
8
# 教师收藏和机构收藏
has_teacher_faved = False
if UserFavorite.objects.filter(user=request.user, fav_type=3, fav_id=teacher.id):
has_teacher_faved = True

has_org_faved = False
if UserFavorite.objects.filter(user=request.user, fav_type=2, fav_id=teacher.org.id):
has_org_faved = True
1
2
3
{% if has_teacher_faved %}已收藏{% else %}收藏{% endif %}

<a class="btn" id="jsRightBtn">{% if has_org_faved %}已收藏{% else %}收藏{% endif %}</a>

Ajax代码参考之前的

全局导航&个人中心&全局搜索

配置全局导航

让index页面也继承base页面,注意首页有个单独的index.js
base页面的导航栏也进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<nav>
<div class="nav">
<div class="wp">
<ul>
<li ><a href="{% url 'index' %}">首页</a></li>
<li >
<a href="{% url 'course:list' %}">
公开课<img class="hot" src="{% static "images/nav_hot.png" %}">
</a>
</li>
<li >
<a href="{% url 'org:teacher_list' %}">授课教师</a>
</li>
<li class="active" ><a href="{% url 'org:org_list' %}">授课机构</a></li>
</ul>
</div>
</div>
</nav>

但是现在我们不知道当前是哪一个页面,因为后端没有传值过来
这里可以使用request.path进行判断
比如http://127.0.0.1:8000/org/teacher_list/,则request.path 就是/org/teacher_list/`
slice:12 是过滤器,取前12位数
利用这种方法可以达到全局的“active”效果,而不用每个子页面都要去设置“active”了

1
2
3
4
5
6
7
8
9
10
11
12
13
<ul>
<li {% if request.path == '/' %}class="active"{% endif %}><a href="{% url 'index' %}">首页</a></li>
<li {% if request.path|slice:'7' == '/course' %}class="active"{% endif %}>
<a href="{% url 'course:list' %}">
公开课<img class="hot" src="{% static 'images/nav_hot.png' %}">
</a>
</li>
<li {% if request.path|slice:'12' == '/org/teacher' %}class="active"{% endif %}>
<a href="{% url 'org:teacher_list' %}">授课教师</a>
</li >
<li {% if request.path|slice:'9' == '/org/list' %}class="active"{% endif %}>
<a href="{% url 'org:org_list' %}">授课机构</a></li>
</ul>

全局搜索功能

通过url中加参数keywords来达到全局搜索的功能

以Course搜索为例:

1
2
3
4
5
6
7
8
# 搜索功能
search_keywords = request.GET.get('keywords', '')
if search_keywords:
# icontains是包含的意思(不区分大小写)
# Q可以实现多个字段,之间是or的关系
all_course = all_course.filter(
Q(name__icontains=search_keywords) | Q(desc__icontains=search_keywords) | Q(
detail__icontains=search_keywords))

搜索的代码放在deco-common.js
如果自己写的url和js文件中的不同,可自行修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//顶部搜索栏搜索方法
function search_click(){
var type = $('#jsSelectOption').attr('data-value'),
keywords = $('#search_keywords').val(),
request_url = '';
if(keywords == ""){
return
}
if(type == "course"){
request_url = "/course/list?keywords="+keywords
}else if(type == "teacher"){
request_url = "/org/teacher/list?keywords="+keywords
}else if(type == "org"){
request_url = "/org/list?keywords="+keywords
}
window.location.href = request_url
}

课程机构搜索功能

1
2
3
4
5
6
# 机构搜索功能
search_keywords = request.GET.get('keywords', '')
if search_keywords:
# 在name字段进行操作,做like语句的操作.i代表不区分大小写
# or操作使用Q
all_orgs = all_orgs.filter(Q(name__icontains=search_keywords) | Q(desc__icontains=search_keywords))

授课老师搜索功能

1
2
3
4
5
6
# 搜索功能
search_keywords = request.GET.get('keywords', '')
if search_keywords:
# 在name字段进行操作,做like语句的操作.i代表不区分大小写
# or操作使用Q
all_teachers = all_teacher.filter(name__icontains=search_keywords)

个人中心信息展示

新建usercenter-bae.html当模板
进行配置

1
path("users/", include('users.urls', namespace="users")),

1
2
3
4
5
6
7
8
9
10
from django.urls import path

from users.views import UserinfoView

app_name = 'users'

urlpatterns = [
#用户信息
path("info/", UserinfoView.as_view(),name='user_info'),
]
1
2
3
4
class UserinfoView(LoginRequiredMixin,View):
'''用户个人信息'''
def get(self,request):
return render(request,'usercenter-info.html',{})

前端显示个人信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<div class="right">
<div class="personal_des ">
<div class="head" style="border:1px solid #eaeaea;">
<h1>个人信息</h1>
</div>
<div class="inforcon">
<div class="left" style="width:242px;">
<iframe id='frameFile' name='frameFile' style='display: none;'></iframe>
<form class="clearfix" id="jsAvatarForm" enctype="multipart/form-data" autocomplete="off" method="post" action="/users/image/upload/" target='frameFile'>
<label class="changearea" for="avatarUp">
<span id="avatardiv" class="pic">
<img width="100" height="100" class="js-img-show" id="avatarShow" src="{{ MEDIA_URL }}{{ request.user.image }}"/>
</span>
<span class="fl upload-inp-box" style="margin-left:70px;">
<span class="button btn-green btn-w100" id="jsAvatarBtn">修改头像</span>
<input type="file" name="image" id="avatarUp" class="js-img-up"/>
</span>
</label>
{% csrf_token %}
</form>
<div style="border-top:1px solid #eaeaea;margin-top:30px;">
<a class="button btn-green btn-w100" id="jsUserResetPwd" style="margin:80px auto;width:100px;">修改密码</a>
</div>
</div>
<form class="perinform" id="jsEditUserForm" autocomplete="off">
<ul class="right">
<li>昵&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;称:
<input type="text" name="nick_name" id="nick_name" value="{{ request.user.name }}" maxlength="10">
<i class="error-tips"></i>
</li>
<li>生&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;日:
<input type="text" id="birth_day" name="birday" value="{{ request.user.birthday }}" readonly="readonly"/>
<i class="error-tips"></i>
</li>
<li>性&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;别:
<label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="radio" name="gender" value="male"
{% ifequal request.user.gender "male" %}
checked="checked"
{% endifequal %}
></label>
<label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="radio" name="gender" value="female"
{% ifequal request.user.gender "female" %}
checked="checked"
{% endifequal %}
></label>
</li>
<li class="p_infor_city">地&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;址:
<input type="text" name="address" id="address" placeholder="请输入你的地址" value="{{ request.user.address }}" maxlength="10">
<i class="error-tips"></i>
</li>
<li>手&nbsp;&nbsp;机&nbsp;&nbsp;号:
<input type="text" name="mobile" id="mobile" placeholder="请输入你的手机号码" value="{{ request.user.mobile|default_if_none:'' }}" maxlength="11">
</li>
<li>邮&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;箱:
<input class="borderno" type="text" name="email" readonly="readonly" value="{{ request.user.email }}"/>
<span class="green changeemai_btn">[修改]</span>
</li>
<li class="button heibtn">
<input type="button" id="jsEditUserBtn" value="保存">
</li>
</ul>
{% csrf_token %}
</form>
</div>
</div>
</div>

修改密码和修改头像

1
2
#用户图像上传
path("image/upload/", UploadImageView.as_view(),name='image_upload'),

新建一个用于保存图片的form
这里继承的是ModelForm,该类具有save功能

1
2
3
4
5
6
# 用于文件上传,修改头像
class UploadImageForm(forms.ModelForm):

class Meta:
model = UserProfile
fields = ['image']

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class UploadImageView(LoginRequiredMixin,View):
'''用户图像修改'''
def post(self,request):
# 这时候用户上传的文件就已经被保存到image_form了 ,为modelform添加instance值直接保存
image_form = UploadImageForm(request.POST, request.FILES, instance=request.user)
if image_form.is_valid():
image_form.save()
# 所有验证通过的字段放在cleaned data
# # 取出cleaned data中的值,一个dict
# image = image_form.cleaned_data['image']
# request.user.image = image
# request.user.save()
return HttpResponse('{"status":"success"}', content_type='application/json')
else:
return HttpResponse('{"status":"fail"}', content_type='application/json')
1
2
#用户个人中心修改密码
path("update/pwd/", UpdatePwdView.as_view(),name='update_pwd'),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 在个人中心修改用户密码
class UpdatePwdView(View):
def post(self, request):
modiypwd_form = ModifyPwdForm(request.POST)
if modiypwd_form.is_valid():
pwd1 = request.POST.get("password1", "")
pwd2 = request.POST.get("password2", "")
# 如果两次密码不相等,返回错误信息
if pwd1 != pwd2:
return HttpResponse('{"status":"fail", "msg":"密码不一致"}', content_type='application/json')
# 如果密码一致
user =request.user
# 加密成密文
user.password = make_password(pwd2)
# save保存到数据库
user.save()
return HttpResponse('{"status":"success"}', content_type='application/json')
# 验证失败说明密码位数不够.
else:
return HttpResponse('{"status":"fail", "msg":"填写错误请检查"}', content_type='application/json')

Ajxa代码放在deco-user.js里面
如果url不正确可自行配置’
如果遇到403,检查base中的csrf_token是否填写

发送修改邮箱验证码

有两个接口需要完成.点击获取验证码时,后台需要向用户新邮箱发送验证码.
邮箱如果出错,会返回错误信息.
输入了邮箱和验证码,验证是否匹配.
EmailVerifyRecord再添加一个选择类型

1
2
3
4
5
SEND_CHOICES = (
("register", "注册"),
("forget", "找回密码"),
("update_email", "修改邮箱")
)

1
2
#发送邮箱验证码
path("sendemail_code/", SendEmailCodeView.as_view(),name='sendemail_code'),
1
2
3
4
5
6
7
8
9
10
11
# 发送邮箱验证码view
class SendEmailCodeView(LoginRequiredMixin, View):
def get(self,request):
# 取出需要发送的邮件
email = request.GET.get("email", "")

# 不能是已注册的邮箱
if UserProfile.objects.filter(email=email):
return HttpResponse('{"email":"邮箱已经存在"}', content_type='application/json')
send_register_eamil(email, "update_email")
return HttpResponse('{"status":"success"}', content_type='application/json')

修改utils/email_send.py

1
2
3
4
5
6
7
8
9
if send_type == "update_email":
email_title = "慕课在线 修改邮箱"
email_body = "验证码:{0}".format(code)

# 使用Django内置函数完成邮件发送.四个参数:主题,邮件内容,从哪里发,接受者list
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
# 如果发送成功
if send_status:
pass

修改邮箱

1
2
#修改邮箱
path("update_email/", UpdateEmailView.as_view(),name='update_email'),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 修改邮箱的view:
class UpdateEmailView(LoginRequiredMixin, View):
def post(self, request):
email = request.POST.get("email", "")
code = request.POST.get("code", "")

existed_records = EmailVerifyRecord.objects.filter(email=email, code=code, send_type='update_email')
if existed_records:
user = request.user
user.email = email
user.save()
return HttpResponse('{"status":"success"}', content_type='application/json')
else:
return HttpResponse('{"email":"验证码无效"}', content_type='application/json')

个人信息修改

1
2
3
4
5
# 个人中心信息修改
class UserInfoForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['nick_name','gender','birthday','address','mobile']

UserinfoView添加post方法

1
2
3
4
5
6
7
8
9
10
11
12
13
class UserinfoView(LoginRequiredMixin,View):
'''用户个人信息'''
def get(self,request):
return render(request,'usercenter-info.html',{})

def post(self, request):
# 需要指明instance.不然无法修改,而是新增用户
user_info_form = UserInfoForm(request.POST, instance=request.user)
if user_info_form.is_valid():
user_info_form.save()
return HttpResponse('{"status":"success"}', content_type='application/json')
else:
return HttpResponse(json.dumps(user_info_form.errors), content_type='application/json')

我的课程

1
2
# 用户中心我的课程
path('mycourse/', MyCourseView.as_view(), name="mycourse"),
1
2
3
4
5
6
7
# 个人中心页我的课程
class MyCourseView(LoginRequiredMixin, View):
def get(self, request):
user_courses = UserCourse.objects.filter(user=request.user)
return render(request, "usercenter-mycourse.html", {
"user_courses":user_courses,
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{% block custom_right_content %}
<div class="right" >
<div class="personal_des Releasecont">
<div class="head">
<h1>我的课程</h1>
</div>
</div>
<div class="personal_des permessage">
<div class="companycenter">
<div class="group_list brief">
{% for cours in user_courses %}
<div class="module1_5 box">
<a href="course-detail.html">
<img width="214" height="190" class="scrollLoading" src="{{ MEDIA_URL }}{{ cours.course.image }}"/>
</a>
<div class="des">
<a href="course-detail.html"><h2>{{ cours.course.name }}</h2></a>
<span class="fl">课时:<i class="key">{{ cours.course.learn_times }}</i></span>
<span class="fr">学习人数:{{ cours.course.students }}</span>
</div>
<div class="bottom">
<span class="fl">{{ cours.course.course_org.name }}</span>
<span class="star fr notlogin" data-favid="15">{{ cours.course.course_org.fav_nums }}</span>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock %}

我的收藏–课程机构

1
2
# 我的收藏--课程机构
path('myfav/org/', MyFavOrgView.as_view(), name="myfav_org"),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 我收藏的课程机构
class MyFavOrgView(LoginRequiredMixin, View):
def get(self, request):
org_list = []
fav_orgs= UserFavorite.objects.filter(user=request.user, fav_type=2)
# 上面的fav_orgs只是存放了id.我们还需要通过id找到机构对象
for fav_org in fav_orgs:
# 取出fav_id也就是机构的id.
org_id = fav_org.fav_id
# 获取这个机构对象
org = CourseOrg.objects.get(id=org_id)
org_list.append(org)
return render(request, "usercenter-fav-org.html", {
"org_list": org_list,
})

我的收藏–授课讲师

Teacher添加一个方法

1
2
def get_course_nums(self):
return self.course_set.all().count()

1
2
# 我收藏的授课讲师
path('myfav/teacher/', MyFavTeacherView.as_view(), name="myfav_teacher"),
1
2
3
4
5
6
7
8
9
10
11
12
13
class MyFavTeacherView(LoginRequiredMixin, View):
'''我收藏的授课讲师'''

def get(self, request):
teacher_list = []
fav_teachers = UserFavorite.objects.filter(user=request.user, fav_type=3)
for fav_teacher in fav_teachers:
teacher_id = fav_teacher.fav_id
teacher = Teacher.objects.get(id=teacher_id)
teacher_list.append(teacher)
return render(request, "usercenter-fav-teacher.html", {
"teacher_list": teacher_list,
})

我的收藏–公开课程

1
2
#我的收藏--课程
path('myfav/course/', MyFavCourseView.as_view(), name="myfav_course"),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyFavCourseView(LoginRequiredMixin,View):
"""
我收藏的课程
"""
def get(self, request):
course_list = []
fav_courses = UserFavorite.objects.filter(user=request.user, fav_type=1)
for fav_course in fav_courses:
course_id = fav_course.fav_id
course = Course.objects.get(id=course_id)
course_list.append(course)

return render(request, 'usercenter-fav-course.html', {
"course_list":course_list,
})

我的消息页面

1
2
#我的消息
path('my_message/', MyMessageView.as_view(), name="my_message"),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyMessageView(LoginRequiredMixin, View):
'''我的消息'''

def get(self, request):
all_message = UserMessage.objects.filter(user= request.user.id)

try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
p = Paginator(all_message, 4,request=request)
messages = p.page(page)
return render(request, "usercenter-message.html", {
"messages":messages,
})

取消收藏

修改usercenter-bae.html模板中的Ajax代码

修改HTML文件

注册时发生欢迎消息

1
2
3
4
5
# 写入欢迎注册消息
user_message = UserMessage()
user_message.user = user_profile.id
user_message.message = "欢迎注册!!"
user_message.save()

页面顶部小喇叭

所有页面都要读取一个共同的变量:未读消息的数量.我们需要向request中注入这个变量
所有页面都有request.user对象.所以我们在userprofile中自定义方法,

1
2
3
4
# 获取用户未读消息的数量
def unread_nums(self):
from operation.models import UserMessage
return UserMessage.objects.filter(user=self.id).count()

退出

1
2
# 退出功能url
path('logout/', LogoutView.as_view(), name="logout"),
1
2
3
4
5
6
class LogoutView(View):
def get(self, request):
# django自带的logout
logout(request)
# 重定向到首页,
return HttpResponseRedirect(reverse("index"))

点击数加1

课程 CourseInfoView

1
2
course.students += 1
course.save()

TeacherDetailView

1
2
teacher.click_nums += 1
teacher.save()

OrgHomeView

1
2
course_org.click_nums += 1
course_org.save()

收藏数

organization/views.py中的 AddFavView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
if exist_records:
# 如果记录已经存在, 则表示用户取消收藏
exist_records.delete()
if int(type) == 1:
course = Course.objects.get(id=int(id))
course.fav_nums -= 1
if course.fav_nums < 0:
course.fav_nums = 0
course.save()
elif int(type) == 2:
org = CourseOrg.objects.get(id=int(id))
org.fav_nums -= 1
if org.fav_nums < 0:
org.fav_nums = 0
org.save()
elif int(type) == 3:
teacher = Teacher.objects.get(id=int(id))
teacher.fav_nums -= 1
if teacher.fav_nums < 0:
teacher.fav_nums = 0
teacher.save()

return HttpResponse('{"status":"success", "msg":"收藏"}', content_type='application/json')
else:
user_fav = UserFavorite()
# 过滤掉未取到fav_id type的默认情况
if int(type) > 0 and int(id) > 0:
user_fav.fav_id = int(id)
user_fav.fav_type = int(type)
user_fav.user = request.user
user_fav.save()
if int(type) == 1:
course = Course.objects.get(id=int(id))
course.fav_nums += 1
course.save()
elif int(type) == 2:
org = CourseOrg.objects.get(id=int(id))
org.fav_nums += 1
org.save()
elif int(type) == 3:
teacher = Teacher.objects.get(id=int(id))
teacher.fav_nums += 1
teacher.save()

return HttpResponse('{"status":"success", "msg":"已收藏"}', content_type='application/json')

修改消息已读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MyMessageView(LoginRequiredMixin, View):
'''我的消息'''

def get(self, request):
all_message = UserMessage.objects.filter(user= request.user.id)
# 用户进入个人中心消息页面,清空未读消息记录
all_unread_messages = all_message.filter(has_read=False)
for unread_message in all_unread_messages:
unread_message.has_read = True
unread_message.save()

try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
p = Paginator(all_message, 4, request=request)
messages = p.page(page)

return render(request, "usercenter-message.html", {
"messages":messages,
})

个人中心左侧active状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div class="left">
<ul>
<li {% ifequal '/users/info/' request.path %}
class="active2"
{% endifequal %}><a href="{% url 'users:user_info' %}">个人资料</a></li>
<li {% ifequal '/users/mycourse/' request.path %}
class="active2"
{% endifequal %}><a href="{% url 'users:mycourse' %}">我的课程</a></li>
<li {% ifequal '/users/myfav/' request.path|slice:'13' %}
class="active2"
{% endifequal %}><a href="{% url 'users:myfav_org' %}">我的收藏</a></li>
<li {% ifequal '/users/my_message/' request.path %}
class="active2"
{% endifequal %}>
<a href="{% url 'users:my_message' %}" style="position: relative;">
我的消息
</a>
</li>
</ul>
</div>

首页和全局404,500配置

  • 轮播图
  • 公开课
  • 授课机构

新建view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## 首页view
class IndexView(View):
def get(self,request):
# 取出轮播图
all_banner = Banner.objects.all().order_by('index')[:5]
# 正常位课程
courses = Course.objects.filter(is_banner=False)[:6]
# 轮播图课程取三个
banner_courses = Course.objects.filter(is_banner=True)[:3]
# 课程机构
course_orgs = CourseOrg.objects.all()[:15]
return render(request, 'index.html', {
"all_banner":all_banner,
"courses":courses,
"banner_courses":banner_courses,
"course_orgs":course_orgs,
})

为Course添加字段: isbanner

1
is_banner = models.BooleanField(default=False, verbose_name="是否轮播")

CourseOrg添加一个字段

1
tag = models.CharField(max_length=10, default= "国内名校",verbose_name="机构标签")

前端页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
{% block content %}
<div class="banner">
<div class="wp">
<div class="fl">
<div class="imgslide">
<ul class="imgs">
{% for banner in all_banner %}
<li>
<a href="{{ banner.url }}">
<img width="1200" height="478" src="{{ MEDIA_URL }}{{ banner.image }}">
</a>
</li>
{% endfor %}
</ul>
</div>
<div class="unslider-arrow prev"></div>
<div class="unslider-arrow next"></div>
</div>
</div>
</div>

<section>
<div class="wp">
<ul class="feature">
<li class="feature1">
<img class="pic" src="{% static "images/feature1.png" %}">
<p class="center">专业权威</p>
</li>
<li class="feature2">
<img class="pic" src="{% static "images/feature2.png" %}">
<p class="center">课程最新</p>
</li>
<li class="feature3">
<img class="pic" src="{% static "images/feature3.png" %}">
<p class="center">名师授课</p>
</li>
<li class="feature4">
<img class="pic" src="{% static "images/feature4.png" %}">
<p class="center">数据真实</p>
</li>
</ul>
</div>
</section>

<section>
<div class="module">
<div class="wp">
<h1>公开课程</h1>
<div class="module1 eachmod">
<div class="module1_1 left">
<img width="228" height="614" src="{% static "images/module1_1.jpg" %}">
<p class="fisrt_word">名师授课<br/>专业权威</p>
<a class="more" href="{% url 'course:list' %}">查看更多课程 ></a>
</div>
<div class="right group_list">
<div class="module1_2 box">
<div class="imgslide2">
<ul class="imgs">
{% for cours in banner_courses %}
<li>
<a href="{% url 'course:course_detail' cours.id %}">
<img width="470" height="300" src="{{ MEDIA_URL }}{{ cours.image }}">
</a>
</li>
{% endfor %}
</ul>
</div>
<div class="unslider-arrow2 prev"></div>
<div class="unslider-arrow2 next"></div>
</div>
{% for cours in courses %}
<div class="module1_{{ forloop.counter|add:2 }} box">
<a href="{% url 'course:course_detail' cours.id %}">
<img width="233" height="190" src="{{ MEDIA_URL }}{{ cours.image }}">
</a>
<div class="des">
<a href="{% url 'course:course_detail' cours.id %}">
<h2 title="{{ cours.name }}">{{ cours.name }}</h2>
</a>
<span class="fl">难度:<i class="key">{{ cours.get_degree_display }}</i></span>
<span class="fr">学习人数:{{ cours.students }}</span>
</div>
<div class="bottom">
<span class="fl"
title="{{ cours.course_org.name }}">{{ cours.course_org.name }}</span>
<span class="star fr">{{ cours.fav_nums }}</span>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</section>
<section>
<div class="module greybg">
<div class="wp">
<h1>课程机构</h1>
<div class="module3 eachmod">
<div class="module3_1 left">
<img width="228" height="463" src="{% static "images/module3_1.jpg" %}">
<p class="fisrt_word">名校来袭<br/>权威认证</p>
<a class="more" href="{% url 'org:org_list' %}">查看更多机构 ></a>
</div>
<div class="right">
<ul>
{% for org in course_orgs %}
<li class="{% if forloop.counter|divisibleby:5 %}five{% endif %}">
<a href="{% url 'org:org_home' org.id %}">
<div class="company">
<img width="184" height="100" src="{{ MEDIA_URL }}{{ org.image }}"/>
<div class="score">
<div class="circle">
<h2>{{ org.tag }}</h2>
</div>
</div>
</div>
<p><span class="key" title="{{ org.name }}">{{ org.name }}</span></p>
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
</section>
{% endblock %}

{% block custom_js %}
<script type="text/javascript" src="{% static "js/index.js" %}"></script>
{% endblock %}

说明1:课程
课程分is_banner=Falseis_banner=True,两种课程的class属性不一样
is_banner=True的class是class="module1_2 box"
is_banner=False的class是class="module1_3 box",
所以这里要

1
class="module1_{{ forloop.counter|add:2 }}

说明2:课程机构
课程机构的class分为class=""class="five"
这里要做个判断,

1
class="{% if forloop.counter|divisibleby:5 %}five{% endif %}

divisibleby过滤器:能不能整除

配置全局404和500

关于全局404和500,
如果不进行配置,Django会自定调用templates下的404.html和500.html
https://docs.djangoproject.com/en/2.1/ref/urls/#handler404
根目录下的urls.py文件MxOnline/urls.py

1
2
3
4
# 全局404页面配置
handler404 = 'users.views.pag_not_found'
# 全局500页面配置
handler500 = 'users.views.page_error'

1
2
3
4
5
6
7
8
9
# 404对应处理view
def page_not_found(request):
from django.shortcuts import render_to_response
response = render_to_response("404.html", {

})
# 设置response的状态码
response.status_code = 404
return response

Debug = True 时 404是不起作用的

1
ALLOWED_HOSTS = ['*']

在debug为false情况下.

我们在访问media的时候配置过用serve来取
告诉它访问media的时候去哪个路径下找

debug为True

会自动前往STATICFILES_DIRS取文件的

一旦debug改为false,django就不会代管你的静态文件,

一般静态文件通过第三方http服务器代理转发.

nignx 和 Apache都会自动代理这些静态文件

方法:我们自己url响应我们的static

1
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

1
re_path(r'^static/(?P<path>.*)', serve, {"document_root": STATIC_ROOT }),
1
2
3
4
5
6
# 全局500处理函数
def page_error(request):
from django.shortcuts import render_to_response
response = render_to_response('500.html', {})
response.status_code = 500
return response

xadmin的进阶开发

因版本问题.有些配置可能无效

自定义icon

xadmin的图标采用的是第三方css样式font awesome,我们可以进官网下载最新的样式替代原本的,下载地址:http://www.fontawesome.com.cn/

下载完后把里面的“css”和“fonts”两个文件夹拷贝到xadmin的源码(路径:xadmin/static/vendor/font-awesome)里面
使用model_icon来进行修改

1
2
3
4
5
6
7
8
# Course的admin管理器
class CourseAdmin(object):
'''课程'''

list_display = [ 'name','desc','detail','degree','learn_times','students']
search_fields = ['name', 'desc', 'detail', 'degree', 'students']
list_filter = [ 'name','desc','detail','degree','learn_times','students']
model_icon = 'fa fa-book'

默认排序、只读字段和不显示的字段

课程:

  • 按点击数倒序排序
  • 点击数不能编辑
  • 不显示收藏人数
1
2
3
4
5
6
7
8
9
10
11
# Course的admin管理器
class CourseAdmin(object):
'''课程'''

list_display = [ 'name','desc','detail','degree','learn_times','students'] #显示的字段
search_fields = ['name', 'desc', 'detail', 'degree', 'students'] #搜索
list_filter = [ 'name','desc','detail','degree','learn_times','students'] #过滤
model_icon = 'fa fa-book' #图标
ordering = ['-click_nums'] #排序
readonly_fields = ['click_nums'] #只读字段,不能编辑
exclude = ['fav_nums'] #不显示的字段

下拉框搜索

写在外键所指的adminx配置中

1
relfield_style = 'fk-ajax'

当有外键指向他,会以ajax方式加载
数据量过大时很有用

inlines添加数据

目前在添加课程的时候没法添加章节和课程资源,我们可以用inlines去实现这一功能

1
2
3
4
5
6
7
8
9
10
11
12
13
class LessonInline(object):
model = Lesson
extra = 0


class CourseResourceInline(object):
model = CourseResource
extra = 0


# 在CourseAdmin中使用inlines添加上面两个的方法
class CourseAdmin(object):
inlines = [LessonInline,CourseResourceInline] #增加章节和课程资源

一张表分两个Model来管理

课程里面分为轮播课程和不是轮播课程两种类型,我们可以分开来管理
course/models.py里面新建一个Model

1
2
3
4
5
6
7
class BannerCourse(Course):
'''显示轮播课程'''
class Meta:
verbose_name = '轮播课程'
verbose_name_plural = verbose_name
#这里必须设置proxy=True,这样就不会再生成一张表,同时还具有Model的功能
proxy = True

course/adminx.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class CourseAdmin(object):
'''课程'''

list_display = [ 'name','desc','detail','degree','learn_times','students'] #显示的字段
search_fields = ['name', 'desc', 'detail', 'degree', 'students'] #搜索
list_filter = [ 'name','desc','detail','degree','learn_times','students'] #过滤
model_icon = 'fa fa-book' #图标
ordering = ['-click_nums'] #排序
readonly_fields = ['click_nums'] #只读字段
exclude = ['fav_nums'] #不显示的字段
inlines = [LessonInline,CourseResourceInline] #增加章节和课程资源

def queryset(self):
# 重载queryset方法,来过滤出我们想要的数据的
qs = super(CourseAdmin, self).queryset()
# 只显示is_banner=True的课程
qs = qs.filter(is_banner=False)
return qs


class BannerCourseAdmin(object):
'''轮播课程'''

list_display = [ 'name','desc','detail','degree','learn_times','students']
search_fields = ['name', 'desc', 'detail', 'degree', 'students']
list_filter = [ 'name','desc','detail','degree','learn_times','students']
model_icon = 'fa fa-book'
ordering = ['-click_nums']
readonly_fields = ['click_nums']
exclude = ['fav_nums']
inlines = [LessonInline,CourseResourceInline]

def queryset(self):
#重载queryset方法,来过滤出我们想要的数据的
qs = super(BannerCourseAdmin, self).queryset()
#只显示is_banner=True的课程
qs = qs.filter(is_banner=True)
return qs

# 将管理器与model进行注册关联
xadmin.site.register(Course, CourseAdmin)
xadmin.site.register(BannerCourse, BannerCourseAdmin)

xadmin的其它常见功能

可以在列表上快速修改内容

1
list_editable = [ 'degree','desc']

自定义函数作为列

1
2
3
def get_zj_nums(self):
return self.lesson_set.all().count()
get_zj_nums.short_description = "章节数"

显示自定义的html代码

1
2
3
4
5
def go_to(self):
from django.utils.safestring import mark_safe
# 如果不mark safe.会对其进行转义
return mark_safe("<a href='http://iceflower.xyz'>跳转</>")
go_to.short_description = "跳转"

refresh定时刷新工具

1
refresh_times = [3,5]           #自动刷新(里面是秒数)

字段联动
应用场景:当添加一门课程的时候,希望课程机构里面的课程数 +1
重写xadmin的save_models方法

1
2
3
4
5
6
7
8
9
10
11
12
def save_models(self):
# 在保存课程的时候统计课程机构的课程数
# 字段联动
obj = self.new_obj
# 新增课程还没有保存,统计的课程数少一个
obj.save()
# 必须确定存在.
if obj.course_org is not None:
# obj实际是一个course对象
course_org = obj.course_org
course_org.course_nums = Course.objects.filter(course_org = course_org).count()
course_org.save()

xadmin自行探究

  • local 语言包
  • migration 数据表的记录
  • plugins 每一个后台页面都是一个plugin 插件机制
  • static文件.js css
  • template xadmin自己用到的html文件
  • 对django admin的封装

增加富文本编辑器Ueditor

下载地址 https://github.com/twz915/DjangoUeditor3/
解压后,把DjangoUeditor文件夹拷贝到项目目录下面

1
2
3
4
5
# settings中添加app

INSTALLED_APPS = [
'DjangoUeditor',
]

1
2
 # 富文本编辑器url
path('ueditor/',include('DjangoUeditor.urls' )),

course/models.py中Course修改detail字段

1
2
3
4
class Course(models.Model):
# detail = models.TextField("课程详情")
detail = UEditorField(verbose_name=u'课程详情', width=600, height=300, imagePath="courses/ueditor/",
filePath="courses/ueditor/", default='')

xadmin/plugs目录下新建ueditor.py文件,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import xadmin
from xadmin.views import BaseAdminPlugin, CreateAdminView, ModelFormAdminView, UpdateAdminView
from DjangoUeditor.models import UEditorField
from DjangoUeditor.widgets import UEditorWidget
from django.conf import settings


class XadminUEditorWidget(UEditorWidget):
def __init__(self, **kwargs):
self.ueditor_options = kwargs
self.Media.js = None
super(XadminUEditorWidget,self).__init__(kwargs)


class UeditorPlugin(BaseAdminPlugin):

def get_field_style(self, attrs, db_field, style, **kwargs):
if style == 'ueditor':
if isinstance(db_field, UEditorField):
widget = db_field.formfield().widget
param = {}
param.update(widget.ueditor_settings)
param.update(widget.attrs)
return {'widget':XadminUEditorWidget(**param)}
return attrs

def block_extrahead(self, context, nodes):
js = '<script type="text/javascript" src="%s"></script>' %(settings.STATIC_URL + "ueditor/ueditor.config.js")
js += '<script type="text/javascript" src="%s"></script>' %(settings.STATIC_URL + "ueditor/ueditor.all.min.js")
nodes.append(js)

xadmin.site.register_plugin(UeditorPlugin, UpdateAdminView)
xadmin.site.register_plugin(UeditorPlugin, CreateAdminView)

xadmin/plugs/__init__.py里面添加ueditor插件

1
2
3
PLUGINS = (
'ueditor',
)

course/adminx.py中使用

1
2
3
class CourseAdmin(object):
#detail就是要显示为富文本的字段名
style_fields = {"detail": "ueditor"}

在模板中必须关闭Django的自动转义才能正常显示

1
2
3
4
5
<div class="tab_cont tab_cont1">
{% autoescape off %}
{{ course.detail }}
{% endautoescape %}
</div>

或者

1
{{ course.detail|safe }}

导入excel

  • 如何注入导入excel代码到菜单
  • 如何只在课程列表显示
  • 如何接收文件对文件进行处理

新建xadmin/plugins/excel.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import xadmin
from xadmin.views import BaseAdminPlugin, ListAdminView
from django.template import loader


#excel 导入
class ListImportExcelPlugin(BaseAdminPlugin):
import_excel = False

def init_request(self, *args, **kwargs):
return bool(self.import_excel)

def block_top_toolbar(self, context, nodes):
nodes.append(loader.render_to_string('xadmin/excel/model_list.top_toolbar.import.html', context=get_context_dict(context)))


xadmin.site.register_plugin(ListImportExcelPlugin, ListAdminView)

添加到init.py文件中

1
2
3
PLUGINS = (
'excel',
)

新建xadmin/templates/xadmin/excel/model_list.top_toolbar.import.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
{% load i18n %}
<div class="btn-group export">
<a class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown" href="#">
<i class="icon-share"></i> 导入 <span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
<li><a data-toggle="modal" data-target="#export-modal-import-excel"><i class="icon-circle-arrow-down"></i> 导入 Excel</a></li>
</ul>
<script>
function fileChange(target){
//检测上传文件的类型
var imgName = document.all.submit_upload.value;
var ext,idx;
if (imgName == ''){
document.all.submit_upload_b.disabled=true;
alert("请选择需要上传的 xls 文件!");
return;
} else {
idx = imgName.lastIndexOf(".");
if (idx != -1){
ext = imgName.substr(idx+1).toUpperCase();
ext = ext.toLowerCase( );
{# alert("ext="+ext);#}
if (ext != 'xls' && ext != 'xlsx'){
document.all.submit_upload_b.disabled=true;
alert("只能上传 .xls 类型的文件!");

return;
}
} else {
document.all.submit_upload_b.disabled=true;
alert("只能上传 .xls 类型的文件!");
return;
}
}

}
</script>
<div id="export-modal-import-excel" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" action="" enctype="multipart/form-data">
{% csrf_token %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">导入 Excel</h4>
</div>
<div class="modal-body">
<input type="file" onchange="fileChange(this)" name="excel" id="submit_upload">

</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button>
<button class="btn btn-success" type="submit" id="submit_upload_b"><i class="icon-share"></i> 导入</button>
</div>
</form>
</div><!-- /.modal-content -->
</div><!-- /.modal-dalog -->
</div><!-- /.modal -->

</div>

只需要在adminx中设置变量import_excel=True就可以开启导入按钮
在adminx中重写post方法,将文件内容保存到数据中

1
2
3
def post(self, request, *args , **kwargs):
if 'excel' in request.FILES:
pass

1
2
3
# 中间pass步骤不管做什么事情,都要最后return父类的

return super(CourseAdmin, self).post(request, args, kwargs)
-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!
0%