Django在线教育平台项目完整实现(一)
- 一、项目准备及数据库表结构设计
- 二、搭建后台管理系统
- 三、后台登录及注册功能的实现
- 四、课程机构相关功能开发
一、项目准备及数据库表结构设计
1.新建python虚拟环境
新建一个文件夹
系统变量里添加这个WORKON_HOME变量,然后将刚才的文件夹路径填进去再重启(可将虚拟环境新建在指定路径,不然会建在C盘)
这里使用的WORKON_HOME变量值为D:\DjangoVir
建立新的文件夹中右键进入cmd
pip install virtualenvwrapper-win
(有的话不用安装)
mkvirtualenv -p D:\Anaconda3\python.exe mxonline
2.安装Django并新建Django项目
pip install django==2.2 -i https://pypi.douban.com/simple
在pycharm中新建Django项目,Location为D:\MxOnline-选择已存在的解释器-选择刚才新建的python虚拟环境下的python.exe
3.生成的目录说明
1.其中自动创建的"应用名“文件夹下的migrations文件夹,在数据库迁移时用到
2.其中的templates文件夹用来存放html文件
3.manage.py为启动Django项目的入口文件
4.新建一些目录
1.在项目中New一个Directory-命名为static,用来存放js、css、图片等文件
2.在项目中New一个Directory-命名为media,用来存放用户上传的文件、图片等
3.Django项目变大的时候应用会增多,此时在项目中New一个Python Package命名为apps
4.之后会引入一些第三方应用,此时在项目中New一个Python Package命名为extra_apps,用来存放第三方源码
5.再在项目中New一个File命名为requirements.txt,用来记录项目依赖于哪些第三方包,写入:
django==2.2
mysqlclient
5.进行相关配置
5.1 新建数据库
在Navicat中新建数据库mxonline,字符集选择utf8 – UTF-8 Unicode,排序规则选择utf8_general_ci
在MxOnline\settings.py中进行配置数据库:
在setting中可以看到使用的默认数据库是sqlite,要改为mysql
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': "mxonline",
'USER': "root",
'PASSWORD': "root",
# 若是线上的可以指向线上的地址
'HOST': "127.0.0.1"
}
}
安装mysql驱动:
在windows下mysql驱动安装容易出错,在https://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclient手动下载安装包mysqlclient-1.4.6-cp37-cp37m-win_amd64.whl
在下载位置的文件框中输入cmd
,再进入虚拟环境workon mxonline
进行安装pip install mysqlclient-1.4.6-cp37-cp37m-win_amd64.whl
5.2 配置static静态文件
推荐使用相对路径:
STATIC_URL = '/static/'
# 指明在哪里搜索静态文件
STATICFILES_DIRS = [
# 若有多个搜索目录可以添加到这个list中
# 使用相对路径
os.path.join(BASE_DIR, 'static')
]
或者可以使用绝对路径(不推荐)
STATIC_URL = '/static/'
# 指明在哪里搜索静态文件
STATICFILES_DIRS = [
# 若有多个搜索目录可以添加到这个list中、
# 右键-Copy path路径,将\改为/防止出现转义问题
"D:/Message/static"
]
启动项目,菜单栏-Run-Run-选择Django项目
控制台显示Django的默认端口为:http://127.0.0.1:8000/
粘贴到浏览器中进行访问,显示:
看到正常启动
5.3 新建app
菜单栏-Tools-run setup.py Task
输入:
新建用户相关app:startapp users
新建课程相关app:startapp courses
新建机构相关app:startapp organizations
新建用户操作相关app:startapp operations
将新建的4个app的文件夹拖动到apps中,采用拖动的方式可以对应修改配置(要勾选Search for references)
settings.py中:
INSTALLED_APPS中并没有被配置,要进行手动配置
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'apps.users.apps.UsersConfig',
'apps.courses.apps.CoursesConfig',
'apps.organizations.apps.OrganizationsConfig',
'apps.operations.apps.OperationsConfig',
]
重新启动Django项目
刷新浏览器中的http://127.0.0.1:8000/,看是否有异常
6.进行users用户表的设计
6.1 自定义UserProfile表,覆盖Django默认的user表
from django.db import models
# Django本身的用户抽象类
from django.contrib.auth.models import AbstractUser
# Create your models here.
# 定义自己的user,继承AbstractUser
# 设置性别选项,为元组类型,里面再写一个元组
GENDER_CHOICES = (
("male","男"),
("female","女")
)
class UserProfile(AbstractUser):
# Django已经自带用户名username和密码字段(为必填字段),添加额外字段
# 允许为空,允许不填 null=True, blank=True或者可替换为default=""表示默认为空字符串
nick_name = models.CharField(max_length=50,verbose_name="昵称", default="")
birthday = models.DateTimeField(verbose_name="生日", null=True, blank=True)
gender = models.CharField(verbose_name="性别",choices=GENDER_CHOICES, max_length=6)
address = models.CharField(max_length=100, verbose_name="地址",default="")
# 系统默认使用手机号注册,所以不为空,而且要唯一
mobile = models.CharField(max_length=11, verbose_name="手机号码")
# 头像,upload_to为新建的media文件的子路径,会自动新建head_image目录,%Y/%m代表年月
# ImageField本质上为CharField,将文件的路径保存到Field中,default设置默认头像
image = models.ImageField(verbose_name="用户头像",upload_to="head_image/%Y/%m", default="default.jpg")
class Meta:
verbose_name = "用户信息"
verbose_name_plural = verbose_name
# 得到UserProfile时,字符串的描述,直接输出时用
def __str__(self):
# 如果设置了昵称
if self.nick_name:
return self.nick_name
else:
# 继承的AbstractUser中的username(必填字段)
return self.username
6.2 把UserProfile设置为之后使用的表,而不使用默认的表
在settings.py中:
AUTH_PASSWORD_VALIDATORS = [
# 省略
]
AUTH_USER_MODEL = "users.UserProfile"
在Navicat中把mxonline数据库中的表全部删除
(删除的时候会报错,因为存在外键依赖,多删几次即可)
6.3 将表直接做映射
菜单栏-Tools-Run manage.py Task
makemigrations
会报错:Cannot use ImageField because Pillow is not installed.
因为底层依赖Pillow库
安装Pillow
在cmd中
workon mxonline
pip install pillow
再次
makemigrations
显示
- Create model UserProfile
再migrate
在Navicat中进行刷新,可看到表没有生成自带的auth_user,取而代之的是users_userprofile
右键设计表users_userprofile可看到,其中除了Django自带的字段外还出现了自己定义的字段
7.备注
7.1 进入虚拟环境的命令
workon mxonline
7.2 可以用Django的命令来新建应用
有两种方式:
注:在创建Django项目时,未在More Settings-Application name中填入“应用名”时需要通过这种方法来手动创建
7.2.1 cmd
cd到项目所在目录下
输入:python manage.py startapp 应用名
7.2.2 Pycharm
菜单栏-Tools-run setup.py Task
输入:startapp 应用名
注:
使用Django命令来新建应用时,settings.py中:
INSTALLED_APPS中并没有被配置,要进行手动配置
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 手动添加
'apps.应用名.apps.应用名Config'
]
mysql数据库密码:root
8.循环引用要避免
user和courses需要相互引用,需要相互import
循环引用在项目中会报错,要避免循环引用
解决循环引用的常用方法就是分层设计
9.分层设计
上层可以引用下层,但下层不能引用上层,同一层之间可以相互引用
10.设计表结构courses models
10.1 设计出实体,并找出每个实体间的关系
设计表结构需要注意一对多关系和多对多关系
实体1 <关系> 实体2
一对多关系:
课程 章节 视频 课程资源
课程相关的实体包括:
设计model:
在apps/courses/models.py中:
# python默认带的库放最前面
from datetime import datetime
# 第三方库放中间,如Django
from django.db import models
# 最后放自己定义的类和方法
from apps.users.models import BaseModel
# Create your models here.
"""
实体1 <关系> 实体2
一对多关系:
课程 章节 视频 课程资源
"""
"""
# 使用继承的机制来继承实体,这个类用来被继承,这样便不用重新写过多的列
# 继承models.Model
class BaseModel(models.Model):
# 给每个数据添加一个添加的时间,用于做日志分析
# 不能直接datetime.now(),这样记录的是Course类编译的时间
# 而是希望记录生成实例时的时间course = Course(),只写方法名称,Django会在适当时间调用此方法
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
# 防止migration时,BaseModel生成表
abstract = True
# 这个BaseModel可能会被operations和organizations中的model导入
# 所以要将BaseModel放到下一层,user层,将这段代码放到apps/users/models.py
"""
# 继承BaseModel
class Course(BaseModel):
class Meta:
verbose_name = "课程信息"
verbose_name_plural = verbose_name
BaseModel可能会被operations和organizations中的model导入,所以要将BaseModel放到下一层,user层,将这段代码放到apps/users/models.py
from datetime import datetime
# 省略
# 设置性别选项,为元组类型,里面再写一个元组
GENDER_CHOICES = (
("male","男"),
("female","女")
)
# 使用继承的机制来继承实体,这个类用来被继承,这样便不用重新写过多的列
# 继承models.Model
class BaseModel(models.Model):
"""
给每个数据添加一个添加的时间,用于做日志分析
不能直接datetime.now(),这样记录的是Course类编译的时间
而是希望记录生成实例时的时间course = Course(),只写方法名称,Django会在适当时间调用此方法
"""
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
# 防止migration时,BaseModel生成表
abstract = True
# 这个BaseModel可能会被operations和organizations中的model导入
# 所以要将BaseModel放到下一层,user层,将这段代码放到apps/users/models.py
class UserProfile(AbstractUser):
# 省略
再在apps/courses/models.py中进行import:
from apps.users.models import BaseModel
10.2 实体的具体字段
根据页面上展示的数据来确定具体字段
要确定每个字段的类型是否必填
设计课程相关的model(表结构设计)
在apps/courses/models.py中:
# python默认带的库放最前面
from datetime import datetime
# 第三方库放中间,如Django
from django.db import models
# 最后放自己定义的类和方法
from apps.users.models import BaseModel
# 引入Teacher
from apps.organizations.models import Teacher
# Create your models here.
"""
实体1 <关系> 实体2
一对多关系:
课程 章节 视频 课程资源
"""
"""
# 使用继承的机制来继承实体,这个类用来被继承,这样便不用重新写过多的列
# 继承models.Model
class BaseModel(models.Model):
# 给每个数据添加一个添加的时间,用于做日志分析
# 不能直接datetime.now(),这样记录的是Course类编译的时间
# 而是希望记录生成实例时的时间course = Course(),只写方法名称,Django会在适当时间调用此方法
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
# 防止migration时,BaseModel生成表
abstract = True
# 这个BaseModel可能会被operations和organizations中的model导入
# 所以要将BaseModel放到下一层,user层,将这段代码放到apps/users/models.py
"""
# 继承BaseModel
# 课程
class Course(BaseModel):
# 添加外键
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE, verbose_name="讲师")
# 实际的具体字段
name = models.CharField(verbose_name="课程名", max_length=50)
desc = models.CharField(verbose_name="课程描述", max_length=300)
# 精确到分钟,要以最小单位去保存到数据库
learn_times = models.IntegerField(default=0, verbose_name="学习时长(分钟数)")
degree = models.CharField(verbose_name="难度", choices=(("cj", "初级"),("zj", "中级"),("gj", "高级")), max_length=2)
students = models.IntegerField(default=0, verbose_name="学习人数")
fav_nums = models.IntegerField(default=0, verbose_name="收藏人数")
click_nums = models.IntegerField(default=0, verbose_name="点击数")
category = models.CharField(default=u"后端开发", max_length=20, verbose_name="课程类别")
tag = models.CharField(default="", verbose_name="课程标签", max_length=10)
youneed_know = models.CharField(default="", max_length=300, verbose_name="课程须知")
teacher_tell = models.CharField(default="", max_length=300, verbose_name="老师告诉你")
# 显示富文本,TextField不限制长度
detail = models.TextField(verbose_name="课程详情")
image = models.ImageField(upload_to="courses/%Y/%m", verbose_name="封面图", max_length=100)
class Meta:
verbose_name = "课程信息"
verbose_name_plural = verbose_name
# 设计第二个实体,继承BaseModel
# 课程章节
class Lesson(BaseModel):
# 设计外键,为上面定义的Course,必须指定on_delete属性,表示对应的外键数据被删除后,当前的数据应该怎么办
# CASCADE()表示,若课程Course信息被删除,凡是外键指向当前这个课程的章节信息会被级联删除
course = models.ForeignKey(Course, on_delete=models.CASCADE)
# # 也可以写为:
# course = models.ForeignKey(Course, on_delete=models.SET_NULL(), null=True, blank=True)
name = models.CharField(max_length=100, verbose_name=u"章节名")
learn_times = models.IntegerField(default=0, verbose_name=u"学习时长(分钟数)")
class Meta:
verbose_name = "课程章节"
verbose_name_plural = verbose_name
# 设计第三个实体,继承BaseModel
# 课程视频
class Video(BaseModel):
lesson = models.ForeignKey(Lesson, verbose_name="章节", on_delete=models.CASCADE)
name = models.CharField(max_length=100, verbose_name=u"视频名")
learn_times = models.IntegerField(default=0, verbose_name=u"学习时长(分钟数)")
url = models.CharField(max_length=200, verbose_name=u"访问地址")
class Meta:
verbose_name = "视频"
verbose_name_plural = verbose_name
# 设计第四个实体,继承BaseModel
# 课程资源
class CourseResource(BaseModel):
course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name="课程")
name = models.CharField(max_length=100, verbose_name=u"名称")
file = models.FileField(upload_to="course/resourse/%Y/%m", verbose_name="下载地址", max_length=200)
class Meta:
verbose_name = "课程资源"
verbose_name_plural = verbose_name
11.设计课程机构相关的model
在apps/organizations/models.py中:
from django.db import models
from apps.users.models import BaseModel
# Create your models here.
# 设计一个外键,在后台添加城市
class City(BaseModel):
name = models.CharField(max_length=20, verbose_name=u"城市名")
desc = models.CharField(max_length=200, verbose_name=u"描述")
class Meta:
verbose_name = "城市"
verbose_name_plural = verbose_name
# 继承BaseModel
# 课程机构
class CourseOrg(BaseModel):
name = models.CharField(max_length=50, verbose_name="机构名称")
# 课程描述为富文本,先暂时设为TextField()
desc = models.TextField(verbose_name="描述")
tag = models.CharField(default="pxjg", verbose_name="机构类别", max_length=4,
choices=(("pxjg","培训机构"), ("gr", "个人"), ("gx", "高校")))
click_nums = models.IntegerField(default=0, verbose_name="点击数")
fav_nums = models.IntegerField(default=0, verbose_name="收藏数")
# 课程机构logo
image = models.ImageField(upload_to="org/%Y/%m", verbose_name=u"logo", max_length=100)
address = models.CharField(max_length=150, verbose_name="机构地址")
students = models.IntegerField(default=0, verbose_name="学习人数")
course_nums = models.IntegerField(default=0, verbose_name="课程数")
# 设计一个外键,在后台添加城市
city = models.ForeignKey(City, on_delete=models.CASCADE, verbose_name=u"所在城市")
class Meta:
verbose_name = "课程机构"
verbose_name_plural = verbose_name
# 课程讲师
class Teacher(BaseModel):
org = models.ForeignKey(CourseOrg, on_delete=models.CASCADE, verbose_name="所属机构")
name = models.CharField(max_length=50, verbose_name=u"教师名")
work_years = models.IntegerField(default=0, verbose_name="工作年限")
work_company = models.CharField(max_length=50, verbose_name="就职公司")
work_position = models.CharField(max_length=50, verbose_name="公司职位")
points = models.CharField(max_length=50, verbose_name="教学特点")
click_nums = models.IntegerField(default=0, verbose_name="点击数")
fav_nums = models.IntegerField(default=0, verbose_name="收藏数")
age = models.IntegerField(default=18, verbose_name="年龄")
image = models.ImageField(upload_to="teacher/%Y/%m", verbose_name="头像", max_length=100)
class Meta:
verbose_name = "教师"
verbose_name_plural = verbose_name
再完善一下课程courses的models,添加外键
# 省略
# 最后放自己定义的类和方法
from apps.users.models import BaseModel
# 引入Teacher
from apps.organizations.models import Teacher
# 继承BaseModel
# 课程
class Course(BaseModel):
# 添加外键
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE, verbose_name="讲师")
# 实际的具体字段
# 省略
12.设计用户操作相关的model
在apps/operations/models.py中:
from django.db import models
from django.contrib.auth import get_user_model
from apps.users.models import BaseModel
from apps.courses.models import Course
# Create your models here.
"""
get_user_model()方法可以看是否覆盖了Dajngo自带的auth_user表
Djano提供了此方法来读到UserProfile是哪一个class
这样便不影响之后使用Django自带的user表
"""
UserProfile = get_user_model()
# 用户咨询
class UserAsk(BaseModel):
name = models.CharField(max_length=20, verbose_name="姓名")
mobile = models.CharField(max_length=11, verbose_name="手机")
course_name = models.CharField(max_length=50, verbose_name=u"课程名")
class Meta:
verbose_name = "用户咨询"
verbose_name_plural = verbose_name
# 课程评论
class CourseComments(BaseModel):
# 定义外键
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, verbose_name="用户")
course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name="课程")
comments = models.CharField(max_length=200, verbose_name="评论内容")
class Meta:
verbose_name = "课程评论"
verbose_name_plural = verbose_name
# 用户收藏
class UserFavorite(BaseModel):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, verbose_name="用户")
fav_id = models.IntegerField(verbose_name="数据id")
fav_type = models.IntegerField(choices=((1,"课程"), (2,"课程机构"), (3,"讲师")), default=1, verbose_name=u"收藏类型")
class Meta:
verbose_name = "用户收藏"
verbose_name_plural = verbose_name
# 用户消息
class UserMessage(BaseModel):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, verbose_name="用户")
message = models.CharField(max_length=200, verbose_name="消息内容")
has_read = models.BooleanField(default=False, verbose_name=u"是否已读")
class Meta:
verbose_name = "用户消息"
verbose_name_plural = verbose_name
# 用户和课程的关系
class UserCourse(BaseModel):
# 先设计为一对多关系,后续再设计为多对多关系
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, verbose_name="用户")
course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name="课程")
class Meta:
verbose_name = "用户课程"
verbose_name_plural = verbose_name
表结构设计完成
选中models.py,菜单栏-View-Tool Windows-Structure,可对表进行浏览
备注:
直接定义外键可以,但这种方式有一定的局限性
# 定义外键
user = models.ForeignKey(UserProfile)
这里用自己定义的UserProfile来覆盖Dajngo自带的auth_user表
若之后在某些情况下要使用Dajngo自带的表,则所有相关的model全部都要将UserProfile改为自带的auth_user表,改动过大,维护困难。
get_user_model()方法可以看是否覆盖了Dajngo自带的auth_user表
Djano提供了此内部的方法来读到UserProfile是哪一个class
from django.contrib.auth import get_user_model
UserProfile = get_user_model()
这样便不影响之后使用Django自带的user表
13.migration来生成需要的的表以及表结构
菜单栏-Tools-Run manage.py Task,如果有问题会报错
makemigrations
migrate
便生成了表到数据库
在Navicat中可看到表更新
其中的django_migrations表中记录了每次做的表结构的修改
运行migrate
之后,才会将makemigrations
中的代码执行,此表中可看到哪些app下的哪一些文件被运行
二、搭建后台管理系统
1.admin后台管理系统
后台管理系统主要用来管理后台数据
Django后台管理系统特点:
- 权限管理
- 少前端样式
- 快速开发
在之前新建Django项目时在More Settings中勾选了Enable Django admin
在MxOnline/settings.py中可看到
INSTALLED_APPS = [
'django.contrib.admin',
在MxOnline/urls.py中可看到
urlpatterns = [
path('admin/', admin.site.urls),
]
若没有勾选要手动进行添加。
菜单栏-Run-Run-选择Django项目
输入http://127.0.0.1:8000/admin可查看Django自带的admin后台管理系统(Django admin的登录页面)
若发现css样式缺失,在settings.py中查看是否有
INSTALLED_APPS = [
'django.contrib.staticfiles',
没有则添加。
但新建项目时没有生成用户,自行新建用户:
菜单栏-Tools-Run manage.py Task
输入:createsuperuser
创建超级用户
输入用户名:bobby
输入邮箱:1@1.com
输入2次密码:admin123
提示密码常见,输入:y
即创建成功。
在数据库中的users_userprofile表中可以看到创建的用户信息,其中的密码做了加密处理,其中的is_staff字段为1则是后台管理系统人员的账号,可以登录后台管理系统。
可回到Django admin的登录页面根据创建的用户进行登录。
显示为英文,若要显示为中文,则在settings.py中改为:
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
1.1 在后台管理系统中新建一个user管理器
在users/admin.py中:
在此写注册的逻辑
from django.contrib import admin
# Register your models here.
from apps.users.models import UserProfile
# 在后台新建一个管理器
# 继承类admin.ModelAdmin
class UserProfileAdmin(admin.ModelAdmin):
pass
# 将表和管理器关联
admin.site.register(UserProfile, UserProfileAdmin)
重启项目,刷新页面,可看到新建成功
其中的USERS为app名称,若要改为想要的文字,则在users/apps.py中:
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'apps.users'
verbose_name = "用户"
重启项目,刷新页面,可看到修改成功。
点入用户信息,可选择动作进行执行。
点击添加用户信息按钮,可以看到对应带出了自己定义的字段信息。
输入对应信息进行添加,可在数据库中的users_userprofile表中看到新添加的用户信息,但此密码未加密,是明文。
因此若使用新添加的账号进行后台管理系统的登录,则无法登录。(未进行密码加密)
1.2 使用Django自带的用户管理器
在users/admin.py中改为:
from django.contrib import admin
# 使用Django自带的用户管理器
from django.contrib.auth.admin import UserAdmin
# Register your models here.
from apps.users.models import UserProfile
# 在后台新建一个管理器
# 继承类admin.ModelAdmin
class UserProfileAdmin(admin.ModelAdmin):
pass
# 将表和管理器关联
# admin.site.register(UserProfile, UserProfileAdmin)
# 使用Django自带的用户管理器
admin.site.register(UserProfile, UserAdmin)
可看到页面重新进行了组织
因为此用户为刚才新建的,为明文格式,所以显示不可用的密码格式。
点击另一个新建的用户,可看到密码格式正确。
若进行用户添加,可以看到缺少了一些字段。
若进行添加,会看到报错。
此为数据库报错,表示不允许存在2条数据中mobile字段都为空。
原因是:
在users/models.py中:
mobile = models.CharField(max_length=11, unique=True, verbose_name="手机号码")
unique=True,电话号,不能重复,初始值为空。
若有两条数据的mobile都为空,则会报错。
去掉此处的unique=True
mobile = models.CharField(max_length=11, verbose_name="手机号码")
也可以修改Django源码(不推荐)。
保证电话号码唯一可以在之后实现注册功能时进行。
修改表之后要进行,makemigrations
并migrate
,便在数据库中做了对应修改。
重启项目,便可以进行用户的添加。
2.用xadmin来替代admin
2.1 环境准备
在https://github.com/sshwsfc/xadmin/tree/django2选择对应版本进行下载。
xadmin也是一个Django的app
1.将xadmin文件拷贝到项目的根目录下
2.在settings.py中INSTALLED_APPS中添加
INSTALLED_APPS = [
'crispy_forms',
'xadmin.apps.XAdminConfig',
]
3.安装xadmin的依赖包
先改变一下requirements.txt中的内容,指定具体版本,以防出现更多问题:
django-crispy-forms==1.10.0
django-import-export==2.5.0
django-reversion==3.0.8
django-formtools==2.2
future==0.18.2
httplib2==0.9.2
six==1.15.0
xlwt==1.3.0
xlsxwriter==1.3.7
requests==2.25.1
进入xadmin文件夹所在路径下,使用requirements.txt进行安装依赖。
在cmd中:
d:
cd D:\MxOnline\xadmin
workon mxonline
pip install -r requirements.txt
4.通过migrate生成xadmin需要的表
菜单栏-Tools-Run manage.py Task
输入:makemigrations
makemigrations xadmin
为只同步xadmin这个app到数据库
看到报错:ImportError: cannot import name 'SKIP_ADMIN_LOG' from 'import_export.admin' (D:\DjangoVir\mxonline\lib\site-packages\import_export\admin.py)
点入报错文件,xadmin\plugins\importexport.py中进行修改:
# from import_export.admin import DEFAULT_FORMATS, SKIP_ADMIN_LOG, TMP_STORAGE_CLASS
from import_export.admin import DEFAULT_FORMATS,ImportMixin,ImportExportMixinBase
# 省略
def get_skip_admin_log(self):
if self.skip_admin_log is None:
# return SKIP_ADMIN_LOG
return ImportMixin(ImportExportMixinBase).get_skip_admin_log()
else:
return self.skip_admin_log
def get_tmp_storage_class(self):
if self.tmp_storage_class is None:
# return TMP_STORAGE_CLASS
return ImportMixin(ImportExportMixinBase).get_tmp_storage_class()
else:
return self.tmp_storage_class
修改完有新的报错:ModuleNotFoundError: No module named 'widgets'
在此链接进行下载:https://github.com/twz915/DjangoUeditor3
复制文件夹里面的DjangoUeditor文件夹,放到环境下的lib/site-packages/下
再重新运行命令即可。
可看到xadmin没有变化,因为xadmin文件夹下的migrations文件夹中已有比特文件(脚本)。
makemigrations命令只生成比特文件,执行migrate才会同步到数据库。
再输入:migrate
进行数据库中的同步。
可看到数据库中新增了xadmin开头的表。
2.2 进行配置
在urls.py中添加:
from django.contrib import admin
from django.urls import path
import xadmin
urlpatterns = [
# path('admin/', admin.site.urls),
path('xadmin/',xadmin.site.urls),
]
运行项目,输入http://127.0.0.1:8000/xadmin
可看到xadmin的界面
xadmin用bootstrap来重构,对手机端也有很好的支持
xadmin会自动获取定义的UserProfile
2.3 注册各模块的xadmin后台
课程模块:
在courses文件夹下新建一个adminx.py
import xadmin
from apps.courses.models import Course
class CourseAdmin(object):
pass
xadmin.site.register(Course, CourseAdmin)
会自动读取到此py文件
再改一下名称:
在apps/courses/apps.py中:
from django.apps import AppConfig
class CoursesConfig(AppConfig):
name = 'apps.courses'
verbose_name = "课程管理"
重启项目可看到课程模块已被添加到后台。
增加课程信息时会看到讲师信息下来没有选项,因为讲师为外键。
将讲师、课程机构、城市进行注册。
在organizations文件夹下新建一个adminx.py
import xadmin
from apps.organizations.models import Teacher,CourseOrg,City
class TeacherAdmin(object):
pass
class CourseOrgAdmin(object):
pass
class CityAdmin(object):
pass
xadmin.site.register(Teacher, TeacherAdmin)
xadmin.site.register(CourseOrg, CourseOrgAdmin)
xadmin.site.register(City, CityAdmin)
重启项目可看到机构管理模块已被添加到后台。
再改一下名称:
在apps/courses/apps.py中:
from django.apps import AppConfig
class OrganizationsConfig(AppConfig):
name = 'apps.organizations'
verbose_name = "机构管理"
先新建一个城市
看到描述符,不是我们想要的格式。
在apps/organizations/models.py中:
# 设计一个外键,在后台添加城市
class City(BaseModel):
name = models.CharField(max_length=20, verbose_name=u"城市")
desc = models.CharField(max_length=200, verbose_name=u"描述")
class Meta:
verbose_name = "城市"
verbose_name_plural = verbose_name
def __str__(self):
return self.name
便可显示出文件的名字。
但此页面显示字段不全,缺少了筛选、搜索功能和直接编辑字段功能,进行添加。
在apps/organizations/models.py中:
import xadmin
from apps.organizations.models import Teacher,CourseOrg,City
class TeacherAdmin(object):
pass
class CourseOrgAdmin(object):
pass
class CityAdmin(object):
# 列表页显示的字段名称,要和models中对应
list_display = ["id", "name", "desc"]
# 根据什么字段进行搜索
search_fields = ["name", "desc"]
# 根据什么字段进行过滤
list_filter = ["name", "desc", "add_time"]
# 根据什么字段在页面上直接进行编辑
list_editable = ["name", "desc"]
xadmin.site.register(Teacher, TeacherAdmin)
xadmin.site.register(CourseOrg, CourseOrgAdmin)
xadmin.site.register(City, CityAdmin)
2.3.1 进行courses模块页面功能的完善
在apps/courses/adminx.py中:
import xadmin
from apps.courses.models import Course, Lesson, Video, CourseResource
class CourseAdmin(object):
list_display = ['name', 'desc', 'detail', 'degree', 'learn_times', 'students']
search_fields = ['name', 'desc', 'detail', 'degree', 'students']
# 根据某一个字段进行过滤,可以加两个下划线
list_filter = ['name', 'teacher__name', 'desc', 'detail', 'degree', 'learn_times', 'students']
list_editable = ["degree", "desc"]
class LessonAdmin(object):
list_display = ['course', 'name', 'add_time']
search_fields = ['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', 'file', 'add_time']
search_fields = ['course', 'name', 'file']
list_filter = ['course', 'name', 'file', 'add_time']
xadmin.site.register(Course, CourseAdmin)
xadmin.site.register(Lesson, LessonAdmin)
xadmin.site.register(Video, VideoAdmin)
xadmin.site.register(CourseResource, CourseResourceAdmin)
在apps/courses/models.py中重载方法,实现改名字:
在
class Course(BaseModel):
class Lesson(BaseModel):
class Video(BaseModel):
class CourseResource(BaseModel):
中都加入(一个缩进):
def __str__(self):
return self.name
2.3.2 在organizations模块中也进行完善
在apps/organizations/models.py中重载方法,改名字:
在
class City(BaseModel):
class CourseOrg(BaseModel):
class Teacher(BaseModel):
中都加入(一个缩进):
def __str__(self):
return self.name
在apps/organizations/adminx.py中:
import xadmin
from apps.organizations.models import Teacher,CourseOrg,City
class TeacherAdmin(object):
list_display = ['org', 'name', 'work_years', 'work_company']
search_fields = ['org', 'name', 'work_years', 'work_company']
list_filter = ['org', 'name', 'work_years', 'work_company']
class CourseOrgAdmin(object):
list_display = ['name', 'desc', 'click_nums', 'fav_nums']
search_fields = ['name', 'desc', 'click_nums', 'fav_nums']
list_filter = ['name', 'desc', 'click_nums', 'fav_nums']
class CityAdmin(object):
# 列表页显示的字段名称,要和models中对应
list_display = ["id", "name", "desc"]
# 根据什么字段进行搜索
search_fields = ["name", "desc"]
# 根据什么字段进行过滤
list_filter = ["name", "desc", "add_time"]
# 根据什么字段在页面上直接进行编辑
list_editable = ["name", "desc"]
xadmin.site.register(Teacher, TeacherAdmin)
xadmin.site.register(CourseOrg, CourseOrgAdmin)
xadmin.site.register(City, CityAdmin)
2.3.3 进行operations模块完善
在apps/operations/adminx.py中:
import xadmin
from apps.operations.models import UserAsk, CourseComments, UserCourse, UserFavorite, UserMessage
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']
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)
在apps/operations/models.py中进行方法的重载,进行改名:
在class UserAsk(BaseModel):中添加:(一个缩进)
def __str__(self):
# 连接字符串
return "{name}_{course}({mobile})".format(name=self.name, course=self.course_name, mobile=self.mobile)
在class CourseComments(BaseModel):中添加:(一个缩进)
def __str__(self):
return self.comments
在class UserFavorite(BaseModel):中添加:(一个缩进)
def __str__(self):
return "{user}_{id}".format(user=self.user.username, id=self.fav_id)
在class UserMessage(BaseModel):中添加:(一个缩进)
def __str__(self):
return self.message
在class UserCourse(BaseModel):中添加:(一个缩进)
def __str__(self):
return self.course.name
再改一下名称:
在apps/operations/apps.py中:
from django.apps import AppConfig
class OperationsConfig(AppConfig):
name = 'apps.operations'
verbose_name = "用户操作"
在页面中:
首先添加一个课程机构,再添加一个教师,再添加课程信息。
因为有外键的依赖,需要按此顺序添加。
2.4 配置xadmin全局
在任意一个文件下的adminx.py中进行配置即可。
这里选择apps/courses/adminx.py
在开头:
import xadmin
from apps.courses.models import Course, Lesson, Video, CourseResource
class GlobalSettings(object):
# 后台左上角显示文字
site_title ="慕学后台管理系统"
# 设置页脚@
site_footer = "慕学在线网"
# 实现左侧菜单的收起及展开,并显示子模块数量
# menu_style = "accordion"
class BaseSettings(object):
# 开启默认主题选择,在右上角可以进行选择
enable_themes = True
use_bootswatch = True
并在末尾进注册此类:
xadmin.site.register(xadmin.views.CommAdminView, GlobalSettings)
xadmin.site.register(xadmin.views.BaseAdminView, BaseSettings)
三、后台登录及注册功能的实现
1.配置前端页面
1.1 首页index.html修改
将前端html源码\html文件夹中的index.html复制到项目的templates文件夹下
在MxOnline/urls.py中
from django.contrib import admin
from django.urls import path
from django.views.generic import TemplateView
import xadmin
urlpatterns = [
# path('admin/', admin.site.urls),
path('xadmin/',xadmin.site.urls),
# 设置直接访问域名的时候直接到此页面
path('', TemplateView.as_view(template_name="index.html"))
]
此时直接访问:http://127.0.0.1:8000/
便可以跳转到index.html,但没有css样式。
将前端html源码\css、images、img、js文件夹等静态文件拷贝到项目的static文件夹下
在index.html中进行修改:
将静态文件定位到static目录(在setting.py中已经进行了映射):
修改css
<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">
修改js:
可CTRL+R进行全局替换
将../js
替换为/static/js
点Replace all
修改images:
将../images
替换为/static/images
再进行页面的刷新,便可正常显示。
但有些图片未进行显示,可以在页面上点击F12选中该元素看少了哪个文件夹。
可看到缺少media文件夹,将前端html源码\media文件夹等静态文件拷贝到项目的static文件夹下
(此处只是为了展示,media文件夹不应拷贝到这里,因为media是后台上传的,不应该放到static文件夹下,static中应只存放前端开发的图片,之后会放到项目根目录下的media文件夹中)
将../media
替换为/static/media
再进行页面的刷新,便可全部显示。
右上角显示已经登录的信息,要显示为登录和注册按钮,
将index.html中
此处注释取消:
<a style="color:white" class="fr registerbtn" href="register.html">注册</a>
<a style="color:white" class="fr loginbtn" href="login.html">登录</a>
将此处加上注释:(为已经登录的信息)
{# <div class="personal">#}
{# <dl class="user fr">#}
{# <dd>bobby<img class="down fr" src="/static/images/top_down.png"/></dd>#}
{# <dt><img width="20" height="20" src="/static/media/image/2016/12/default_big_14.png"/></dt>#}
{# </dl>#}
{# <div class="userdetail">#}
{# <dl>#}
{# <dt><img width="80" height="80" src="/static/media/image/2016/12/default_big_14.png"/></dt>#}
{# <dd>#}
{# <h2>django</h2>#}
{# <p>bobby</p>#}
{# </dd>#}
{# </dl>#}
{# <div class="btn">#}
{# <a class="personcenter fl" href="usercenter-info.html">进入个人中心</a>#}
{# <a class="fr" href="/logout/">退出</a>#}
{# </div>#}
{# </div>#}
{# </div>#}
再进行页面的刷新,便可显示为登录和注册按钮。
1.2 登录页login.html修改
在urls.py中进行配置
urlpatterns = [
# path('admin/', admin.site.urls),
path('xadmin/',xadmin.site.urls),
# 设置直接访问域名的时候直接到此页面
path('', TemplateView.as_view(template_name="index.html")),
path('login/', TemplateView.as_view(template_name="login.html"))
]
将前端html源码\html文件夹中的login.html复制到项目的templates文件夹下
输入http://127.0.0.1:8000/login
便可以访问
再在login.html中进行静态文件的配置:
将../css
替换为/static/css
将../js
替换为/static/js
将../images
替换为/static/images
(此处没有media和img,不用替换)
刷新页面,便可正常显示。
2.配置按钮进行页面跳转
在index.html中进行修改:
href原本指向html文件,现在为网络,应该指向网络地址
<a style="color:white" class="fr loginbtn" href="/login/">登录</a>
Django提供了更可靠的方法:
先在urls.py中进行修改:
path('login/', TemplateView.as_view(template_name="login.html"), name= "login")
再修改index.html
<a style="color:white" class="fr loginbtn" href="{% url 'login' %}">登录</a>
再修改login.html
<li class="active"><a href="{% url 'login' %}">[登录]</a></li>
此处根据urls.py中配置的name来进行url的解析。
此时再进行刷新,在首页中点击登录便可以实现页面的跳转。
3.完成后台的登录逻辑
并未实现login的数据校验,不能直接使用TemplateView,这只实现了简单返回html页面。
自己编写View,实现提取用户数据和做用户数据校验。
知识点:
1.CBV(Class Base View)
基于类的方式来完成View,之后都会使用此方式,此方式有利于代码的重用,因为class可以继承
2.FBV(Function Base View)
基于function的方式来完成View,此前的留言板项目使用此方式。
在apps/users/views.py中:
from django.shortcuts import render
from django.views.generic.base import View
# Create your views here.
# 继承Django内部的View
class LoginView(View):
# 重载get方法,request为django自动注入的参数
# 有可能传递多个参数,设置接受多变量的方式,可以点入View中从def dispatch中的return中查看源码
def get(self, request, *args, **kwargs):
return render(request, "login.html")
# 用户数据的获取
def post(self, request, *args, **kwargs):
pass
再将自己写的View进行配置:
在urls.py中进行修改:
import xadmin
from apps.users.views import LoginView
urlpatterns = [
# path('admin/', admin.site.urls),
path('xadmin/',xadmin.site.urls),
# 设置直接访问域名的时候直接到此页面
path('', TemplateView.as_view(template_name="index.html")),
# 在此处login后要加/,解决访问时http://127.0.0.1:8000/login后面多加/出现的Page not found的问题
# path('login/', TemplateView.as_view(template_name="login.html"), name= "login")
path('login/', LoginView.as_view(), name= "login")
]
4.账号登录功能的实现
在apps/users/views.py中:
from django.shortcuts import render
from django.views.generic.base import View
from django.contrib.auth import authenticate, login
# Create your views here.
# 继承Django内部的View
class LoginView(View):
# 重载get方法,request为django自动注入的参数
# 有可能传递多个参数,设置接受多变量的方式,可以点入View中从def dispatch中的return中查看源码
def get(self, request, *args, **kwargs):
return render(request, "login.html")
# 用户数据的获取
def post(self, request, *args, **kwargs):
# 在页面上点击F12选中login.html中手机号/邮箱输入栏,可看到input name="username",据此来进行获取
# 获取username,默认值为空
user_name = request.POST.get("username","")
password = request.POST.get("password","")
# 用于通过用户名和密码查询用户是否存在,使用django内置的方法,因为数据库中存储的密码为密文,所以不能直接使用UserProfile来验证
user = authenticate(username=user_name, password=password)
if user is not None:
# 若查询到用户,则进行登录
# Django内部的login方法会自动完成cookie的设置
login(request, user)
# 登录成功之后返回页面
return render(request, "index.html")
else:
# 未查询到用户
return render(request, "login.html", {"msg":"用户名或密码错误"})
在templates/login.html中:
修改form表单的action:
<form class="tab-form" action="{% url 'login' %}" method="post" autocomplete="off" id="form1">
并添加:
{% csrf_token %}
</form>
在页面输入之前的用户名和密码,bobby和admin123进行登录,发现转跳后的页面url仍然为http://127.0.0.1:8000/login/
这是因为render模式不会引起url的变化。
使用Django内置的HttpResponseRedirect类来解决此问题。
在apps/users/views.py中进行修改:
from django.http import HttpResponseRedirect
from django.urls import reverse
# 省略
# reverse方法通过url的名称来定位url
return HttpResponseRedirect(reverse("index"))
在urls.py中进行修改:
path('', TemplateView.as_view(template_name="index.html"), name="index"),
再重新运行,登录跳转后的页面url便变为http://127.0.0.1:8000/
5.右上角显示信息的设置
根据用户的登录状态显示页面右上角是显示用户信息还是登录注册的按钮
即判断index.html中显示哪一段html
在templates/index.html中:
使用Django template来实现逻辑的判断
<div class="wp">
<div class="fl"><p>服务电话:<b>33333333</b></p></div>
<!--登录后跳转-->
<!--如果用户已经登录-->
{% if request.user.is_authenticated %}
<div class="personal">
<dl class="user fr">
<dd>bobby<img class="down fr" src="/static/images/top_down.png"/></dd>
<dt><img width="20" height="20" src="/static/media/image/2016/12/default_big_14.png"/></dt>
</dl>
<div class="userdetail">
<dl>
<dt><img width="80" height="80" src="/static/media/image/2016/12/default_big_14.png"/></dt>
<dd>
<h2>django</h2>
<p>bobby</p>
</dd>
</dl>
<div class="btn">
<a class="personcenter fl" href="usercenter-info.html">进入个人中心</a>
<a class="fr" href="/logout/">退出</a>
</div>
</div>
</div>
{% else %}
<!--如果用户未登录-->
<a style="color:white" class="fr registerbtn" href="register.html">注册</a>
<a style="color:white" class="fr loginbtn" href="{% url 'login' %}">登录</a>
{% endif %}
</div>
此处在request.user.is_authenticated中可以拿到信息是因为在django中的setting.py中进行了配置
# 上下文机制
'context_processors': [
'django.template.context_processors.debug',
# request为全局变量,这些值会默认放到所有的html页面中
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
此处有上下文的机制
注:
页面中刷新也可以判断到已经登录,这是因为cookie和session的原因,按F12,点击Applicaiton-Cookies
此处sessionid保证了用户为登录状态
若改变sessionid的过期的时间,使其过期,或者点击上方×按钮进行删除,再刷新页面便不会判断到用户已经登录。
6.登录功能的完善
6.1 手写来判断逻辑
在apps/users/views.py中进行添加:
def post(self, request, *args, **kwargs):
# 在页面上点击F12选中login.html中手机号/邮箱输入栏,可看到input name="username",据此来进行获取
# 获取username,默认值为空
user_name = request.POST.get("username","")
password = request.POST.get("password","")
# 若用户名为空,没有必要执行之后的逻辑
if not user_name:
return render(request, "login.html", {"msg": "请输入用户名"})
# 密码为空
if not password:
return render(request, "login.html", {"msg": "请输入密码"})
if len(password) < 3:
return render(request, "login.html", {"msg": "密码格式不正确"})
在templates/login.html中进行修改:
其中预留了一些错误消息来进行错误的显示:
<div class="error btns login-form-tips" id="jsLoginTips">{{ msg }}</div>
但此方法过为繁琐复杂,可使用表单验证的方法来实现。
6.2 表单验证
需要先进行表单的设计
在user文件夹新建Python file命名为forms:
from django import forms
# 继承Form类
class LoginForm(forms.Form):
# 定义需要验证的表单字段,此处的字段名称要和views.py中的request.POST.get("username","")中一致也就是前端html中的input属性保持一致
# required=True代表必填字段
username = forms.CharField(required=True, min_length=2)
password = forms.CharField(required=True, min_length=3)
只需要设置这2个属性,表单便会通过属性设置来判断提交的值是否符合属性要求
在apps/users/views.py中进行修改:
from apps.users.forms import LoginForm
# 省略
def post(self, request, *args, **kwargs):
# 表单验证
login_form = LoginForm(request.POST)
if login_form.is_valid():
# # 在页面上点击F12选中login.html中手机号/邮箱输入栏,可看到input name="username",据此来进行获取
# # 获取username,默认值为空
# user_name = request.POST.get("username","")
# password = request.POST.get("password","")
# # 若用户名为空,没有必要执行之后的逻辑
# if not user_name:
# return render(request, "login.html", {"msg": "请输入用户名"})
# # 密码为空
# if not password:
# return render(request, "login.html", {"msg": "请输入密码"})
# if len(password) < 3:
# return render(request, "login.html", {"msg": "密码格式不正确"})
# 用于通过用户名和密码查询用户是否存在,使用django内置的方法,因为数据库中存储的密码为密文,所以不能直接使用UserProfile来验证
# 取出表单中的数据
user_name = login_form.cleaned_data["username"]
password = login_form.cleaned_data["password"]
user = authenticate(username=user_name, password=password)
if user is not None:
# 若查询到用户,则进行登录
# Django内部的login方法会自动完成cookie的设置
login(request, user)
# 登录成功之后返回页面
# reverse方法通过url的名称来定位url
return HttpResponseRedirect(reverse("index"))
else:
# 未查询到用户,要将login_form传递回去,保留用户输入的错误的用户名和密码在页面上
return render(request, "login.html", {"msg":"用户名或密码错误", "login_form": login_form})
else:
return render(request, "login.html", {"login_form": login_form})
在templates/login.html中进行修改:
利用form表单来展示错误消息:
<div class="error btns login-form-tips" id="jsLoginTips">{% if login_form.errors %}{% for key, error in login_form.errors.items %}{{ error }}{% endfor %}{% else %}{{ msg }}{% endif %}</div>
实现如果错误,对应的文本框进行标红显示:
<!-- login_form的错误信息error包含password错误,将密码这栏标红显示-->
<div class="form-group marb20 {% if login_form.errors.password %}errorput{% endif %}">
<input name="username" id="account_l" type="text" placeholder="手机号/邮箱" />
</div>
<!-- login_form的错误信息error包含password错误,将密码这栏标红显示-->
<div class="form-group marb8 {% if login_form.errors.password %}errorput{% endif %}">
<input name="password" id="password_l" type="password" placeholder="请输入您的密码" />
</div>
若未查询到用户,要将login_form传递回去,保留用户输入的错误的用户名和密码在页面上,新加一个value值:
<input name="username" id="account_l" value="{{ login_form.username.value }}" type="text" placeholder="手机号/邮箱" />
<input name="password" id="password_l" value="{{ login_form.password.value }}" type="password" placeholder="请输入您的密码" />
6.3 判断用户是否登录
在apps/users/views.py中进行添加:
def get(self, request, *args, **kwargs):
# 在index.html已经判断了用户是否登录,获取此属性is_authenticated
if request.user.is_authenticated:
# 如果已经是登录状态,重定向到首页
return HttpResponseRedirect(reverse("index"))
return render(request, "login.html")
若要退出用户,可以在http://127.0.0.1:8000/xadmin/中进行注销,因为xadmin和django中共用了一套session机制。
也可以像之前将cookie进行删除或改为过期。
6.4 用户退出登录接口的开发
在apps/users/views.py中进行添加:
from django.contrib.auth import authenticate, login, logout
# 注销
class LogoutView(View):
def get(self, request, *args, **kwargs):
logout(request)
return HttpResponseRedirect(reverse("index"))
# 用户登录
# 继承Django内部的View
class LoginView(View):
# 省略
在MxOnline/urls.py中:
进行url的配置:
from apps.users.views import LoginView, LogoutView
urlpatterns = [
# path('admin/', admin.site.urls),
path('xadmin/',xadmin.site.urls),
# 设置直接访问域名的时候直接到此页面
path('', TemplateView.as_view(template_name="index.html"), name="index"),
# 在此处login后要加/,解决访问时http://127.0.0.1:8000/login后面多加/出现的Page not found的问题
# path('login/', TemplateView.as_view(template_name="login.html"), name= "login")
path('login/', LoginView.as_view(), name= "login"),
path('logout/', LogoutView.as_view(), name="logout")
]
在index.html中进行修改:
<a class="fr" href="{% url 'logout' %}">退出</a>
7.动态验证码登录的实现
7.1 短信验证码
使用阿里云接口进行短信的发送:
购买阿里云短信服务,申请签名以及短信模板。(可项目部署上线后进行申请,较好通过)
安装sdk依赖:
打开cmd
workon mxonline
pip install alibabacloud_dysmsapi20170525==2.0.9
在apps文件夹下新家一个Python Package命名为utils,在utils下新建一个Python File命名为AliSms
在apps/utils/AliSms.py中:
# -*- coding: utf-8 -*-
import json
from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models
def send_single_sms(mobile, code):
def create_client():
config = open_api_models.Config(
# AccessKey ID,从用户信息管理-安全信息管理中复制
access_key_id="自行填写",
# AccessKey Secret
access_key_secret="自行填写"
)
# 访问的域名
config.endpoint = f'dysmsapi.aliyuncs.com'
return Dysmsapi20170525Client(config)
client = create_client()
send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
# 电话
phone_numbers=mobile,
# 复制出签名
sign_name='KLearn',
# 复制出模板code
template_code='SMS_238462872',
# 传入模板中的code值
# 使用f格式化,传入code参数,要多加一对括号
template_param=f'{{"code":"{code}"}}'
# template_param='{"code":"9999"}'
)
res = client.send_sms(send_sms_request)
# API返回值
return res
# 当这个程序被导入时,__name__不为__main__,便不会执行以下
if __name__ == "__main__":
res = send_single_sms('18535111908', 2727)
# 转化为字符串,再将单引号变为双引号
res = str(res).replace("'", '"')
# 变为json字典形式
res_json = json.loads(res)
# 取code的值
code = res_json["body"]["Code"]
print(code)
# 取msg的值
msg = res_json["body"]["Message"]
print(msg)
if code == "OK":
print("发送成功")
else:
print("发送失败: {}".format(msg))
print(res)
运行此文件即可收到验证码。
7.2 图片验证码
使用https://github.com/mbi/django-simple-captcha来实现
可查看文档https://django-simple-captcha.readthedocs.io/en/latest/
7.2.1 django-simple-captcha安装
可参考:https://django-simple-captcha.readthedocs.io/en/latest/usage.html#adding-to-a-form
1.进入cmd命令行:
workon mxonline
pip install django-simple-captcha
2.在MxOnline/settings.py中添加:
INSTALLED_APPS = [
#省略
'captcha'
]
3.Tools-Run manage.py Task:
migrate
便在数据库中生成了captcha_captchastore表
4.在MxOnline/urls.py中:
from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include
from django.views.generic import TemplateView
import xadmin
from apps.users.views import LoginView, LogoutView
urlpatterns = [
# path('admin/', admin.site.urls),
path('xadmin/',xadmin.site.urls),
# 设置直接访问域名的时候直接到此页面
path('', TemplateView.as_view(template_name="index.html"), name="index"),
# 在此处login后要加/,解决访问时http://127.0.0.1:8000/login后面多加/出现的Page not found的问题
# path('login/', TemplateView.as_view(template_name="login.html"), name= "login")
path('login/', LoginView.as_view(), name= "login"),
path('logout/', LogoutView.as_view(), name="logout"),
url(r'^captcha/', include('captcha.urls')),
]
依赖Pillow包,之前已经安装过,若在Linux系统中,则需要执行apt-get -y install libz-dev libjpeg-dev libfreetype6-dev python-dev
,再进行Pillow的安装。
7.2.2 添加到html中
在apps/users/forms.py中:
from django import forms
from captcha.fields import CaptchaField
# 继承Form类
class LoginForm(forms.Form):
# 定义需要验证的表单字段,此处的字段名称要和views.py中的request.POST.get("username","")中一致也就是前端html中的input属性保持一致
# required=True代表必填字段
username = forms.CharField(required=True, min_length=2)
password = forms.CharField(required=True, min_length=3)
class DynamicLoginForm(forms.Form):
# 此mobile字段和static/js/login.j中url:"/send_sms/",data:{mobile保持一致
# 最大最小都为11,就是只能输入11位的手机号
mobile = forms.CharField(required=True, min_length=11, max_length=11)
captcha = CaptchaField()
在apps/users/views.py中:
# 省略
from apps.users.forms import LoginForm, DynamicLoginForm
# 省略
# 用户登录
# 继承Django内部的View
class LoginView(View):
# 重载get方法,request为django自动注入的参数
# 有可能传递多个参数,设置接受多变量的方式,可以点入View中从def dispatch中的return中查看源码
def get(self, request, *args, **kwargs):
# 在index.html已经判断了用户是否登录,获取此属性is_authenticated
if request.user.is_authenticated:
# 如果已经是登录状态,重定向到首页
return HttpResponseRedirect(reverse("index"))
# 进行实例化
login_form = DynamicLoginForm()
return render(request, "login.html", {
# 将变量传递进html中
"login_form": login_form
})
7.2.3 将验证码显示到html页面中
在templates/login.html中:
删除此行:
<input autocomplete="off" class="form-control-captcha mobile-register-captcha" id="mobile-register-captcha_1" name="captcha_m_1" placeholder="请输入验证码" type="text"> <input class="form-control-captcha mobile-register-captcha" id="mobile-register-captcha_0" name="captcha_m_0" placeholder="请输入验证码" type="hidden" value="f7ee32b98dff72e7c9248104b81b56c55188ec8a"> <img src="./多米乐首页_files/saved_resource" alt="captcha" class="captcha">
改为:
{{ login_form.captcha }}
运行项目,访问:http://127.0.0.1:8000/login/,便可在动态登录中看到验证码已显示,点击可以更换验证码。
实现刷新验证码的逻辑写在static/js/login.js中。
7.3 结合图片验证码和手机发送短信来完成发送短信验证码
前端逻辑已在static/js/login.js实现。
现编写后端接口,来验证用户输入的手机号码和验证码是否正确。
在apps/utils下新建一个Python File命名为random_str.py,在其中写入:
import string
from random import choice
def generate_random(random_length, type):
'''
随机字符串生成函数
:param random_length:字符串长度
:param type:字符串类型(0:纯数字 or 1:数字+字符 or 2:数字+字符+特殊字符)
:return:生成的随机字符串
'''
#随机字符串种子
if type == 0:
random_seed = string.digits
elif type == 1:
random_seed = string.digits + string.ascii_letters
elif type == 2:
random_seed = string.digits + string.ascii_letters + string.punctuation
random_str = []
while (len(random_str) < random_length):
random_str.append(choice(random_seed))
return ''.join(random_str)
if __name__ == "__main__":
# 随机生成4位数字
print(generate_random(4, 0))
将在apps/utils/AliSms.py中的返回值改为json格式:
# -*- coding: utf-8 -*-
import json
from alibabacloud_dysmsapi20170525.client import Client as Dysmsapi20170525Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_dysmsapi20170525 import models as dysmsapi_20170525_models
def send_single_sms(code, mobile):
def create_client():
config = open_api_models.Config(
# AccessKey ID,从用户信息管理-安全信息管理中复制
access_key_id="自行填写",
# AccessKey Secret
access_key_secret="自行填写"
)
# 访问的域名
config.endpoint = f'dysmsapi.aliyuncs.com'
return Dysmsapi20170525Client(config)
client = create_client()
send_sms_request = dysmsapi_20170525_models.SendSmsRequest(
# 电话
phone_numbers=mobile,
# 复制出签名
sign_name='KLearn',
# 复制出模板code
template_code='SMS_238462872',
# 传入模板中的code值
# 使用f格式化,传入code参数,要多加一对括号
template_param=f'{{"code":"{code}"}}'
# template_param='{"code":"9999"}'
)
# API返回值
res = client.send_sms(send_sms_request)
# 转化为字符串,再将单引号变为双引号
res = str(res).replace("'", '"')
# 变为json字典形式
res_json = json.loads(res)
return res_json
# 当这个程序被导入时,__name__不为__main__,便不会执行以下
if __name__ == "__main__":
res = send_single_sms(2727, '185351111')
# 转化为字符串,再将单引号变为双引号
res = str(res).replace("'", '"')
# 变为json字典形式
res_json = json.loads(res)
# 取code的值
code = res_json["body"]["Code"]
print(code)
# 取msg的值
msg = res_json["body"]["Message"]
print(msg)
if code == "OK":
print("发送成功")
else:
print("发送失败: {}".format(msg))
print(res)
在apps/users/views.py中:
# 发送短信
class SendSmsView(View):
def post(self, request, *args, **kwargs):
send_sms_form = DynamicLoginForm(request.POST)
re_dict = {}
# 验证图片验证码是否正确
if send_sms_form.is_valid():
# 获取手机号码
mobile = send_sms_form.cleaned_data["mobile"]
# 随机生成数字验证码
code = generate_random(4, 0)
# 发送验证码
re_json = send_single_sms(code, mobile=mobile)
# 验证成功
if re_json["body"]["Code"] == "OK":
re_dict["status"] = "success"
r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0, charset="utf8", decode_responses=True)
r.set(str(mobile), code)
# 设置验证码5分钟过期,Redis的特性使得有新的验证码会覆盖之前的
r.expire(str(mobile), 60 * 5)
else:
# 验证失败返回错误信息到前端进行显示
re_dict["msg"] = re_json["body"]["Message"]
else:
# 表单验证失败,返回错误信息
for key, value in send_sms_form.errors.items():
re_dict[key] = value[0]
# 将发送成功或失败的信息返回到前端,前端发起的是ajax请求,不能直接return render,需要使用jason数据进行交互
return JsonResponse(re_dict)
在MxOnline/urls.py中:
# 省略
from django.views.decorators.csrf import csrf_exempt
import xadmin
from apps.users.views import LoginView, LogoutView, SendSmsView
urlpatterns = [
# 省略
# 此处send_sms对应static/js/login.js中的url:"/send_sms/"接口
# 去掉csrf_token的验证。报错误403,但使用之前的方式在form表单中末尾添加{% csrf_token %}的方式无效,因为并不是通过html直接提交,而是使用ajax异步的方式进行发送,经常在前后端分离中使用
url(r'^send_sms/', csrf_exempt(SendSmsView.as_view()), name="send_sms"),
]
在templates/login.html中:(107行左右)
<input class="btn btn-green" id="jsMobileRegBtn" type="button" value="立即登录">
{% csrf_token %}
</form>
运行项目,在页面中点击发送验证码便可以进行发送。
7.4 动态验证码的登录
7.4.1 使用Redis进行验证码的保存
将生成的验证码保存在内存中用来之后比对
在MxOnline/settings.py中:
# 验证码
code_dict = {
"mobile": "code"
}
但这样保存在内存中会出现问题,重启Django,此变量将不存在,且随着验证码越来越多,内存的占用会越来越大
若使用数据库,验证码过期可以在数据库中处理,但代码复杂度高
这里使用Redis,为Key-Value数据库,可以理解为将dict放到内存中管理,Redis为数据库,会进行持久化,尽量使用内存来操作。
Redis的一个特性为在设置值的时候可以设置过期时间,一但过期,值会被Redis自动清理。
除在此问题中,Redis还被大量应用到Web应用中,解决缓存问题。
可在http://doc.redisfans.com/中参考Redis命令
安装Redis:
在Linux系统中:
sudo apt-get install redis-server
sudo apt-get install redis-cli
在Windows中:
下载:https://github.com/ServiceStack/redis-windows/blob/master/downloads/redis-latest.zip
并解压,在解压后的文件夹根目录路径位置输入cmd
启动redis:
redis-server.exe
再新建一个cmd窗口,cd到此目录下
连接到Redis数据库
redis-cli.exe
查看Redis中的值
keys *
刷新,将值清空
flushdb
将mobile的值定为18888888888
set "mobile" "18888888888"
获取变量mobile的值
get "mobile"
通过Python来设置值:
可查看https://github.com/redis/redis-py
需要Python的驱动:
workon mxonline
pip install redis
在MxOnline根目录下新建Python Package名为tools,用来写一些测试脚本
新建一个Python File名为redis-test
import redis
# 设置charset和decode_responses,将值进行类型转换,不然默认为bytes类型,输出为b'123'
r = redis.Redis(host='localhost', port=6379, db=0, charset="utf8", decode_responses=True)
r.set('mobile', '123')
# 可看到mobile的值被修改为123
print(r.get('mobile'))
# 运行,输出123
import redis
# 设置charset和decode_responses,将值进行类型转换,不然默认为bytes类型,输出为b'123'
r = redis.Redis(host='localhost', port=6379, db=0, charset="utf8", decode_responses=True)
r.set('mobile', '123')
# 设置过期时间为1秒
r.expire("mobile", 1)
import time
# 休眠1秒
time.sleep(1)
print(r.get('mobile'))
# 运行,输出None
在MxOnline/settings.py中进行Redis的配置:
# # 验证码
# code_dict = {
# "mobile": "code"
# }
# Redis相关配置
REDIS_HOST = "127.0.0.1"
REDIS_PORT = 6379
在apps/users/views.py中:
# 省略
from django.urls import reverse
import redis
from apps.users.forms import LoginForm, DynamicLoginForm
# 省略
from MxOnline.settings import REDIS_HOST, REDIS_PORT
# Create your views here.
# 发送短信
class SendSmsView(View):
# 省略
# 验证成功
if re_json["code"] == 0:
re_dict["status"] = "success"
r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0, charset="utf8", decode_responses=True)
r.set(str(mobile), code)
# 设置验证码5分钟过期,Redis的特性使得有新的验证码会覆盖之前的
r.expire(str(mobile), 60*5)
# 省略
7.4.2 动态验证码的登录
apps/users/forms.py中末尾添加:
# 省略
import redis
from MxOnline.settings import REDIS_HOST, REDIS_PORT
# 省略
# 登录提交的Form
class DynamicLoginPostForm(forms.Form):
mobile = forms.CharField(required=True, min_length=11, max_length=11)
# 验证码,设置为4位
code = forms.CharField(required=True, min_length=4, max_length=4)
# 添加自己的验证逻辑,只验证code
def clean_code(self):
mobile = self.data.get("mobile")
# 取出code
code = self.data.get("code")
# 在Redis中进行查询
r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0, charset="utf8", decode_responses=True)
redis_code = r.get(str(mobile))
# 若与redis中的code不相等
if code != redis_code:
# 抛出异常
raise forms.ValidationError("验证码不正确")
# 若相等
return self.cleaned_data
在apps/users/views.py中:
# 省略
import redis
from apps.users.forms import LoginForm, DynamicLoginForm, DynamicLoginPostForm
# 省略
from MxOnline.settings import REDIS_HOST, REDIS_PORT
# Create your views here.
# 动态验证码的登录
class DynamicLoginView(View):
def post(self, request, *args, **kwargs):
login_form = DynamicLoginPostForm(request.POST)
dynamic_login = True
if login_form.is_valid():
# 设置没有注册账号仍然可以登录
# 先查询用户是否存在
mobile = login_form.cleaned_data["mobile"]
existed_users = UserProfile.objects.filter(mobile=mobile)
if existed_users:
user = existed_users[0]
else:
# 用户不存在,新建用户
# 用户名为必填字段,用手机号填充
user = UserProfile(username=mobile)
# 此时用户未输入密码,可以进行密码的生成,生成10位
password = generate_random(10, 2)
# 密码为加密的,不能直接赋值明文,使用set_password方法进行密码的加密
user.set_password(password)
# 填入必填字段手机号
user.mobile = mobile
user.save()
# 进行登录
login(request, user)
# 跳转到首页
return HttpResponseRedirect(reverse("index"))
else:
d_form = DynamicLoginForm()
return render(request, "login.html", {"login_form": login_form,
"d_form": DynamicLoginForm,
"dynamic_login": dynamic_login})
在MxOnline/urls.py中:
# 省略
from apps.users.views import LoginView, LogoutView, SendSmsView, DynamicLoginView
urlpatterns = [
# 省略
# 动态登录
path('d_login/', DynamicLoginView.as_view(), name= "d_login"),
# 省略
在templates/login.html中配置action:
<form class="tab-form hide" action="{% url 'd_login' %}" id="mobile_register_form" autocomplete="off" method="post" id="form2">
在templates/login.html中控制报错显示的标签:
账号登录,若为dynamic_login则显示空(不显示),不为dynamic_login才显示,active控制下面的form的class的hide
动态登录相反
<h2 class="{% if dynamic_login %}{% else %}active{% endif %}">账号登录</h2>
<h2 class="{% if dynamic_login %}active{% else %}{% endif %}">动态登录</h2>
<form class="tab-form {% if dynamic_login %}hide{% else %}{% endif %}" action="{% url 'login' %}" method="post" autocomplete="off" id="form1">
<form class="tab-form {% if dynamic_login %}{% else %}hide{% endif %}" action="{% url 'd_login' %}" id="mobile_register_form" autocomplete="off" method="post" id="form2">
当出错时候,在此标签中显示之前填入的手机号和验证码并显示出报错信息:
<input id="jsRegMobile" name="mobile" value="{{ login_form.mobile.value|default_if_none:'' }}" type="text" placeholder="请输入您的手机号码">
<div class="clearfix">
<div class="form-group marb8 verify-code">
<input id="jsPhoneRegCaptcha" name="code" value="{{ login_form.code.value|default_if_none:'' }}" type="text" placeholder="输入手机验证码">
</div>
<input class="verify-code-btn sendcode" id="jsSendCode" value="发送验证码">
</div>
<div class="error btns" id="jsMobileTips" style="">{% if login_form.errors %}{% for key, error in login_form.errors.items %}{{ error }}{% endfor %}{% else %}{{ msg }}{% endif %}</div>
出错时也显示验证码:
<div class="form-group marb20 blur" id="jsRefreshCode">
{{ login_form.captcha }}
{{ d_form.captcha}}
</div>
注:
编写一个View的步骤:
1.编写view代码
2.配置url
3.修改html页面中相关的地址
8.注册功能的实现
将register.html拷贝到templates目录下
在apps/users/views.py中:
# 省略
# Create your views here.
# 用户注册
class RegisterView(View):
def get(self, request, *args, **kwargs):
return render(request, "register.html")
def post(self, request, *args, **kwargs):
return render(request, "register.html")
在MxOnline/urls.py中:
from apps.users.views import LoginView, LogoutView, SendSmsView, DynamicLoginView, RegisterView
urlpatterns = [
# 省略
path('login/', LoginView.as_view(), name= "login"),
# 注册
path('register/', RegisterView.as_view(), name= "register"),
在register.html中:
CTRL+R进行全局替换
将../images
替换为/static/images
点Replace all
将../js
替换为/static/js
点Replace all
将../css
替换为/static/css
点Replace all
在http://127.0.0.1:8000/register/进行注册页面的访问
在apps/users/forms.py中:
# 省略
from MxOnline.settings import REDIS_HOST, REDIS_PORT
# 注册的表单
class RegisterGetForm(forms.Form):
captcha = CaptchaField()
在apps/users/views.py中:
# 省略
from apps.users.forms import LoginForm, DynamicLoginForm, DynamicLoginPostForm
from apps.users.forms import RegisterGetForm
# 省略
# Create your views here.
# 用户注册
class RegisterView(View):
def get(self, request, *args, **kwargs):
register_get_form = RegisterGetForm()
return render(request, "register.html", {
"register_get_form": register_get_form
})
def post(self, request, *args, **kwargs):
return render(request, "register.html")
将templates/register.html中进行修改:
<div class="form-group marb20 blur" id="jsRefreshCode">
{{ register_get_form.captcha }}
</div>
CRTL+F5刷新页面,便可显示验证码。
在apps/users/forms.py中:
# 省略
from MxOnline.settings import REDIS_HOST, REDIS_PORT
from apps.users.models import UserProfile
# 注册的表单
class RegisterGetForm(forms.Form):
captcha = CaptchaField()
class RegisterPostForm(forms.Form):
mobile = forms.CharField(required=True, min_length=11, max_length=11)
# 验证码,设置为4位
code = forms.CharField(required=True, min_length=4, max_length=4)
password = forms.CharField(required=True)
# 验证mobile
def clean_mobile(self):
mobile = self.data.get("mobile")
# 验证手机号码是否已经注册
users = UserProfile.objects.filter(mobile=mobile)
# 若用户已经存在
if users:
# 抛出异常
raise forms.ValidationError("该手机号码已注册")
return mobile
# 添加自己的验证逻辑,只验证code
def clean_code(self):
mobile = self.data.get("mobile")
# 取出code
code = self.data.get("code")
# 在Redis中进行查询
r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0, charset="utf8", decode_responses=True)
redis_code = r.get(str(mobile))
# 若与redis中的code不相等
if code != redis_code:
# 抛出异常
raise forms.ValidationError("验证码不正确")
return code
在apps/users/views.py中:
from apps.users.forms import LoginForm, DynamicLoginForm, DynamicLoginPostForm
from apps.users.forms import RegisterGetForm,RegisterPostForm
# 省略
# Create your views here.
# 用户注册
class RegisterView(View):
def get(self, request, *args, **kwargs):
register_get_form = RegisterGetForm()
return render(request, "register.html", {
"register_get_form": register_get_form
})
def post(self, request, *args, **kwargs):
register_post_form = RegisterPostForm(request.POST)
if register_post_form.is_valid():
mobile = register_post_form.cleaned_data["mobile"]
password = register_post_form.cleaned_data["password"]
# 新建一个用户
user = UserProfile(username=mobile)
user.set_password(password)
user.mobile = mobile
user.save()
# 进行登录
login(request, user)
# 跳转到首页
return HttpResponseRedirect(reverse("index"))
else:
register_get_form = RegisterGetForm()
return render(request, "register.html", {
"register_get_form": register_get_form
})
在templates/register.html中:
<form id="mobile_register_form" method="post" action="{% url 'register' %}" autocomplete="off">
<div class="form-group marb20 ">
<input id="jsRegMobile" name="mobile" type="text" placeholder="请输入您的手机号码">
<input id="jsPhoneRegCaptcha" name="code" type="text" placeholder="输入手机验证码">
<input id="jsPhoneRegPwd" name="password" type="password" placeholder="请输入6-20位非中文字符密码">
<input class="btn btn-green" id="jsMobileRegBtn" type="button" value="注册并登录">
{% csrf_token %}
</form>
将其余问题完善
将页面的跳转改为url模式
在templates/index.html中:
<a style="color:white" class="fr registerbtn" href="{% url 'register' %}">注册</a>
可进行ctrl+shift+f 全局字符串搜索
勾选File mask,选为后缀*.html
将login.html都替换为{% url ‘login’ %}
将register.html都替换为{% url ‘register’ %}
进行注册的错误情况的处理:
在apps/users/views.py中:
# 省略
# 用户注册
class RegisterView(View):
# 省略
else:
register_get_form = RegisterGetForm()
return render(request, "register.html", {
"register_get_form": register_get_form,
"register_post_form": register_post_form
})
显示错误信息:
在templates/register.html中:
<div class="form-group marb20 {% if register_post_form.errors.mobile %}errorput{% endif %}">
<input id="jsRegMobile" name="mobile" type="text" placeholder="请输入您的手机号码">
<div class="form-group marb8 verify-code {% if register_post_form.errors.code %}errorput{% endif %}">
<input id="jsPhoneRegCaptcha" name="code" type="text" placeholder="输入手机验证码">
<div class="error btns" id="jsMobileTips" >{% if register_post_form.errors %}{% for key, error in register_post_form.errors.items %}{{ error }}{% endfor %}{% else %}{{ msg }}{% endif %}</div>
9.Session和Cookie
Session机制:
给用户一串随机字符串(令牌),字符串要满足:
1.足够随机,无法伪造
2.这个字符串由服务器生成
3.这个字符串需要和用户对应起来
服务器生成字符串,并传递给浏览器,浏览器在每一次请求中都带上Session,服务器即可识别用户。
1.登录的过程(Django):
- 查询用户
- login的逻辑(Django内部的login方法)
1.先将用户的基本信息组成json,然后加密生成加密的session字符串
2.随机生成一串长的字符,叫做sessionid
3.将sessionid和session值绑定在一起保存到数据库中,有一张名为django_session的表,登录时才会有数据,退出登录会清空
4.将sessionid写入到cookie中
5.返回请求给浏览器
2.浏览器
- 拿到文本发现里面在cookie中写入了sessionid
- 将cookie中的所有值(key:value)形式,写入到本地存储(文件)
- 后续的针对该网站的所有请求都会代入cookie
3.Django是如何确定某个请求是否登录?
- 拦截器拦截所有的请求
- 在拦截器中发现了在cookie中的sessionid后,通过该sessionid查询到session(在django_session表中查询),从session中解析出用户的id,通过id查询到用户
- 给每个request都设置一个user属性
四、课程机构相关功能开发
1.设置静态文件的前缀为url模式
在templates/index.html中:
<!DOCTYPE html>
<html>
{% load staticfiles %}
<head>
<link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
<script src="{% static 'js/jquery.min.js' %}" type="text/javascript"></script>
其余的,CTRL+R进行全局替换
将/static/
替换为{% static '
再在后面添加' %}
输入:http://127.0.0.1:8000/访问主页可看到页面显示完全
在templates/login.html中也进行同样修改
输入:http://127.0.0.1:8000/login/可看到页面显示完全
在templates/register.html中也进行同样修改
输入:http://127.0.0.1:8000/register/可看到页面显示完全
2.授课机构模块开发
将org-list.html复制到templates中
使用Django的include机制将另一个文件中的内容拿过来
Django提供的模板继承机制比include机制更加强大,可查看Django文档:https://docs.djangoproject.com/zh-hans/2.2/ref/templates/builtins/,查看extends模块。
在templates中新建base.html,将org-list.html中的内容全部拷贝覆盖过来。
在templates/base.html中:
<!DOCTYPE html>
<html>
{% load staticfiles %}
<head>
CTRL+R进行全局替换
将../
替换为{% static '
再在后面添加' %}
若要某一个块需要被其他页面覆盖,需要使用block包起来。
<!--crumbs start-->
{% block custom_bread %}
<!--剪贴到了org-list.html中-->
{% endblock %}
{% block content %}
<!--剪贴到了org-list.html中-->
{% endblock %}
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" >
<title>{% block title %}首页 - 慕学在线网{% endblock %}</title>
<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' %}">
{% block custom_css %}
{% endblock %}
<script src="{% static 'js/deco-common.js' %}" type="text/javascript"></script>
{% block custom_js %}
{% endblock %}
<!--剪贴到了org-list.html中-->
</body>
</html>
在templates/org-list.html中:在此页面中使用base.html
{% extends 'base.html' %}
{% load staticfiles %}
{% block title %}机构列表页-慕学在线网{% endblock %}
{% block custom_bread %}
<section>
<div class="wp">
<ul class="crumbs">
<li><a href="index.html">首页</a>></li>
<li>课程机构</li>
</ul>
</div>
</section>
{% endblock %}
{% block content %}
<section>
<div class="wp butler_list_box list">
<div class='left'>
<div class="listoptions">
<ul>
<li>
<h2>机构类别</h2>
<div class="cont">
<a href="?city="><span class="active2">全部</span></a>
<a href="?ct=pxjg&city="><span class="">培训机构</span></a>
<a href="?ct=gx&city="><span class="">高校</span></a>
<a href="?ct=gr&city="><span class="">个人</span></a>
</div>
</li>
<li>
<h2>所在地区</h2>
<div class="more">更多</div>
<div class="cont">
<a href="?ct="><span class="active2">全部</span></a>
<a href="?city=1&ct="><span class="">北京市</span></a>
<a href="?city=2&ct="><span class="">上海市</span></a>
<a href="?city=3&ct="><span class="">广州市</span></a>
<a href="?city=4&ct="><span class="">深圳市</span></a>
<a href="?city=5&ct="><span class="">天津市</span></a>
</div>
</li>
</ul>
</div>
<div class="all">共<span class="key">15</span>家</div>
<div class="butler_list company list">
<div class="layout">
<div class="head">
<ul class="tab_header">
<li class="active"><a href="?ct=&city=">全部</a> </li>
<li class=""><a href="?sort=students&ct=&city=">学习人数 ↓</a></li>
<li class=""><a href="?sort=courses&ct=&city=">课程数 ↓</a></li>
</ul>
</div>
<dl class="des difdes">
<dt>
<a href="org-detail-homepage.html">
<img width="200" height="120" class="scrollLoading" data-url="{% static 'media/org/2016/11/imooc.png' %}"/>
</a>
</dt>
<dd>
<div class="clearfix">
<a href="org-detail-homepage.html">
<h1>慕课网</h1>
<div class="pic fl">
<img src="{% static 'images/authentication.png' %}"/>
<img src="{% static 'images/gold.png' %}"/>
</div>
</a>
</div>
<ul class="cont">
<li class="first"><p class="pic9">课程数:<span>1</span></p><p class="c7">学习人数:<span>1000</span></p></li>
<li class="c8" style="padding-left:18px;">北京市海淀区中关村北大街</li>
<li class="pic10" style="padding-left:18px;">经典课程:
<a href="/diary/19/">c语言基础入门</a>
<a href="/diary/16/">数据库基础</a>
</li>
</ul>
</dd>
<div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>
</dl>
<dl class="des difdes">
<dt>
<a href="org-detail-homepage.html">
<img width="200" height="120" class="scrollLoading" data-url="{% static 'media/org/2016/11/bjdx.jpg' %}"/>
</a>
</dt>
<dd>
<div class="clearfix">
<a href="org-detail-homepage.html">
<h1>北京大学</h1>
<div class="pic fl">
<img src="{% static 'images/authentication.png' %}"/>
<img src="{% static 'images/gold.png' %}"/>
</div>
</a>
</div>
<ul class="cont">
<li class="first"><p class="pic9">课程数:<span>1</span></p><p class="c7">学习人数:<span>1000</span></p></li>
<li class="c8" style="padding-left:18px;">北京市海淀区中关村北大街</li>
<li class="pic10" style="padding-left:18px;">经典课程:
<a href="/diary/19/">c语言基础入门</a>
<a href="/diary/16/">数据库基础</a>
</li>
</ul>
</dd>
<div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>
</dl>
<dl class="des difdes">
<dt>
<a href="org-detail-homepage.html">
<img width="200" height="120" class="scrollLoading" data-url="{% static 'media/org/2016/11/qhdx-logo.png' %}"/>
</a>
</dt>
<dd>
<div class="clearfix">
<a href="org-detail-homepage.html">
<h1>清华大学</h1>
<div class="pic fl">
<img src="{% static 'images/authentication.png' %}"/>
<img src="{% static 'images/gold.png' %}"/>
</div>
</a>
</div>
<ul class="cont">
<li class="first"><p class="pic9">课程数:<span>1</span></p><p class="c7">学习人数:<span>1000</span></p></li>
<li class="c8" style="padding-left:18px;">北京市海淀区中关村北大街</li>
<li class="pic10" style="padding-left:18px;">经典课程:
<a href="/diary/19/">c语言基础入门</a>
<a href="/diary/16/">数据库基础</a>
</li>
</ul>
</dd>
<div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>
</dl>
<dl class="des difdes">
<dt>
<a href="org-detail-homepage.html">
<img width="200" height="120" class="scrollLoading" data-url="{% static 'media/org/2016/11/njdx.jpg' %}"/>
</a>
</dt>
<dd>
<div class="clearfix">
<a href="org-detail-homepage.html">
<h1>南京大学</h1>
<div class="pic fl">
<img src="{% static 'images/authentication.png' %}"/>
<img src="{% static 'images/gold.png' %}"/>
</div>
</a>
</div>
<ul class="cont">
<li class="first"><p class="pic9">课程数:<span>1</span></p><p class="c7">学习人数:<span>1000</span></p></li>
<li class="c8" style="padding-left:18px;">北京市海淀区中关村北大街</li>
<li class="pic10" style="padding-left:18px;">经典课程:
<a href="/diary/19/">c语言基础入门</a>
<a href="/diary/16/">数据库基础</a>
</li>
</ul>
</dd>
<div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>
</dl>
<dl class="des difdes">
<dt>
<a href="org-detail-homepage.html">
<img width="200" height="120" class="scrollLoading" data-url="{% static 'media/org/2016/11/imooc_klgAUn5.png' %}"/>
</a>
</dt>
<dd>
<div class="clearfix">
<a href="org-detail-homepage.html">
<h1>慕课网2</h1>
<div class="pic fl">
<img src="{% static 'images/authentication.png' %}"/>
<img src="{% static 'images/gold.png' %}"/>
</div>
</a>
</div>
<ul class="cont">
<li class="first"><p class="pic9">课程数:<span>1</span></p><p class="c7">学习人数:<span>1000</span></p></li>
<li class="c8" style="padding-left:18px;">北京市海淀区中关村北大街</li>
<li class="pic10" style="padding-left:18px;">经典课程:
<a href="/diary/19/">c语言基础入门</a>
<a href="/diary/16/">数据库基础</a>
</li>
</ul>
</dd>
<div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>
</dl>
</div>
<div class="pageturn">
<ul class="pagelist">
<li class="active"><a href="?page=1">1</a></li>
<li><a href="?page=2" class="page">2</a></li>
<li><a href="?page=3" class="page">3</a></li>
<li class="long"><a href="?page=2">下一页</a></li>
</ul>
</div>
</div>
</div>
<div class="right companyright">
<div class="head">我要学习</div>
<form class="rightform" id="jsStayForm">
<div>
<img src="{% static 'images/rightform1.png' %}"/>
<input type="text" name="name" id="companyName" placeholder="名字" maxlength="25" />
</div>
<div>
<img src="{% static 'images/rightform2.png' %}"/>
<input type="text" name="mobile" id="companyMobile" placeholder="联系电话"/>
</div>
<div>
<img src="{% static 'images/rightform3.png' %}"/>
<input type="text" name="course_name" id="companyAddress" placeholder="课程名" maxlength="50" />
</div>
<p class="error company-tips" id="jsCompanyTips"></p>
<input class="btn" type="text" id="jsStayBtn" value="立即咨询 >" />
</form>
</div>
<div class="right companyrank layout">
<div class="head">授课机构排名</div>
<dl class="des">
<dt class="num fl">1</dt>
<dd>
<a href="/company/2/"><h1>慕课网</h1></a>
<p>北京市</p>
</dd>
</dl>
<dl class="des">
<dt class="num fl">2</dt>
<dd>
<a href="/company/2/"><h1>慕课网2</h1></a>
<p>深圳市</p>
</dd>
</dl>
<dl class="des">
<dt class="num fl">3</dt>
<dd>
<a href="/company/2/"><h1>北京大学</h1></a>
<p>北京市</p>
</dd>
</dl>
</div>
</div>
</section>
{% endblock %}
{% block custom_js %}
<script>
$(function(){
$(document).ready(function() {
$('#jsStayBtn').on('click', function () {
$.ajax({
cache: false,
type: "POST",
url: "/org/add_ask/",
data: $('#jsStayForm').serialize(),
async: true,
success: function (data) {
if (data.status == 'success') {
$('#jsStayForm')[0].reset();
$('#jsCompanyTips').html("");
alert("提交成功")
} else if (data.status == 'fail') {
$('#jsCompanyTips').html(data.msg)
}
},
});
});
});
})
</script>
{% endblock %}
在apps/organizations/views.py中:
from django.shortcuts import render
from django.views.generic.base import View
# Create your views here.
class OrgView(View):
def get(self, request, *args, **kwargs):
return render(request, "org-list.html")
设置url,在MxOnline/urls.py中:
# 省略
from apps.users.views import LoginView, LogoutView, SendSmsView, DynamicLoginView, RegisterView
from apps.organizations.views import OrgView
urlpatterns = [
# 省略
url(r'^send_sms/', csrf_exempt(SendSmsView.as_view()), name="send_sms"),
# 机构相关页面
url(r'^org_list', OrgView.as_view(), name="org_list"),
]
输入http://127.0.0.1:8000/org_list/,可显示课程机构页面
3.从后台数据库获取数据进行展示
3.1 获取数据并配置
在apps/organizations/views.py中:
from django.shortcuts import render
from django.views.generic.base import View
from apps.organizations.models import CourseOrg
# Create your views here.
class OrgView(View):
def get(self, request, *args, **kwargs):
# 从数据库中获取数据
all_orgs = CourseOrg.objects.all()
return render(request, "org-list.html", {
"all_orgs": all_orgs
})
在templates/org-list.html中:
{% for org in all_orgs %}
<dl class="des difdes">
<dt>
<a href="org-detail-homepage.html">
<img width="200" height="120" class="scrollLoading" data-url="{% static 'media/org/2016/11/imooc.png' %}"/>
</a>
</dt>
<dd>
<div class="clearfix">
<a href="org-detail-homepage.html">
<h1>慕课网</h1>
<div class="pic fl">
<img src="{% static 'images/authentication.png' %}"/>
<img src="{% static 'images/gold.png' %}"/>
</div>
</a>
</div>
<ul class="cont">
<li class="first"><p class="pic9">课程数:<span>1</span></p><p class="c7">学习人数:<span>1000</span></p></li>
<li class="c8" style="padding-left:18px;">北京市海淀区中关村北大街</li>
<li class="pic10" style="padding-left:18px;">经典课程:
<a href="/diary/19/">c语言基础入门</a>
<a href="/diary/16/">数据库基础</a>
</li>
</ul>
</dd>
<div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>
</dl>
{% endfor %}
并将下面的四个<dl class="des difdes">
标签都删掉
在后台管理系统中添加一些数据:
输入http://127.0.0.1:8000/xadmin/
先添加一些城市,再添加一些课程机构
添加课程机构上传完图片保存后,项目根目录下会产生文件夹org,这是因为在
# 课程机构logo
image = models.ImageField(upload_to="org/%Y/%m", verbose_name=u"logo", max_length=100)
中进行了课程机构logo的保存,但点开课程机构此图片无法查看,因为使用的相对路径,未在MxOnline/urls.py中进行配置。
删除掉生成的org文件夹。
此时为了规范文件的存储,将上传的图片都存在media文件夹下,在MxOnline/settings.py中末尾添加:
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
再重启项目,重新上传的课程机构的logo,此时会在media文件夹下生成org文件夹。
此时此图片还无法访问,还需要配置url:
url配置相关文档:https://docs.djangoproject.com/zh-hans/2.2/topics/http/urls/
在MxOnline/urls.py中:
# 省略
from django.contrib import admin
from django.urls import path,re_path
# 省略
from django.views.static import serve
# 省略
from apps.organizations.views import OrgView
from MxOnline.settings import MEDIA_ROOT
urlpatterns = [
# 省略
# 配置上传文件访问的url,此正则表达式的意思为:将media后面的所有字符串截取出来放到path变量中
url(r'media/(?P<path>.*)$', serve, {"document_root": MEDIA_ROOT}),
# 省略
]
此时课程机构的logo便可以显示。
再将后台数据显示到前端页面中,
使用上下文机制,将media变量配置到全局的html中:
在MxOnline/settings.py中:
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',
# request为全局变量,这些值会默认放到所有的html页面中
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
# 配置全局media的url,可点入源码查看
'django.template.context_processors.media'
],
},
},
]
在templates/org-list.html中:
<dd>
<div class="clearfix">
<a href="org-detail-homepage.html">
<h1>{{ org.name }}</h1>
<div class="pic fl">
<ul class="cont">
<li class="first"><p class="pic9">课程数:<span>{{ org.course_nums }}</span></p><p class="c7">学习人数:<span>{{ org.students }}</span></p></li>
<li class="c8" style="padding-left:18px;">{{ org.address }}</li>
<li class="pic10" style="padding-left:18px;">经典课程:
{% for org in all_orgs %}
<dl class="des difdes">
<dt>
<a href="org-detail-homepage.html">
<img width="200" height="120" class="scrollLoading" data-url="{{ MEDIA_URL }}{{ org.image }}"/>
</a>
</dt>
右上角共多少家机构的显示:
在apps/organizations/views.py中:
from django.shortcuts import render
from django.views.generic.base import View
from apps.organizations.models import CourseOrg
# Create your views here.
class OrgView(View):
def get(self, request, *args, **kwargs):
# 从数据库中获取数据
all_orgs = CourseOrg.objects.all()
# 会执行select语句,查出有多少家机构
org_nums = CourseOrg.objects.count()
return render(request, "org-list.html", {
"all_orgs": all_orgs,
"org_nums": org_nums
})
再配置到html中:
在templates/org-list.html中:
<div class="all">共<span class="key">{{org_nums}}</span>家</div>
<div class="butler_list company list">
页面部分地区的筛选的城市数据要从后端获取:
from django.shortcuts import render
from django.views.generic.base import View
from apps.organizations.models import CourseOrg
from apps.organizations.models import City
# Create your views here.
class OrgView(View):
def get(self, request, *args, **kwargs):
# 从数据库中获取数据
all_orgs = CourseOrg.objects.all()
# 会执行select语句,查出有多少家机构
org_nums = CourseOrg.objects.count()
all_citys = City.objects.all()
return render(request, "org-list.html", {
"all_orgs": all_orgs,
"org_nums": org_nums,
"all_citys": all_citys,
})
在前端页面中for循环,在templates/org-list.html中:
<h2>所在地区</h2>
<div class="more">更多</div>
<div class="cont">
<a href="?ct="><span class="active2">全部</span></a>
{% for city in all_citys %}
<a href="?city=1&ct="><span class="">{{ city.name }}</span></a>
{% endfor %}
</div>
</li>
</ul>
</div>
<div class="all">共<span class="key">{{org_nums}}</span>家</div>
便可读取后台的城市数据进行显示。
3.2 设置是否为认证机构、金牌机构
在apps/organizations/models.py中:
# 省略
course_nums = models.IntegerField(default=0, verbose_name="课程数")
# 是否为认证机构
is_auth = models.BooleanField(default=False, verbose_name="是否认证")
# 是否为金牌机构
is_gold = models.BooleanField(default=False, verbose_name="是否金牌")
# 设计一个外键,在后台添加城市
city = models.ForeignKey(City, on_delete=models.CASCADE, verbose_name=u"所在城市")
# 省略
菜单栏-Tools-Run manage.py Task
输入:makemigrations
migrate
在后台管理系统中:http://127.0.0.1:8000/xadmin
便可看到新增了字段
在templates/org-list.html中:
<div class="clearfix">
<a href="org-detail-homepage.html">
<h1>{{ org.name }}</h1>
<div class="pic fl">
{% if org.is_auth %}
<img src="{% static 'images/authentication.png' %}"/>
{% endif %}
{% if org.is_gold %}
<img src="{% static 'images/gold.png' %}"/>
{% endif %}
</div>
</a>
</div>
3.3 经典课程的设置
在course的model中,设置了外键为teacher,course属于teacher,如果要查询和机构相关的课程,要查询到course相关的teacher。先找到机构下的teacher,再找course。
为了方便,添加外键,指向CourseOrg,也可以优化数据库性能,避免join操作。
在apps/courses/models.py中:
# 省略
from apps.organizations.models import Teacher
from apps.organizations.models import CourseOrg
# 省略
# 课程
class Course(BaseModel):
# 添加外键
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE, verbose_name="讲师")
course_org = models.ForeignKey(CourseOrg, null=True, blank= True, on_delete=models.CASCADE, verbose_name="课程机构")
菜单栏-Tools-Run manage.py Task
输入:makemigrations
migrate
添加完外键之后,在后台管理系统中先添加教师,再添加课程信息:http://127.0.0.1:8000/xadmin/
在apps/organizations/models.py中:
# 设计一个外键,在后台添加城市
city = models.ForeignKey(City, on_delete=models.CASCADE, verbose_name=u"所在城市")
# 获取机构对应的课程
def courses(self):
# from apps.courses.models import Course
# courses = Course.objects.filter(course_org=self)
# return courses
# 使用此属性course_set,避免相互import,前半部分course为model的名称,当前的model为CourseOrg是Course的外键,此时可以通过CourseOrg反向取到Course
courses = self.course_set.all()
return courses
在templates/org-list.html中:(80行左右处)
<ul class="cont">
<li class="first"><p class="pic9">课程数:<span>{{ org.course_nums }}</span></p><p class="c7">学习人数:<span>{{ org.students }}</span></p></li>
<li class="c8" style="padding-left:18px;">{{ org.address }}</li>
<li class="pic10" style="padding-left:18px;">经典课程:
{% for course in org.courses %}
<a href="/diary/19/">{{ course.name }}</a>
{% endfor %}
</li>
</ul>
注释掉约99-235行写死在html中的其他机构
{# <dl class="des difdes">#}
{# <dt>#}
{# <a href="org-detail-homepage.html">#}
{# <img width="200" height="120" class="scrollLoading" data-url="{% static 'media/org/2016/11/bjdx.jpg' %}"/>#}
{# </a>#}
{# </dt>#}
{# <dd>#}
{# <div class="clearfix">#}
{# <a href="org-detail-homepage.html">#}
{# <h1>北京大学</h1>#}
{# <div class="pic fl">#}
{##}
{# <img src="{% static 'images/authentication.png' %}"/>#}
{##}
{# <img src="{% static 'images/gold.png' %}"/>#}
{##}
{# </div>#}
{# </a>#}
{# </div>#}
{# <ul class="cont">#}
{# <li class="first"><p class="pic9">课程数:<span>1</span></p><p class="c7">学习人数:<span>1000</span></p></li>#}
{# <li class="c8" style="padding-left:18px;">北京市海淀区中关村北大街</li>#}
{# <li class="pic10" style="padding-left:18px;">经典课程:#}
{##}
{# <a href="/diary/19/">c语言基础入门</a>#}
{##}
{# <a href="/diary/16/">数据库基础</a>#}
{##}
{# </li>#}
{# </ul>#}
{# </dd>#}
{# <div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>#}
{# </dl>#}
{##}
{# <dl class="des difdes">#}
{# <dt>#}
{# <a href="org-detail-homepage.html">#}
{# <img width="200" height="120" class="scrollLoading" data-url="{% static 'media/org/2016/11/qhdx-logo.png' %}"/>#}
{# </a>#}
{# </dt>#}
{# <dd>#}
{# <div class="clearfix">#}
{# <a href="org-detail-homepage.html">#}
{# <h1>清华大学</h1>#}
{# <div class="pic fl">#}
{##}
{# <img src="{% static 'images/authentication.png' %}"/>#}
{##}
{# <img src="{% static 'images/gold.png' %}"/>#}
{##}
{# </div>#}
{# </a>#}
{# </div>#}
{# <ul class="cont">#}
{# <li class="first"><p class="pic9">课程数:<span>1</span></p><p class="c7">学习人数:<span>1000</span></p></li>#}
{# <li class="c8" style="padding-left:18px;">北京市海淀区中关村北大街</li>#}
{# <li class="pic10" style="padding-left:18px;">经典课程:#}
{# {% for course in org.courses %}#}
{# <a href="/diary/19/">{{ course.name }}</a>#}
{# {% endfor %}#}
{# <a href="/diary/19/">c语言基础入门</a>#}
{##}
{# <a href="/diary/16/">数据库基础</a>#}
{##}
{# </li>#}
{# </ul>#}
{# </dd>#}
{# <div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>#}
{# </dl>#}
{##}
{# <dl class="des difdes">#}
{# <dt>#}
{# <a href="org-detail-homepage.html">#}
{# <img width="200" height="120" class="scrollLoading" data-url="{% static 'media/org/2016/11/njdx.jpg' %}"/>#}
{# </a>#}
{# </dt>#}
{# <dd>#}
{# <div class="clearfix">#}
{# <a href="org-detail-homepage.html">#}
{# <h1>南京大学</h1>#}
{# <div class="pic fl">#}
{##}
{# <img src="{% static 'images/authentication.png' %}"/>#}
{##}
{# <img src="{% static 'images/gold.png' %}"/>#}
{##}
{# </div>#}
{# </a>#}
{# </div>#}
{# <ul class="cont">#}
{# <li class="first"><p class="pic9">课程数:<span>1</span></p><p class="c7">学习人数:<span>1000</span></p></li>#}
{# <li class="c8" style="padding-left:18px;">北京市海淀区中关村北大街</li>#}
{# <li class="pic10" style="padding-left:18px;">经典课程:#}
{##}
{# <a href="/diary/19/">c语言基础入门</a>#}
{##}
{# <a href="/diary/16/">数据库基础</a>#}
{##}
{# </li>#}
{# </ul>#}
{# </dd>#}
{# <div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>#}
{# </dl>#}
{##}
{# <dl class="des difdes">#}
{# <dt>#}
{# <a href="org-detail-homepage.html">#}
{# <img width="200" height="120" class="scrollLoading" data-url="{% static 'media/org/2016/11/imooc_klgAUn5.png' %}"/>#}
{# </a>#}
{# </dt>#}
{# <dd>#}
{# <div class="clearfix">#}
{# <a href="org-detail-homepage.html">#}
{# <h1>慕课网2</h1>#}
{# <div class="pic fl">#}
{##}
{# <img src="{% static 'images/authentication.png' %}"/>#}
{##}
{# <img src="{% static 'images/gold.png' %}"/>#}
{##}
{# </div>#}
{# </a>#}
{# </div>#}
{# <ul class="cont">#}
{# <li class="first"><p class="pic9">课程数:<span>1</span></p><p class="c7">学习人数:<span>1000</span></p></li>#}
{# <li class="c8" style="padding-left:18px;">北京市海淀区中关村北大街</li>#}
{# <li class="pic10" style="padding-left:18px;">经典课程:#}
{##}
{# <a href="/diary/19/">c语言基础入门</a>#}
{##}
{# <a href="/diary/16/">数据库基础</a>#}
{##}
{# </li>#}
{# </ul>#}
{# </dd>#}
{# <div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>#}
{# </dl>#}
此时在:http://127.0.0.1:8000/org_list/
便可看到从数据库中读取出的经典课程
判断是否为经典课程
在apps/courses/models.py中:
# 省略
class Course(BaseModel):
# 省略
teacher_tell = models.CharField(default="", max_length=300, verbose_name="老师告诉你")
# 是否为经典课程
is_classics = models.BooleanField(default=False, verbose_name="是否经典")
菜单栏-Tools-Run manage.py Task
输入:makemigrations
migrate
再设置从数据库中进行数据的查询
在apps/organizations/models.py中:
# 设计一个外键,在后台添加城市
city = models.ForeignKey(City, on_delete=models.CASCADE, verbose_name=u"所在城市")
# 获取机构对应的课程
def courses(self):
# from apps.courses.models import Course
# courses = Course.objects.filter(course_org=self)
# return courses
# 使用此属性course_set,避免相互import,前半部分course为model的名称,当前的model为CourseOrg是Course的外键,此时可以通过CourseOrg反向取到Course
# 是否为经典课程,进行切片,防止数据显示出问题,设置只显示3个
courses = self.course_set.filter(is_classics=True)[:3]
return courses
再在后台管理系统中http://127.0.0.1:8000/xadmin/
将之前添加的课程进行编辑,勾选为是经典课程,便可在页面上http://127.0.0.1:8000/org_list/
显示出此经典课程
若没有经典课程的话,显示为无:
在templates/org-list.html中:(80行左右处)
<ul class="cont">
<li class="first"><p class="pic9">课程数:<span>{{ org.course_nums }}</span></p><p class="c7">学习人数:<span>{{ org.students }}</span></p></li>
<li class="c8" style="padding-left:18px;">{{ org.address }}</li>
<li class="pic10" style="padding-left:18px;">经典课程:
{% if org.courses %}
{% for course in org.courses %}
<a href="/diary/19/">{{ course.name }}</a>
{% endfor %}
{% else %}
无
{% endif %}
</li>
</ul>
3.4 数据过多时进行分页
进行分页较为复杂,可使用Django现有的app:
https://github.com/jamespacileo/django-pure-pagination
可参考其中的文档参考使用方式。
在cmd命令行中:
workon mxonline
pip install django-pure-pagination
并记录在requirements.txt中:
django==2.2
mysqlclient
pillow
django-pure-pagination
在MxOnline/settings.py中:
INSTALLED_APPS = [
# 省略
'captcha',
'pure_pagination'
]
在末尾加:
# 分页相关设置
PAGINATION_SETTINGS = {
'PAGE_RANGE_DISPLAYED': 10,
'MARGIN_PAGES_DISPLAYED': 2,
'SHOW_FIRST_PAGE_WHEN_INVALID': True,
}
在apps/organizations/views.py中:
from django.shortcuts import render
from django.views.generic.base import View
from pure_pagination import Paginator, EmptyPage, PageNotAnInteger
from apps.organizations.models import CourseOrg
from apps.organizations.models import City
# Create your views here.
class OrgView(View):
def get(self, request, *args, **kwargs):
# 从数据库中获取数据
all_orgs = CourseOrg.objects.all()
# 会执行select语句,查出有多少家机构
org_nums = CourseOrg.objects.count()
all_citys = City.objects.all()
# 对课程机构数据进行分页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# per_page为每页显示几条
p = Paginator(all_orgs, per_page=5, request=request)
orgs = p.page(page)
return render(request, "org-list.html", {
"all_orgs": orgs,
"org_nums": org_nums,
"all_citys": all_citys,
})
在templates/org-list.html中:
修改60行左右:
{% for org in all_orgs.object_list %}
<dl class="des difdes">
<div class="pageturn">
<ul class="pagelist">
<!-- 如果有上一页 -->
{% if all_orgs.has_previous %}
<!-- querystring方法自动生成上一页的链接 -->
<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">...</li>
{% endif %}
{% endfor %}
<!-- 如果还有下一页 -->
{% if all_orgs.has_next %}
<li class="long"><a href="?{{ all_orgs.next_page_number.querystring }}">下一页</a></li>
{% endif %}
{# <li class="active"><a href="?page=1">1</a></li>#}
{# <li><a href="?page=2" class="page">2</a></li>#}
{# <li><a href="?page=3" class="page">3</a></li>#}
{# <li class="long"><a href="?page=2">下一页</a></li>#}
</ul>
</div>
在后台管理系统:http://127.0.0.1:8000/xadmin/organizations/courseorg/
中多添加一些课程机构
访问:http://127.0.0.1:8000/org_list/
可看到底部页码已可以显示,并可以进行翻页。
3.5 根据顶部机构类别和所在地区进行筛选
在apps/organizations/models.py中:
# 省略
# 课程机构
class CourseOrg(BaseModel):
name = models.CharField(max_length=50, verbose_name="机构名称")
# 课程描述为富文本,先暂时设为TextField()
desc = models.TextField(verbose_name="描述")
tag = models.CharField(default="pxjg", verbose_name="机构类别", max_length=4,
choices=(("pxjg","培训机构"), ("gr", "个人"), ("gx", "高校")))
category = models.CharField(default="pxjg", verbose_name="机构类别", max_length=4,
choices=(("pxjg", "培训机构"), ("gr", "个人"), ("gx", "高校")))
菜单栏-Tools-Run manage.py Task
输入:makemigrations
migrate
在apps/organizations/views.py中:
from django.shortcuts import render
from django.views.generic.base import View
from pure_pagination import Paginator, EmptyPage, PageNotAnInteger
from apps.organizations.models import CourseOrg
from apps.organizations.models import City
# Create your views here.
class OrgView(View):
def get(self, request, *args, **kwargs):
# 从数据库中获取数据
all_orgs = CourseOrg.objects.all()
all_citys = City.objects.all()
# 通过机构类别对课程机构进行筛选,参数名称ct和html中对应
category = request.GET.get("ct", "")
if category:
all_orgs = all_orgs.filter(category=category)
# 放在此处进行统计,显示出筛选后的数量
# 会执行select语句,查出有多少家机构
org_nums = all_orgs.count()
# 对课程机构数据进行分页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# per_page为每页显示几条
p = Paginator(all_orgs, per_page=5, request=request)
orgs = p.page(page)
return render(request, "org-list.html", {
"all_orgs": orgs,
"org_nums": org_nums,
"all_citys": all_citys,
"category" : category,
})
点击筛选选项后,改为选中的状态:
在templates/org-list.html中:
<div class="listoptions">
<ul>
<li>
<h2>机构类别</h2>
<div class="cont">
<!-- active2为选中状态的设置,已在css中写好 -->
<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>
</li>
<li>
<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_citys %}
<!-- city_id一个为字符串一个为数字,不会相等,使用stringformat:'i'将int类型转换为字符串-->
<a href="?city={{ city.id }}&ct={{ category }}"><span class="{% ifequal city_id city.id|stringformat:'i' %}active2{% endifequal %}">{{ city.name }}</span></a>
{% endfor %}
</div>
</li>
</ul>
</div>
运行项目,访问:http://127.0.0.1:8000/org_list
便可完成对机构类别和所在地区的组合筛选
可在Navicat中对organizations_courseorg表的category字段进行修改,显示效果更为直观,可将默认的pxjg改为gr或gx。
3.6 课程机构的排序
在apps/organizations/views.py中:
# 省略
# Create your views here.
class OrgView(View):
def get(self, request, *args, **kwargs):
# 省略
# 通过所在城市对课程机构进行筛选
city_id = request.GET.get("city", "")
if city_id:
# 前端传来的是字符串,要做int转换,但输入url可能会抛异常,要判断是否为数字
if city_id.isdigit():
# model中没有city_id字段,但外键在数据库中存储时会生成city_id字段
all_orgs = all_orgs.filter(city_id=int(city_id))
# 对机构进行排序
sort = request.GET.get("sort", "")
if sort == "students":
# 对学习人数进行排序,前面的-代表倒序
all_orgs = all_orgs.order_by("-students")
elif sort == "courses":
# 对课程数量进行排序,course_nums对应model中的字段
all_orgs = all_orgs.order_by("-course_nums")
# 省略
return render(request, "org-list.html", {
# 省略
"city_id" : city_id,
"sort": sort,
})
在templates/org-list.html中(50行左右):
<div class="head">
<ul class="tab_header">
<li class="{% if sort == '' %}active{% endif %}"><a href="?ct=&city=">全部</a> </li>
<!-- 填入category和city_id参数,防止筛选条件重置 -->
<li class="{% if sort == 'students' %}active{% endif %}"><a href="?sort=students&ct={{ category }}&city={{ city_id }}">学习人数 ↓</a></li>
<li class="{% if sort == 'courses' %}active{% endif %}"><a href="?sort=courses&ct={{ category }}&city={{ city_id }}">课程数 ↓</a></li>
</ul>
</div>
便可完成对课程机构的排序
运行项目,访问:http://127.0.0.1:8000/org_list
进行查看
可在Navicat中对organizations_courseorg表的students和course_nums字段进行修改,显示效果更为直观。
3.7 右边栏广告位课程机构排序
可根据model中click_nums字段进行排序
在apps/organizations/views.py中:
# 省略
# Create your views here.
class OrgView(View):
def get(self, request, *args, **kwargs):
# 从数据库中获取数据
all_orgs = CourseOrg.objects.all()
all_citys = City.objects.all()
# 根据点击数进行机构排序,右边栏广告位有限,做切片
hot_org = all_orgs.order_by("-click_nums")[:3]
# 通过机构类别对课程机构进行筛选,参数名称ct和html中对应
# 省略
return render(request, "org-list.html", {
# 省略
"sort": sort,
"hot_orgs" : hot_org,
})
在templates/org-list.html中(290行左右):
<div class="right companyrank layout">
<div class="head">授课机构排名</div>
{% for org in hot_orgs %}
<dl class="des">
<!-- 使用forloop.counter来计数是第几次循环,显示出图片上标注是第几名 -->
<dt class="num fl">{{ forloop.counter }}</dt>
<dd>
<a href="/company/2/"><h1>{{ org.name }}</h1></a>
<p>{{ org.address }}</p>
</dd>
</dl>
{% endfor %}
</div>
</div>
运行项目,访问:http://127.0.0.1:8000/org_list
进行查看
3.8 子url的配置
进行子url的管理,防止混乱
在apps/organizations目录下新家一个Python File命名为url.py:
from django.conf.urls import url
from apps.organizations.views import OrgView
urlpatterns = [
# 代表以list结尾的数据
url(r'^list/$', OrgView.as_view(), name="list"),
]
并在MxOnline/urls.py中指向此文件:
# 省略
urlpatterns = [
# 省略
# 机构相关页面
# 进行子url的管理
url(r'^org/', include('apps.organizations.urls', namespace="org")),
]
运行项目,访问的url由http://127.0.0.1:8000/org_list
变为http://127.0.0.1:8000/org/list/
再进行首页html中配置跳转:
在templates/index.html中(90行左右):
<!-- :前为命名空间namespace-->
<li ><a href="{% url 'org:list' %}">授课机构</a></li>
访问首页:http://127.0.0.1:8000/
点击授课机构便可以进行跳转。
3.9 右边栏我要学习(咨询)栏目的提交
通过modelform来完成表单的提交,可以从model直接生成form
在apps/organizations目录下新建一个Python File命名为forms.py
import re
from django import forms
from apps.operations.models import UserAsk
# # 使用普通模式
# class AddAskForm(forms):
# name = forms.CharField(required=True, min_length=2, max_length=20)
# # 设置只能11位
# mobile = forms.CharField(required=True, min_length=11, max_length=11)
# course_name = forms.CharField(required=True, min_length=2, max_length=20)
# 使用modelform
class AddAskForm(forms.ModelForm):
# UserAsk没有限制mobile长度,在此限制
mobile = forms.CharField(max_length=11, min_length=11, required=True)
# 指明继承哪个model
class Meta:
model = UserAsk
# 指明将哪些字段生成form
fields = ["name", "mobile", "course_name"]
# 对字段进行表单验证
# 验证手机号码是否合法
def clean_mobile(self):
mobile = self.cleaned_data["mobile"]
# 正则表达式
regx_mobile = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"
p = re.compile(regx_mobile)
if p.match(mobile):
return mobile
else:
raise forms.ValidationError("手机号码非法", code="mobile_invalid")
在apps/organizations/views.py中:
from django.shortcuts import render
from django.views.generic.base import View
from pure_pagination import Paginator, EmptyPage, PageNotAnInteger
from django.http import JsonResponse
from apps.organizations.models import CourseOrg
from apps.organizations.models import City
from apps.organizations.forms import AddAskForm
# 右边栏我要学习(咨询)表单,处理用户咨询
class AddAskView(View):
# 只需要提交数据,使用post
def post(self, request, *args, **kwargs):
userask_form = AddAskForm(request.POST)
# 验证表单,如果没有问题,保存数据
if userask_form.is_valid():
# 交给数据库执行保存
userask_form.save(commit=True)
# 返回json数据
return JsonResponse({
"status":"success"
})
else:
return JsonResponse({
"status": "fail",
"msg":"添加出错"
})
# Create your views here.
# 省略
在apps/organizations/urls.py中:
定义一个url
from django.conf.urls import url
from apps.organizations.views import OrgView, AddAskView
urlpatterns = [
# 代表以list结尾的数据
url(r'^list/$', OrgView.as_view(), name="list"),
url(r'^add_ask/$', AddAskView.as_view(), name="add_ask"),
]
在templates/org-list.html中:
使用Ajax技术,前端不会刷新页面。
<script>
$(function(){
$(document).ready(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();
$('#jsCompanyTips').html("");
alert("提交成功")
} else if (data.status == 'fail') {
$('#jsCompanyTips').html(data.msg)
}
},
});
});
});
})
</script>
<input class="btn" type="text" id="jsStayBtn" value="立即咨询 >" />
{% csrf_token %}
</form>
4.课程机构的详情页面
4.1 机构首页
将页面
org-detail-course.html
org-detail-desc.html
org-detail-homepage.html
org-detail-teachers.html
拷贝到templates目录下
课程机构首页显示:
在apps/organizations/views.py中:
from django.shortcuts import render
from django.views.generic.base import View
from pure_pagination import Paginator, EmptyPage, PageNotAnInteger
from django.http import JsonResponse
from apps.organizations.models import CourseOrg
from apps.organizations.models import City
from apps.organizations.forms import AddAskForm
# 课程机构首页显示
class OrgHomeView(View):
# 在url中进行了配置,要与其中的参数名称保持一致org_id
def get(self, request, org_id, *args, **kwargs):
return render(request, "org-detail-homepage.html")
# 右边栏我要学习(咨询)表单,处理用户咨询
# 省略
在apps/organizations/urls.py中:
from django.conf.urls import url
from django.urls import path
from apps.organizations.views import OrgView, AddAskView, OrgHomeView
urlpatterns = [
# 代表以list结尾的数据
url(r'^list/$', OrgView.as_view(), name="list"),
url(r'^add_ask/$', AddAskView.as_view(), name="add_ask"),
# 传递id,用正则表达式,取后边的数字
url(r'^(?P<org_id>\d+)/$', OrgHomeView.as_view(), name="home"),
# # 或者可以使用此种模式,效果相同
# path('<int:org_id>/', OrgHomeView.as_view(), name= "home"),
]
此时输入:http://127.0.0.1:8000/org/1/
可显示页面,说明此种url方式配置正确
在templates/org-detail-homepage.html中:
<!DOCTYPE html>
<html>
{% load staticfiles %}
<head>
<meta charset="UTF-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" >
<title>机构首页</title>
<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' %}">
<link rel="stylesheet" type="text/css" href="{% static 'js/plugins/queryCity/css/cityLayout.css' %}">
<script src="{% static 'js/jquery.min.js' %}" type="text/javascript"></script>
<script src="{% static 'js/jquery-migrate-1.2.1.min.js' %}" type="text/javascript"></script>
进行html页面的重构:
org-detail-homepage.html中的子页面共用一个头部,用页面继承来实现。
写一个base页面,在templates文件夹下新建一个org_base.html
将org-detail-homepage.html中的内容复制过来进行修改。
<!DOCTYPE html>
<html>
{% load staticfiles %}
<head>
<meta charset="UTF-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" >
<title>{% block title %}机构首页{% endblock %}</title>
<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' %}">
<link rel="stylesheet" type="text/css" href="{% static 'js/plugins/queryCity/css/cityLayout.css' %}">
{% block custom_css %}{% endblock %}
<script src="{% static 'js/jquery.min.js' %}" type="text/javascript"></script>
<script src="{% static 'js/jquery-migrate-1.2.1.min.js' %}" type="text/javascript"></script>
</head>
<body>
<section class="headerwrap headerwrap2">
<header>
<div class="header2 header">
<div class="top">
<div class="wp">
<div class="fl">
<p>联系方式:<b>3333333333</b></p>
<!--如果用户已经登录-->
{% if request.user.is_authenticated %}
<div class="personal">
<dl class="user fr">
<dd>bobby<img class="down fr" src="{% static 'images/top_down.png' %}"/></dd>
<dt><img width="20" height="20" src="{% static 'media/image/2016/12/default_big_14.png' %}"/></dt>
</dl>
<div class="userdetail">
<dl>
<dt><img width="80" height="80" src="{% static 'media/image/2016/12/default_big_14.png' %}"/></dt>
<dd>
<h2>django</h2>
<p>bobby</p>
</dd>
</dl>
<div class="btn">
<a class="personcenter fl" href="usercenter-info.html">进入个人中心</a>
<a class="fr" href="{% url 'logout' %}">退出</a>
</div>
</div>
</div>
{% else %}
<!--如果用户未登录-->
<a style="color:white" class="fr registerbtn" href="{% url 'register' %}">注册</a>
<a style="color:white" class="fr loginbtn" href="{% url 'login' %}">登录</a>
{% endif %}
</div>
</div>
</div>
<div class="middle companyheader">
<div class="wp">
<img class="fl" style="width: 112px;height: 103px" src="../media/org/2016/11/imooc.png"/>
<div class="head fl">
<h1>
慕课网
<img src="{% static 'images/authentication.png' %}"/>
<img src="{% static 'images/gold.png' %}"/>
</h1>
<p class="fl">
<span class="fl" style="margin-top:8px;color:#848484;">推荐指数: </span>
<span class="precision company-credit" data-star-scope="5.0"></span>
<span class="key">5.0</span>
</p>
</div>
<div class="btn fr collectionbtn notlogin
"data-favid="22" data-fav-type="1">
收藏
</div>
</div>
</div>
</div>
</header>
</section>
<section>
<div class="wp">
<ul class="crumbs">
<li><a href="{% url 'index'%}">首页</a>></li>
<li><a href="{% url 'org:list' %}">课程机构</a>></li>
<li>{% block path_path %}机构首页{% endblock %}</li>
</ul>
</div>
</section>
<section>
<div class="wp list personal_list comp">
<div class="left">
<ul>
<li class="active2"><a href="org-detail-homepage.html">机构首页</a></li>
<li class=""><a href="org-detail-course.html">机构课程</a></li>
<li class=""><a href="org-detail-desc.html">机构介绍</a></li>
<li class=""><a href="org-detail-teachers.html">机构讲师</a></li>
</ul>
</div>
{% block right_form %}
{% endblock %}
<div class="right companycenter layout" >
<div class="head">
<h1>机构教师</h1>
<a class="green fr more" href="org-detail-teachers.html">查看更多 > </a>
</div>
<div class="diarys">
<div class="module5 share company-diary-box" style="padding:10px 0;">
<div class="left">
<img class="pic" src="../media/teacher/2016/11/aobama_CXWwMef.png"/>
<p>昵称:bobby</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>
</div>
<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"> <p> </p><h1 class="ue_t" label="Title center" name="tc" style="border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;text-align:center;margin:0px 0px 20px;"><span style="color:#c0504d;">[键入文档标题]</span></h1><p style="text-align:center;"><strong class="ue_t">[键入文档副标题]</strong></p><h3><span class="ue_t" style="font-family:幼圆">[标题 1]</span></h3><p class="ue_t" style="text-indent:2em;">对于“插入”选项卡上的库,在设计时都充分考虑了其中的项与文档整体外观的协调性。 您可以使用这些库来插入表格、页眉、页脚、列表、封面以及其他文档构建基块。 您创建的图片、图表或关系图也将与当前的文档外观协调一致。</p><p class="ue_t" style="text-indent:2em;"><img src="../media/courses/ueditor/57aa86a0000145c512000460_20161210234050_865.jpg" title="" alt="57aa86a0000145c512000460.jpg"/> </p><h3><span class="ue_t" style="font-family:幼圆">[标题 2]</span></h3><p><img src="http://api.map.baidu.com/staticimage?center=116.410899,39.863624&zoom=11&width=530&height=340&markers=116.404,39.915" width="530" height="340"/> </p><p class="ue_t" style="text-indent:2em;">在“开始”选项卡上,通过从快速样式库中为所选文本选择一种外观,您可以方便地更改文档中所选文本的格式。 您还可以使用“开始”选项卡上的其他控件来直接设置文本格式。大多数控件都允许您选择是使用当前主题外观,还是使用某种直接指定的格式。</p><h3><span class="ue_t" style="font-family:幼圆">[标题 3]</span></h3><p>2016-12-10</p><p class="ue_t">对于“插入”选项卡上的库,在设计时都充分考虑了其中的项与文档整体外观的协调性。 您可以使用这些库来插入表格、页眉、页脚、列表、封面以及其他文档构建基块。 您创建的图片、图表或关系图也将与当前的文档外观协调一致。</p><p class="ue_t"><br/> </p><p><br/></p><p><br/></p><a href="/company/desc/22/"><span class="green">[查看更多]</span></a></div>
</div>
</section>
<!--sidebar start-->
<section>
<ul class="sidebar">
<li class="qq">
<a target="_blank" href="http://wpa.qq.com/msgrd?v=3&uin=2023525077&site=qq&menu=yes"></a>
</li>
<li class="totop"></li>
</ul>
</section>
<!--sidebar end-->
<!--header start-->
</div>
<!--弹出省省市-->
<script src="{% static 'js/selectUi.js' %}" type='text/javascript'></script>
<script type="text/javascript" src="{% static 'js/plugins/laydate/laydate.js' %}"></script>
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
<script src="{% static 'js/plugins/queryCity/js/public.js' %}" type="text/javascript"></script>
<script type="text/javascript" src="{% static 'js/plugins/jquery.raty.js' %}"></script>
<script type="text/javascript">
{% block custom_js %}{% endblock %}
//收藏分享
//收藏分享
function add_fav(current_elem, fav_id, fav_type){
$.ajax({
cache: false,
type: "POST",
url:"/org/add_fav/",
data:{'fav_id':fav_id, 'fav_type':fav_type},
async: true,
beforeSend:function(xhr, settings){
xhr.setRequestHeader("X-CSRFToken", "5I2SlleZJOMUX9QbwYLUIAOshdrdpRcy");
},
success: function(data) {
if(data.status == 'fail'){
if(data.msg == '用户未登录'){
window.location.href="login.html";
}else{
alert(data.msg)
}
}else if(data.status == 'success'){
current_elem.text(data.msg)
}
},
});
}
$(document).ready(function() {
$('.collectionbtn').on('click', function(){
add_fav($(this), 1, 2);
});
});
$(function(){
var $precision = $('.precision'),
score = $precision.attr('data-star-scope'),
option = {
half : true,
path : '{% static 'images/' %}',
precision : true,
size : 24,
starOff : 'g_star.png',
starOn : 'r_star.png',
starHalf : 'h_star.png',
hints : ['极差', '差', '一般', '好评', '非常满意'],
noRatedMsg : '暂时还未获得评价!',
readOnly : true,
score : score
};
$precision.raty(option);
$(document).ready(function() {
$('.jsFavBtn').on('click', function () {
var type = $(this).attr('data-fav-type');
if (type == '1') {
favPraise($(this), 'fav', 1, '收藏');
} else if (type == '3') {
favPraise($(this), 'fav', 3);
} else if (type == '11') {
favPraise($(this), 'pra', 1);
} else if (type == '4') {
favPraise($(this), 'fav', 4);
}
});
});
})
</script>
<script type="text/javascript">
$(function() {
$('.recordbtn1').click(function(){
$('.recordbox1').show();
});
$('.recordbtn2').click(function(){
$('.recordbox2').show();
});
$('.imgslide').unslider({
speed: 500, // The speed to animate each slide (in milliseconds)
delay: 3000, // The delay between slide animations (in milliseconds)
complete: function() {}, // A function that gets called after every slide animation
keys: true, // Enable keyboard (left, right) arrow shortcuts
dots: true, // Display dot navigation
fluid: false // Support responsive design. May break non-responsive designs
});
var unslider = $('.imgslide').unslider();
$('.unslider-arrow').click(function() {
var fn = this.className.split(' ')[1];
unslider.data('unslider')[fn]();
});
});
</script>
</body>
</html>
将templates/org-detail-homepage.html中的内容删掉,重新进行编辑:
{% extends "org_base.html" %}
{% block title %}机构首页{% endblock %}
{% block right_form %}
<div class="right companycenter layout grouping" >
<div class="head">
<h1>全部课程</h1>
<a class="green fr more" href="org-detail-course.html">查看更多 > </a>
</div>
<div class="brief group_list">
<div class="module1_5 box">
<a href="course-detail.html"><img width="214" src="../media/courses/2016/11/mysql.jpg"/></a>
<div class="des">
<a href="course-detail.html"><h2>django入门</h2></a>
<span class="fl">课时:<i class="key">0</i></span>
<span class="fr">参加人数:3</span>
</div>
<div class="bottom">
<span class="fl">慕课网</span>
<span class="star fr notlogin
" data-favid="13" data-fav-type="4">
0
</span>
</div>
</div>
<div class="module1_5 box">
<a href="course-detail.html"><img width="214" src="../media/courses/2016/11/540e57300001d6d906000338-240-135_mvvGYHL.jpg"/></a>
<div class="des">
<a href="course-detail.html"><h2>django实战项目</h2></a>
<span class="fl">课时:<i class="key">0</i></span>
<span class="fr">参加人数:0</span>
</div>
<div class="bottom">
<span class="fl">慕课网</span>
<span class="star fr notlogin
" data-favid="13" data-fav-type="4">
0
</span>
</div>
</div>
<div class="module1_5 box">
<a href="course-detail.html"><img width="214" src="../media/courses/2016/12/python错误和异常.jpg"/></a>
<div class="des">
<a href="course-detail.html"><h2>python错误和异常</h2></a>
<span class="fl">课时:<i class="key">0</i></span>
<span class="fr">参加人数:0</span>
</div>
<div class="bottom">
<span class="fl">慕课网</span>
<span class="star fr notlogin
" data-favid="13" data-fav-type="4">
0
</span>
</div>
</div>
</div>
</div>
{% endblock %}
此时输入:http://127.0.0.1:8000/org/1/
可显示页面,说明页面重构正确,但个人中心的样式出现问题,之后解决。
从数据库中取数:
在apps/organizations/views.py中:
# 省略
from apps.organizations.forms import AddAskForm
# 课程机构首页显示
class OrgHomeView(View):
# 在url中进行了配置,要与其中的参数名称保持一致org_id
def get(self, request, org_id, *args, **kwargs):
# 从数据库中取数
course_org = CourseOrg.objects.get(id=int(org_id))
course_org.click_nums += 1
course_org.save()
# 取出首页需要的数据
all_courses = course_org.course_set.all()[:3]
all_teahcer = course_org.teacher_set.all()[:1]
return render(request, "org-detail-homepage.html", {
"all_courses":all_courses,
"all_teacher":all_teahcer,
"course_org":course_org,
})
将数据显示在页面上:
在templates/org_base.html中:
<div class="middle companyheader">
<div class="wp">
<img class="fl" style="width: 112px;height: 103px" src="{{ MEDIA_URL }}{{ course_org.image }}"/>
在templates/org-list.html中:
{% for org in all_orgs.object_list %}
<dl class="des difdes">
<dt>
<a href="{% url 'org:home' org.id %}">
<img width="200" height="120" class="scrollLoading" data-url="{{ MEDIA_URL }}{{ org.image }}"/>
</a>
</dt>
运行项目访问:http://127.0.0.1:8000/
点击课程机构,再选一个机构进入详情页面,可看到此时显示的url为:http://127.0.0.1:8000/org/2/
将templates/org_base.html中:(约106-139行)
<div class="right companycenter layout" >
<div class="head">
<h1>机构教师</h1>
<a class="green fr more" href="org-detail-teachers.html">查看更多 > </a>
</div>
<div class="diarys">
<div class="module5 share company-diary-box" style="padding:10px 0;">
<div class="left">
<img class="pic" src="../media/teacher/2016/11/aobama_CXWwMef.png"/>
<p>昵称:bobby</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>
</div>
<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"> <p> </p><h1 class="ue_t" label="Title center" name="tc" style="border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;text-align:center;margin:0px 0px 20px;"><span style="color:#c0504d;">[键入文档标题]</span></h1><p style="text-align:center;"><strong class="ue_t">[键入文档副标题]</strong></p><h3><span class="ue_t" style="font-family:幼圆">[标题 1]</span></h3><p class="ue_t" style="text-indent:2em;">对于“插入”选项卡上的库,在设计时都充分考虑了其中的项与文档整体外观的协调性。 您可以使用这些库来插入表格、页眉、页脚、列表、封面以及其他文档构建基块。 您创建的图片、图表或关系图也将与当前的文档外观协调一致。</p><p class="ue_t" style="text-indent:2em;"><img src="../media/courses/ueditor/57aa86a0000145c512000460_20161210234050_865.jpg" title="" alt="57aa86a0000145c512000460.jpg"/> </p><h3><span class="ue_t" style="font-family:幼圆">[标题 2]</span></h3><p><img src="http://api.map.baidu.com/staticimage?center=116.410899,39.863624&zoom=11&width=530&height=340&markers=116.404,39.915" width="530" height="340"/> </p><p class="ue_t" style="text-indent:2em;">在“开始”选项卡上,通过从快速样式库中为所选文本选择一种外观,您可以方便地更改文档中所选文本的格式。 您还可以使用“开始”选项卡上的其他控件来直接设置文本格式。大多数控件都允许您选择是使用当前主题外观,还是使用某种直接指定的格式。</p><h3><span class="ue_t" style="font-family:幼圆">[标题 3]</span></h3><p>2016-12-10</p><p class="ue_t">对于“插入”选项卡上的库,在设计时都充分考虑了其中的项与文档整体外观的协调性。 您可以使用这些库来插入表格、页眉、页脚、列表、封面以及其他文档构建基块。 您创建的图片、图表或关系图也将与当前的文档外观协调一致。</p><p class="ue_t"><br/> </p><p><br/></p><p><br/></p><a href="/company/desc/22/"><span class="green">[查看更多]</span></a></div>
</div>
剪贴到templates/org-detail-homepage.html中,再进行修改:
{% extends "org_base.html" %}
{% block title %}机构首页{% endblock %}
{% block right_form %}
<div class="right companycenter layout grouping" >
<div class="head">
<h1>全部课程</h1>
<a class="green fr more" href="org-detail-course.html">查看更多 > </a>
</div>
<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_org.name }}</span>
<span class="star fr notlogin
" data-favid="13" data-fav-type="4">
0
</span>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="right companycenter layout" >
<div class="head">
<h1>机构教师</h1>
<a class="green fr more" href="org-detail-teachers.html">查看更多 > </a>
</div>
<div class="diarys">
{% for teacher in all_teacher %}
<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>{{ teacher.work_position }}</h1></a>
<span>工作年限:{{ teacher.work_years }}</span>
</div>
</div>
<div class="middle" style="border-bottom:0;">{{ teacher.points }}</div>
</div>
</div>
{% endfor %}
</div>
</div>
<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>
{% endblock %}
刷新:http://127.0.0.1:8000/org/2/
页面,便可将信息正常显示。机构详情页面的首页即完成。
4.2 机构讲师页面
在apps/organizations/views.py中:
# 省略
from apps.organizations.forms import AddAskForm
# 机构讲师页面
class OrgTeacherView(View):
def get(self, request, org_id, *args, **kwargs):
course_org = CourseOrg.objects.get(id=int(org_id))
course_org.click_nums += 1
course_org.save()
all_teahcer = course_org.teacher_set.all()
return render(request, "org-detail-teachers.html", {
"all_teacher": all_teahcer,
"course_org": course_org,
})
# 省略
在apps/organizations/urls.py中:
from django.conf.urls import url
from django.urls import path
from apps.organizations.views import OrgView, AddAskView, OrgHomeView, OrgTeacherView
urlpatterns = [
# 代表以list结尾的数据
url(r'^list/$', OrgView.as_view(), name="list"),
url(r'^add_ask/$', AddAskView.as_view(), name="add_ask"),
# 传递id,用正则表达式,取后边的数字
url(r'^(?P<org_id>\d+)/$', OrgHomeView.as_view(), name="home"),
# # 或者可以使用此种模式,效果相同
# path('<int:org_id>/', OrgHomeView.as_view(), name= "home"),
# 机构讲师页面
url(r'^(?P<org_id>\d+)/teacher/$', OrgTeacherView.as_view(), name="teacher"),
]
在apps/organizations/models.py中:
# 省略
# 课程讲师
class Teacher(BaseModel):
# 省略
def __str__(self):
return self.name
# 获取课程数量
def course_nums(self):
return self.course_set.all().count()
将templates/org-detail-teachers.html修改为:
{% extends 'org_base.html' %}
{% block title %}机构讲师{% endblock %}
{% 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>{{ teacher.course_nums }}</span></li>
</ul>
</dd>
</dl>
{% endfor %}
</div>
</div>
</div>
{% endblock %}
运行项目,访问:http://127.0.0.1:8000/org/2/teacher/
可看到机构讲师页面。
再配置从课程机构详情页面的首页的跳转:
在templates/org_base.html中:
<section>
<div class="wp list personal_list comp">
<div class="left">
<ul>
<li class="active2"><a href="{% url 'org:home' course_org.id %}">机构首页</a></li>
<li class=""><a href="org-detail-course.html">机构课程</a></li>
<li class=""><a href="org-detail-desc.html">机构介绍</a></li>
<li class=""><a href="{% url 'org:teacher' course_org.id %}">机构讲师</a></li>
</ul>
</div>
{% block right_form %}
{% endblock %}
</section>
运行项目,访问:http://127.0.0.1:8000/org/2/
点击左边机构首页和机构讲师栏即可进行页面的跳转。
显示侧边框的选中状态,添加current_page变量来指示。
在apps/organizations/views.py中:
# 机构讲师页面
class OrgTeacherView(View):
def get(self, request, org_id, *args, **kwargs):
# 用来指示当前页面是哪个页面,来显示侧边框的选中状态
current_page = "teacher"
course_org = CourseOrg.objects.get(id=int(org_id))
course_org.click_nums += 1
course_org.save()
all_teahcer = course_org.teacher_set.all()
return render(request, "org-detail-teachers.html", {
"all_teacher": all_teahcer,
"course_org": course_org,
"current_page":current_page
})
# 课程机构首页显示
class OrgHomeView(View):
# 在url中进行了配置,要与其中的参数名称保持一致org_id
def get(self, request, org_id, *args, **kwargs):
current_page = "home"
# 从数据库中取数
course_org = CourseOrg.objects.get(id=int(org_id))
course_org.click_nums += 1
course_org.save()
# 取出首页需要的数据
all_courses = course_org.course_set.all()[:3]
all_teahcer = course_org.teacher_set.all()[:1]
return render(request, "org-detail-homepage.html", {
"all_courses":all_courses,
"all_teacher":all_teahcer,
"course_org":course_org,
"current_page": current_page
})
在templates/org_base.html中:
<section>
<div class="wp list personal_list comp">
<div class="left">
<ul>
<li class="{% ifequal current_page 'home' %}active2{% endifequal %}"><a href="{% url 'org:home' course_org.id %}">机构首页</a></li>
<li class=""><a href="org-detail-course.html">机构课程</a></li>
<li class=""><a href="org-detail-desc.html">机构介绍</a></li>
<li class="{% ifequal current_page 'teacher' %}active2{% endifequal %}"><a href="{% url 'org:teacher' course_org.id %}">机构讲师</a></li>
</ul>
</div>
{% block right_form %}
{% endblock %}
</section>
运行项目,访问:http://127.0.0.1:8000/org/2/
点击左边机构首页和机构讲师栏即可进行页面的跳转,并实现选中状态。
4.3 机构课程页面
在apps/organizations/views.py中:
# 省略
from apps.organizations.forms import AddAskForm
# 机构讲师页面
class OrgCourseView(View):
def get(self, request, org_id, *args, **kwargs):
# 用来指示当前页面是哪个页面,来显示侧边框的选中状态
current_page = "course"
course_org = CourseOrg.objects.get(id=int(org_id))
course_org.click_nums += 1
course_org.save()
all_courses = course_org.course_set.all()
# 对课程机构数据进行分页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# per_page为每页显示几条
p = Paginator(all_courses, per_page=5, request=request)
courses = p.page(page)
return render(request, "org-detail-course.html", {
"all_courses": courses,
"course_org": course_org,
"current_page":current_page
})
# 机构讲师页面
# 省略
在apps/organizations/urls.py中:
from django.conf.urls import url
from django.urls import path
from apps.organizations.views import OrgView, AddAskView, OrgHomeView, OrgTeacherView, OrgCourseView
urlpatterns = [
# 代表以list结尾的数据
url(r'^list/$', OrgView.as_view(), name="list"),
url(r'^add_ask/$', AddAskView.as_view(), name="add_ask"),
# 传递id,用正则表达式,取后边的数字
url(r'^(?P<org_id>\d+)/$', OrgHomeView.as_view(), name="home"),
# # 或者可以使用此种模式,效果相同
# path('<int:org_id>/', OrgHomeView.as_view(), name= "home"),
# 机构讲师页面
url(r'^(?P<org_id>\d+)/teacher/$', OrgTeacherView.as_view(), name="teacher"),
# 机构课程页面
url(r'^(?P<org_id>\d+)/course/$', OrgCourseView.as_view(), name="course"),
]
将templates/org-detail-course.html修改为:
{% extends "org_base.html" %}
{% block title %}机构课程{% endblock %}
{% block right_form %}
<div class="right companycenter layout" >
<div class="head">
<h1>机构课程</h1>
</div>
<div class="brief group_list">
{% for course in all_courses.object_list %}
<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_org.name }}</span>
<span class="star fr notlogin
" data-favid="13" data-fav-type="4">
{{ course.fav_nums }}
</span>
</div>
</div>
{% endfor %}
</div>
<div class="pageturn">
<ul class="pagelist">
<!-- 如果有上一页 -->
{% if all_courses.has_previous %}
<!-- querystring方法自动生成上一页的链接 -->
<li class="long"><a href="?{{ all_courses.previous_page_number.querystring }}">上一页</a></li>
{% endif %}
{% for page in all_courses.pages %}
{% if page %}
<!-- 如果为当前页 -->
{% ifequal page all_courses.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">...</li>
{% endif %}
{% endfor %}
<!-- 如果还有下一页 -->
{% if all_courses.has_next %}
<li class="long"><a href="?{{ all_courses.next_page_number.querystring }}">下一页</a></li>
{% endif %}
{# <li class="active"><a href="?page=1">1</a></li>#}
{# <li><a href="?page=2" class="page">2</a></li>#}
{# <li><a href="?page=3" class="page">3</a></li>#}
{# <li class="long"><a href="?page=2">下一页</a></li>#}
</ul>
</div>
</div>
{% endblock %}
在templates/org_base.html中:
<section>
<div class="wp list personal_list comp">
<div class="left">
<ul>
<li class="{% ifequal current_page 'home' %}active2{% endifequal %}"><a href="{% url 'org:home' course_org.id %}">机构首页</a></li>
<li class="{% ifequal current_page 'course' %}active2{% endifequal %}"><a href="{% url 'org:course' course_org.id %}">机构课程</a></li>
<li class=""><a href="org-detail-desc.html">机构介绍</a></li>
<li class="{% ifequal current_page 'teacher' %}active2{% endifequal %}"><a href="{% url 'org:teacher' course_org.id %}">机构讲师</a></li>
</ul>
</div>
运行项目,访问:http://127.0.0.1:8000/org/2/
点击左边机构课程即可进行页面的跳转,并实现选中状态。
4.4 机构介绍页面
在apps/organizations/views.py中:
# 省略
from apps.organizations.forms import AddAskForm
# 机构介绍页面
class OrgDescView(View):
def get(self, request, org_id, *args, **kwargs):
# 用来指示当前页面是哪个页面,来显示侧边框的选中状态
current_page = "desc"
course_org = CourseOrg.objects.get(id=int(org_id))
course_org.click_nums += 1
course_org.save()
return render(request, "org-detail-desc.html", {
"course_org": course_org,
"current_page":current_page
})
# 机构讲师页面
# 省略
在apps/organizations/urls.py中:
from django.conf.urls import url
from django.urls import path
from apps.organizations.views import OrgView, AddAskView, OrgHomeView, OrgTeacherView, OrgCourseView, OrgDescView
urlpatterns = [
# 代表以list结尾的数据
url(r'^list/$', OrgView.as_view(), name="list"),
url(r'^add_ask/$', AddAskView.as_view(), name="add_ask"),
# 传递id,用正则表达式,取后边的数字
url(r'^(?P<org_id>\d+)/$', OrgHomeView.as_view(), name="home"),
# # 或者可以使用此种模式,效果相同
# path('<int:org_id>/', OrgHomeView.as_view(), name= "home"),
# 机构讲师页面
url(r'^(?P<org_id>\d+)/teacher/$', OrgTeacherView.as_view(), name="teacher"),
# 机构课程页面
url(r'^(?P<org_id>\d+)/course/$', OrgCourseView.as_view(), name="course"),
# 机构介绍页面
url(r'^(?P<org_id>\d+)/desc/$', OrgDescView.as_view(), name="desc"),
]
将templates/org-detail-desc.html改为:
{% extends "org_base.html" %}
{% block title %}机构描述{% endblock %}
{% block right_form %}
<div class="right companycenter layout" >
<div class="head">
<h1>机构介绍</h1>
</div>
<div class="des">
{{ course_org.desc }}
</div>
</div>
{% endblock %}
在templates/org_base.html中:
<div class="left">
<ul>
<li class="{% ifequal current_page 'home' %}active2{% endifequal %}"><a href="{% url 'org:home' course_org.id %}">机构首页</a></li>
<li class="{% ifequal current_page 'course' %}active2{% endifequal %}"><a href="{% url 'org:course' course_org.id %}">机构课程</a></li>
<li class="{% ifequal current_page 'desc' %}active2{% endifequal %}"><a href="{% url 'org:desc' course_org.id %}">机构介绍</a></li>
<li class="{% ifequal current_page 'teacher' %}active2{% endifequal %}"><a href="{% url 'org:teacher' course_org.id %}">机构讲师</a></li>
</ul>
</div>
运行项目,访问:http://127.0.0.1:8000/org/2/
点击左边机构介绍即可进行页面的跳转,并实现选中状态。
此时点击图表可进入课程机构详情页面,点文字无法进入报错404,进行设置:
在templates/org-list.html中(68行左右):
<div class="clearfix">
<a href="{% url 'org:home' org.id %}">
便可进行跳转。
个人中心的显示还有问题,进行修改:
4.5 对课程机构完成收藏功能
之前左上角个人中心的显示还存在问题,进行完善:
在templates/org_base.html中:(28行左右)
将此段代码放在div标签外即可:
<section class="headerwrap headerwrap2">
<header>
<div class="header2 header">
<div class="top">
<div class="wp">
<div class="fl">
<p>联系方式:<b>3333333333</b></p>
</div>
<!--如果用户已经登录-->
{% if request.user.is_authenticated %}
<div class="personal">
<dl class="user fr">
<dd>bobby<img class="down fr" src="{% static 'images/top_down.png' %}"/></dd>
<dt><img width="20" height="20" src="{% static 'media/image/2016/12/default_big_14.png' %}"/></dt>
</dl>
<div class="userdetail">
<dl>
<dt><img width="80" height="80" src="{% static 'media/image/2016/12/default_big_14.png' %}"/></dt>
<dd>
<h2>django</h2>
<p>bobby</p>
</dd>
</dl>
<div class="btn">
<a class="personcenter fl" href="usercenter-info.html">进入个人中心</a>
<a class="fr" href="{% url 'logout' %}">退出</a>
</div>
</div>
</div>
{% else %}
<!--如果用户未登录-->
<a style="color:white" class="fr registerbtn" href="{% url 'register' %}">注册</a>
<a style="color:white" class="fr loginbtn" href="{% url 'login' %}">登录</a>
{% endif %}
</div>
</div>
刷新页面,个人中心便正常显示在右上角。
进行机构收藏接口的完成:
添加一个表单的验证:
在apps/operations/文件夹下新建一个Python File命名为forms.py:
import re
from django import forms
from apps.operations.models import UserFavorite
# 使用modelform
class UserFavForm(forms.ModelForm):
# 指明继承哪个model
class Meta:
model = UserFavorite
# 指明将哪些字段生成form
fields = ["fav_id", "fav_type"]
配置前端的url跳转,并生成csrf_token:
注:{% csrf_token %}生成的是一段html代码
{{ csrf_token }}是取值,这里使用这种模式
若未登录不可进行收藏,跳转到登录页面:
在templates/org_base.html中135行左右:
//收藏分享
function add_fav(current_elem, fav_id, fav_type){
$.ajax({
cache: false,
type: "POST",
url:"{% url 'op: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="{% url 'login' %}";
}else{
alert(data.msg)
}
}else if(data.status == 'success'){
current_elem.text(data.msg)
}
},
});
}
$(document).ready(function() {
$('.collectionbtn').on('click', function(){
<!-- 此处要和org的id保持一致 -->
add_fav($(this), {{ course_org.id }}, 2);
});
});
在apps/operations/views.py中:
from django.views.generic import View
from django.http import JsonResponse
from apps.operations.forms import UserFavForm
from apps.operations.models import UserFavorite
from apps.courses.models import Course
from apps.organizations.models import CourseOrg, Teacher
# Create your views here.
# 用户收藏,取消收藏
class AddFavView(View):
def post(self, request, *args, **kwargs):
# 判断用户是否登录
if not request.user.is_authenticated:
# 若未登录,前端转跳到登录页面,对应templates/org_base.html中约146行
return JsonResponse({
"status":"fail",
"msg":"用户未登录"
})
user_fav_form = UserFavForm(request.POST)
if user_fav_form.is_valid():
fav_id = user_fav_form.cleaned_data["fav_id"]
fav_type = user_fav_form.cleaned_data["fav_type"]
# 是否已经收藏
existed_records = UserFavorite.objects.filter(user=request.user, fav_id=fav_id, fav_type=fav_type)
# 若已经收藏
if existed_records:
# 再点击为取消收藏
existed_records.delete()
# 如果收藏的为课程
if fav_type == 1:
course = Course.objects.get(id=fav_id)
# 将收藏数字段减1
course.fav_nums -= 1
# 保存到数据库中
course.save()
# 如果收藏的为课程机构
elif fav_type == 2:
course_org = CourseOrg.objects.get(id=fav_id)
# 将收藏数字段减1
course_org.fav_nums -= 1
# 保存到数据库中
course_org.save()
# 如果收藏的为讲师
elif fav_type == 3:
teacher = Teacher.objects.get(id=fav_id)
# 将收藏数字段减1
teacher.fav_nums -= 1
# 保存到数据库中
teacher.save()
return JsonResponse({
"status": "success",
"msg": "收藏"
})
# 若还未收藏
else:
# 添加收藏记录
user_fav = UserFavorite()
user_fav.fav_id = fav_id
user_fav.fav_type = fav_type
user_fav.user = request.user
user_fav.save()
return JsonResponse({
"status": "success",
"msg": "已收藏"
})
# 若表单验证失败
else:
return JsonResponse({
"status": "fail",
"msg": "参数错误"
})
在apps/operations/文件夹下新建一个Python File命名为urls.py:
from django.conf.urls import url
from apps.operations.views import AddFavView
urlpatterns = [
url(r'^fav/$', AddFavView.as_view(), name="fav"),
]
在MxOnline/urls.py中:
# 省略
urlpatterns = [
# 省略
# 机构相关页面
# 进行子url的管理
url(r'^org/', include(('apps.organizations.urls', "organizations"), namespace="org")),
# 用户相关操作
url(r'^op/', include(('apps.operations.urls', "operations"), namespace="op")),
]
判断是否已经被收藏:
在四个页面的View中都加入此判断逻辑
在apps/organizations/views.py中:
from django.shortcuts import render
from django.views.generic.base import View
from pure_pagination import Paginator, EmptyPage, PageNotAnInteger
from django.http import JsonResponse
from apps.organizations.models import CourseOrg
from apps.organizations.models import City
from apps.organizations.forms import AddAskForm
from apps.operations.models import UserFavorite
# 机构介绍页面
class OrgDescView(View):
def get(self, request, org_id, *args, **kwargs):
# 用来指示当前页面是哪个页面,来显示侧边框的选中状态
current_page = "desc"
course_org = CourseOrg.objects.get(id=int(org_id))
course_org.click_nums += 1
course_org.save()
# 判断机构是否已收藏
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
})
# 机构讲师页面
class OrgCourseView(View):
def get(self, request, org_id, *args, **kwargs):
# 用来指示当前页面是哪个页面,来显示侧边框的选中状态
current_page = "course"
course_org = CourseOrg.objects.get(id=int(org_id))
course_org.click_nums += 1
course_org.save()
all_courses = course_org.course_set.all()
# 判断机构是否已收藏
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
# 对课程机构数据进行分页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# per_page为每页显示几条
p = Paginator(all_courses, per_page=5, request=request)
courses = p.page(page)
return render(request, "org-detail-course.html", {
"all_courses": courses,
"course_org": course_org,
"current_page":current_page,
"has_fav": has_fav
})
# 机构讲师页面
class OrgTeacherView(View):
def get(self, request, org_id, *args, **kwargs):
# 用来指示当前页面是哪个页面,来显示侧边框的选中状态
current_page = "teacher"
course_org = CourseOrg.objects.get(id=int(org_id))
course_org.click_nums += 1
course_org.save()
all_teahcer = course_org.teacher_set.all()
return render(request, "org-detail-teachers.html", {
"all_teacher": all_teahcer,
"course_org": course_org,
"current_page":current_page
})
# 课程机构首页显示
class OrgHomeView(View):
# 在url中进行了配置,要与其中的参数名称保持一致org_id
def get(self, request, org_id, *args, **kwargs):
current_page = "home"
# 从数据库中取数
course_org = CourseOrg.objects.get(id=int(org_id))
course_org.click_nums += 1
course_org.save()
# 判断机构是否已收藏
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
# 取出首页需要的数据
all_courses = course_org.course_set.all()[:3]
all_teahcer = course_org.teacher_set.all()[:1]
return render(request, "org-detail-homepage.html", {
"all_courses":all_courses,
"all_teacher":all_teahcer,
"course_org":course_org,
"current_page": current_page,
"has_fav": has_fav
})
# 右边栏我要学习(咨询)表单,处理用户咨询
class AddAskView(View):
# 只需要提交数据,使用post
def post(self, request, *args, **kwargs):
userask_form = AddAskForm(request.POST)
# 验证表单,如果没有问题,保存数据
if userask_form.is_valid():
# 交给数据库执行保存
userask_form.save(commit=True)
# 返回json数据
return JsonResponse({
"status":"success"
})
else:
return JsonResponse({
"status": "fail",
"msg":"添加出错"
})
# Create your views here.
class OrgView(View):
def get(self, request, *args, **kwargs):
# 从数据库中获取数据
all_orgs = CourseOrg.objects.all()
all_citys = City.objects.all()
# 根据点击数进行机构排序,右边栏广告位有限,做切片
hot_orgs = all_orgs.order_by("-click_nums")[:3]
# 通过机构类别对课程机构进行筛选,参数名称ct和html中对应
category = request.GET.get("ct", "")
if category:
all_orgs = all_orgs.filter(category=category)
# 通过所在城市对课程机构进行筛选
city_id = request.GET.get("city", "")
if city_id:
# 前端传来的是字符串,要做int转换,但输入url可能会抛异常,要判断是否为数字
if city_id.isdigit():
# model中没有city_id字段,但外键在数据库中存储时会生成city_id字段
all_orgs = all_orgs.filter(city_id=int(city_id))
# 对机构进行排序
sort = request.GET.get("sort", "")
if sort == "students":
# 对学习人数进行排序,前面的-代表倒序
all_orgs = all_orgs.order_by("-students")
elif sort == "courses":
# 对课程数量进行排序,course_nums对应model中的字段
all_orgs = all_orgs.order_by("-course_nums")
# 放在此处进行统计,显示出筛选后的数量
# 会执行select语句,查出有多少家机构
org_nums = all_orgs.count()
# 对课程机构数据进行分页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# per_page为每页显示几条
p = Paginator(all_orgs, per_page=5, request=request)
orgs = p.page(page)
return render(request, "org-list.html", {
"all_orgs": orgs,
"org_nums": org_nums,
"all_citys": all_citys,
"category" : category,
"city_id" : city_id,
"sort": sort,
"hot_orgs": hot_orgs
})
在前端页面中判断是否收藏:
在templates/org_base.html中:(约76行)
<div class="btn fr collectionbtn notlogin
"data-favid="22" data-fav-type="1">
{% if has_fav %}已收藏{% else %}收藏{% endif %}
</div>
更改前端页面查看更多的页面更改:
在templates/org-detail-homepage.html中:
<h1>全部课程</h1>
<a class="green fr more" href="{% url 'org:course' course_org.id %}">查看更多 > </a>
<h1>机构教师</h1>
<a class="green fr more" href="{% url 'org:teacher' course_org.id %}">查看更多 > </a>
<h1>机构介绍</h1>
<a class="green fr more" href="{% url 'org:desc' course_org.id %}">查看更多 > </a>
左上角显示是否为金牌机构、认证机构:
在templates/org_base.html中:
<div class="middle companyheader">
<div class="wp">
<img class="fl" style="width: 112px;height: 103px" src="{{ MEDIA_URL }}{{ course_org.image }}"/>
<div class="head fl">
<h1>
慕课网
{% if course_org.is_auth %}
<img src="{% static 'images/authentication.png' %}"/>
{% endif %}
{% if course_org.is_gold %}
<img src="{% static 'images/gold.png' %}"/>
{% endif %}
运行项目,点击收藏按钮可进行收藏和取消收藏,课程机构的收藏功能便完成。