django-admin startproject meiduo_mall
TEMPLATES = [
{
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'DIRS': [os.path.join(BASE_DIR,'templates'),],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
'environment':'app.base_jinja2.environment'
},
},
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
}
]
from jinja2 import Environment
from django.urls import reverse
from django.contrib.staticfiles.storage import staticfiles_storage
def jinja2_environment(**options):
"""jinja2 环境"""
# 创建环境对象
env = Environment(**options)
# 自定义语法 {
{static('静态文件相对路径')}} {
{url('路由命名空间')}}
env.globals.update({
'static': staticfiles_storage.url, # 获取静态文件前缀
'url': reverse, # 反向解析
})
# 返回环境对象
return env
"""
确保可以使用模板引擎中的{
{ url('') }} {
{ static('') }}这类语句
"""
TEMPLATES = [
{
'BACKEND': 'django.template.backends.jinja2.Jinja2', # jinja2模板引擎
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
# 补充Jinja2模板引擎环境
'environment': 'meiduo_mall.utils.jinja2_env.jinja2_environment',
},
},
]
create database meiduo_mall charset=utf8;
create user itcast identified by '123456';
grant all on meiduo_mall.* to 'itcast'@'%';
flush privileges;
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 数据库引擎
'HOST': '127.0.0.1', # 数据库主机
'PORT': 3306, # 数据库端口
'USER': 'itcast', # 数据库用户名
'PASSWORD': '123456', # 数据库用户密码
'NAME': 'meiduo_mall' # 数据库名字
},
}
需要安装PyMySQL的驱动程序
7. 工程同名目录下的 __init__文件中 加入代码
import pymysql
pymysql.version_info = (1, 4, 2, "final", 0)
pymysql.install_as_MySQLdb()
pip install django-redis
配置Redis数据库
CACHES = {
"default": {
# 默认
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
"session": {
# session
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
}
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "session"
default:
默认的Redis配置项,采用0号Redis库。
session:
状态保持的Redis配置项,采用1号Redis库。
SESSION_ENGINE
修改session存储机制使用Redis保存。
SESSION_CACHE_ALIAS:
使用名为"session"的Redis配置项存储session数据。
LOGGING = {
'version': 1,
'disable_existing_loggers': False, # 是否禁用已经存在的日志器
'formatters': {
# 日志信息显示的格式
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
},
'simple': {
'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
},
},
'filters': {
# 对日志进行过滤
'require_debug_true': {
# django在debug模式下才输出日志
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': {
# 日志处理方法
'console': {
# 向终端中输出日志
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'file': {
# 向文件中输出日志
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(os.path.dirname(BASE_DIR), 'logs/meiduo.log'), # 日志文件的位置
'maxBytes': 300 * 1024 * 1024,
'backupCount': 10,
'formatter': 'verbose'
},
},
'loggers': {
# 日志器
'django': {
# 定义了一个名为django的日志器
'handlers': ['console', 'file'], # 可以同时向终端与文件中输出日志
'propagate': True, # 是否继续传递日志信息
'level': 'INFO', # 日志器接收的最低日志级别
},
}
}
日志记录器的使用
import logging
# 创建日志记录器
logger = logging.getLogger('django')
# 输出日志
logger.debug('测试logging模块debug')
logger.info('测试logging模块info')
logger.error('测试logging模块error')
STATIC_URL = '/static/'
# 配置静态文件加载路径
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
python ../../manage.py startapp users
需要在setting文件里注册用户模块应用
为了方便注册用户模块应用需要追加apps导包路径
# 追加导包路径指向apps
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'users', # 用户模块应用
]
class RegisterView(View):
def get(self, request):
# 返回一个html界面 request 就是当前界面
return render(request, "register.html")
总路由配置
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
# users
# 这里include 里是users.urls 不是 users:urls 我在这里一直报错最后才发现
path('', include('users.urls')),
]
在users目录下创建urls.py文件
from django.urls import path
from . import views
urlpatterns = [
# 注册用户界面
path('register/', views.RegisterView.as_view(), name="register"),
]
运行程序
3. 用户模型类
定义用户模型类
这个项目用到的字段有 ↓
用户名 密码 手机号 Email 是否是管理员 is_delete
Django 框架默认使用一个 User 模型类, 保存我们有关用户的字段
User对象基本属性创建用户必选: username、password
创建用户可选:email、first_name、last_name、last_login、date_joined、is_active、is_staff、is_superuse
判断用户是否通过认证:is_authenticated
创建用户的方法
user = User.objects.create_user(username, email, password,
**extra_fields) 用户认证的方法
Django 自带用户认证系统
它处理用户账号、组、权限以及基于 cookie 的用户会话
Django 认证系统同时处理认证和授权
认证:验证一个用户是否它声称的那个人,可用于账号登录. 授权:授权决定一个通过了认证的用户被允许做什么. Django 认证系统包含的内容用户:用户模型类、用户认证. 权限:标识一个用户是否可以做一个特定的任务,MIS 系统常用到.
组:对多个具有相同权限的用户进行统一管理,MIS 系统常用到. 密码:一个可配置的密码哈希系统,设置密码、密码校验. # 导入
from django.contrib.auth import authenticate
进行认证校验, 查看用户是否是声明的那一个
user = authenticate(username=username, password=password, **kwargs) 处理密码的方法设置密码:set_password(raw_password)
校验密码:check_password(raw_password)
通过观察我们发现 注册数据中需要用户的手机号mobile信息,但是默认的django默认用户模型类中没有mobile字段,所以要自定义用户模型类
自定义用户模型类 (继承AbstractUser)
from django.db import models
# Create your models here.
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
"""自定义用户模型类"""
mobile = models.CharField(max_length=11, unique=True, verbose_name="手机号")
# 对当前表进行相关设计
class Meta:
# 表名
db_table = 'tb_users'
# 复杂的名称 用作中文解释
verbose_name = '用户'
# 复数名称
verbose_name_plural = verbose_name
# 在str魔法方法中 返回用户名称
def __str__(self):
return self.username
要指定用户模型类
# django 中默认使用 auth 子应用下面的 User 作为用户模型类
# 默认:
AUTH_USER_MODEL = 'auth.User'
# Django 用户模型类是通过全局配置项 AUTH_USER_MODEL 决定的
因为我们重写了用户模型类, 所以我们需要重新指定默认的用户模型类:
设置格式:
AUTH_USER_MODEL = '应用名.模型类名'
在dev文件中更改默认的用户模型类 格式:
# 指定本项目用户模型类
AUTH_USER_MODEL = 'users.User'
执行迁移文件
python manage.py migrate
这样就创建成功了
前端代码.html
能大概看懂就行,毕竟不是做前端开发
输入框焦点消失,发送axios请求给后端,后端返回判断结果给前端,前端判断是否显示后端给的数据。大概就是这样
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>美多商城-注册title>
<link rel="stylesheet" type="text/css" href="{
{ static('css/reset.css') }}">
<link rel="stylesheet" type="text/css" href="{
{ static('css/main.css') }}">
<script type="text/javascript" src="{
{ static('js/vue-2.5.16.js') }}">script>
<script type="text/javascript" src="{
{ static('js/axios-0.18.0.min.js') }}">script>
head>
<body>
<div id="app">
<div class="register_con">
<div class="l_con fl">
<a href="index.html" class="reg_logo"><img src="../static/images/logo.png">a>
<div class="reg_slogan">商品美 · 种类多 · 欢迎光临div>
<div class="reg_banner">div>
div>
<div class="r_con fr">
<div class="reg_title clearfix">
<h1>用户注册h1>
<a href="login.html">登录a>
div>
<div class="reg_form clearfix">
{#v-cloak 取消因为Vue加载过慢的显示问题#}
<form method="post" class="register_form" @submit="on_submit" v-cloak>
{
{ csrf_input }}
<ul>
<li>
<label>用户名:label>
{#@blur焦点#}
<input type="text" name="username" id="user_name" v-model="username" @blur="check_username">
<span class="error_tip" v-show="error_name">[[error_name_message]]span>
li>
<li>
<label>密码:label>
<input type="password" name="password" id="pwd" v-model="password" @blur="check_password">
<span class="error_tip" v-show="error_password">请输入8-20位的密码span>
li>
<li>
<label>确认密码:label>
<input type="password" name="password2" id="cpwd" v-model="password2"
@blur="check_password2">
<span class="error_tip" v-show="error_password2">两次输入的密码不一致span>
li>
<li>
<label>手机号:label>
<input type="text" name="mobile" id="phone" v-model="mobile" @blur="check_mobile">
<span class="error_tip" v-show="error_mobile">[[error_mobile_message]]span>
li>
<li>
<label>图形验证码:label>
<input type="text" name="image_code" id="pic_code" class="msg_input" v-model="image_code"
@blur="check_image_code">
<img :src="image_code_url" alt="图形验证码" class="pic_code" @click="generate_image_code">
<span class="error_tip" v-show="error_image_code">[[error_image_code_message]]span>
li>
<li>
<label>短信验证码:label>
<input type="text" name="sms_code" id="msg_code" class="msg_input" v-model="sms_code"
@blur="check_sms_code">
<a class="get_msg_code" @click="send_sms_code">[[sms_code_tip]]a>
<span class="error_tip" v-show="error_sms_code">[[error_sms_code_message]]span>
{% if sms_code_errmsg %}
<span class="error_tip">{
{ sms_code_errmsg }}span>
{% endif %}
li>
<li class="agreement">
<input type="checkbox" name="allow" id="allow" v-model="allow" @change="check_allow">
<label>同意”美多商城用户使用协议“label>
<span class="error_tip" v-show="error_allow">请勾选用户协议span>
{% if register_errmsg %}
<span class="error_tip">{
{ register_errmsg }}span>
{% endif %}
li>
<li class="reg_sub">
<input type="submit" value="注 册">
li>
ul>
form>
div>
div>
<div class="footer no-mp">
<div class="foot_link">
<a href="#">关于我们a>
<span>|span>
<a href="#">联系我们a>
<span>|span>
<a href="#">招聘人才a>
<span>|span>
<a href="#">友情链接a>
div>
<p>CopyRight © 2016 北京美多商业股份有限公司 All Rights Reservedp>
<p>电话:010-****888 京ICP备*******8号p>
div>
div>
<script src="{
{ static('js/common.js') }}">script>
<script src="{
{ static('js/register.js') }}">script>
body>
html>
对应的js文件 主要就是判断输入框里的数据是否合法,是否要在html界面显示对应的提示信息
// 创建vue对象
// 我们才用的ES6语法
let vm = new Vue({
el: '#app', // 通过id选择器找到绑定的html内容
// 修改Vue读取变量的语法
delimiters: ['[[', ']]'],
data: {
// 数据对象
// v-model
username: '',
password: '',
password2: '',
mobile: '',
allow: '',
image_code_url: '',
uuid: '',
image_code: '',
sms_code_tip: '获取短信验证码',
send_flag: false,
sms_code: '',
// v-show
error_name: false,
error_password: false,
error_password2: false,
error_mobile: false,
error_allow: false,
error_image_code: false,
error_sms_code: false,
// error_message
error_name_message: '',
error_mobile_message: '',
error_image_code_message: '',
error_sms_code_message: '',
},
// vue生命周期 页面加载完成
mounted() {
//生成图形验证码
this.generate_image_code();
},
methods: {
// 定义和实现事件方法
check_sms_code() {
if (this.sms_code.length != 6) {
this.error_sms_code_message = '请填入短信验证码'
this.error_sms_code = true;
} else {
this.error_sms_code = false;
}
},
// 发送短信验证码
send_sms_code() {
if (this.send_flag == true) {
// 判断是否已经发送短信
// 避免恶意用户频繁点击获取短信验证码的标签
return;
}
// 校验数据 mobile image_code
this.check_mobile();
this.check_image_code();
if (this.error_mobile == true || this.error_image_code == true) {
this.send_flag = false;
return;
}
this.send_flag = true;// 如果可以运行到这里,立即设置已经发送过短信
let url = '/sms_codes/' + this.mobile
+ '/?image_code=' + this.image_code
+ '&uuid=' + this.uuid
axios.get(url, {
responseType: 'json'
}).then(response => {
if (response.data.code == '0') {
// 展示倒计时60S
let num = 60;
let t = setInterval(() => {
if (num == 1) {
// 倒计时即将结束
// 停止回调函数
clearInterval(t);
// 还原sms_code_tip的提示文字
this.sms_code_tip = '获取短信验证码';
// 重新生成图形验证码
this.generate_image_code();
this.send_flag = false;
} else {
//正在倒计时
num -= 1;
this.sms_code_tip = num + '秒'
}
}, 1000)
} else {
//
if (response.data.code == '4001') {
// 图形验证码错误
this.error_image_code_message = response.data.errmsg;
this.error_image_code = true;
} else {
// 4002 短信验证码错误
this.error_sms_code_message = response.data.errmsg;
this.error_sms_code = true;
}
this.send_flag = false;
}
})
.catch(error => {
console.log(error.response);
this.send_flag = false;
})
},
// 生成图形验证码的方法:封装的思想,代码复用
generate_image_code() {
this.uuid = generateUUID();
this.image_code_url = '/image_codes/' + this.uuid + '/';
},
// 校验用户名
check_username() {
let re = /^[a-zA-Z0-9_-]{5,20}$/;
if (re.test(this.username)) {
this.error_name = false;
} else {
this.error_name_message = '请输入5-20个字符的用户名';
this.error_name = true;
}
// 判断用户名是否重复注册
if (this.error_name == false) {
//只有当用户输入条件满足之后再去判断
let url = '/usernames/' + this.username + '/count/'
axios.get(url, {
// 请求头 告诉后端数据响应格式
responseType: 'JSON'
})
.then(response => {
if (response.data.count == 1) {
// 用户名已存在
this.error_name_message = '用户名已存在'
this.error_name = true;
} else {
// 用户名不存在
this.error_name = false;
}
})
.catch(error => {
console.log(error.response);
})
}
},
// 校验密码
check_password() {
let re = /^[0-9A-Za-z]{8,20}$/;
if (re.test(this.password)) {
this.error_password = false;
} else {
this.error_password = true;
}
if (this.password2 != this.password) {
this.error_password2 = true;
} else {
this.error_password2 = false;
}
},
// 校验确认密码
check_password2() {
if (this.password != this.password2) {
this.error_password2 = true;
} else {
this.error_password2 = false;
}
},
// 校验手机号
check_mobile() {
let re = /^1[3-9]\d{9}$/;
if (re.test(this.mobile)) {
this.error_mobile = false;
} else {
this.error_mobile_message = '您输入的手机号格式不正确';
this.error_mobile = true;
}
if (this.error_mobile == false) {
let url = '/mobiles/' + this.mobile + '/count/'
axios.get(url, {
responseType: 'JSON'
})
.then(response => {
if (response.data.count == 1) {
this.error_mobile_message = '手机号已存在'
this.error_mobile = true;
} else {
this.error_mobile = false;
}
})
.catch(error => {
console.log(error.response);
})
}
},
// 校验图形验证码
check_image_code() {
if (this.image_code.length != 4) {
this.error_image_code_message = '请输入图形验证码';
this.error_image_code = true;
} else {
this.error_image_code = false;
}
},
// 校验是否勾选协议
check_allow() {
if (!this.allow()) {
this.error_allow = true;
} else {
this.error_allow = false;
}
},
// 校验表单提交
on_submit() {
this.check_username();
this.check_password();
this.check_password2();
this.check_mobile();
this.check_sms_code();
this.check_allow();
if (this.error_name == true || this.error_password == true || this.error_password2 == true
|| this.error_mobile == true || this.error_allow == true) {
// 禁用表单的提交
window.event.returnValue = false;
}
}
}
})
编写后端代码
def post(self, request):
"""实现用户注册"""
# 接收参数
username = request.POST.get('username')
password = request.POST.get('password')
password2 = request.POST.get('password2')
mobile = request.POST.get('mobile')
allow = request.POST.get('allow')
# 校验参数 判断参数是否齐全
if not all([username, password, password2, mobile, allow]):
return http.HttpResponseForbidden('缺少必传参数')
# 判断用户名是否是5-20个字符
if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
return http.HttpResponseForbidden('请输入5-20个字符的用户名')
# 判断密码是否是8-20个数字
if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
return http.HttpResponseForbidden('请输入8-20位的密码')
# 判断两次密码是否一致
if password != password2:
return http.HttpResponseForbidden('两次输入的密码不一致')
# 判断手机号是否合法
if not re.match(r'^1[3-9]\d{9}$', mobile):
return http.HttpResponseForbidden('请输入正确的手机号码')
# 判断是否勾选用户协议
if allow != 'on':
return http.HttpResponseForbidden('请勾选用户协议')
# 保存注册数据
try:
User.objects.create_user(username=username, password=password, mobile=mobile)
except DatabaseError:
return render(request, 'register.html', {
'register_errmsg': '注册失败'})
# 响应注册结果
return http.HttpResponse('注册成功,重定向到首页')
创建首页广告应用 contents
python ../../manage.py startapp contents
添加主路由
urlpatterns = [
# content
path('', include(('contents.urls', 'contents'), namespace='contents')),
]
添加子路由
urlpatterns = [
# 注册用户界面
path('', views.IndexView.as_view(), name="index"),
]
然后运行项目 测试能否正常访问
http://127.0.0.1:8000/
修改注册成功后重定向到首页
# 响应注册结果
return redirect(reverse('contents:index'))
然后运行项目按步骤进行注册,注册完成后是否会跳转到首页
同时查看数据库是否有用户注册信息
# 实现状态保持
login(request, user)
再次注册
查看状态保持结果
思路就是前端把用户名输入框的数据发送一个ajax请求(就是一个带有参数get请求)后端接收前端这个请求,并且把username提取出来,用这个数据查询数据库这个username的count返回给前端,前端判断count==1就说明这个数据已经存在,就在网页上显示用户名已存在,并且用户名异常设置为true
后端逻辑
class UsernameCountView(View):
"""判断用户名是否重复注册"""
def get(self, request, username):
"""
:param request: 请求对象
:param username: 用户名
:return: JSON
"""
# 判断username在表中的个数
count = User.objects.filter(username=username).count()
return http.JsonResponse({
'code': RETCODE.OK, 'errmsg': 'OK', 'count': count})
前端逻辑
// 校验用户名
check_username() {
let re = /^[a-zA-Z0-9_-]{5,20}$/;
if (re.test(this.username)) {
this.error_name = false;
} else {
this.error_name_message = '请输入5-20个字符的用户名';
this.error_name = true;
}
// 判断用户名是否重复注册
if (this.error_name == false) {
//只有当用户输入条件满足之后再去判断
let url = '/usernames/' + this.username + '/count/'
axios.get(url, {
// 请求头 告诉后端数据响应格式
responseType: 'JSON'
})
.then(response => {
if (response.data.count == 1) {
// 用户名已存在
this.error_name_message = '用户名已存在'
this.error_name = true;
} else {
// 用户名不存在
this.error_name = false;
}
})
.catch(error => {
console.log(error.response);
})
}
},
设置路由
如果路由中有正则。需要把path换成re_path 其他的应该没有什么变化吧。而且如果用到了\w \d一类的 要加r转义
# 判断用户名是否重复注册
re_path(r'usernames/(?P\w{5,20})/count/' , views.UsernameCountView.as_view()),
class MobileCountView(View):
"""判断手机号是否重复注册"""
def get(self, request, mobile):
"""
:param request: 请求对象
:param mobile: 手机号
:return: JSON
"""
count = User.objects.filter(mobile=mobile).count()
return http.JsonResponse({
'code': RETCODE.OK, "errmsg": "OK", "count": count})
# 路由
# 判断手机号是否重复注册
re_path(r'mobiles/(?P1[3-9]\d{9})/count/' , views.MobileCountView.as_view()),
后端相应的的是json数据
js逻辑
// 校验手机号
check_mobile() {
let re = /^1[3-9]\d{9}$/;
if (re.test(this.mobile)) {
this.error_mobile = false;
} else {
this.error_mobile_message = '您输入的手机号格式不正确';
this.error_mobile = true;
}
if (this.error_mobile == false) {
let url = '/mobiles/' + this.mobile + '/count/'
axios.get(url, {
responseType: 'JSON'
})
.then(response => {
if (response.data.count == 1) {
this.error_mobile_message = '手机号已存在'
this.error_mobile = true;
} else {
this.error_mobile = false;
}
})
.catch(error => {
console.log(error.response);
})
}
},