Django+xadmin打造在线教育平台(二)
Django+xadmin打造在线教育平台(二)
在这篇文章中:
代码
三、xadmin后台管理
3.1.xadmin的安装
django2.0的安装(源码安装方式):
https://github.com/sshwsfc/xadmin/tree/django2
把zip文件放到pip目录下,运行下面命令安装:
pip install xadmin-django2
是文件README.rst 出现了 Unicode 解码错误,这个文件是没有什么用处的,可以新建一个同名的空白文件替换掉
首先下载zip源码包:github.com/sshwsfc/xadmin
解压后,打开README.rst文件,清空里面的内容,然后保存。
再压缩成zip,放到pip目录下:C:\Users\Administrator\AppData\Local\Programs\Python\Python36\Lib\site-packages\pip
此时打开cmd进行安装:pip install xadmin-master.zip
如果上面安装提示错误:
更换安装源(使用豆瓣源)
安装xadmin的命令如下:
pip install -i https://pypi.douban.com/simple xadmin-django2
安装成功后,同时也安装了很多依赖的包,后面也需要用到。
3.2.xadmin的设置
(1)新建Python Package "extra_apps",把源码xadmin文件夹放到extra_apps文件夹下面,此时目录结构如下:

(2)把extra_apps右键mark为Source Root并在settings中加入
sys.path.insert(0,os.path.join(BASE_DIR, 'extra_apps'))

(3)因为我们用源码的xadmin,所以要卸载之前安装的
pip uinstall xadmin
(4)配置路由
把admin改成xadmin
# urls.py
from django.urls import path
import xadmin
urlpatterns = [
path('xadmin/', xadmin.site.urls),
]
(5)注册app
把下面两个app注册到settings.py的INSTALLED_APPS中
'xadmin',
'crispy_forms'
(6)重新生成数据库
python manage.py makemigrations
python manage.py migrate
(7)设置成中文
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
(8)创建一个管理员用户
python manage.py createsuperuser
现在就可以运行了
python manage.py runserver
访问后台:http://127.0.0.1:8000/xadmin
可以看到成功进入管理界面

datetimefield报错问题解决:
当我们点增加用户信息,会报错

可以看到报的是xadmin/widgets中第80行
def render(self, name, value, attrs=None):
input_html = [ht for ht in super(AdminSplitDateTime, self).render(name, value, attrs).split('\n') if ht != '']
# return input_html
return mark_safe('<div class="datetime clearfix"><div class="input-group date bootstrap-datepicker"><span class="input-group-addon"><i class="fa fa-calendar"></i></span>%s'
'<span class="input-group-btn"><button class="btn btn-default" type="button">%s</button></span></div>'
'<div class="input-group time bootstrap-clockpicker"><span class="input-group-addon"><i class="fa fa-clock-o">'
'</i></span>%s<span class="input-group-btn"><button class="btn btn-default" type="button">%s</button></span></div></div>' % (input_html[0], _(u'Today'), input_html[1], _(u'Now')))
上面贴出来的最后一行代码就是widgets.py的第80行代码。
可以看出这句代码是希望用“\n”把input_html里的两个标签拆开,但两个标签之间没有换行,所以没能拆分,导致报错。
input_html[1]就是报错的代码,因为input_html里只有一个元素。
解决办法:
既然“\n”不能拆分标签,那么就换一种拆分方式,使用“/><”拆分。
原代码:
input_html = [ht for ht in super(AdminSplitDateTime, self).render(name, value, attrs).split('\n') if ht != '']
修改后代码:
input_html = [ht for ht in super(AdminSplitDateTime, self).render(name, value, attrs).split('/><') if ht != '']
input_html[0] = input_html[0] + "/>"
input_html[1] = "<" + input_html[1]
再运行就正常了
3.3.users app的models注册
(1)在users下面创建adminx.py,代码如下:
# users/adminx.py
import xadmin
from .models import EmailVerifyRecord
#xadmin中这里是继承object,不再是继承admin
class EmailVerifyRecordAdmin(object):
pass
xadmin.site.register(EmailVerifyRecord,EmailVerifyRecordAdmin)
(2)完善功能,增加显示字段,搜索和过滤
修改users/adminx.py,代码如下:
# users/adminx.py
import xadmin
from .models import EmailVerifyRecord
#xadmin中这里是继承object,不再是继承admin
class EmailVerifyRecordAdmin(object):
# 显示的列
list_display = ['code', 'email', 'send_type', 'send_time']
# 搜索的字段,不要添加时间搜索
search_fields = ['code', 'email', 'send_type']
# 过滤
list_filter = ['code', 'email', 'send_type', 'send_time']
xadmin.site.register(EmailVerifyRecord,EmailVerifyRecordAdmin)
刷新后的界面:

