Django商城项目实战之注册界面
项目架构
- 前后端分离
- 前端 : HTML5 、css、js
- 后端 : DRF、Django1.11.11、python3.5
开始创建项目文件,和一些配置
创建项目
django-admin startproject 项目名
创建app
python manage.py startapp app的名字
然后创建对应的文件夹,为了分类各种文件内容
1.MySQL数据库配置
settings文件里:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'shanghui',
'USER': 'yy',
'PASSWORD': '123456',
'HOST': '127.0.0.1',
'PORT': '3306',
}
}
项目的同名文件夹下的__init__().py文件:
import pymysql
pymysql.install_as_MySQLdb()
2.Redis数据库配置
settings文件里:
# 这是redis作为session的保存数据库的设置
CACHES = {
'default':{
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS':{
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
},
'session':{
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/2',
'OPTIONS':{
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'session'
# 修改session的机制,使用redis进行保存,并且使用的是session对应的redis配置
3.跨域请求问题配置
安装:
pip install django-cors-headers
settings文件添加:
INSTALLED_APPS = [
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # 最好添加至第一行
]
CORS_ORIGIN_WHITELIST = (
# 这里设置域名
'http://127.0.0.1:8080',
)
CORS_ALLOW_CREDENTIALS = True #允许携带cookie
4.自定义异常处理配置
settings文件配置:
# 异常处理配置,后面是这个exceptions自定义异常的文件的路径
REST_FRAMEWORK = {
'EXCEPTION_HANDLER':'shanghuiproject.utils.exceptions.exception_handler',
}
先在utils文件夹里创建exceptions.py文件,然后编写下面内容:
from rest_framework.views import exception_handler as drf_handler
from rest_framework.response import Response
from rest_framework.status import HTTP_500_INTERNAL_SERVER_ERROR
def exception_handler(exc, content):
# 如果框架能处理的异常,还是让框架处理
response = drf_handler(exc, content)
# 框架不能处理的异常,自己处理
if response is None:
# 项目出错了,但是DRF框架对出错的异常没有处理
# 可以在此处对异常进行统一处理,比如:保存出错信息到日志文件
view = content['view'] # 出错的视图
error = '服务器内部错误, %s' % exc
print('%s: %s' %(view, error))
# return Response({'detail': error}, status=500)
response = Response({'message': '服务器错误'}, status=HTTP_500_INTERNAL_SERVER_ERROR)
return response
创建用户模型,可以使用django用户认证模型
优点:可以自动验证,并省了自己写字段
在users文件下的 models.py内容
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class User(AbstractUser):
phone = models.CharField(max_length=11,unique=True, verbose_name='手机号码')
class Meta:
verbose_name = '用户'
verbose_name_plural = verbose_name
然后在自己的settings文件里添加:
# 声明使用的是django自带的认证系统
AUTH_USER_MODEL = 'users.User'
登录页面html和css
这里主要讲后端内容,上面为我写完html和css内容后的效果图,当然在调试的时候还需要启动前端服务器。前端服务器的安装配置方法:https://blog.csdn.net/dakengbi/article/details/90764642
前端js文件内容:
//生成UUID
var myuuid = '';
function uuid() {
var s = [];
var hexDigits = "0123456789abcdef";
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = "-";
var uuid = s.join("");
return uuid;
}
//获取图片验证码
function getCode() {
myuuid = uuid();
var image_url = 'http://127.0.0.1:8000/image_code/' + myuuid ;
$('.piccode img').attr('src', image_url);
}
//获取短信验证码,同时校验图片验证码是否正确
//校验用户名
//密码和输入密码是否一致,且密码还有格式
function getMsgCode() {
// 0.获取值
var phone = $('#phone').val(); //手机号码
var html_text = $('#logintext1').val(); //图片验证吗
//1.1 获取到的数据进行正则的校验
var reg_uuid = /^[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}$/;
//发送GET数据,验证图片验证码
if(phone_flag && imageCode_flag) {
$('#errortext').text('');
var msgurl = 'http://127.0.0.1:8000/image_code/msg_code/?phone=' + phone + '&html_text=' + html_text + '&myuuid=' + myuuid;
// 2.ajax请求
$.ajax({
url: msgurl,
method: 'GET',
contentType: 'application/json',
success: function (data) {
console.log(data);
$('#errortext').text(data);
},
error: function (data) {
console.log(data);
}
})
}
else{
$('#errortext').text('手机号码或者图片验证码不正确');
}
}
//判断密码格式是否正确
var pwd1_flag = false;
function passwordInputBlur() {
// 验证密码
var pwd1 = $('#pwd1').val();
var reg_pwd1 = /^[a-z A-Z 0-9]{6,16}$/;
// console.log(reg_pwd1.exec(pwd1));
//密码校验
if(!reg_pwd1.test(pwd1)){
$('#pwd1_error').text('密码格式不正确');
pwd1_flag = false;
}
else{
$('#pwd1_error').text('');
pwd1_flag = true;
}
}
//判断再次输入的密码是否和第一次一样
var pwd2_flag = false;
function passwordAgainInputBlur() {
var pwd2 = $('#pwd2').val();
var pwd1 = $('#pwd1').val();
//第二次密码校验
if(pwd1_flag){
if(pwd1 != pwd2){
$('#pwd2_error').text('第二次密码输入不正确');
pwd2_flag = false;
}
else {
$('#pwd2_error').text('');
pwd2_flag = true;
}
}
}
//判断手机号码是否正确
var phone_flag = false;
function phoneInputBlur() {
var phone = $('#phone').val(); //手机号码
var reg_phone = /^1[3456789]\d{9}$/;
// console.log(reg_phone.exec(phone)); // 正则匹配结果
// 1.2 利用正则进行校验,如果不通过则弹窗
if(!reg_phone.test(phone)){
$('#phoneerror').text('手机号码校验未通过');
phone_flag = false;
}
else{
$('#phoneerror').text('');
phone_flag = true;
}
}
//判断用户名的格式是否正确
var username_flag = false;
function usernameInputBlur() {
var username = $('#username').val();
var reg_username = /^[\w]+$/;
console.log(reg_username.exec(username));
//用户名校验
if(!reg_username.test(username)){
$('#username_error').text('用户名不正确');
username_flag = false;
}
else{
$('#username_error').text('');
username_flag = true;
}
}
//图片验证码输入框失去焦点事件
var imageCode_flag = false;
function imageTextBlur() {
var html_text = $('#logintext1').val();
var reg_text = /^[\w]{4}$/;
if(!reg_text.test(html_text)){
$('#imagecode_error').text('图片验证码要为4位');
imageCode_flag = false;
}else {
$('#imagecode_error').text('');
imageCode_flag = true;
}
//通过ajax发送数据到后端
if (html_text && imageCode_flag) {
var data = {
'uuid' : myuuid,
'code' : html_text,
};
data = JSON.stringify(data);
$.ajax({
url:'http://127.0.0.1:8000/image_code/1235/',
type:'POST',
contentType: 'application/json',
data:data,
success: function (data) {
console.log(data);
if(data=='ok'){
$('#imagecode_error').text('');
imageCode_flag = true;
}
else{
$('#imagecode_error').text('图片验证码错误');
imageCode_flag = false;
}
},
error: function (data) {
console.log(data);
$('#imagecode_error').text('图片验证码错误');
imageCode_flag = false;
}
})
}
}
//判断邮箱格式是否正确
var email_flag = false;
function emailInputBlur() {
var email = $('#email').val();
// var reg_email = /[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/;
var reg_email = /^[\w]+@[\w]+\.com$/;
console.log(reg_email.exec(email));
if (!reg_email.test(email)){
$('#emailerror').text('邮箱格式错误');
email_flag = false;
}
else{
$('#emailerror').text('');
email_flag = true;
}
}
//注册点击事件
function submitChick() {
// todo 获取所有需要提交的数据
var username = $('#username').val();
var pwd1 = $('#pwd1').val();
var pwd2 = $('#pwd2').val();
var phone = $('#phone').val();
var email = $('#email').val();
var messageCode = $('#logintext2').val();
// todo 显示效果
$('#errortext').text('');
// todo 将获取的数据进行校验
if (messageCode && username_flag && pwd2_flag && phone_flag && email_flag){
// todo 将所有的数据拼接成json数据
var data = {
'username':username,
'password':pwd1,
'pwdagain':pwd2,
'phone':phone,
'email': email,
'messagecode':messageCode
};
data = JSON.stringify(data);
$.ajax({
// todo 使用ajax提交POST请求
url: 'http://127.0.0.1:8000/users/myusers/',
method: 'POST',
contentType: 'application/json',
data: data,
success:function (data) {
$('#errortext').text('注册成功');
console.log(data);
},
error: function (data) {
$('#errortext').text('注册失败');
console.log(data);
}
})
}
}
//入口函数
$(document).ready(function () {
getCode();
});
后端生成验证码图片,当得到前端GET请求时返回验证码图片
在django项目新建一个模块image_code, 在里面的views.py文件内容:
import random
from django.shortcuts import render
from django.http import HttpResponse
from rest_framework.views import APIView
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
import redis
from shanghuiproject.libs.yuntongxun.sms import CCP
from shanghuiproject.libs.captcha.captcha import captcha
from .constants import *
from . import serializers
# Create your views here.
class ImageView(APIView):
conn = redis.Redis(host='localhost', port=6379, db=1, password=None)
# 前段获取验证码图片
def get(self,request,image_id):
# 1.生成验证吗
text, image = captcha.generate_captcha()
# print('---------->', image_id)
# 2.要保存验证吗上面的文字部分
self.conn.setex(image_id, IMAGE_UUID_REDIS_EXPIRES, text)
# 3.返回图片
return HttpResponse(image, content_type='image/jpg')
# 图片验证码post验证是否正确
def post(self,request,image_id):
# print(request.data)
uuid = request.data.get('uuid')
code = request.data.get('code').upper()
redis_data = self.conn.get(uuid)
if redis_data:
if redis_data.decode() == code:
return Response('ok')
return Response('wrong')
当点击获取短信验证码按钮时,后端会对手机号和图片验证码进行检测,如果通过就发送短信验证码:
在django项目里的image_code模块, 在里面的views.py文件内容添加:
class MsgView(GenericAPIView):
serializer_class = serializers.ImageTextSerializers
def get(self,request):
# 1.校验图片验证码
print('-------->>>>', request.query_params)
query_dict = request.query_params
serializer = self.get_serializer(data=query_dict)
serializer.is_valid(raise_exception=True)
# 2.在5分钟不再重复发送(利用redis可以设置过期时间的特点,将表示在5分钟内发送的标准设置放入redis)
# 也就是说5分钟内如果能在redis中找到数据,就是代表着重复操作
# redis 中标志的例子1311111111111_flag: 1
# 3.生成短信验证码
msg = '%04d' %random.randint(0,9999)
print('生成的短信验证码', msg)
# 4.保存短信验证码
# todo 2.1 连接校验专用的redis数据
conn = redis.Redis(host='localhost', port=6379, db=1, password=None)
# todo 2.2 插入数据库到redis,但是要设置过期时间
# todo 保存短信证码,将来注册的时候需要进行对比
# 获取手机号码
phone = query_dict['phone']
conn.setex('%s_msg'%phone, PHONE_MSG_CODE_EXPIRES,msg) # 保存手机短信信息和相对应的手机号,信息有效时间为5分钟。
conn.setex('%s_flag'%phone,MSG_CODE_REQUEST,msg) # 让前端发送数据的标签 2分钟才可以发送一次。
# 5.发送手机短信
# todo 这里采用容联云 ,这个第三方模块来发送短信
# send_template_sms(self, to, datas, temp_id):
ccp = CCP()
ret_data = ccp.send_template_sms(phone,[msg,2],1)
print(ret_data)
# 6.返回相应的相应数据
return Response('短信已发送')
urls.py路径跳转内容:
from django.conf.urls import url
from . import views
# . 是当前路径下找
urlpatterns = [
url(r'^msg_code/$', views.MsgView.as_view()),
url(r'^(?P<image_id>[\w|-]+)/$', views.ImageView.as_view()),
]
新建一个serilizers.py文件来写数据的校验逻辑(如手机号码和图片验证码)
from rest_framework import serializers
import redis
class ImageTextSerializers(serializers.Serializer):
myuuid = serializers.UUIDField()
phone = serializers.CharField(max_length=11,min_length=11)
html_text = serializers.CharField(max_length=4,min_length=4)
def validate(self, attrs):
uuid = attrs['myuuid']
phone = attrs['phone']
text = attrs['html_text'].upper()
conn = redis.Redis(host='localhost', port=6379, db=1, password=None)
# todo 将用户输入的验证码与真实的验证码对比
redis_text = conn.get('%s'%uuid)
# redis数据可能会失效,如果没失效,就把值从bytes转成str
if redis_text:
redis_text = redis_text.decode()
if text != redis_text:
raise serializers.ValidationError('图片验证码错误')
# todo 取出该手机号码对应的发送状态,如果能取到,说明已经在5分钟内发过了
flag = conn.get('%s_flag'%phone)
if flag:
raise serializers.ValidationError('在5分钟内请勿重复发送')
return attrs
接收前端的用户信息,并进行校验,最后把用户信息存到数据库
用之前创建好的users模块,在views.py文件里的内容为:
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.generics import CreateAPIView
from rest_framework.response import Response
import redis
class UserRegisterView(CreateAPIView):
serializer_class = serializers.RegisterUserSerializer
urls.py文件:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^myusers/$', views.UserRegisterView.as_view()),
]
新建一个seralizers.py文件来对用户信息进行校验:
前后端都需要校验,因为有软件可以绕过前端直接向后端发送请求
from rest_framework import serializers
import re
from django_redis import get_redis_connection
from . import models
class RegisterUserSerializer(serializers.ModelSerializer):
# todo 因为User模型里面没有再次输入密码和短信信息,所以这里需要补充
# 第二次密码,短信信息
pwdagain = serializers.CharField(max_length=12,min_length=6,write_only=True)
messagecode = serializers.CharField(max_length=4,min_length=4,write_only=True)
class Meta:
model = models.User
# todo 上面补充的字段也是需要写到下面的序列化器的
fields = ('username','email','password','phone','pwdagain','messagecode',)
def validate(self, attrs):
# 验证用户名是不是符合要求
if not re.match('^[\w]+$', attrs['username']):
raise serializers.ValidationError('用户名输入格式不对')
# 验证邮箱是不是符合要求
if not re.match('^[\w]+@[\w]+\.com$', attrs['email']):
raise serializers.ValidationError('请输入合法的邮件地址')
# 验证密码是不是符合要求
if not re.match('^[a-z A-Z 0-9]{6,16}$', attrs['password']):
raise serializers.ValidationError('密码格式不对')
# 验证再次输入密码是否和第一次输入的密码相同
if attrs['password']!=attrs['pwdagain']:
raise serializers.ValidationError('第二次输入密码不一致')
# 验证手机号码格式
if not re.match('^1[3456789]\d{9}$', attrs['phone']):
raise serializers.ValidationError('手机号码格式不对')
# 验证短信信息验证码是否正确
conn = get_redis_connection('default')
if conn.get('%s_msg'%attrs['phone']):
mycode = conn.get('%s_msg'%attrs['phone']).decode()
if mycode != attrs['messagecode']:
raise serializers.ValidationError('短信验证码不正确')
else:
raise serializers.ValidationError('请先获取短信验证码')
return attrs
def create(self, validated_data):
print('此时的validated_data里面包含短信信息和再次输入密码')
print(validated_data)
del validated_data['pwdagain']
del validated_data['messagecode']
print(validated_data)
# 此时的 validated_data将不再包含 pwdagain 和 messagecode
user_obj = models.User.objects.create(**validated_data)
return user_obj