<Django>天天生鲜项目(一)
1.认识电商
-
B2B---企业对企业:阿里巴巴
- C2C---个人对个人:淘宝,瓜子二手车
- B2C---企业对个人:唯品会
- C2B---个人对企业:海尔商城(定制)----需要做的项目
- O2O---线上到线下:美团,饿了么
- F2C---工厂到个人:戴尔的一部分
- B2B2C---企业-企业-个人:京东商城,天猫商城
2.Web项目开发流程
- 项目立项:领导决定做这个项目
- 需求分析:需求人提出自己的需求--客户,老板等,分析到底实现什么功能
- 分析功能能不能实现,
- 功能技术实现难度
- 原型设计:产品经理根据需求画产品的原型图(Axure)
- 后端进行架构设计---架构师
- 模块划分
- 功能架构
- 开发环境的选择
- 项目中用到的其他技术
- 项目部署架构
- 网址开发模式
- 前后端分离:Flask
- 前后端不分离:Django
- 数据库设计
- 分析数据表和表字段
- 表之间的关系
- 模块代码实现和单元测试----我们需要做的
- 根据分工,实现模块代码
- 对你写的代码进行单元测试
- 代码整合
- 前端UI设计,根据UI形成前端页面,
- 整合前后端
- 集成测试:代码合起来,检测整个流程有没有问题
- 网址发布:项目部署上线
3.需求分析
3.1 用户模块
1) 注册页
- 注册时校验用户名是否已被注册。
- 完成用户信息的注册。
- 给用户的注册邮箱发送邮件,用户点击邮件中的激活链接完成用户账户的激活。
2) 登录页
- 实现用户的登录功能。
3) 用户中心
- 用户中心信息页:显示登录用户的信息,包括用户名、电话和地址,同时页面下方显示出用户最近浏览的商品信息。
- 用户中心地址页:显示登录用户的默认收件地址,页面下方的表单可以新增用户的收货地址。
- 用户中心订单页:显示登录用户的订单信息。
4) 其他
- 如果用户已经登录,页面顶部显示登录用户的信息。
3.2 商品相关
1) 首页
- 动态指定首页轮播商品信息。
- 动态指定首页活动信息。
- 动态获取商品的种类信息并显示。
- 动态指定首页显示的每个种类的商品(包括图片商品和文字商品)。
- 点击某一个商品时跳转到商品的详情页面。
2) 商品详情页
- 显示出某个商品的详情信息。
- 页面的左下方显示出该种类商品的2个新品信息。
3)商品列表页
- 显示出某一个种类商品的列表数据,分页显示并支持按照默认、价格、和人气进行排序。
- 页面的左下方显示出该种类商品的2个新品信息。
4)其他
- 通过页面搜索框搜索商品信息。
3.3 购物车相关
- 列表页和详情页将商品添加到购物车。
- 用户登录后,首页,详情页,列表页显示登录用户购物车中商品的数目。
- 购物车页面:对用户购物车中商品的操作。如选择某件商品,增加或减少购物车中商品的数目。
3.4 订单相关
- 提交订单页面:显示用户准备购买的商品信息。
- 点击提交订单完成订单的创建。
- 用户中心订单页显示用户的订单信息。
- 点击支付完成订单的支付。
4.架构设计
4.1 页面图
4.2 功能图
4.3 部署图
项目架构
5.数据库设计
用户模块
- 用户表
- ID
- 用户名
- 密码
- 邮箱
- 激活标识:0,1
- 权限标识
- 地址表
- ID
- 收件人
- 详细信息
- 联系方式
- 地址
- 是否默认:0,1
- 用户ID
商品模块
-
商品表(SKU)
- ID
- 名称
- 简介
- 价格
- 单位
- 商品库存
- 商品图片---记录一张,增加效率,以空间换取时间
- 评论
- 状态标记:0,1
- 种类ID
- SPUID
- 商品种类表
- ID
- 种类名称
- LOGO
- 种类图片
- 商品图片表
- ID
- 图片
- 商品ID(sku)
- 商品SPU表
- ID
- 名称
- 详情
- 首页轮播商品表
- ID
- SKUID
- 图片
- index
- 首页促销表
- ID
- 图片
- 活动URL地址
- index
- 首页分类商品展示表
- ID
- sku ID
- 种类ID
- index
- 展示标识:图片或文字
购物车模块
redis实现购物车功能
redis保存用户历史浏览记录
订单模块
- 订单信息表
- 订单编号
- 地址ID
- 支付方式
- 总金额
- 总数目
- 运费
- 支付状态
- 创建时间
- 订单商品表
- ID
- SKU ID
- 商品数量
- 商品价格
- 评论
SKU与SPU概念
SPU = Standard Product Unit (标准产品单位)
SPU 是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述 了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个 SPU。
例如:iphone7 就是一个 SPU,与商家,与颜色、款式、套餐都无关。
SKU=stock keeping unit(库存量单位)
SKU 即库存进出计量的单位, 可以是以件、盒、托盘等为单位。
SKU 是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。 在服装、鞋类商品中使用最多最普遍。
例如:纺织品中一个 SKU 通常表示:规格、颜色、款式。
正式开始写。。。
001.创建项目
django-admin startproject dailyfresh
002.配置静态文件路径及拷贝静态文件
配置静态文件(settings.py)
# 用于隐藏(伪装),配置更改(逻辑显示路径) STATIC_URL = '/static/' # 静态文件的存放路径(真实路径) STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ]
创建static文件夹,在这个文件夹下创建应用名的文件夹(类似模板的创建)---与manage.py文件同级
并将静态文件拷贝进去链接:https://pan.baidu.com/s/1eXFDCimhBdT_HDZFAgZZ6Q 提取码:r0kh
配置模板文件路径(settings.py)
'DIRS': [os.path.join(BASE_DIR,"templates")],
创建模板文件夹---与manage.py同级
配置数据库信息(settings.py)
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'tiantian', 'USER':'root', 'PASSWORD':'root', 'HOST':'localhost', 'POST':'3306', } }
手动在建立tiantian数据库
mysql> create database tiantian charset=utf8; Query OK, 1 row affected (0.12 sec)
003.创建四个模块
与manage.py同级
用户模块
python manage.py startapp user
商品模块
python manage.py startapp goods
购物车模块
python manage.py startapp cart
订单模块
python manage.py startapp order
如果应用较多,新键一个apps包,将所有应用都放在apps包中
为apps文件夹加一个搜索路径(settings.py)
import sys # 放在BASE_DIR下方 sys.path.insert(0,os.path.join(BASE_DIR,'apps'))
安装,注册应用
INSTALLED_APPS = [ 'user', 'goods', 'order', 'cart', ]
配中文时区(settings.py)
LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai'
配根级url(urls.py)
from django.conf.urls import url from django.contrib import admin # 分发url from django.conf.urls import include urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^user/', include('user.urls',namespace='user')), # 用户模块 url(r'^cart/', include('cart.urls',namespace='cart')), # 购物车模块 url(r'^order/', include('order.urls',namespace='order')), # 订单模块 url(r'^', include('goods.urls',namespace='goods')), # 商品模块 ]
每个应用建立相应的子级urls.py
from django.conf.urls import url from . import views urlpatterns=[ ]
建立模型类继承模板,新键db包,在里面新键base_model.py文件
from django.db import models class BaseModel(models.Model): '''模型抽象基类''' create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') is_delete = models.BooleanField(default=False, verbose_name='删除标记') class Meta: # 说明是一个抽象模型类 abstract = True
设计用户模型类(models.py)
from django.db import models from django.contrib.auth.models import AbstractUser from db.base_model import BaseModel # Create your models here. class User(AbstractUser, BaseModel): '''用户模型类''' class Meta: db_table = 'df_user' verbose_name = '用户' verbose_name_plural = verbose_name class Address(BaseModel): '''地址模型类''' user = models.ForeignKey('User', verbose_name='所属账户') receiver = models.CharField(max_length=20, verbose_name='收件人') addr = models.CharField(max_length=256, verbose_name='收件地址') zip_code = models.CharField(max_length=6, null=True, verbose_name='邮政编码') phone = models.CharField(max_length=11, verbose_name='联系电话') is_default = models.BooleanField(default=False, verbose_name='是否默认') class Meta: db_table = 'df_address' verbose_name = '地址' verbose_name_plural = verbose_name
商品模型类(models.py)
from django.db import models from db.base_model import BaseModel from tinymce.models import HTMLField # Create your models here. class GoodsType(BaseModel): '''商品类型模型类''' name = models.CharField(max_length=20, verbose_name='种类名称') logo = models.CharField(max_length=20, verbose_name='标识') image = models.ImageField(upload_to='type', verbose_name='商品类型图片') class Meta: db_table = 'df_goods_type' verbose_name = '商品种类' verbose_name_plural = verbose_name def __str__(self): return self.name class GoodsSKU(BaseModel): '''商品SKU模型类''' status_choices = ( (0, '下线'), (1, '上线'), ) type = models.ForeignKey('GoodsType', verbose_name='商品种类') goods = models.ForeignKey('Goods', verbose_name='商品SPU') name = models.CharField(max_length=20, verbose_name='商品名称') desc = models.CharField(max_length=256, verbose_name='商品简介') price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='商品价格') unite = models.CharField(max_length=20, verbose_name='商品单位') image = models.ImageField(upload_to='goods', verbose_name='商品图片') stock = models.IntegerField(default=1, verbose_name='商品库存') sales = models.IntegerField(default=0, verbose_name='商品销量') status = models.SmallIntegerField(default=1, choices=status_choices, verbose_name='商品状态') class Meta: db_table = 'df_goods_sku' verbose_name = '商品' verbose_name_plural = verbose_name class Goods(BaseModel): '''商品SPU模型类''' name = models.CharField(max_length=20, verbose_name='商品SPU名称') # 富文本类型:带有格式的文本 detail = HTMLField(blank=True, verbose_name='商品详情') class Meta: db_table = 'df_goods' verbose_name = '商品SPU' verbose_name_plural = verbose_name class GoodsImage(BaseModel): '''商品图片模型类''' sku = models.ForeignKey('GoodsSKU', verbose_name='商品') image = models.ImageField(upload_to='goods', verbose_name='图片路径') class Meta: db_table = 'df_goods_image' verbose_name = '商品图片' verbose_name_plural = verbose_name class IndexGoodsBanner(BaseModel): '''首页轮播商品展示模型类''' sku = models.ForeignKey('GoodsSKU', verbose_name='商品') image = models.ImageField(upload_to='banner', verbose_name='图片') index = models.SmallIntegerField(default=0, verbose_name='展示顺序') class Meta: db_table = 'df_index_banner' verbose_name = '首页轮播商品' verbose_name_plural = verbose_name class IndexTypeGoodsBanner(BaseModel): '''首页分类商品展示模型类''' DISPLAY_TYPE_CHOICES = ( (0, "标题"), (1, "图片") ) type = models.ForeignKey('GoodsType', verbose_name='商品类型') sku = models.ForeignKey('GoodsSKU', verbose_name='商品SKU') display_type = models.SmallIntegerField(default=1, choices=DISPLAY_TYPE_CHOICES, verbose_name='展示类型') index = models.SmallIntegerField(default=0, verbose_name='展示顺序') class Meta: db_table = 'df_index_type_goods' verbose_name = "主页分类展示商品" verbose_name_plural = verbose_name class IndexPromotionBanner(BaseModel): '''首页促销活动模型类''' name = models.CharField(max_length=20, verbose_name='活动名称') url = models.URLField(verbose_name='活动链接') image = models.ImageField(upload_to='banner', verbose_name='活动图片') index = models.SmallIntegerField(default=0, verbose_name='展示顺序') class Meta: db_table = 'df_index_promotion' verbose_name = "主页促销活动" verbose_name_plural = verbose_name
其中类型HTMLFiel需要在settings.py中设置
INSTALLED_APPS = [ 'tinymce', # HttpField--富文本编辑器 ]
# 富文本编辑器配置 TINYMCE_DEFAULT_CONFIG = { 'theme': 'advanced', 'width': 600, 'height': 400, }
在根目录urls.py中配置链接
url(r'^tinyme/', include('tinymce.urls')), # 富文本编辑器
订单模型类
from django.db import models from db.base_model import BaseModel # Create your models here. class OrderInfo(BaseModel): '''订单模型类''' PAY_METHOD_CHOICES = ( (1, '货到付款'), (2, '微信支付'), (3, '支付宝'), (4, '银联支付') ) ORDER_STATUS_CHOICES = ( (1, '待支付'), (2, '待发货'), (3, '待收货'), (4, '待评价'), (5, '已完成') ) order_id = models.CharField(max_length=128, primary_key=True, verbose_name='订单id') user = models.ForeignKey('user.User', verbose_name='用户') addr = models.ForeignKey('user.Address', verbose_name='地址') pay_method = models.SmallIntegerField(choices=PAY_METHOD_CHOICES, default=3, verbose_name='支付方式') total_count = models.IntegerField(default=1, verbose_name='商品数量') total_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='商品总价') transit_price = models.DecimalField(max_digits=10, decimal_places=2,verbose_name='订单运费') order_status = models.SmallIntegerField(choices=ORDER_STATUS_CHOICES, default=1, verbose_name='订单状态') trade_no = models.CharField(max_length=128, verbose_name='支付编号') class Meta: db_table = 'df_order_info' verbose_name = '订单' verbose_name_plural = verbose_name class OrderGoods(BaseModel): '''订单商品模型类''' order = models.ForeignKey('OrderInfo', verbose_name='订单') sku = models.ForeignKey('goods.GoodsSKU', verbose_name='商品SKU') count = models.IntegerField(default=1, verbose_name='商品数目') price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='商品价格') comment = models.CharField(max_length=256, verbose_name='评论') class Meta: db_table = 'df_order_goods' verbose_name = '订单商品' verbose_name_plural = verbose_name
替换Django自身认证系统使用的模型类,需要在settings.py中设置
# django认证系统使用的模型类,应用名.类名 AUTH_USER_MODEL = 'user.User'
在settings.py同级的__init__文件中,写入mysql_db对python3的支持
import pymysql pymysql.install_as_MySQLdb()
生成迁移文件
python manage.py makemigrations
执行迁移
python manage.py migrate
出现错误
ValueError: Related model 'user.User' cannot be resolved
原因是外键约束阻止的其他表的生成,先生成user表就可以了
python manage.py makemigrations --empty user
python manage.py makemigrations
python manage.py migrate user python manage.py migrate
001.用户模块
在模板下创建APP同名目录,将用户相关的模板文件拷贝进来
定义视图(views.py)---第一次显示注册页面
from django.shortcuts import render # Create your views here. def register(request): ''' :param request: :return:注册页面 ''' return render(request,'user/register.html')
在APP目录下建子级urls.py文件(urls.py-子级)
from django.conf.urls import url from . import views urlpatterns=[
url(r'^register/$', views.register,name='register'),
]
运行项目查看效果
python manage.py runserver
页面 http://127.0.0.1:8000/user/register/效果
静态文件没有引用过来,可能原因1.静态文件路径没配好。2.没有导入静态文件。3.模板导入的静态文件路径不对,这里是第三条
修改为:
刷新页面---成功显示
根据页面的继承情况,创建底部模板文件base_bottom.html---templates文件夹下和df_user同级(base_bottom.html)
<!--继承的头部--> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <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 }}天天生鲜</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/jquery-1.12.4.min.js"></script> {% block head%} {% endblock head%} </head> <body> {% block body%} {% endblock body%} <!--继承的底部--> <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 Reserved</p> <p>电话:010-****888 京ICP备*******8号</p> </div> </body> </html>
根据继承情况重新编辑register.html文件(register.html)
<!--继承于base_bottom.html页面--> {% extends 'base_bottom.html' %} <!--head--> {% block head%} <script type="text/javascript" src="/static/js/register.js"></script> {% endblock head%} <!--body--> {% block body%} <div class="register_con"> <div class="l_con fl"> <a class="reg_logo"><img src="/static/images/logo02.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="#">登录</a> </div> <div class="reg_form clearfix"> <form> <ul> <li> <label>用户名:</label> <input type="text" name="user_name" id="user_name"> <span class="error_tip">提示信息</span> </li> <li> <label>密码:</label> <input type="password" name="pwd" id="pwd"> <span class="error_tip">提示信息</span> </li> <li> <label>确认密码:</label> <input type="password" name="cpwd" id="cpwd"> <span class="error_tip">提示信息</span> </li> <li> <label>邮箱:</label> <input type="text" name="email" id="email"> <span class="error_tip">提示信息</span> </li> <li class="agreement"> <input type="checkbox" name="allow" id="allow" checked="checked"> <label>同意”天天生鲜用户使用协议“</label> <span class="error_tip2">提示信息</span> </li> <li class="reg_sub"> <input type="submit" value="注 册" name=""> </li> </ul> </form>
{{ errmsg }} </div> </div> </div> {% endblock body%} <!--bottom-->
重新查看register页面,如果没有变化就说明模板设置成功
修改form表单的提交位置及防止跨越请求伪造---表单提交都需要(register.html)
<form action="/user/register_handle/" method="post"> {% csrf_token %}
定义视图接受注册信息
# coding=utf-8 from django.shortcuts import render, redirect # 反向解析地址 from django.core.urlresolvers import reverse from .models import User from hashlib import sha1 import re # json from django.http import JsonResponse # Create your views here. # /user/register/ def register(request): ''' :param request: :return:显示注册页面 ''' return render(request, 'user/register.html') def register_handle(request): '''进行注册处理''' # 1.接受数据 post = request.POST # 接收用户输入的值 username = post.get('user_name') password = post.get('pwd') password2 = post.get('cpwd') email = post.get('email') allow = post.get('allow') # 2.进行数据校验 # 全为true,才返回true if not all([username, password, password2, email]): # 数据不完整 return render(request, 'user/register.html', {'errmsg': '数据不完整'}) # 校验邮箱 if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$',email): return render(request, 'user/register.html', {'errmsg': '邮箱格式不正确'}) # 判断2次密码是否一致 if password != password2: return render(request, 'user/register.html', {'errmsg': '两次密码不一致'}) # 校验是否勾选协议 if allow != 'on': return render(request, 'user/register.html', {'errmsg': '请同意协议'}) # 校验用户名是否重复 try: user = User.objects.get(username=username) except User.DoesNotExist: # 用户名不存在 user = None if user: # 用户名已存在 return render(request, 'user/register.html', {'errmsg': '用户名已存在'}) # 3.进行业务处理:进行业务注册 # 密码加密 s1 = sha1() s1.update(password.encode('utf-8')) password3 = s1.hexdigest() # 加密后的结果 # 创建对象 user = User() # 属性赋值 user.username = username user.password = password3 user.email = email # 默认激活账户,置为0表示没有激活 user.is_active = 0 user.save() # 存到数据库里面 # 返回应答:注册成功,转到登录页面 # return redirect('/user/login/') # 返回首页 反向解析 namespace:name return redirect(reverse('goods:index'))
添加对应url(urls.py)
url(r'^register_handle/$', views.register_handle,name='register_handle'),
配置goods中的index页面(views.py)
from django.shortcuts import render # Create your views here. # 首页 def index(request): '''首页''' return render(request, 'goods/index.html')
配置相应的url(urls.py)
from django.conf.urls import url from . import views urlpatterns=[ url(r'^$', views.index,name='index'), ]
在注册表填入正确信息,能跳转到index页面,说明注册页面配置成功
查看数据库,发现相应注册信息
注册和处理使用同一个地址,修改表单提交地址---通过请求方式判断
<form action="/user/register/" method="post">
修改views.py----register_handle相关函数和url可以删除
def register(request): '''注册''' if request.method == 'GET': return render(request, 'user/register.html') else: '''进行注册处理''' # 1.接受数据 post = request.POST # 接收用户输入的值 username = post.get('user_name') password = post.get('pwd') password2 = post.get('cpwd') email = post.get('email') allow = post.get('allow') # 2.进行数据校验 # 全为true,才返回true if not all([username, password, password2, email]): # 数据不完整 return render(request, 'user/register.html', {'errmsg': '数据不完整'}) # 校验邮箱 if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email): return render(request, 'user/register.html', {'errmsg': '邮箱格式不正确'}) # 判断2次密码是否一致 if password != password2: return render(request, 'user/register.html', {'errmsg': '两次密码不一致'}) # 校验是否勾选协议 if allow != 'on': return render(request, 'user/register.html', {'errmsg': '请同意协议'}) # 校验用户名是否重复 try: user = User.objects.get(username=username) except User.DoesNotExist: # 用户名不存在 user = None if user: # 用户名已存在 return render(request, 'user/register.html', {'errmsg': '用户名已存在'}) # 3.进行业务处理:进行业务注册 # 密码加密 s1 = sha1() s1.update(password.encode('utf-8')) password3 = s1.hexdigest() # 加密后的结果 # 创建对象 user = User() # 属性赋值 user.username = username user.password = password3 user.email = email # 默认激活账户,置为0表示没有激活 user.is_active = 0 user.save() # 存到数据库里面 # 返回应答:注册成功,转到登录页面 # return redirect('/user/login/') # 返回首页 反向解析 namespace:name return redirect(reverse('goods:index'))
使用类视图,将请求方式得到的不同结果区分开---更加直观---修改views.py
访问一个地址的时候,不同的请求方式对应哪个函数
# coding=utf-8 from django.shortcuts import render, redirect # 反向解析地址 from django.core.urlresolvers import reverse # 类视图 from django.views.generic import View from .models import User from hashlib import sha1 import re # json from django.http import JsonResponse # Create your views here. # /user/register/ class RegisterView(View): '''注册''' def get(self, request): '''显示注册页面''' return render(request, 'user/register.html') def post(self, request): '''进行注册处理''' # 1.接受数据 post = request.POST # 接收用户输入的值 username = post.get('user_name') password = post.get('pwd') password2 = post.get('cpwd') email = post.get('email') allow = post.get('allow') # 2.进行数据校验 # 全为true,才返回true if not all([username, password, password2, email]): # 数据不完整 return render(request, 'user/register.html', {'errmsg': '数据不完整'}) # 校验邮箱 if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email): return render(request, 'user/register.html', {'errmsg': '邮箱格式不正确'}) # 判断2次密码是否一致 if password != password2: return render(request, 'user/register.html', {'errmsg': '两次密码不一致'}) # 校验是否勾选协议 if allow != 'on': return render(request, 'user/register.html', {'errmsg': '请同意协议'}) # 校验用户名是否重复 try: user = User.objects.get(username=username) except User.DoesNotExist: # 用户名不存在 user = None if user: # 用户名已存在 return render(request, 'user/register.html', {'errmsg': '用户名已存在'}) # 3.进行业务处理:进行业务注册 # 密码加密 s1 = sha1() s1.update(password.encode('utf-8')) password3 = s1.hexdigest() # 加密后的结果 # 创建对象 user = User() # 属性赋值 user.username = username user.password = password3 user.email = email # 默认激活账户,置为0表示没有激活 user.is_active = 0 user.save() # 存到数据库里面 # 返回应答:注册成功,转到登录页面 # return redirect('/user/login/') # 返回首页 反向解析 namespace:name return redirect(reverse('goods:index'))
修改url
from django.conf.urls import url from . import views from .views import RegisterView urlpatterns=[ url(r'^register/$', RegisterView.as_view(),name='register'), ]
增加邮箱验证功能,Django中内置了邮件发送功能,被定义在django.core.mail模块中。发送邮件需要使用SMTP服务器,免费服务器有:163、126、QQ
1)注册163邮箱,登录后设置。
2)在新页面中点击“客户端授权密码”,勾选“开启”,弹出新窗口填写手机验证码。
3)填写授权码。
4)提示开启成功。
settings.py文件写入文件
# 发送邮件配置 EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'smtp.163.com' EMAIL_PORT = 25 # 发送邮件的邮箱 EMAIL_HOST_USER = 'hk15827979698@163.com' # 在邮箱中设置的客户端授权密码 EMAIL_HOST_PASSWORD = '****' # 收件人看到的发件人 EMAIL_FROM = '天天生鲜<hk15827979698@163.com>'
修改views.py,可以发送邮件
# coding=utf-8 # 过期之后提示 from django.http import HttpResponse from django.shortcuts import render, redirect # 反向解析地址 from django.core.urlresolvers import reverse # 类视图 from django.views.generic import View # itsdangerous加密 from itsdangerous import TimedJSONWebSignatureSerializer as Serializer # 过期异常 from itsdangerous import SignatureExpired from django.conf import settings # 发送邮件 from django.core.mail import send_mail from .models import User from hashlib import sha1 import re # json from django.http import JsonResponse # Create your views here. # /user/register/ class RegisterView(View): '''注册''' def get(self, request): '''显示注册页面''' return render(request, 'user/register.html') def post(self, request): '''进行注册处理''' # 1.接受数据 post = request.POST # 接收用户输入的值 username = post.get('user_name') password = post.get('pwd') password2 = post.get('cpwd') email = post.get('email') allow = post.get('allow') # 2.进行数据校验 # 全为true,才返回true if not all([username, password, password2, email]): # 数据不完整 return render(request, 'user/register.html', {'errmsg': '数据不完整'}) # 校验邮箱 if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email): return render(request, 'user/register.html', {'errmsg': '邮箱格式不正确'}) # 判断2次密码是否一致 if password != password2: return render(request, 'user/register.html', {'errmsg': '两次密码不一致'}) # 校验是否勾选协议 if allow != 'on': return render(request, 'user/register.html', {'errmsg': '请同意协议'}) # 校验用户名是否重复 try: user = User.objects.get(username=username) except User.DoesNotExist: # 用户名不存在 user = None if user: # 用户名已存在 return render(request, 'user/register.html', {'errmsg': '用户名已存在'}) # 3.进行业务处理:进行业务注册 # 密码加密 s1 = sha1() s1.update(password.encode('utf-8')) password3 = s1.hexdigest() # 加密后的结果 # 创建对象 user = User() # 属性赋值 user.username = username user.password = password3 user.email = email # 默认激活账户,置为0表示没有激活 user.is_active = 0 user.save() # 存到数据库里面 # 发送激活邮件,包含激活链接 # 激活链接需要包含用户的身份信息:http://127.0.0.1:8000/user/active/用户ID # 将用户ID-身份信息加密:itsdangerous 需要先安装 serializer = Serializer(settings.SECRET_KEY, 3600) info = {'confirm': user.id} token = serializer.dumps(info) # token加密信息 # 解密 # res = serializer.loads(token) # 发邮件 subject = '天天生鲜欢迎信息' # 邮件标题 message = '邮件正文' sender = settings.EMAIL_FROM # 发件人 receiver = [email] # 收件人列表 send_mail(subject,message,sender,receiver) # 返回应答:注册成功,转到登录页面 # return redirect('/user/login/') # 返回首页 反向解析 namespace:name return redirect(reverse('goods:index'))
可以注册一下试试,可以发送邮件
修改一下发送邮件的信息就可以发送激活邮件
serializer = Serializer(settings.SECRET_KEY, 3600) info = {'confirm': user.id} token = serializer.dumps(info) # token加密信息 token = token.decode() # 解密 # res = serializer.loads(token) # 发邮件 subject = '天天生鲜欢迎信息' # 邮件标题 message = '' sender = settings.EMAIL_FROM # 发件人 receiver = [email] # 收件人列表 html_message = '<h1>%s,欢迎您成为天天生鲜注册会员</h1>请点击下面链接激活您的账户<br/>
<a href="http://127.0.0.1:8000/user/active/%s">http://127.0.0.1:8000/user/active/%s</a>'%(username,token,token) send_mail(subject, message, sender, receiver,html_message=html_message) # 返回应答:注册成功,转到登录页面
定义类视图,处理激活邮件
class ActiveView(View): '''用户激活账户''' def get(self, request, token): '''用户激活''' # 解密:获取要激活的信息 serializer = Serializer(settings.SECRET_KEY, 3600) try: info = serializer.loads(token) # 获取待激活用户ID user_id = info['confirm'] # 根据用户ID获取用户信息 user = User.objects.get(id=user_id) # 激活标记改为1 user.is_active = 1 user.save() # 跳转到登录页面 return redirect(reverse('user:login')) except SignatureExpired as e: # 激活链接已过期 return HttpResponse('激活链接已过期')
配置相应的url
from .views import RegisterView, ActiveView,LoginView urlpatterns = [ url(r'^register/$', RegisterView.as_view(), name='register'), # 注册 url(r'^active/(?P<token>.*)$', ActiveView.as_view(), name='active'), # 用户激活 url(r'^login/$', LoginView.as_view(),name='login'), ]
在配置登录view
# /user/login/ class LoginView(View): '''登录''' def get(self, request): '''显示登录页面''' return render(request, 'user/login.html')
激活文件可以跳转到登录页面-修改登录页错误的static文件路径----同注册
celery异步发送邮件,避免邮件堵塞,造成网址等待时间过长
用户需要在我们的网站填写注册信息,我们发给用户一封注册激活邮件到用户邮箱,如果由于各种原因,这封邮件发送所需时间较长,那么客户端将会等待很久,造成不好的用户体验.
我们将耗时任务放到后台异步执行。不会影响用户其他操作。除了注册功能,例如上传,图形处理等等耗时的任务,都可以按照这种思路来解决。
处理者所在电脑必须联网
在项目目录下方建立celery_tasks包,并建立tasks.py文件(tasks.py)
# 使用celery from django.core.mail import send_mail from django.conf import settings from celery import Celery import time # 创建celery类实例对象 8号数据库 app = Celery('celery_tasks.tasks', broker='redis://127.0.0.1:6379/8') # 定义任务函数 @app.task def send_register_active_email(to_email, username, token): '''发送激活邮件''' # 发邮件 subject = '天天生鲜欢迎信息' # 邮件标题 message = '' sender = settings.EMAIL_FROM # 发件人 receiver = [to_email] # 收件人列表 html_message = '<h1>%s,欢迎您成为天天生鲜注册会员</h1>请点击下面链接激活您的账户<br/>' \ '<a href="http://127.0.0.1:8000/user/active/%s">http://127.0.0.1:8000/user/active/%s</a>' % ( username, token, token) send_mail(subject, message, sender, receiver, html_message=html_message)
修改view中发送邮件部分
# 导入celery发送邮件函数 from celery_tasks.tasks import send_register_active_email
# 发邮件,使用celery去发 send_register_active_email.delay(email,username,token)
启动redis服务器
启动客户端
redis-cli
拷贝一份代码到另外一个地方,可以一台电脑,也可以不是同一台电脑
启动任务处理者
E:\celery_worker\dailyfresh>celery -A celery_tasks.tasks worker -l info
启动项目,重新注册一次,看看邮件的发送情况
任务处理者报错,redis版本过高
AttributeError: 'str' object has no attribute 'items'
重新启动一个低版本的redis
启动项目,并注册,提示收到了任务,报错,没有对项目的配置文件进行初始化
Received task: celery_tasks.tasks.send_register_active_email[758c686c-acba-42ed-9bec-00027f7c4bea]
在处理者一端的tasks.py代码中加入,Django环境的初始化
import os import django os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings") django.setup()
在次启动项目,启动处理者,进行注册操作,发送成功
用户登录
修改login.html中的静态文件及from提交地址
<form method="post" action=""> {% csrf_token %}
修改类视图
# django的认证函数和session from django.contrib.auth import authenticate, login class LoginView(View): '''登录''' def get(self, request): '''显示登录页面''' return render(request, 'user/login.html') def post(self, request): '''登录校验''' # 1.接受数据 post = request.POST username = post.get('username') password = post.get('pwd') s1 = sha1() s1.update(password.encode('utf-8')) password2 = s1.hexdigest() # 2.校验数据 if not all([username, password]): render(request, 'user/login.html', {'errmsg': '数据不完整'}) # 3.业务处理:登录校验 # 不知道为啥校验不了sh1加密的密码,还是直接查询 # user = authenticate(username=username, password=password2) user = User.objects.get(username=username, password=password2) if user is not None: if user.is_active: # 用户已激活,记录用户的登录状态 login(request, user) # 跳转到首页 return redirect(reverse('goods:index')) else: return render(request, 'user/login.html', {'errmsg': '账户未激活'}) else: # 用户名或密码错误 return render(request, 'user/login.html', {'errmsg': '用户名或密码错误'})
登录成功可以跳转到i首页
为了降低用户对数据库的读写压力,将session放在redis中,
安装----安装这个,将django自动升级到3.0很多东西不兼容
pip install django-redis
卸载django
pip uninstall django
安装指定版本
pip install django==1.11.26 -i https://pypi.tuna.tsinghua.edu.cn/simple
配置settings.py
# Django的缓存配置项 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/9", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } } # 配置session的存储 SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_CACHE_ALIAS = "default"
启动项目,登录
redis数据库有数据
session已经存储到redis数据库中
记住用户名
if user is not None: if user.is_active: # 用户已激活,记录用户的登录状态 login(request, user) # 首页 response = redirect(reverse('goods:index')) # 判断是否需要记住用户名 remember = post.get('remember') if remember == 'on': # 记住用户名 response.set_cookie('username',username,max_age=7*24*3600) else: response.delete_cookie('username') # 跳转到首页 return response else: return render(request, 'user/login.html', {'errmsg': '账户未激活'}) else: # 用户名或密码错误 return render(request, 'user/login.html', {'errmsg': '用户名或密码错误'})
修改登录时,判断cookie中是否有username
class LoginView(View): '''登录''' def get(self, request): '''显示登录页面''' # 判断是否记住了用户名 if 'username' in request.COOKIES: username = request.COOKIES.get('username') # 勾选 checked = 'checked' else: username = '' checked = '' # 使用模板 return render(request, 'user/login.html',{'username':username,'checked':checked})
修改前端中用户名的value,和check,接受提交的内容
<input type="text" name="username" class="name_input" value="{{ username }}" placeholder="请输入用户名">
<input type="checkbox" name="remember" {{ checkeds }}>
清除cookie,重新登录,成功记住用户名
登录和注册页面完成