area.sql准备数据
首先数据库设计分析:
设计成自关联的一张表
字段:id name parent_id
省:parent_id = null
市: parent_id = 省的id
区(县):parent_id = 市的id
需求:
刷新页面时,显示全部的省份信息
选择省时,显示所有属于该省的市的信息
选择市时,显示所有属于该市的区(县)信息
1.创建一个模型类
class Area(models.Model):
"""
行政区划
"""
name = models.CharField(max_length=20, verbose_name='名称')
parent = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='area_set', null=True, blank=True,
verbose_name='上级行政区划')
class Meta:
db_table = 'tb_areas'
verbose_name = '行政区划'
verbose_name_plural = '行政区划'
def __str__(self):
return self.name
数据库迁移:
python manage.py makemigrations
python manage.py migrate
2.导入数据
1.直接在数据库中粘贴所有area.sql文件中的内容
2.在终端中source导入area.sql文件(需要注意area.sql文件所在的位置)
3.脚本导入
在项目中创建一个script文件夹
在文件夹中创建一个my_area.sh文件
在my_area.sh添加内容
#!/usr/bin/env bash
mysql -h127.0.0.1 -uusername -ppassword databasename < ./areas.sql
修改文件的执行权限:
chmod +x my_area.sh
在Teminal中运行
./my_area.sh
3.代码实现
后端接口设计
1)请求省份数据
请求方式 GET /areas/infos/
返回数据
返回值 | 类型 | 是否必传 | 说明 |
---|---|---|---|
id | int | 是 | 省份id |
name | str | 是 | 省份名称 |
2)请求城市或区县数据
请求方式 GET/areas/infos/(?P<pk>\d+)/
请求参数
参数 | 类型 | 是否必传 | 说明 |
---|---|---|---|
pk | int | 是 | 上级区划id(省份id用于获取城市数据,或城市id用于获取区县数据) |
返回数据
返回值 | 类型 | 是否必传 | 说明 |
---|---|---|---|
id | int | 是 | 上级区划id(省份id或城市id) |
name | str | 是 | 上级区划的名称 |
subs | list[] | 是 | 下属所有区划信息 |
分析接口文档:
可以使用2个视图类来处理,也可以使用viewset来处理
可以看到这里都是get请求,我们可以使用ReadOnlyModelViewSet
由于省的查询集(parent= null)和市区的查询集null(parent=上一级id)不同,所以需要重写get_queryset方法
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()
serializer_class = AreaSerializer
序列化器:
class AreaSerializer(serializers.ModelSerializer):
"""
行政区划信息序列化器
"""
class Meta:
model = Area
fields = ['id', 'name', 'parent_id']
为什么市区的可以直接 return Area.objects.all()呢?
ReadOnlyModelViewSet继承了mixins.RetrieveModelMixin
RetrieveModelMixin里面有instance = self.get_object(),根据返回的PK值获取到对象
get_object()根据前端传过来的PK值获取到具体的对象
这里的URL路径是由router生成的,所以我们要了解路由Router的具体生成url的过程
from django.conf.urls import url,include
from rest_framework.routers import DefaultRouter
from .views import AreasViewSet
router = DefaultRouter()
router.register(r'infos',AreasViewSet,base_name='area')
urlpatterns = [
# url(r'^',include(router.urls))
]
#添加省市区信息查询路由
urlpatterns += router.urls
用postman测试
这时候添加在路径加入省的ID只能获取到省的信息,要怎么才能获取到市的信息呢?
我们知道序列化器中使用关联对象的序列化器
重写序列化器:
class AreaSerializer(serializers.ModelSerializer):
"""
行政区划信息序列化器
"""
class Meta:
model = Area
fields = ['id', 'name', 'parent_id']
class SubAreaSerializer(serializers.ModelSerializer):
area_set = AreaSerializer(many=True,read_only=True)
class Meta:
model = Area
fields = ['area_set']
重写视图函数:
class AreasViewSet(CacheResponseMixin,ReadOnlyModelViewSet):
pagination_class = None # 区划信息不分页
def get_queryset(self):
"""
提供数据集
"""
if self.action == 'list':
return Area.objects.filter(parent=None)
else:
return Area.objects.all()
# serializer_class = AreaSerializer
def get_serializer_class(self):
if self.action == 'list':
return AreaSerializer
else:
return SubAreaSerializer
当获取省的信息时,调用AreaSerializer
当获取市,区信息时,调用SubAreaSerializer
前端代码:
<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>
JS
watch: {
'form_address.province_id': function(){
if (this.form_address.province_id) {
axios.get(this.host + '/areas/infos/'+ this.form_address.province_id + '/', {
responseType: 'json'
})
.then(response => {
this.cities = response.data.area_set;
})
.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/infos/'+ this.form_address.city_id + '/', {
responseType: 'json'
})
.then(response => {
this.districts = response.data.area_set;
})
.catch(error => {
console.log(error.response.data);
this.districts = [];
});
}
}
},
postman测试:
浏览器测试:
4.使用缓存
省市区的数据是经常被用户查询使用的,而且数据基本不变化,所以我们可以将省市区数据进行缓存处理,减少数据库的查询次数。
在Django REST framework中使用缓存,可以通过drf-extensions
扩展来实现。
安装:
pip install drf-extensions
为省市区视图添加缓存
from rest_framework_extensions.cache.mixins import CacheResponseMixin
# Create your views here.
class AreaViewSet(CacheResponseMixin,ReadOnlyModelViewSet):
"""
行政区划信息
list: GET /areas/
retrieve: GET /areas/(?P<pk>\d+)/
"""
在setting里添加配置信息
# DRF扩展
REST_FRAMEWORK_EXTENSIONS = {
# 缓存时间
'DEFAULT_CACHE_RESPONSE_TIMEOUT': 60 * 60,
# 缓存存储
'DEFAULT_USE_CACHE': 'default',
}