users中Banner也注册进去
class BannerAdmin(object):
list_display = ['title', 'image', 'url','index', 'add_time']
search_fields = ['title', 'image', 'url','index']
list_filter = ['title', 'image', 'url','index', 'add_time']
xadmin.site.register(Banner,BannerAdmin)
3.4.剩余app model注册
(1)course
代码如下: 注意外键
# course/adminx.py
import xadmin
from .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','desc','detail','degree','learn_times','students']
class LessonAdmin(object):
'''章节'''
list_display = ['course', 'name', 'add_time']
search_fields = ['course', 'name']
#这里course__name是根据课程名称过滤
list_filter = ['course__name', 'name', 'add_time']
class VideoAdmin(object):
'''视频'''
list_display = ['lesson', 'name', 'add_time']
search_fields = ['lesson', 'name']
list_filter = ['lesson', 'name', 'add_time']
class CourseResourceAdmin(object):
'''课程资源'''
list_display = ['course', 'name', 'download', 'add_time']
search_fields = ['course', 'name', 'download']
list_filter = ['course__name', 'name', 'download', 'add_time']
# 将管理器与model进行注册关联
xadmin.site.register(Course, CourseAdmin)
xadmin.site.register(Lesson, LessonAdmin)
xadmin.site.register(Video, VideoAdmin)
xadmin.site.register(CourseResource, CourseResourceAdmin)
(2)organizations
代码如下:
# organization/adminx.py
import xadmin
from .models import CityDict, CourseOrg, Teacher
class CityDictAdmin(object):
'''城市'''
list_display = ['name', 'desc', 'add_time']
search_fields = ['name', 'desc']
list_filter = ['name', 'desc', 'add_time']
class CourseOrgAdmin(object):
'''机构'''
list_display = ['name', 'desc', 'click_nums', 'fav_nums','add_time' ]
search_fields = ['name', 'desc', 'click_nums', 'fav_nums']
list_filter = ['name', 'desc', 'click_nums', 'fav_nums','city__name','address','add_time']
class TeacherAdmin(object):
'''老师'''
list_display = [ 'name','org', 'work_years', 'work_company','add_time']
search_fields = ['org', 'name', 'work_years', 'work_company']
list_filter = ['org__name', 'name', 'work_years', 'work_company','click_nums', 'fav_nums', 'add_time']
xadmin.site.register(CityDict, CityDictAdmin)
xadmin.site.register(CourseOrg, CourseOrgAdmin)
xadmin.site.register(Teacher, TeacherAdmin)
(3)operation
代码如下:
# operation/adminx.py
import xadmin
from .models import UserAsk, UserCourse, UserMessage, CourseComments, UserFavorite
class UserAskAdmin(object):
'''用户表单我要学习'''
list_display = ['name', 'mobile', 'course_name', 'add_time']
search_fields = ['name', 'mobile', 'course_name']
list_filter = ['name', 'mobile', 'course_name', 'add_time']
#
class UserCourseAdmin(object):
'''用户课程学习'''
list_display = ['user', 'course', 'add_time']
search_fields = ['user', 'course']
list_filter = ['user', 'course', 'add_time']
class UserMessageAdmin(object):
'''用户消息后台'''
list_display = ['user', 'message', 'has_read', 'add_time']
search_fields = ['user', 'message', 'has_read']
list_filter = ['user', 'message', 'has_read', 'add_time']
class CourseCommentsAdmin(object):
'''用户评论后台'''
list_display = ['user', 'course', 'comments', 'add_time']
search_fields = ['user', 'course', 'comments']
list_filter = ['user', 'course', 'comments', 'add_time']
class UserFavoriteAdmin(object):
'''用户收藏后台'''
list_display = ['user', 'fav_id', 'fav_type', 'add_time']
search_fields = ['user', 'fav_id', 'fav_type']
list_filter = ['user', 'fav_id', 'fav_type', 'add_time']
# 将后台管理器与models进行关联注册。
xadmin.site.register(UserAsk, UserAskAdmin)
xadmin.site.register(UserCourse, UserCourseAdmin)
xadmin.site.register(UserMessage, UserMessageAdmin)
xadmin.site.register(CourseComments, CourseCommentsAdmin)
xadmin.site.register(UserFavorite, UserFavoriteAdmin)
全部代码:
# users/adminx.py
import xadmin
from .models import EmailVerifyRecord,Banner
#xadmin中这里是继承object,不再是继承admin
class EmailVerifyRecordAdmin(object):
# 显示的列
list_display = ['code', 'email', 'send_type', 'send_time']
# 搜索的字段
search_fields = ['code', 'email', 'send_type']
# 过滤
list_filter = ['code', 'email', 'send_type', 'send_time']
class BannerAdmin(object):
list_display = ['title', 'image', 'url','index', 'add_time']
search_fields = ['title', 'image', 'url','index']
list_filter = ['title', 'image', 'url','index', 'add_time']
xadmin.site.register(EmailVerifyRecord,EmailVerifyRecordAdmin)
xadmin.site.register(Banner,BannerAdmin)
# course/adminx.py
import xadmin
from .models import Course, Lesson, Video, CourseResource
# Course的admin管理器
class CourseAdmin(object):
'''课程'''
list_display = [ 'name','desc','detail','degree','learn_times','students']
search_fields = ['name', 'desc', 'detail', 'degree', 'students']
list_filter = [ 'name','desc','detail','degree','learn_times','students']
class LessonAdmin(object):
'''章节'''
list_display = ['course', 'name', 'add_time']
search_fields = ['course', 'name']
#这里course__name是根据课程名称过滤
list_filter = ['course__name', 'name', 'add_time']
class VideoAdmin(object):
'''视频'''
list_display = ['lesson', 'name', 'add_time']
search_fields = ['lesson', 'name']
list_filter = ['lesson', 'name', 'add_time']
class CourseResourceAdmin(object):
'''课程资源'''
list_display = ['course', 'name', 'download', 'add_time']
search_fields = ['course', 'name', 'download']
list_filter = ['course__name', 'name', 'download', 'add_time']
# 将管理器与model进行注册关联
xadmin.site.register(Course, CourseAdmin)
xadmin.site.register(Lesson, LessonAdmin)
xadmin.site.register(Video, VideoAdmin)
xadmin.site.register(CourseResource, CourseResourceAdmin)
# organization/adminx.py
import xadmin
from .models import CityDict, CourseOrg, Teacher
class CityDictAdmin(object):
'''城市'''
list_display = ['name', 'desc', 'add_time']
search_fields = ['name', 'desc']
list_filter = ['name', 'desc', 'add_time']
class CourseOrgAdmin(object):
'''机构'''
list_display = ['name', 'desc', 'click_nums', 'fav_nums','add_time' ]
search_fields = ['name', 'desc', 'click_nums', 'fav_nums']
list_filter = ['name', 'desc', 'click_nums', 'fav_nums','city__name','address','add_time']
class TeacherAdmin(object):
'''老师'''
list_display = [ 'name','org', 'work_years', 'work_company','add_time']
search_fields = ['org', 'name', 'work_years', 'work_company']
list_filter = ['org__name', 'name', 'work_years', 'work_company','click_nums', 'fav_nums', 'add_time']
xadmin.site.register(CityDict, CityDictAdmin)
xadmin.site.register(CourseOrg, CourseOrgAdmin)
xadmin.site.register(Teacher, TeacherAdmin)
# operation/adminx.py
import xadmin
from .models import UserAsk, UserCourse, UserMessage, CourseComments, UserFavorite
class UserAskAdmin(object):
'''用户表单我要学习'''
list_display = ['name', 'mobile', 'course_name', 'add_time']
search_fields = ['name', 'mobile', 'course_name']
list_filter = ['name', 'mobile', 'course_name', 'add_time']
#
class UserCourseAdmin(object):
'''用户课程学习'''
list_display = ['user', 'course', 'add_time']
search_fields = ['user', 'course']
list_filter = ['user', 'course', 'add_time']
class UserMessageAdmin(object):
'''用户消息后台'''
list_display = ['user', 'message', 'has_read', 'add_time']
search_fields = ['user', 'message', 'has_read']
list_filter = ['user', 'message', 'has_read', 'add_time']
class CourseCommentsAdmin(object):
'''用户评论后台'''
list_display = ['user', 'course', 'comments', 'add_time']
search_fields = ['user', 'course', 'comments']
list_filter = ['user', 'course', 'comments', 'add_time']
class UserFavoriteAdmin(object):
'''用户收藏后台'''
list_display = ['user', 'fav_id', 'fav_type', 'add_time']
search_fields = ['user', 'fav_id', 'fav_type']
list_filter = ['user', 'fav_id', 'fav_type', 'add_time']
# 将后台管理器与models进行关联注册。
xadmin.site.register(UserAsk, UserAskAdmin)
xadmin.site.register(UserCourse, UserCourseAdmin)
xadmin.site.register(UserMessage, UserMessageAdmin)
xadmin.site.register(CourseComments, CourseCommentsAdmin)
xadmin.site.register(UserFavorite, UserFavoriteAdmin)
此时项目目录结构:

