本教程的知识点为: 项目准备 项目准备 配置 1. 修改settings/dev.py 文件中的路径信息 2. INSTALLED_APPS 3. 数据库 用户部分 图片 1. 后端接口设计: 视图原型 2. 具体视图实现 用户部分 使用Celery完成发送 判断帐号是否存在 1. 判断用户名是否存在 后端接口设计: 用户部分 JWT 什么是JWT 起源 传统的session认证 用户部分 登录 1. 业务说明 2. 后端接口设计 3. 后端实现 登录 使用登录的流程 创建模型类 urllib使用说明 登录回调处理 登录 使用登录的流程 创建模型类 urllib使用说明 绑定用户身份接口 邮件与验证 学习目标: 业务说明: 技术说明: 保存邮箱并发送验证邮件 省市区地址查询 数据库建表 说明 页面静态化 注意 定时任务 安装 部分 详情页 异步任务的触发 。 后端接口设计 收货地址 使用缓存 安装 使用方法 为省市区视图添加缓存 数据库表设计 表结构 数据表结构 首页数据表结构 Docker使用 Docker简介 用户浏览历史记录 1. 保存 后端接口设计 后端实现 搜索 1. 需求分析 2. 搜索引擎原理 3. Elasticsearch 部分 业务需求分析 技术实现 数据存储设计 1. Redis保存已登录用户 商品部分 业务需求分析 技术实现 查询数据 1. 后端接口设计 部分 业务需求分析 技术实现 登录合并 修改登录视图 部分 保存 1. 后端接口设计 2. 后端实现 保存的思路 创建数据库模型类 接入 开发平台登录 沙箱环境 Xadmin 1. 安装 2. 使用 站点的全局配置 站点Model管理。 在Ubuntu中安装 2. 启动与停止 3. 镜像操作 端与自定义文件存储系统 1. 的Python客户端 安装 使用。
完整笔记资料代码:https://gitee.com/yinuo112/Backend/tree/master/Django/django美多商城项目完整开发4.0/note.md
感兴趣的小伙伴可以自取哦~
全套教程部分目录:
部分文件图片:
收货地址
在这个页面中,我们要实现用户地址的管理,主要的业务逻辑有:
- 省市区地址的数据库建立与查询
- 用户地址的增删改查处理
- 设置默认地址
- 设置地址标题
我们主要讲解省市区地址的三级联动和缓存,其余留给大家自己来实现。
学习目标:
- 省市区三级联动
- 在Django REST framework中使用缓存
省市区地址查询
在用户录入地址时,需要进行省市区的选择。在页面加载时,向后端请求省份数据,当用户选择确定省份后,向后端请求该省份的城市数据;在用户选择确定城市数据后,向后端请求该城市的区县信息。我们把这个过程称为省市区三级联动。
我们新建一个应用areas来实现省市区三级联动。
数据库建表
在areas/models.py中,我们创建省市区数据表,采用自关联方式。
class Area(models.Model):
"""
行政区划
"""
name = models.CharField(max_length=20, verbose_name='名称')
parent = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='subs', null=True, blank=True, verbose_name='上级行政区划')
class Meta:
db_table = 'tb_areas'
verbose_name = '行政区划'
verbose_name_plural = '行政区划'
def __str__(self):
return self.name
说明
- 自关联字段的外键指向自身,所以
ForeignKey('self')
- 需要使用
related_name
指明查询一个行政区划的所有下级行政区划时,使用哪种语法查询,如本模型类中指明通过Area模型类对象.subs查询所有下属行政区划,而不是使用Django默认的Area模型类对象.area_set语法。
迁移到数据库后,我们向数据库中添加全国省市区数据,将areas.sql导入数据库中。
我们可以将导入数据库的过程创建一个脚本,在scripts目录中创建import_areas_data_to_db.sh文件
mysql -h数据库ip地址 -u数据库用户名 -p 数据库密码 < areas.sql
# mysql -h10.211.55.5 -umeiduo -p meiduo_mall < areas.sql
如:
#!/bin/bash
mysql -h10.211.55.5 -umeiduo -p meiduo_mall < areas.sql
修改文件的执行权限
chmod +x import_areas_data_to_db.sh
然后执行如下命令导入数据
./import_areas_data_to_db.sh
后端接口设计
1)请求省份数据
请求方式: GET /areas/
请求参数: 无
返回数据: JSON
[
{
"id": 110000,
"name": "北京市"
},
{
"id": 120000,
"name": "天津市"
},
{
"id": 130000,
"name": "河北省"
},
...
]
返回值 | 类型 | 是否必传 | 说明 |
---|---|---|---|
id | int | 是 | 省份id |
name | str | 是 | 省份名称 |
2)请求城市或区县数据
请求方式: GET /areas/(?P<pk>\d+)/
请求参数: 路径参数
参数 | 类型 | 是否必传 | 说明 |
---|---|---|---|
pk | int | 是 | 上级区划id(省份id用于获取城市数据,或城市id用于获取区县数据) |
返回数据: JSON
返回值 | 类型 | 是否必传 | 说明 |
---|---|---|---|
id | int | 是 | 上级区划id(省份id或城市id) |
name | str | 是 | 上级区划的名称 |
subs | list[] | 是 | 下属所有区划信息 |
如:
{
"id": "110100",
"name": "北京市",
"subs": [
{
"id": "110101",
"name": "东城区"
},
{
"id": "110102",
"name": "西城区"
}
]
}
在areas/serializers.py中新建序列化器
from rest_framework import serializers
from .models import Area
class AreaSerializer(serializers.ModelSerializer):
"""
行政区划信息序列化器
"""
class Meta:
model = Area
fields = ('id', 'name')
class SubAreaSerializer(serializers.ModelSerializer):
"""
子行政区划信息序列化器
"""
subs = AreaSerializer(many=True, read_only=True)
class Meta:
model = Area
fields = ('id', 'name', 'subs')
在areas/views.py中新建视图
from django.shortcuts import render
from rest_framework.viewsets import ReadOnlyModelViewSet
from .models import Area
from .serializers import AreaSerializer, SubAreaSerializer
# Create your views here.
class AreasViewSet(ReadOnlyModelViewSet):
"""
行政区划信息
"""
pagination_class = None # 区划信息不分页
def get_queryset(self):
"""
提供数据集
"""
if self.action == 'list':
return Area.objects.filter(parent=None)
else:
return Area.objects.all()
def get_serializer_class(self):
"""
提供序列化器
"""
if self.action == 'list':
return AreaSerializer
else:
return SubAreaSerializer
定义路由
router = DefaultRouter()
router.register(r'areas', views.AreasViewSet, base_name='areas')
urlpatterns = []
urlpatterns += router.urls
前端
修改user_center_site.html文件,增加vue变量
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
<html xmlns=" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>美多商城-用户中心</title>
<link rel="stylesheet" type="text/css" href="css/reset.css">
<link rel="stylesheet" type="text/css" href="css/main.css">
<script type="text/javascript" src="js/host.js"></script>
<script type="text/javascript" src="js/vue-2.5.16.js"></script>
<script type="text/javascript" src="js/axios-0.18.0.min.js"></script>
<script>
var user_id = sessionStorage.user_id || localStorage.user_id;
var token = sessionStorage.token || localStorage.token;
if (!(user_id && token)) {
location.href = '/login.html?next=/user_center_site.html';
}
</script>
</head>
<body>
<div id="app" v-cloak>
<div class="header_con">
<div class="header">
<div class="welcome fl">欢迎来到美多商城!</div>
<div class="fr">
<div class="login_btn fl">
欢迎您:<em>{{ username }}</em>
<span>|</span>
<a @click="logout">退出</a>
</div>
<div class="user_link fl">
<span>|</span>
<a rel="nofollow" href="user_center_info.html">用户中心</a>
<span>|</span>
<a rel="nofollow" href="cart.html">我的购物车</a>
<span>|</span>
<a rel="nofollow" href="user_center_order.html">我的订单</a>
</div>
</div>
</div>
</div>
<div class="search_bar clearfix">
<a rel="nofollow" href="index.html" class="logo fl"><img src="images/logo.png"></a>
<div class="sub_page_name fl">| 用户中心</div>
<form method="get" action="/search.html" class="search_con fr mt40">
<input type="text" class="input_text fl" name="q" placeholder="搜索商品">
<input type="submit" class="input_btn fr" name="" value="搜索">
</form>
</div>
<div class="main_con clearfix">
<div class="left_menu_con clearfix">
<h3>用户中心</h3>
<ul>
<li><a rel="nofollow" href="user_center_info.html">· 个人信息</a></li>
<li><a rel="nofollow" href="user_center_order.html">· 全部订单</a></li>
<li><a rel="nofollow" href="user_center_site.html" class="active">· 收货地址</a></li>
<li><a rel="nofollow" href="user_center_pass.html">· 修改密码</a></li>
</ul>
</div>
<div class="right_content clearfix">
<div class="site_top_con">
<a @click="show_add">新增收货地址</a>
<span>您已创建了<b>{{addresses.length}}</b>个收货地址,最多可创建<b>{{ limit }}</b>个</span>
</div>
<div class="site_con" v-for="(address, index) in addresses">
<div class="site_title">
<div v-if="is_set_title[index]">
<input v-model="input_title" type="text" name="">
<input @click="save_title(index)" type="button" name="" value="保 存">
<input @click="cancel_title(index)" type="reset" name="" value="取 消">
</div>
<div v-else>
<h3>{{ address.title }}</h3>
<a @click="show_edit_title(index)"></a>
</div>
<em v-if="address.id==default_address_id">默认地址</em>
<span @click="del_address(index)">×</span>
</div>
<ul class="site_list">
<li><span>收货人:</span><b>{{ address.receiver }}</b></li>
<li><span>所在地区:</span><b>{{ address.province }} {{address.city}} {{ address.district }}</b></li>
<li><span>地址:</span><b>{{ address.place }}</b></li>
<li><span>手机:</span><b>{{ address.mobile }}</b></li>
<li><span>固定电话:</span><b>{{ address.tel }}</b></li>
<li><span>电子邮箱:</span><b>{{ address.email }}</b></li>
</ul>
<div class="down_btn">
<a v-if="address.id!=default_address_id" @click="set_default(index)">设为默认</a>
<a @click="show_edit(index)">编辑</a>
</div>
</div>
</div>
</div>
<div class="footer">
<div class="foot_link">
<a rel="nofollow" href="#">关于我们</a>
<span>|</span>
<a rel="nofollow" href="#">联系我们</a>
<span>|</span>
<a rel="nofollow" href="#">招聘人才</a>
<span>|</span>
<a rel="nofollow" href="#">友情链接</a>
</div>
<p>CopyRight © 2016 北京美多商业股份有限公司 All Rights Reserved</p>
<p>电话:010-****888 京ICP备*******8号</p>
</div>
<div class="pop_con" v-show="is_show_edit">
<div class="site_con site_pop">
<div class="site_pop_title">
<h3 v-if="editing_address_index">编辑收货地址</h3>
<h3 v-else>新增收货地址</h3>
<a @click="is_show_edit=false">×</a>
</div>
<form>
<div class="form_group">
<label>*收货人:</label>
<input v-model="form_address.receiver" @blur="check_receiver" type="text" name="">
<span v-show="error_receiver" class="error_tip">请填写收件人</span>
</div>
<div class="form_group">
<label>*所在地区:</label>
<select v-model="form_address.province_id">
<option v-for="province in provinces" v-bind:value="province.id">{{ province.name }}</option>
</select>
<select v-model="form_address.city_id">
<option v-for="city in cities" v-bind:value="city.id">{{ city.name }}</option>
</select>
<select v-model="form_address.district_id">
<option v-for="district in districts" v-bind:value="district.id">{{ district.name }}</option>
</select>
</div>
<div class="form_group">
<label>*详细地址:</label>
<input v-model="form_address.place" @blur="check_place" type="text" name="">
<span v-show="error_place" class="error_tip">请填写地址信息</span>
</div>
<div class="form_group">
<label>*手机:</label>
<input v-model="form_address.mobile" @blur="check_mobile" type="text" name="">
<span v-show="error_mobile" class="error_tip">手机信息有误</span>
</div>
<div class="form_group">
<label>固定电话:</label>
<input v-model="form_address.tel" type="text" name="">
</div>
<div class="form_group">
<label>邮箱:</label>
<input v-model="form_address.email" @blur="check_email" type="text" name="">
<span v-show="error_email" class="error_tip">邮箱信息有误</span>
</div>
<input @click="save_address" type="button" name="" value="保 存" class="info_submit">
<input @click="is_show_edit=false" type="reset" name="" value="取 消" class="info_submit info_reset">
</form>
</div>
<div class="mask"></div>
</div>
</div>
<script type="text/javascript" src="js/user_center_site.js"></script>
</body>
</html>
新建user_center_site.js文件
var vm = new Vue({
el: '#app',
data: {
host: host,
user_id: sessionStorage.user_id || localStorage.user_id,
token: sessionStorage.token || localStorage.token,
username: sessionStorage.username || localStorage.username,
is_show_edit: false,
provinces: [],
cities: [],
districts: [],
addresses: [],
limit: '',
default_address_id: '',
form_address: {
receiver: '',
province_id: '',
city_id: '',
district_id: '',
place: '',
mobile: '',
tel: '',
email: '',
},
error_receiver: false,
error_place: false,
error_mobile: false,
error_email: false,
editing_address_index: '', // 正在编辑的地址在addresses中的下标,''表示新增地址
is_set_title: [],
input_title: ''
},
mounted: function(){
axios.get(this.host + '/areas/', {
responseType: 'json'
})
.then(response => {
this.provinces = response.data;
})
.catch(error => {
alert(error.response.data);
});
},
watch: {
'form_address.province_id': function(){
if (this.form_address.province_id) {
axios.get(this.host + '/areas/'+ this.form_address.province_id + '/', {
responseType: 'json'
})
.then(response => {
this.cities = response.data.subs;
})
.catch(error => {
console.log(error.response.data);
this.cities = [];
});
}
},
'form_address.city_id': function(){
if (this.form_address.city_id){
axios.get(this.host + '/areas/'+ this.form_address.city_id + '/', {
responseType: 'json'
})
.then(response => {
this.districts = response.data.subs;
})
.catch(error => {
console.log(error.response.data);
this.districts = [];
});
}
}
},
methods: {
// 退出
logout: function(){
sessionStorage.clear();
localStorage.clear();
location.href = '/login.html';
},
clear_all_errors: function(){
this.error_receiver = false;
this.error_mobile = false;
this.error_place = false;
this.error_email = false;
},
// 展示新增地址界面
show_add: function(){
this.clear_all_errors();
this.editing_address_index = '';
this.form_address.receiver = '';
this.form_address.province_id = '';
this.form_address.city_id = '';
this.form_address.district_id = '';
this.form_address.place = '';
this.form_address.mobile = '';
this.form_address.tel = '';
this.form_address.email = '';
this.is_show_edit = true;
},
// 展示编辑地址界面
show_edit: function(index){
this.clear_all_errors();
this.editing_address_index = index;
// 只获取数据,防止修改form_address影响到addresses数据
this.form_address = JSON.parse(JSON.stringify(this.addresses[index]));
this.is_show_edit = true;
},
check_receiver: function(){
if (!this.form_address.receiver) {
this.error_receiver = true;
} else {
this.error_receiver = false;
}
},
check_place: function(){
if (!this.form_address.place) {
this.error_place = true;
} else {
this.error_place = false;
}
},
check_mobile: function(){
var re = /^1[345789]\d{9}$/;
if(re.test(this.form_address.mobile)) {
this.error_mobile = false;
} else {
this.error_mobile = true;
}
},
check_email: function(){
if (this.form_address.email) {
var re = /^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$/;
if(re.test(this.form_address.email)) {
this.error_email = false;
} else {
this.error_email = true;
}
}
},
// 保存地址
save_address: function(){
},
// 删除地址
del_address: function(index){
},
// 设置默认地址
set_default: function(index){
},
// 展示编辑标题
show_edit_title: function(index){
} ,
// 保存地址标题
save_title: function(index){
},
// 取消保存地址
cancel_title: function(index){
}
}
})