后台登录
- 60.后端开发准备工作
- 61.adminlte框架集成和登录页面实现
- 62.User模型创建
- 63.登录功能实现(1)
- 64.登录功能实现(2)
- 65.重构Restful API的实现
- 66.将静态页面改造成Django模板(1)
- 67.将静态页面改造成Django模板(2)
- 68.登录模态对话框和网站的集成
- 69.登录功能和模态对话框集成
- 70.toast提示错误消息
- 71.登录状态更改和退出登录
- 72.图形验证码的制作和点击更换
- 73.短信验证码的发送
- 74.django项目中集成短信验证码发送
- 75.短信验证码与注册页面的集成
- 76.memcached存储验证码
- 77.注册功能后台代码完成
- 78.注册页面前端逻辑完成
- 79.CMS管理系统访问和限制
- 80.CMS管理页面主框架搭建
后端开发准备工作
1.配置好数据库
2.配置好模板文件的路径
3.配置好静态文件的路径
4.配置好时区
5.配置模板的static过滤器
1.配置好数据库
使用Navicat Premium
创建数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': "xfz",
'HOST': '127.0.0.1',
'PORT': '3306',
'USER': 'root',
'PASSWORD': 'root'
}
}
2.配置好模板文件的路径.
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'front','templates')] //配置模板文件的路径
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
'builtins':[
'django.templatetags.static'
]
},
},
]
3.配置好静态文件的路径
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'front','dist')
]
4.配置好时区
TIME_ZONE = 'Asia/Shanghai'
5.配置模板的static过滤器
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'front','templates')] //配置模板文件的路径
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
'builtins':[
'django.templatetags.static' // 配置模板的static过滤器
]
},
},
]
测试代码
#encoding: utf-8
from django.urls import path
from . import views
app_name = 'news'
urlpatterns = [
path('',views.index,name='index')
]
from django.shortcuts import render
def index(request):
return render(request,'news/index.html')
from django.urls import path,include
urlpatterns = [
path('',include("apps.news.urls"))]
<head>
<meta charset="UTF-8">
<title>知了课堂</title>
<link rel="stylesheet" href="{% static 'css/news/index.min.css'%}">
<link rel="stylesheet" href="//at.alicdn.com/t/font_681895_w5poic4qj6vfgvi.css">
<script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>
<script src="{% static 'js/index.min.js' %}"></script>
</head>
adminlte框架集成和登录页面实现
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>CMS后台管理系统|登录</title>
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- Bootstrap 3.3.7 -->
<link rel="stylesheet" href="{% static 'adminlte/bower_components/bootstrap/dist/css/bootstrap.min.css' %}">
<!-- Font Awesome -->
<link rel="stylesheet" href="{% static 'adminlte/bower_components/font-awesome/css/font-awesome.min.css' %}">
<!-- Ionicons -->
<link rel="stylesheet" href="{% static 'adminlte/bower_components/Ionicons/css/ionicons.min.css' %}">
<!-- Theme style -->
<link rel="stylesheet" href="{% static 'adminlte/dist/css/AdminLTE.min.css' %}">
<!-- iCheck -->
<link rel="stylesheet" href="{% static 'adminlte/plugins/iCheck/square/blue.css' %}">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<!-- Google Font -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">
</head>
<body class="hold-transition login-page">
<div class="login-box">
<div class="login-logo">
<a href="{% url 'cms:login' %}">CMS管理系统</a>
</div>
<!-- /.login-logo -->
<div class="login-box-body">
<p class="login-box-msg">请登录</p>
<form action="" method="post">
<div class="form-group has-feedback">
<input type="email" class="form-control" placeholder="邮箱">
<span class="glyphicon glyphicon-envelope form-control-feedback"></span>
</div>
<div class="form-group has-feedback">
<input type="password" class="form-control" placeholder="密码">
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
</div>
<div class="row">
<div class="col-xs-8">
<div class="checkbox icheck">
<label>
<input type="checkbox"> 记住我
</label>
</div>
</div>
<!-- /.col -->
<div class="col-xs-4">
<button type="submit" class="btn btn-primary btn-block btn-flat">登录</button>
</div>
<!-- /.col -->
</div>
</form>
</div>
<!-- /.login-box-body -->
</div>
<!-- /.login-box -->
<!-- jQuery 3 -->
<script src="{% static 'adminlte/bower_components/jquery/dist/jquery.min.js' %}"></script>
<!-- Bootstrap 3.3.7 -->
<script src="{% static 'adminlte/bower_components/bootstrap/dist/js/bootstrap.min.js' %}"></script>
<!-- iCheck -->
<script src="{% static 'adminlte/plugins/iCheck/icheck.min.js' %}"></script>
<script>
$(function () {
$('input').iCheck({
checkboxClass: 'icheckbox_square-blue',
radioClass: 'iradio_square-blue',
increaseArea: '20%' /* optional */
});
});
</script>
</body>
</html>
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'apps.news',
'apps.cms'
]
from django.urls import path
from . import views
app_name='cms'
urlpatterns=[
path('login/',views.login_view,name='login')
]
from django.shortcuts import render
def login_view(request):
return render(request,'cms/login.html')
urlpatterns = [
path('cms/', include('apps.cms.urls')),
path('',include("apps.news.urls")),
]
User模型创建
关于用户系统
1.使用django内置的User系统
2.需要重新定制
3.前后台使用的是同一个User系统
自定义User模型
1.创建一个xfzauth的app,用来管理用户系统
2.全部重写,继承自AbstractBaseUser
3.定义UserManager
4.设置AUTH_USER_MODEL
5.映射到数据库
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
AUTH_USER_MODEL='xfzauth.User'
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'apps.news',
'apps.cms',
'apps.xfzauth'
]
#encoding: utf-8
from django.contrib.auth.models import AbstractBaseUser,PermissionsMixin,BaseUserManager
from shortuuidfield import ShortUUIDField
from django.db import models
class UserManager(BaseUserManager):
def _create_user(self,telephone,username,password,**kwargs):
if not telephone:
raise ValueError('请传入手机号码!')
if not username:
raise ValueError('请传入用户名!')
if not password:
raise ValueError('请传入密码!')
user = self.model(telephone=telephone,username=username,**kwargs)
user.set_password(password)
user.save()
return user
def create_user(self,telephone,username,password,**kwargs):
kwargs['is_superuser'] = False
return self._create_user(telephone,username,password,**kwargs)
def create_superuser(self,telephone,username,password,**kwargs):
kwargs['is_superuser'] = True
kwargs['is_staff'] = True
return self._create_user(telephone,username,password,**kwargs)
class User(AbstractBaseUser,PermissionsMixin):
# 我们不使用默认的自增长的主键
# id:100,101,102,103
# uuid/shortuuid
# Shortuuidfield:pip install django-shortuuidfield
uid = ShortUUIDField(primary_key=True)
telephone = models.CharField(max_length=11,unique=True)
email = models.EmailField(unique=True,null=True)
username = models.CharField(max_length=100)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
data_joined = models.DateTimeField(auto_now_add=True)
USERNAME_FIELD = 'telephone'
# telephone,username,password
REQUIRED_FIELDS = ['username']
EMAIL_FIELD = 'email'
objects = UserManager()
def get_full_name(self):
return self.username
def get_short_name(self):
return self.username
登录功能实现(1)
1.因为共用一个账号,所以只要一个登陆界面
2.因为登陆界面是一个模态对话框,所以走ajax请求
3.把所有的登陆逻辑写在xfzauth这个app
登录功能实现(2)
重构Restful API的实现
#encoding: utf-8
from django.http import JsonResponse
class HttpCode(object):
ok = 200
paramserror = 400
unauth = 401
methoderror = 405
servererror = 500
# {"code":400,"message":"","data":{}}
def result(code=HttpCode.ok,message="",data=None,kwargs=None):
json_dict = {"code":code,"message":message,"data":data}
if kwargs and isinstance(kwargs,dict) and kwargs.keys():
json_dict.update(kwargs)
return JsonResponse(json_dict)
def ok():
return result()
def params_error(message="",data=None):
return result(code=HttpCode.paramserror,message=message,data=data)
def unauth(message="",data=None):
return result(code=HttpCode.unauth,message=message,data=data)
def method_error(message='',data=None):
return result(code=HttpCode.methoderror,message=message,data=data)
def server_error(message='',data=None):
return result(code=HttpCode.servererror,message=message,data=data)
将静态页面改造成Django模板(1)
{% extends 'base/front_base.html' %}
{% load news_filters %}
{% block title %}
小饭桌
{% endblock %}
{% block head %}
<link rel="stylesheet" href="{% static 'css/news/index.min.css' %}">
<script src="{% static 'arttemplate/template-web.js' %}"></script>
<script src="{% static 'js/index.min.js' %}"></script>
<script id="news-item" type="text/html">
{% verbatim %}
{{ each newses news index }}
<li>
<div class="thumbnail-group">
<a href="/news/{{ news.id }}/">
<img src="{{ news.thumbnail }}" alt="">
</a>
</div>
<div class="news-group">
<p class="title">
<a href="/news/{{ news.id }}/">{{ news.title }}</a>
</p>
<p class="desc">
{{ news.desc }}
</p>
<p class="more">
<span class="category">{{ news.category.name }}</span>
<span class="pub-time">{{ news.pub_time|timeSince }}</span>
<span class="author">{{ news.author.username }}</span>
</p>
</div>
</li>
{{ /each }}
{% endverbatim %}
</script>
{% endblock %}
{% block main %}
<div class="main">
<div class="wrapper">
<div class="main-content-wrapper">
<div class="banner-group" id="banner-group">
<ul class="banner-ul" id="banner-ul">
{% for banner in banners %}
<li>
<a href="{{ banner.link_to }}">
<img src="{{ banner.image_url }}" alt="">
</a>
</li>
{% endfor %}
</ul>
<span class="arrow left-arrow">‹</span>
<span class="arrow right-arrow">›</span>
<div class="page-control-group">
<ul class="page-control">
</ul>
</div>
</div>
<div class="list-outer-group">
<ul class="list-tab">
<li data-category="0" class="active"><a href="javascript:void(0);">最新资讯</a></li>
{% for category in categories %}
<li data-category="{{ category.pk }}"><a href="javascript:void(0);">{{ category.name }}</a></li>
{% endfor %}
</ul>
<ul class="list-inner-group">
{% for news in newses %}
<li>
<div class="thumbnail-group">
<a href="{% url 'news:news_detail' news_id=news.pk %}">
<img src="{{ news.thumbnail }}" alt="">
</a>
</div>
<div class="news-group">
<p class="title">
<a href="{% url 'news:news_detail' news_id=news.pk %}">{{ news.title }}</a>
</p>
<p class="desc">
{{ news.desc }}
</p>
<p class="more">
<span class="category">{{ news.category.name }}</span>
<span class="pub-time">{{ news.pub_time|time_since }}</span>
<span class="author">{{ news.author.username }}</span>
</p>
</div>
</li>
{% endfor %}
</ul>
<div class="load-more-group">
<button class="load-more" id="load-more-btn">查看更多</button>
</div>
</div>
</div>
{% include 'common/sidebar.html' %}
</div>
</div>
{% endblock %}
{% extends 'base/front_base.html' %}
{% load news_filters %}
{% block title %}
新闻详情页
{% endblock %}
{% block head %}
<link rel="stylesheet" href="{% static 'css/news/news_detail.min.css' %}">
<script src="{% static 'arttemplate/template-web.js' %}"></script>
<script src="{% static 'js/news_detail.min.js' %}"></script>
<script id="comment-item" type="text/html">
{% verbatim %}
<li>
<div class="comment-info">
<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1528129845916&di=536993042d5223862f8c4ab157ba6e72&imgtype=0&src=http%3A%2F%2Fpic1.ipadown.com%2Fimgs%2F201206120933354195.jpg" alt="" class="avatar">
<span class="author">{{ comment.author.username }}</span>
<span class="pub-time">{{ comment.pub_time|timeSince }}</span>
</div>
<p class="comment-content">{{ comment.content }}</p>
</li>
{% endverbatim %}
</script>
{% endblock %}
{% block main %}
<div class="main">
<div class="wrapper">
<div class="main-content-wrapper">
<div class="news-wrapper">
<h1 class="title">{{ news.title }}</h1>
<div class="news-info">
<div class="info-group">
<span class="author">{{ news.author.username }}</span>
<span class="pub-time">{{ news.pub_time|time_since }}</span>
<span class="category">{{ news.category.name }}</span>
</div>
<div class="share-group">
<span>分享至:</span>
<a class="weixin share-item"></a>
<a href="#" class="weibo share-item"></a>
</div>
</div>
<article class="article">
{{ news.content|safe }}
</article>
</div>
<div class="comment-wrapper">
<h3 class="title">文章评论(0)</h3>
<textarea name="comment" class="comment-textarea logined-textarea" placeholder="立即登录,参与评论~"></textarea>
<div class="submit-btn-group">
<button class="submit-btn" data-news-id="{{ news.pk }}">立即评论</button>
</div>
<ul class="comment-list">
{% for comment in news.comments.all %}
<li>
<div class="comment-info">
<img src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1528129845916&di=536993042d5223862f8c4ab157ba6e72&imgtype=0&src=http%3A%2F%2Fpic1.ipadown.com%2Fimgs%2F201206120933354195.jpg" alt="" class="avatar">
<span class="author">{{ comment.author.username }}</span>
<span class="pub-time">{{ comment.pub_time|time_since }}</span>
</div>
<p class="comment-content">{{ comment.content }}</p>
</li>
{% endfor %}
</ul>
</div>
</div>
{% include 'common/sidebar.html' %}
</div>
</div>
{% endblock %}
<div class="sidebar-wrapper">
<div class="title-group">
<span class="title">在线课堂</span>
<a class="more" href="#">更多</a>
</div>
<div class="advertise-group">
<a href="#">
<img src="http://www.xfz.cn/static/build/images/side-gift-banner.png" alt="">
</a>
</div>
<div class="platform-group">
<div class="title-group">
<span class="title">关注小饭桌</span>
</div>
<div class="focus-group">
<ul class="left-group">
<li class="zhihu">
<a href="#" target="_blank">小饭桌创业课堂</a>
</li>
<li class="weibo">
<a href="#" target="_blank">小饭桌创业课堂</a>
</li>
<li class="toutiao">
<a href="#" target="_blank">小饭桌</a>
</li>
</ul>
<div class="right-group">
<p class="desc">扫码关注小饭桌微信公众平台xfz008</p>
</div>
</div>
</div>
<div class="hot-news-group">
<div class="title-group">
<span class="title">热门推荐</span>
</div>
<ul class="hot-list-group">
<li>
<div class="left-group">
<p class="title">
<a href="#">王健林卖掉进军海外首个项目:17亿售伦敦ON...</a>
</p>
<p class="more">
<span class="category">深度报道</span>
<span class="pub-time">1小时前</span>
</p>
</div>
<div class="right-group">
<a href="#">
<img src="http://static-image.xfz.cn/1516169692_914.jpg-website.news.list" alt="">
</a>
</div>
</li>
</ul>
</div>
</div>
{% extends 'base/front_base.html' %}
{% load news_filters %}
{% block title %}
搜索
{% endblock %}
{% block head %}
<link rel="stylesheet" href="{% static 'css/search/search.min.css' %}">
{% endblock %}
{% block main %}
<div class="main">
<div class="wrapper">
<div class="main-content-wrapper">
<div class="search-group">
<form action="" method="get">
<input type="text" name="q" class="search-input" placeholder="请输入关键字" value="{{ query }}">
<input type="submit" class="search-btn" value="搜索">
</form>
</div>
<div class="recommend-group">
<p class="recommend-title">热门推荐</p>
<ul class="recommend-list">
{% for result in page.object_list %}
{% with result.object as news %}
<li>
<div class="thumbnail-group">
<a href="#">
<img src="{{ news.thumbnail }}" alt="">
</a>
</div>
<div class="news-group">
<p class="title">
<a href="#">{{ news.title }}</a>
</p>
<p class="desc">
{{ news.desc }}
</p>
<p class="more">
<span class="category">{{ news.category.name }}</span>
<span class="pub-time">{{ news.pub_time|time_since }}</span>
<span class="author">{{ news.author.username }}</span>
</p>
</div>
</li>
{% endwith %}
{% endfor %}
</ul>
</div>
</div>
{% include 'common/sidebar.html' %}
</div>
</div>
{% endblock %}
将静态页面改造成Django模板(2)
在这里插入代码片
在这里插入代码片
在这里插入代码片
在这里插入代码片
在这里插入代码片
在这里插入代码片
登录模态对话框和网站的集成
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200203161457653.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMzc1MzE4,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200203161518883.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMzc1MzE4,size_16,color_FFFFFF,t_70)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>授权页面</title>
<link rel="stylesheet" href="../../dist/css/common/auth.min.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_681895_ufv8ns8pw60z4cxr.css">
<script src="../../dist/js/jquery-3.3.1.min.js"></script>
<script src="../../dist/js/auth.min.js"></script>
</head>
<body>
<button id="btn">登录/注册</button>
<div class="mask-wrapper">
<div class="auth-wrapper">
<div class="close-wrapper">
<span class="close-btn icon-close iconfont"></span>
</div>
<div class="auth-inner-wrapper">
<div class="scroll-wrapper">
<div class="signin-group auth-group">
<div class="top-group">
<span class="title">请登录</span>
<a href="javascript: void(0);" class="switch">立即注册></a>
</div>
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control" name="telephone" placeholder="请输入手机号码">
</div>
<div class="input-group">
<input type="password" class="form-control" name="password" placeholder="请输入密码">
</div>
<div class="input-group">
<label class="remember-label">
<input type="checkbox" name="remember" value="1">
记住我
</label>
</div>
<div class="input-group">
<input type="submit" class="submit-btn" value="立即登录">
</div>
</div>
</div>
<div class="signup-group auth-group">
<div class="top-group">
<span class="title">请注册</span>
<a href="javascript: void(0);" class="switch">立即登录></a>
</div>
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control" name="telephone" placeholder="手机号码">
</div>
<div class="input-group">
<input type="text" class="form-control" name="username" placeholder="用户名">
</div>
<div class="input-group">
<div class="short-input-group">
<input type="text" class="form-control" name="img_captcha" placeholder="图形验证码">
</div>
<div class="input-group-addon">
<img src="" alt="">
</div>
</div>
<div class="input-group">
<input type="text" class="form-control" name="password1" placeholder="密码">
</div>
<div class="input-group">
<input type="text" class="form-control" name="password2" placeholder="确认密码">
</div>
<div class="input-group">
<div class="short-input-group">
<input type="text" class="form-control" name="sms_captcha" placeholder="短信验证码">
</div>
<div class="input-group-addon">
<span class="sms-captcha-btn">发送验证码</span>
</div>
</div>
<div class="input-group">
<input type="submit" class="submit-btn" value="立即注册">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
//
// // 点击登录按钮,弹出模态对话框
// $(function () {
// $("#btn").click(function () {
// $(".mask-wrapper").show();
// });
//
// $(".close-btn").click(function () {
// $(".mask-wrapper").hide();
// });
// });
//
//
// $(function () {
// $(".switch").click(function () {
// var scrollWrapper = $(".scroll-wrapper");
// var currentLeft = scrollWrapper.css("left");
// currentLeft = parseInt(currentLeft);
// if(currentLeft < 0){
// scrollWrapper.animate({"left":'0'});
// }else{
// scrollWrapper.animate({"left":"-400px"});
// }
// });
// });
function Auth() {
var self = this;
self.maskWrapper = $('.mask-wrapper');
self.scrollWrapper = $(".scroll-wrapper");
}
Auth.prototype.run = function () {
var self = this;
self.listenShowHideEvent();
self.listenSwitchEvent();
self.listenSigninEvent();
};
Auth.prototype.showEvent = function () {
var self = this;
self.maskWrapper.show();
};
Auth.prototype.hideEvent = function () {
var self = this;
self.maskWrapper.hide();
};
Auth.prototype.listenShowHideEvent = function () {
var self = this;
var signinBtn = $('.signin-btn');
var signupBtn = $('.signup-btn');
var closeBtn = $('.close-btn');
signinBtn.click(function () {
self.showEvent();
self.scrollWrapper.css({"left":0});
});
signupBtn.click(function () {
self.showEvent();
self.scrollWrapper.css({"left":-400});
});
closeBtn.click(function () {
self.hideEvent();
});
};
Auth.prototype.listenSwitchEvent = function () {
var self = this;
var switcher = $(".switch");
switcher.click(function () {
var currentLeft = self.scrollWrapper.css("left");
currentLeft = parseInt(currentLeft);
if(currentLeft < 0){
self.scrollWrapper.animate({"left":'0'});
}else{
self.scrollWrapper.animate({"left":"-400px"});
}
});
};
Auth.prototype.listenSigninEvent = function () {
var self = this;
var signinGroup = $('.signin-group');
var telephoneInput = signinGroup.find("input[name='telephone']");
var passwordInput = signinGroup.find("input[name='password']");
var rememberInput = signinGroup.find("input[name='remember']");
var submitBtn = signinGroup.find(".submit-btn");
submitBtn.click(function () {
var telephone = telephoneInput.val();
var password = passwordInput.val();
var remember = rememberInput.prop("checked");
xfzajax.post({
'url': '/account/login/',
'data': {
'telephone': telephone,
'password': password,
'remember': remember?1:0
},
'success': function (result) {
if(result['code'] == 200){
self.hideEvent();
window.location.reload();
}else{
var messageObject = result['message'];
if(typeof messageObject == 'string' || messageObject.constructor == String){
window.messageBox.show(messageObject);
}else{
// {"password":['密码最大长度不能超过20为!','xxx'],"telephone":['xx','x']}
for(var key in messageObject){
var messages = messageObject[key];
var message = messages[0];
window.messageBox.show(message);
}
}
}
},
'fail': function (error) {
console.log(error);
}
});
});
};
$(function () {
var auth = new Auth();
auth.run();
});
登录功能和模态对话框集成
toast提示错误消息
登录状态更改和退出登录
图形验证码的制作和点击更换
pip install Pillow
# -*- coding: utf-8 -*-
# @Author: huangyong
# @Date: 2016-10-29 22:18:38
# @Last Modified by: Administrator
# @Last Modified time: 2016-11-21 20:08:31
import random
# pip install Pillow
# Image:是一个画板(context),ImageDraw:是一个画笔, ImageFont:画笔的字体
from PIL import Image,ImageDraw,ImageFont
import time
import os
import string
# Captcha验证码
class Captcha(object):
# 把一些常量抽取成类属性
#字体的位置
font_path = os.path.join(os.path.dirname(__file__),'verdana.ttf')
# font_path = 'utils/captcha/verdana.ttf'
#生成几位数的验证码
number = 4
#生成验证码图片的宽度和高度
size = (100,40)
#背景颜色,默认为白色 RGB(Re,Green,Blue)
bgcolor = (0,0,0)
#随机字体颜色
random.seed(int(time.time()))
fontcolor = (random.randint(200,255),random.randint(100,255),random.randint(100,255))
# 验证码字体大小
fontsize = 20
#随机干扰线颜色。
linecolor = (random.randint(0,250),random.randint(0,255),random.randint(0,250))
# 是否要加入干扰线
draw_line = True
# 是否绘制干扰点
draw_point = True
# 加入干扰线的条数
line_number = 3
SOURCE = list(string.ascii_letters)
for index in range(0, 10):
SOURCE.append(str(index))
#用来随机生成一个字符串(包括英文和数字)
# 定义成类方法,然后是私有的,对象在外面不能直接调用
@classmethod
def gene_text(cls):
return ''.join(random.sample(cls.SOURCE,cls.number))#number是生成验证码的位数
#用来绘制干扰线
@classmethod
def __gene_line(cls,draw,width,height):
begin = (random.randint(0, width), random.randint(0, height))
end = (random.randint(0, width), random.randint(0, height))
draw.line([begin, end], fill = cls.linecolor)
# 用来绘制干扰点
@classmethod
def __gene_points(cls,draw,point_chance,width,height):
chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100]
for w in range(width):
for h in range(height):
tmp = random.randint(0, 100)
if tmp > 100 - chance:
draw.point((w, h), fill=(0, 0, 0))
#生成验证码
@classmethod
def gene_code(cls):
width,height = cls.size #宽和高
image = Image.new('RGBA',(width,height),cls.bgcolor) #创建画板
font = ImageFont.truetype(cls.font_path,cls.fontsize) #验证码的字体
draw = ImageDraw.Draw(image) #创建画笔
text = cls.gene_text() #生成字符串
font_width, font_height = font.getsize(text)
draw.text(((width - font_width) / 2, (height - font_height) / 2),text,font= font,fill=cls.fontcolor) #填充字符串
# 如果需要绘制干扰线
if cls.draw_line:
# 遍历line_number次,就是画line_number根线条
for x in range(0,cls.line_number):
cls.__gene_line(draw,width,height)
# 如果需要绘制噪点
if cls.draw_point:
cls.__gene_points(draw,10,width,height)
return (text,image)
短信验证码的发送
注册短信API:聚合网站:https://www.juhe.cn/
pip install requests
#encoding: utf-8
import requests
def send(mobile,captcha):
url = "http://v.juhe.cn/sms/send"
params = {
"mobile": mobile,
"tpl_id": "121674",
"tpl_value": "#code#="+captcha,
"key": "4f2dc49ce16b8538522f0f11fb6cd0a2"
}
response = requests.get(url,params=params)
result = response.json()
if result['error_code'] == 0:
return True
else:
return False
def sms_captcha(request):
# /sms_captcha/?telephone=xxx
telephone = request.GET.get('telephone')
code = Captcha.gene_text()
cache.set(telephone,code,5*60)
print('短信验证码:',code)
# result = aliyunsms.send_sms(telephone,code)
result = smssender.send(telephone,code)
if result:
return restful.ok()
else:
return result.params_error(message="短信验证码发送失败!")
django项目中集成短信验证码发送
短信验证码与注册页面的集成
memcached存储验证码
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
AUTH_USER_MODEL='xfzauth.User'
# 缓存配置
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211'
}
}
注册功能后台代码完成
#encoding: utf-8
from django import forms
from apps.forms import FormMixin
from django.core.cache import cache
from .models import User
class LoginForm(forms.Form,FormMixin):
telephone = forms.CharField(max_length=11)
password = forms.CharField(max_length=20,min_length=6,error_messages={"max_length":"密码最多不能超过20个字符!","min_length":"密码最少不能少于6个字符!"})
remember = forms.IntegerField(required=False)
class RegisterForm(forms.Form,FormMixin):
telephone = forms.CharField(max_length=11)
username = forms.CharField(max_length=20)
password1 = forms.CharField(max_length=20, min_length=6,
error_messages={"max_length": "密码最多不能超过20个字符!", "min_length": "密码最少不能少于6个字符!"})
password2 = forms.CharField(max_length=20, min_length=6,
error_messages={"max_length": "密码最多不能超过20个字符!", "min_length": "密码最少不能少于6个字符!"})
img_captcha = forms.CharField(min_length=4,max_length=4)
sms_captcha = forms.CharField(min_length=4,max_length=4)
def clean(self):
cleaned_data = super(RegisterForm, self).clean()
password1 = cleaned_data.get('password1')
password2 = cleaned_data.get('password2')
if password1 != password2:
raise forms.ValidationError('两次密码输入不一致!')
img_captcha = cleaned_data.get('img_captcha')
cached_img_captcha = cache.get(img_captcha.lower())
if not cached_img_captcha or cached_img_captcha.lower() != img_captcha.lower():
raise forms.ValidationError("图形验证码错误!")
telephone = cleaned_data.get('telephone')
sms_captcha = cleaned_data.get('sms_captcha')
cached_sms_captcha = cache.get(telephone)
if not cached_sms_captcha or cached_sms_captcha.lower() != sms_captcha.lower():
raise forms.ValidationError('短信验证码错误!')
exists = User.objects.filter(telephone=telephone).exists()
if exists:
forms.ValidationError('该手机号码已经被注册!')
return cleaned_data
def register(request):
form = RegisterForm(request.POST)
if form.is_valid():
telephone = form.cleaned_data.get('telephone')
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password1')
user = User.objects.create_user(telephone=telephone,username=username,password=password)
login(request,user)
return restful.ok()
else:
print(form.get_errors())
return restful.params_error(message=form.get_errors())
def img_captcha(request):
text,image = Captcha.gene_code()
# BytesIO:相当于一个管道,用来存储图片的流数据
out = BytesIO()
# 调用image的save方法,将这个image对象保存到BytesIO中
image.save(out,'png')
# 将BytesIO的文件指针移动到最开始的位置
out.seek(0)
response = HttpResponse(content_type='image/png')
# 从BytesIO的管道中,读取出图片数据,保存到response对象上
response.write(out.read())
response['Content-length'] = out.tell()
# 12Df:12Df.lower()
cache.set(text.lower(),text.lower(),5*60)
return response
def sms_captcha(request):
# /sms_captcha/?telephone=xxx
telephone = request.GET.get('telephone')
code = Captcha.gene_text()
cache.set(telephone,code,5*60)
print('短信验证码:',code)
# result = aliyunsms.send_sms(telephone,code)
result = smssender.send(telephone,code)
if result:
return restful.ok()
else:
return result.params_error(message="短信验证码发送失败!")
def cache_test(request):
cache.set('username','zhiliao',60)
result = cache.get('username')
print(result)
return HttpResponse('success')
注册页面前端逻辑完成
Auth.prototype.listenSignupEvent = function () {
var signupGroup = $('.signup-group');
var submitBtn = signupGroup.find('.submit-btn');
submitBtn.click(function (event) {
event.preventDefault();
var telephoneInput = signupGroup.find("input[name='telephone']");
var usernameInput = signupGroup.find("input[name='username']");
var imgCaptchaInput = signupGroup.find("input[name='img_captcha']");
var password1Input = signupGroup.find("input[name='password1']");
var password2Input = signupGroup.find("input[name='password2']");
var smsCaptchaInput = signupGroup.find("input[name='sms_captcha']");
var telephone = telephoneInput.val();
var username = usernameInput.val();
var img_captcha = imgCaptchaInput.val();
var password1 = password1Input.val();
var password2 = password2Input.val();
var sms_captcha = smsCaptchaInput.val();
xfzajax.post({
'url': '/account/register/',
'data': {
'telephone': telephone,
'username': username,
'img_captcha': img_captcha,
'password1': password1,
'password2': password2,
'sms_captcha': sms_captcha
},
'success': function (result) {
window.location.reload();
}
});
});
};
var xfzajax = {
'get': function (args) {
args['method'] = 'get';
this.ajax(args);
},
'post': function (args) {
args['method'] = 'post';
this._ajaxSetup();
this.ajax(args);
},
'ajax': function (args) {
var success = args['success'];
args['success'] = function (result) {
if(result['code'] === 200){
if(success){
success(result);
}
}else{
var messageObject = result['message'];
if(typeof messageObject == 'string' || messageObject.constructor == String){
window.messageBox.showError(messageObject);
}else{
// {"password":['密码最大长度不能超过20为!','xxx'],"telephone":['xx','x']}
for(var key in messageObject){
var messages = messageObject[key];
var message = messages[0];
window.messageBox.showError(message);
}
}
if(success){
success(result);
}
}
};
args['fail'] = function (error) {
console.log(error);
window.messageBox.showError('服务器内部错误!');
};
$.ajax(args);
},
'_ajaxSetup': function () {
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
}
};
CMS管理系统访问和限制
<div class="user-more-box">
<i class="triangle"></i>
{% if user.is_staff %}
<a href="{% url 'cms:index' %}">管理系统</a>
{% endif %}
<a href="{% url 'xfzauth:logout' %}">退出登录</a>
</div>
@staff_member_required(login_url='index')
def index(request):
return render(request,'cms/index.html')
CMS管理页面主框架搭建
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{% block title %}{% endblock %} | 管理系统</title>
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<link rel="stylesheet" href="{% static 'adminlte/bower_components/bootstrap/dist/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'adminlte/bower_components/font-awesome/css/font-awesome.min.css' %}">
<link rel="stylesheet" href="{% static 'adminlte/dist/css/AdminLTE.min.css' %}">
<link rel="stylesheet" href="{% static 'adminlte/dist/css/skins/_all-skins.min.css' %}">
<link rel="stylesheet" href="{% static 'adminlte/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css' %}">
<link rel="stylesheet" href="{% static 'sweetalert/sweetalert.css' %}">
<script src="{% static 'adminlte/bower_components/jquery/dist/jquery.min.js' %}"></script>
<script src="{% static 'adminlte/bower_components/bootstrap/dist/js/bootstrap.min.js' %}"></script>
<script src="{% static 'adminlte/dist/js/adminlte.min.js' %}"></script>
<script src="{% static 'sweetalert/sweetalert.min.js' %}"></script>
<script src="{% static 'js/xfzajax.min.js' %}"></script>
<script src="{% static 'js/xfzalert.min.js' %}"></script>
<script src="{% static 'js/message.min.js' %}"></script>
{% block head %}{% endblock %}
</head>
<body class="hold-transition skin-blue sidebar-mini">
<div class="wrapper">
<header class="main-header">
<!-- Logo -->
<a href="/" class="logo">
<!-- mini logo for sidebar mini 50x50 pixels -->
<span class="logo-mini"><b>CMS</b></span>
<!-- logo for regular state and mobile devices -->
<span class="logo-lg">后台管理系统</span>
</a>
<!-- Header Navbar: style can be found in header.less -->
<nav class="navbar navbar-static-top">
<!-- Sidebar toggle button-->
<a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button">
<span class="sr-only">Toggle navigation</span>
</a>
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<li class="dropdown user user-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<img src="http://edu-image.nosdn.127.net/4B9BE586B1F982317B930C2C2E6F702D.png"
class="user-image" alt="User Image">
<span class="hidden-xs"></span>
</a>
<ul class="dropdown-menu">
<!-- User image -->
<li class="user-header">
<img src="http://edu-image.nosdn.127.net/4B9BE586B1F982317B930C2C2E6F702D.png"
class="img-circle" alt="User Image">
<p>
<small>Python web开发工程师</small>
</p>
</li>
<!-- Menu Footer-->
<li class="user-footer">
<div class="pull-left">
<a href="#" class="btn btn-default btn-flat">个人中心</a>
</div>
<div class="pull-right">
<a href="#" class="btn btn-default btn-flat">退出登录</a>
</div>
</li>
</ul>
</li>
</ul>
</div>
</nav>
</header>
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar">
<form action="#" method="get" class="sidebar-form">
<div class="input-group">
<input type="text" name="q" class="form-control" placeholder="首页">
<span class="input-group-btn">
<button type="submit" name="search" id="search-btn" class="btn btn-flat"><i class="fa fa-search"></i>
</button>
</span>
</div>
</form>
<!-- /.search form -->
<!-- sidebar menu: : style can be found in sidebar.less -->
<ul class="sidebar-menu" data-widget="tree">
<li class="active">
<a href="{% url 'cms:index' %}">
<i class="fa fa-home"></i>
<span>首页</span>
</a>
</li>
{% if perms.news.change_news %}
<li class="header">新闻管理</li>
<li>
<a href="{% url 'cms:news_list' %}">
<i class="fa fa-list"></i>
<span>新闻列表</span>
</a>
</li>
<li>
<a href="{% url 'cms:write_news' %}">
<i class="fa fa-edit"></i>
<span>发布新闻</span>
</a>
</li>
<li>
<a href="{% url 'cms:news_category' %}">
<i class="fa fa-tag"></i>
<span>新闻分类</span>
</a>
</li>
<li>
<a href="{% url 'cms:banners' %}">
<i class="fa fa-window-restore"></i>
<span>轮播图</span>
</a>
</li>
{% endif %}
{% if perms.course.change_course %}
<li class="header">课程管理</li>
<li>
<a href="{% url 'cms:pub_course' %}">
<i class="fa fa-tv"></i>
<span>发布课程</span>
</a>
</li>
{% endif %}
{% if user.is_superuser %}
<li class="header">员工管理</li>
<li>
<a href="{% url 'cms:staffs' %}">
<i class="fa fa-group"></i>
<span>员工管理</span>
</a>
</li>
{% endif %}
</ul>
</section>
</aside>
<div class="content-wrapper">
<section class="content-header">
{% block content-header %}{% endblock %}
</section>
<section class="content">
{% block content %}{% endblock %}
</section>
</div>
<footer class="main-footer">
<strong>小</strong>后台管理系统
</footer>
</div>
</body>
</html>