运行项目,进后台管理界面如下:

3.5.xadmin的全局配置
将全局配置修改:
- 如左上角:django Xadmin。下面的我的公司
- 主题修改,app名称汉化,菜单收叠。
使用Xadmin的主题功能。
把全站的配置放在users\adminx.py中:
(1)添加主题功能
from xadmin import views
# 创建xadmin的最基本管理器配置,并与view绑定
class BaseSetting(object):
# 开启主题功能
enable_themes = True
use_bootswatch = True
# 将基本配置管理与view绑定
xadmin.site.register(views.BaseAdminView,BaseSetting)
没添加主题前,右上角界面

添加主题后,可以选择自己喜欢的主题

(2)全局配置
修改django admin 和下面的我的公司收起菜单
# 全局修改,固定写法
class GlobalSettings(object):
# 修改title
site_title = 'NBA后台管理界面'
# 修改footer
site_footer = '科比的公司'
# 收起菜单
menu_style = 'accordion'
# 将title和footer信息进行注册
xadmin.site.register(views.CommAdminView,GlobalSettings)
# users/adminx.py
import xadmin
from .models import EmailVerifyRecord,Banner
from xadmin import views
# 创建xadmin的最基本管理器配置,并与view绑定
class BaseSetting(object):
# 开启主题功能
enable_themes = True
use_bootswatch = True
# 全局修改,固定写法
class GlobalSettings(object):
# 修改title
site_title = 'NBA后台管理界面'
# 修改footer
site_footer = '科比的公司'
# 收起菜单
menu_style = 'accordion'
#xadmin中这里是继承object,不再是继承admin
class EmailVerifyRecordAdmin(object):
# 显示的列
list_display = ['code', 'email', 'send_type', 'send_time']
# 搜索的字段
search_fields = ['code', 'email', 'send_type']
# 过滤
list_filter = ['code', 'email', 'send_type', 'send_time']
class BannerAdmin(object):
list_display = ['title', 'image', 'url','index', 'add_time']
search_fields = ['title', 'image', 'url','index']
list_filter = ['title', 'image', 'url','index', 'add_time']
xadmin.site.register(EmailVerifyRecord,EmailVerifyRecordAdmin)
xadmin.site.register(Banner,BannerAdmin)
# 将基本配置管理与view绑定
xadmin.site.register(views.BaseAdminView,BaseSetting)
# 将title和footer信息进行注册
xadmin.site.register(views.CommAdminView,GlobalSettings)
再进后台的界面,如下:

(3)修改app的名字
在apps.py里面配置app的显示名称
以users/apps.py为例,其它三个同样操作
默认apps.py里面的代码
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'
修改后:
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'
verbose_name = '用户'
还要在users/__init__.py中引用apps.py的配置
添加代码如下:
# users/__init__.py
default_app_config = 'users.apps.UsersConfig'
其它三个app也同样方法改成显示中文
大功告成

四、完成登录功能
4.1.首页和登录页面的配置
(1)把html文件中index.html拷贝到templates文件夹内
(2)新建static目录用来存放静态文件
在settings.py中设置路径
STATICFILES_DIRS = (
os.path.join(BASE_DIR,'static'),
)

(3)引用静态文件
使用ctrl+f查找出所有“../”, 然后ctrl+r 全部替换为“/static/”

(4)配置静态文件的url
MxOnline/urls.py中
# MxOnline/urls.py
import xadmin
from django.urls import path
from django.views.generic import TemplateView
urlpatterns = [
path('xadmin/', xadmin.site.urls),
path('', TemplateView.as_view(template_name='index.html'),name='index'),
]
(5)登录页面
把login.html拷贝到templates文件夹下
使用ctrl+f查找出所有“../”, 然后ctrl+r 全部替换为“/static/”
配置login的url
# MxOnline/urls.py
urlpatterns = [
path('xadmin/', xadmin.site.urls),
path('', TemplateView.as_view(template_name='index.html'),name='index'),
path('login/', TemplateView.as_view(template_name='login.html'),name='login'),
]
更改index.html里面跳转到登录界面的url
原始样子
<!-- <a style="color:white" class="fr registerbtn" href="register.html">注册</a> -->
<!-- <a style="color:white" class="fr loginbtn" href="login.html">登录</a> -->
取消注释,将login.html改为“login/”
<a style="color:white" class="fr registerbtn" href="register.html">注册</a>
<a style="color:white" class="fr loginbtn" href="/login/">登录</a>
现在可以访问index页面,然后点‘’登录”,跳转到登录页面了
4.2.用户登录
(1)修改login的路由
from django.views.generic import TemplateView
from users import views
urlpatterns = [
path('xadmin/', xadmin.site.urls),
path('', TemplateView.as_view(template_name='index.html'),name='index'),
path('login/',views.user_login,name = 'login'), #修改login路由
]
(2)写login的视图
from django.shortcuts import render
from django.contrib.auth import authenticate,login
def user_login(request):
if request.method == 'POST':
# 获取用户提交的用户名和密码
user_name = request.POST.get('username',None)
pass_word = request.POST.get('password',None)
# 成功返回user对象,失败None
user = authenticate(username=user_name,password=pass_word)
# 如果不是null说明验证成功
if user is not None:
# 登录
login(request,user)
return render(request,'index.html')
else:
return render(request,'login.html',{'msg':'用户名或密码错误'})
elif request.method == 'GET':
return render(request,'login.html')
(3)更改login.html
<form action="/login/" method="post" autocomplete="off">
<input type='hidden' name='csrfmiddlewaretoken' value='mymQDzHWl2REXIfPMg2mJaLqDfaS1sD5' />
<div class="form-group marb20 ">
<label>用 户 名</label>
<input name="username" id="account_l" type="text" placeholder="手机号/邮箱" />
</div>
<div class="form-group marb8 ">
<label>密 码</label>
<input name="password" id="password_l" type="password" placeholder="请输入您的密码" />
</div>
<div class="error btns login-form-tips" id="jsLoginTips">{{ msg }}</div>
<div class="auto-box marb38">
<a class="fr" href="forgetpwd.html">忘记密码?</a>
</div>
<input class="btn btn-green" id="jsLoginBtn" type="submit" value="立即登录 > " />
<input type='hidden' name='csrfmiddlewaretoken' value='5I2SlleZJOMUX9QbwYLUIAOshdrdpRcy' />
{% csrf_token %}
</form>

如果用户登录错误,应该有提示错误信息,下面代码:
<div class="error btns login-form-tips" id="jsLoginTips">{{ msg }}</div>

(4)修改index.html
原始index.html的代码
<div class=" header">
<div class="top">
<div class="wp">
<div class="fl"><p>服务电话:<b>33333333</b></p></div>
<!--登录后跳转-->
<a style="color:white" class="fr registerbtn" href="register.html">注册</a>
<a style="color:white" class="fr loginbtn" href="/login/">登录</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>
</div>
</div>
我们应该做个验证,当用户已登录状态的时候,显示用户姓名和图像及其个人中心信息
如果没有登录,则显示登录和注册
更改代码如下:

<div class=" header">
<div class="top">
{% 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 %}
<div class="wp">
<div class="fl"><p>服务电话:<b>33333333</b></p></div>
<!--登录后跳转-->
<a style="color:white" class="fr registerbtn" href="register.html">注册</a>
<a style="color:white" class="fr loginbtn" href="/login/">登录</a>
</div>
{% endif %}
</div>
(5)增加邮箱登录
让用户可以通过邮箱或者用户名都可以登录,用自定义authenticate方法
这里是继承ModelBackend类来做的验证
class ModelBackend:
"""
Authenticates against settings.AUTH_USER_MODEL.
"""
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
UserModel().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
def user_can_authenticate(self, user):
"""
Reject users with is_active=False. Custom user models that don't have
that attribute are allowed.
"""
is_active = getattr(user, 'is_active', None)
return is_active or is_active is None
def _get_user_permissions(self, user_obj):
return user_obj.user_permissions.all()
def _get_group_permissions(self, user_obj):
user_groups_field = get_user_model()._meta.get_field('groups')
user_groups_query = 'group__%s' % user_groups_field.related_query_name()
return Permission.objects.filter(**{user_groups_query: user_obj})
def _get_permissions(self, user_obj, obj, from_name):
"""
Return the permissions of `user_obj` from `from_name`. `from_name` can
be either "group" or "user" to return permissions from
`_get_group_permissions` or `_get_user_permissions` respectively.
"""
if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
return set()
perm_cache_name = '_%s_perm_cache' % from_name
if not hasattr(user_obj, perm_cache_name):
if user_obj.is_superuser:
perms = Permission.objects.all()
else:
perms = getattr(self, '_get_%s_permissions' % from_name)(user_obj)
perms = perms.values_list('content_type__app_label', 'codename').order_by()
setattr(user_obj, perm_cache_name, {"%s.%s" % (ct, name) for ct, name in perms})
return getattr(user_obj, perm_cache_name)
def get_user_permissions(self, user_obj, obj=None):
"""
Return a set of permission strings the user `user_obj` has from their
`user_permissions`.
"""
return self._get_permissions(user_obj, obj, 'user')
def get_group_permissions(self, user_obj, obj