以下是要实现的后端逻辑
email_active
字段提示:
Vue.js
来实现。===========================
由于在渲染用户基本信息时,需要渲染用户邮箱验证的状态,所以需要给用户模型补充email_active字段
补充完字段后,需要进行迁移。
$ python manage.py makemigrations $ python manage.py migrate
class User(AbstractUser):
"""自定义用户模型类"""
mobile = models.CharField(max_length=11, unique=True, verbose_name='手机号')
email_active = models.BooleanField(default=False, verbose_name='邮箱验证状态')
class Meta:
db_table = 'tb_users'
verbose_name = '用户'
verbose_name_plural = verbose_name
def __str__(self):
return self.username
class UserInfoView(LoginRequiredMixin, View):
"""用户中心"""
def get(self, request):
"""提供个人信息界面"""
context = {
'username': request.user.username,
'mobile': request.user.mobile,
'email': request.user.email,
'email_active': request.user.email_active
}
return render(request, 'user_center_info.html', context=context)
1.将后端模板数据传递到Vue.js
- 为了方便实现用户添加邮箱时的界面局部刷新
- 我们将后端提供的用户数据传入到
user_center_info.js
中
data: {
username: username,
mobile: mobile,
email: email,
email_active: email_active,
},
2.Vue渲染用户基本信息:
user_center_info.html
基本信息
- 用户名:[[ username ]]
- 联系方式:[[ mobile ]]
-
Email:
邮箱格式错误
已验证
待验证
=============================
1.请求方式
选项 | 方案 |
---|---|
请求方法 | PUT |
请求地址 | /emails/ |
2.请求参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
string | 是 | 邮箱 |
3.响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
class EmailView(View):
"""添加邮箱"""
def put(self, request):
"""实现添加邮箱逻辑"""
# 接收参数
json_dict = json.loads(request.body.decode())
email = json_dict.get('email')
# 校验参数
if not email:
return http.HttpResponseForbidden('缺少email参数')
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return http.HttpResponseForbidden('参数email有误')
# 赋值email字段
try:
request.user.email = email
request.user.save()
except Exception as e:
logger.error(e)
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '添加邮箱失败'})
# 响应添加邮箱结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '添加邮箱成功'})
重要提示:
方案一:
- 使用Django用户认证系统提供的
is_authenticated()
class EmailView(View):
"""添加邮箱"""
def put(self, request):
"""实现添加邮箱逻辑"""
# 判断用户是否登录并返回JSON
if not request.user.is_authenticated():
return http.JsonResponse({'code': RETCODE.SESSIONERR, 'errmsg': '用户未登录'})
pass
方案二:
- 自定义返回JSON的
login_required
装饰器- 在
meiduo_mall.utils.views.py
中
def login_required_json(view_func):
"""
判断用户是否登录的装饰器,并返回json
:param view_func: 被装饰的视图函数
:return: json、view_func
"""
# 恢复view_func的名字和文档
@wraps(view_func)
def wrapper(request, *args, **kwargs):
# 如果用户未登录,返回json数据
if not request.user.is_authenticated():
return http.JsonResponse({'code': RETCODE.SESSIONERR, 'errmsg': '用户未登录'})
else:
# 如果用户登录,进入到view_func中
return view_func(request, *args, **kwargs)
return wrapper
class LoginRequiredJSONMixin(object):
"""验证用户是否登陆并返回json的扩展类"""
@classmethod
def as_view(cls, **initkwargs):
view = super().as_view(**initkwargs)
return login_required_json(view)
LoginRequiredJSONMixin
的使用
class EmailView(LoginRequiredJSONMixin, View):
"""添加邮箱"""
def put(self, request):
"""实现添加邮箱逻辑"""
# 判断用户是否登录并返回JSON
pass
===========================
send_mall()
方法介绍
- 位置:
- 在
django.core.mail
模块提供了send_mail()
来发送邮件。- 方法参数:
send_mail(subject, message, from_email, recipient_list, html_message=None)
subject 邮件标题
message 普通邮件正文,普通字符串
from_email 发件人
recipient_list 收件人列表
html_message 多媒体邮件正文,可以是html字符串
1.点击进入《设置》界面
3.开启《授权码》,并完成验证短信
4.填写《授权码》
5.完成《授权码》设置
6.配置邮件服务器
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # 指定邮件后端
EMAIL_HOST = 'smtp.163.com' # 发邮件主机
EMAIL_PORT = 25 # 发邮件端口
EMAIL_HOST_USER = '[email protected]' # 授权的邮箱
EMAIL_HOST_PASSWORD = 'hmmeiduo123' # 邮箱授权时获得的密码,非注册登录密码
EMAIL_FROM = '美多商城' # 发件人抬头
=============================
重要提示:
@celery_app.task(bind=True, name='send_verify_email', retry_backoff=3)
def send_verify_email(self, to_email, verify_url):
"""
发送验证邮箱邮件
:param to_email: 收件人邮箱
:param verify_url: 验证链接
:return: None
"""
subject = "美多商城邮箱验证"
html_message = '尊敬的用户您好!
' \
'感谢您使用美多商城。
' \
'您的邮箱为:%s 。请点击此链接激活您的邮箱:
' \
'' % (to_email, verify_url, verify_url)
try:
send_mail(subject, "", settings.EMAIL_FROM, [to_email], html_message=html_message)
except Exception as e:
logger.error(e)
# 有异常自动重试三次
raise self.retry(exc=e, max_retries=3)
2.注册发邮件的任务:main.py
- 在发送邮件的异步任务中,我们用到了Django的配置文件。
- 所以我们需要修改celery的启动文件main.py。
- 在其中指明celery可以读取的Django配置文件。
- 最后记得注册新添加的email的任务
# celery启动文件
from celery import Celery
# 为celery使用django配置文件进行设置
import os
if not os.getenv('DJANGO_SETTINGS_MODULE'):
os.environ['DJANGO_SETTINGS_MODULE'] = 'meiduo_mall.settings.dev'
# 创建celery实例
celery_app = Celery('meiduo')
# 加载celery配置
celery_app.config_from_object('celery_tasks.config')
# 自动注册celery任务
celery_app.autodiscover_tasks(['celery_tasks.sms', 'celery_tasks.email'])
3.调用发送邮件异步任务
# 赋值email字段
try:
request.user.email = email
request.user.save()
except Exception as e:
logger.error(e)
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '添加邮箱失败'})
# 异步发送验证邮件
verify_url = '邮件验证链接'
send_verify_email.delay(email, verify_url)
# 响应添加邮箱结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '添加邮箱成功'})
4.启动Celery
$ celery -A celery_tasks.main worker -l info
1.定义生成邮箱验证链接方法
def generate_verify_email_url(user):
"""
生成邮箱验证链接
:param user: 当前登录用户
:return: verify_url
"""
serializer = Serializer(settings.SECRET_KEY, expires_in=constants.VERIFY_EMAIL_TOKEN_EXPIRES)
data = {'user_id': user.id, 'email': user.email}
token = serializer.dumps(data).decode()
verify_url = settings.EMAIL_VERIFY_URL + '?token=' + token
return verify_url
2.配置相关参数
# 邮箱验证链接
EMAIL_VERIFY_URL = 'http://www.meiduo.site:8000/emails/verification/'
3.使用邮箱验证链接
verify_url = generate_verify_email_url(request.user)
send_verify_email.delay(email, verify_url)
=============================
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /emails/verification/ |
2.请求参数:查询参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
token | string | 是 | 邮箱激活链接 |
3.响应结果:HTML
字段 | 说明 |
---|---|
邮箱验证失败 | 响应错误提示 |
邮箱验证成功 | 重定向到用户中心 |
def check_verify_email_token(token):
"""
验证token并提取user
:param token: 用户信息签名后的结果
:return: user, None
"""
serializer = Serializer(settings.SECRET_KEY, expires_in=constants.VERIFY_EMAIL_TOKEN_EXPIRES)
try:
data = serializer.loads(token)
except BadData:
return None
else:
user_id = data.get('user_id')
email = data.get('email')
try:
user = User.objects.get(id=user_id, email=email)
except User.DoesNotExist:
return None
else:
return user
验证邮箱的核心:就是将用户的
email_active
字段设置为True
class VerifyEmailView(View):
"""验证邮箱"""
def get(self, request):
"""实现邮箱验证逻辑"""
# 接收参数
token = request.GET.get('token')
# 校验参数:判断token是否为空和过期,提取user
if not token:
return http.HttpResponseBadRequest('缺少token')
user = check_verify_email_token(token)
if not user:
return http.HttpResponseForbidden('无效的token')
# 修改email_active的值为True
try:
user.email_active = True
user.save()
except Exception as e:
logger.error(e)
return http.HttpResponseServerError('激活邮件失败')
# 返回邮箱验证结果
return redirect(reverse('users:info'))
==========================
用户地址的主要业务逻辑有:
==================================
提示:
- 省市区数据是在收货地址界面展示的,所以我们先渲染出收货地址界面。
- 收货地址界面中基础的交互已经提前实现。
class AddressView(LoginRequiredMixin, View):
"""用户收货地址"""
def get(self, request):
"""提供收货地址界面"""
return render(request, 'user_center_site.html')
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
模型说明:
models.ForeignKey('self')
related_name
指明父级查询子级数据的语法
Area模型类对象.area_set
语法related_name='subs'
Area模型类对象.subs
语法导入省市区数据
mysql -h数据库ip地址 -u数据库用户名 -p数据库密码 数据库 < areas.sql
mysql -h127.0.0.1 -uroot -pmysql meiduo_mall < areas.sql
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /areas/ |
2.请求参数:查询参数
- 如果前端没有传入
area_id
,表示用户需要省份数据- 如果前端传入了
area_id
,表示用户需要市或区数据
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
area_id | string | 否 | 地区ID |
3.响应结果:JSON
省份数据
{
"code":"0",
"errmsg":"OK",
"province_list":[
{
"id":110000,
"name":"北京市"
},
{
"id":120000,
"name":"天津市"
},
{
"id":130000,
"name":"河北省"
},
......
]
}
市或区数据
{
"code":"0",
"errmsg":"OK",
"sub_data":{
"id":130000,
"name":"河北省",
"subs":[
{
"id":130100,
"name":"石家庄市"
},
......
]
}
}
4.查询省市区数据后端逻辑实现
- 如果前端没有传入
area_id
,表示用户需要省份数据- 如果前端传入了
area_id
,表示用户需要市或区数据
class AreasView(View):
"""省市区数据"""
def get(self, request):
"""提供省市区数据"""
area_id = request.GET.get('area_id')
if not area_id:
# 提供省份数据
try:
# 查询省份数据
province_model_list = Area.objects.filter(parent__isnull=True)
# 序列化省级数据
province_list = []
for province_model in province_model_list:
province_list.append({'id': province_model.id, 'name': province_model.name})
except Exception as e:
logger.error(e)
return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '省份数据错误'})
# 响应省份数据
return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'province_list': province_list})
else:
# 提供市或区数据
try:
parent_model = Area.objects.get(id=area_id) # 查询市或区的父级
sub_model_list = parent_model.subs.all()
# 序列化市或区数据
sub_list = []
for sub_model in sub_model_list:
sub_list.append({'id': sub_model.id, 'name': sub_model.name})
sub_data = {
'id': parent_model.id, # 父级pk
'name': parent_model.name, # 父级name
'subs': sub_list # 父级的子集
}
except Exception as e:
logger.error(e)
return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '城市或区数据错误'})
# 响应市或区数据
return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'sub_data': sub_data})
1.
user_center_site.js
中
mounted() {
// 获取省份数据
this.get_provinces();
},
// 获取省份数据
get_provinces(){
let url = '/areas/';
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
this.provinces = response.data.province_list;
} else {
console.log(response.data);
this.provinces = [];
}
})
.catch(error => {
console.log(error.response);
this.provinces = [];
})
},
watch: {
// 监听到省份id变化
'form_address.province_id': function(){
if (this.form_address.province_id) {
let url = '/areas/?area_id=' + this.form_address.province_id;
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
this.cities = response.data.sub_data.subs;
} else {
console.log(response.data);
this.cities = [];
}
})
.catch(error => {
console.log(error.response);
this.cities = [];
})
}
},
// 监听到城市id变化
'form_address.city_id': function(){
if (this.form_address.city_id){
let url = '/areas/?area_id='+ this.form_address.city_id;
axios.get(url, {
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
this.districts = response.data.sub_data.subs;
} else {
console.log(response.data);
this.districts = [];
}
})
.catch(error => {
console.log(error.response);
this.districts = [];
})
}
}
},
2.
user_center_site.html
中
提示:
1.缓存工具
from django.core.cache import cache
cache.set('key', 内容, 有效期)
cache.get('key')
cache.delete('key')
2.缓存逻辑
3.缓存逻辑实现
- 省份缓存数据
cache.set('province_list', province_list, 3600)
- 市或区缓存数据
cache.set('sub_area_' + area_id, sub_data, 3600)
class AreasView(View):
"""省市区数据"""
def get(self, request):
"""提供省市区数据"""
area_id = request.GET.get('area_id')
if not area_id:
# 读取省份缓存数据
province_list = cache.get('province_list')
if not province_list:
# 提供省份数据
try:
# 查询省份数据
province_model_list = Area.objects.filter(parent__isnull=True)
# 序列化省级数据
province_list = []
for province_model in province_model_list:
province_list.append({'id': province_model.id, 'name': province_model.name})
except Exception as e:
logger.error(e)
return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '省份数据错误'})
# 存储省份缓存数据
cache.set('province_list', province_list, 3600)
# 响应省份数据
return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'province_list': province_list})
else:
# 读取市或区缓存数据
sub_data = cache.get('sub_area_' + area_id)
if not sub_data:
# 提供市或区数据
try:
parent_model = Area.objects.get(id=area_id) # 查询市或区的父级
sub_model_list = parent_model.subs.all()
# 序列化市或区数据
sub_list = []
for sub_model in sub_model_list:
sub_list.append({'id': sub_model.id, 'name': sub_model.name})
sub_data = {
'id': parent_model.id, # 父级pk
'name': parent_model.name, # 父级name
'subs': sub_list # 父级的子集
}
except Exception as e:
logger.error(e)
return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '城市或区数据错误'})
# 储存市或区缓存数据
cache.set('sub_area_' + area_id, sub_data, 3600)
# 响应市或区数据
return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'sub_data': sub_data})
==============================
1.用户地址模型类
class Address(BaseModel):
"""用户地址"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='addresses', verbose_name='用户')
title = models.CharField(max_length=20, verbose_name='地址名称')
receiver = models.CharField(max_length=20, verbose_name='收货人')
province = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='province_addresses', verbose_name='省')
city = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='city_addresses', verbose_name='市')
district = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='district_addresses', verbose_name='区')
place = models.CharField(max_length=50, verbose_name='地址')
mobile = models.CharField(max_length=11, verbose_name='手机')
tel = models.CharField(max_length=20, null=True, blank=True, default='', verbose_name='固定电话')
email = models.CharField(max_length=30, null=True, blank=True, default='', verbose_name='电子邮箱')
is_deleted = models.BooleanField(default=False, verbose_name='逻辑删除')
class Meta:
db_table = 'tb_address'
verbose_name = '用户地址'
verbose_name_plural = verbose_name
ordering = ['-update_time']
2.
Address
模型类说明
Address
模型类中的外键指向areas/models
里面的Area
。指明外键时,可以使用应用名.模型类名
来定义。ordering
表示在进行Address
查询时,默认使用的排序方式。
ordering = ['-update_time']
: 根据更新的时间倒叙。3.补充用户模型默认地址字段
class User(AbstractUser):
"""自定义用户模型类"""
mobile = models.CharField(max_length=11, unique=True, verbose_name='手机号')
email_active = models.BooleanField(default=False, verbose_name='邮箱验证状态')
default_address = models.ForeignKey('Address', related_name='users', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='默认地址')
class Meta:
db_table = 'tb_users'
verbose_name = '用户'
verbose_name_plural = verbose_name
def __str__(self):
return self.username
1.请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /addresses/create/ |
2.请求参数:JSON
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
receiver | string | 是 | 收货人 |
province_id | string | 是 | 省份ID |
city_id | string | 是 | 城市ID |
district_id | string | 是 | 区县ID |
place | string | 是 | 收货地址 |
mobile | string | 是 | 手机号 |
tel | string | 否 | 固定电话 |
string | 否 | 邮箱 |
3.响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
id | 地址ID |
receiver | 收货人 |
province | 省份名称 |
city | 城市名称 |
district | 区县名称 |
place | 收货地址 |
mobile | 手机号 |
tel | 固定电话 |
邮箱 |
提示:
- 用户地址数量有上限,最多20个,超过地址数量上限就返回错误信息
class CreateAddressView(LoginRequiredJSONMixin, View):
"""新增地址"""
def post(self, request):
"""实现新增地址逻辑"""
# 判断是否超过地址上限:最多20个
# Address.objects.filter(user=request.user).count()
count = request.user.addresses.count()
if count >= constants.USER_ADDRESS_COUNTS_LIMIT:
return http.JsonResponse({'code': RETCODE.THROTTLINGERR, 'errmsg': '超过地址数量上限'})
# 接收参数
json_dict = json.loads(request.body.decode())
receiver = json_dict.get('receiver')
province_id = json_dict.get('province_id')
city_id = json_dict.get('city_id')
district_id = json_dict.get('district_id')
place = json_dict.get('place')
mobile = json_dict.get('mobile')
tel = json_dict.get('tel')
email = json_dict.get('email')
# 校验参数
if not all([receiver, province_id, city_id, district_id, place, mobile]):
return http.HttpResponseForbidden('缺少必传参数')
if not re.match(r'^1[3-9]\d{9}$', mobile):
return http.HttpResponseForbidden('参数mobile有误')
if tel:
if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
return http.HttpResponseForbidden('参数tel有误')
if email:
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return http.HttpResponseForbidden('参数email有误')
# 保存地址信息
try:
address = Address.objects.create(
user=request.user,
title = receiver,
receiver = receiver,
province_id = province_id,
city_id = city_id,
district_id = district_id,
place = place,
mobile = mobile,
tel = tel,
email = email
)
# 设置默认地址
if not request.user.default_address:
request.user.default_address = address
request.user.save()
except Exception as e:
logger.error(e)
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '新增地址失败'})
# 新增地址成功,将新增的地址响应给前端实现局部刷新
address_dict = {
"id": address.id,
"title": address.title,
"receiver": address.receiver,
"province": address.province.name,
"city": address.city.name,
"district": address.district.name,
"place": address.place,
"mobile": address.mobile,
"tel": address.tel,
"email": address.email
}
# 响应保存结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '新增地址成功', 'address':address_dict})
save_address(){
if (this.error_receiver || this.error_place || this.error_mobile || this.error_email || !this.form_address.province_id || !this.form_address.city_id || !this.form_address.district_id ) {
alert('信息填写有误!');
} else {
// 新增地址
let url = '/addresses/create/';
axios.post(url, this.form_address, {
headers: {
'X-CSRFToken':getCookie('csrftoken')
},
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
// 局部刷新界面:展示所有地址信息
} else if (response.data.code == '4101') {
location.href = '/login/?next=/addresses/';
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
}
},
===============================
1.请求方式
选项 | 方案 |
---|---|
请求方法 | GET |
请求地址 | /addresses/ |
2.请求参数
无
3.响应结果:HTML
user_center_site.html
class AddressView(LoginRequiredMixin, View):
"""用户收货地址"""
def get(self, request):
"""提供收货地址界面"""
# 获取用户地址列表
login_user = request.user
addresses = Address.objects.filter(user=login_user, is_deleted=False)
address_dict_list = []
for address in addresses:
address_dict = {
"id": address.id,
"title": address.title,
"receiver": address.receiver,
"province": address.province.name,
"city": address.city.name,
"district": address.district.name,
"place": address.place,
"mobile": address.mobile,
"tel": address.tel,
"email": address.email
}
context = {
'default_address_id': login_user.default_address_id,
'addresses': address_dict_list,
}
return render(request, 'user_center_site.html', context)
1.将后端模板数据传递到Vue.js
data: {
addresses: JSON.parse(JSON.stringify(addresses)),
default_address_id: default_address_id,
},
2.
user_center_site.html
中渲染地址信息
3.完善
user_center_site.js
中成功新增地址后的局部刷新
if (response.data.code == '0') {
// 局部刷新界面:展示所有地址信息,将新的地址添加到头部
this.addresses.splice(0, 0, response.data.address);
this.is_show_edit = false;
}
========================
1.请求方式
选项 | 方案 |
---|---|
请求方法 | PUT |
请求地址 | /addresses/(?P |
2.请求参数:路径参数 和 JSON
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
address_id | string | 是 | 要修改的地址ID(路径参数) |
receiver | string | 是 | 收货人 |
province_id | string | 是 | 省份ID |
city_id | string | 是 | 城市ID |
district_id | string | 是 | 区县ID |
place | string | 是 | 收货地址 |
mobile | string | 是 | 手机号 |
tel | string | 否 | 固定电话 |
string | 否 | 邮箱 |
3.响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
id | 地址ID |
receiver | 收货人 |
province | 省份名称 |
city | 城市名称 |
district | 区县名称 |
place | 收货地址 |
mobile | 手机号 |
tel | 固定电话 |
邮箱 |
提示
- 删除地址后端逻辑和新增地址后端逻辑非常的相似。
- 都是更新用户地址模型类,需要保存用户地址信息。
class UpdateDestroyAddressView(LoginRequiredJSONMixin, View):
"""修改和删除地址"""
def put(self, request, address_id):
"""修改地址"""
# 接收参数
json_dict = json.loads(request.body.decode())
receiver = json_dict.get('receiver')
province_id = json_dict.get('province_id')
city_id = json_dict.get('city_id')
district_id = json_dict.get('district_id')
place = json_dict.get('place')
mobile = json_dict.get('mobile')
tel = json_dict.get('tel')
email = json_dict.get('email')
# 校验参数
if not all([receiver, province_id, city_id, district_id, place, mobile]):
return http.HttpResponseForbidden('缺少必传参数')
if not re.match(r'^1[3-9]\d{9}$', mobile):
return http.HttpResponseForbidden('参数mobile有误')
if tel:
if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
return http.HttpResponseForbidden('参数tel有误')
if email:
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
return http.HttpResponseForbidden('参数email有误')
# 判断地址是否存在,并更新地址信息
try:
Address.objects.filter(id=address_id).update(
user = request.user,
title = receiver,
receiver = receiver,
province_id = province_id,
city_id = city_id,
district_id = district_id,
place = place,
mobile = mobile,
tel = tel,
email = email
)
except Exception as e:
logger.error(e)
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '更新地址失败'})
# 构造响应数据
address = Address.objects.get(id=address_id)
address_dict = {
"id": address.id,
"title": address.title,
"receiver": address.receiver,
"province": address.province.name,
"city": address.city.name,
"district": address.district.name,
"place": address.place,
"mobile": address.mobile,
"tel": address.tel,
"email": address.email
}
# 响应更新地址结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '更新地址成功', 'address': address_dict})
1.添加修改地址的标记
data: {
editing_address_index: '',
},
2.实现
编辑
按钮对应的事件
show_edit_site(index){
this.is_show_edit = true;
this.clear_all_errors();
this.editing_address_index = index.toString();
},
3.展示要重新编辑的数据
show_edit_site(index){
this.is_show_edit = true;
this.clear_all_errors();
this.editing_address_index = index.toString();
// 只获取要编辑的数据
this.form_address = JSON.parse(JSON.stringify(this.addresses[index]));
},
4.发送修改地址请求
- 重要提示:
0 == ''
返回true
0 === ''
返回false
- 为了避免第0个索引出错,我们选择
this.editing_address_index === ''
的方式进行判断
if (this.editing_address_index === '') {
// 新增地址
......
} else {
// 修改地址
let url = '/addresses/' + this.addresses[this.editing_address_index].id + '/';
axios.put(url, this.form_address, {
headers: {
'X-CSRFToken':getCookie('csrftoken')
},
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
this.addresses[this.editing_address_index] = response.data.address;
this.is_show_edit = false;
} else if (response.data.code == '4101') {
location.href = '/login/?next=/addresses/';
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
alert(error.response);
})
}
=========================
1.请求方式
选项 | 方案 |
---|---|
请求方法 | DELETE |
请求地址 | /addresses/(?P |
2.请求参数:路径参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
address_id | string | 是 | 要修改的地址ID(路径参数) |
3.响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
提示:
- 删除地址不是物理删除,是逻辑删除。
class UpdateDestroyAddressView(LoginRequiredJSONMixin, View):
"""修改和删除地址"""
def put(self, request, address_id):
"""修改地址"""
......
def delete(self, request, address_id):
"""删除地址"""
try:
# 查询要删除的地址
address = Address.objects.get(id=address_id)
# 将地址逻辑删除设置为True
address.is_deleted = True
address.save()
except Exception as e:
logger.error(e)
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '删除地址失败'})
# 响应删除地址结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '删除地址成功'})
delete_address(index){
let url = '/addresses/' + this.addresses[index].id + '/';
axios.delete(url, {
headers: {
'X-CSRFToken':getCookie('csrftoken')
},
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
// 删除对应的标签
this.addresses.splice(index, 1);
} else if (response.data.code == '4101') {
location.href = '/login/?next=/addresses/';
}else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
},
================================
1.请求方式
选项 | 方案 |
---|---|
请求方法 | PUT |
请求地址 | /addresses/(?P |
2.请求参数:路径参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
address_id | string | 是 | 要修改的地址ID(路径参数) |
3.响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
class DefaultAddressView(LoginRequiredJSONMixin, View):
"""设置默认地址"""
def put(self, request, address_id):
"""设置默认地址"""
try:
# 接收参数,查询地址
address = Address.objects.get(id=address_id)
# 设置地址为默认地址
request.user.default_address = address
request.user.save()
except Exception as e:
logger.error(e)
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '设置默认地址失败'})
# 响应设置默认地址结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '设置默认地址成功'})
set_default(index){
let url = '/addresses/' + this.addresses[index].id + '/default/';
axios.put(url, {}, {
headers: {
'X-CSRFToken':getCookie('csrftoken')
},
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
// 设置默认地址标签
this.default_address_id = this.addresses[index].id;
} else if (response.data.code == '4101') {
location.href = '/login/?next=/addresses/';
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
},
==========================
1.请求方式
选项 | 方案 |
---|---|
请求方法 | PUT |
请求地址 | /addresses/(?P |
2.请求参数:路径参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
address_id | string | 是 | 要修改的地址ID(路径参数) |
3.响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
class UpdateTitleAddressView(LoginRequiredJSONMixin, View):
"""设置地址标题"""
def put(self, request, address_id):
"""设置地址标题"""
# 接收参数:地址标题
json_dict = json.loads(request.body.decode())
title = json_dict.get('title')
try:
# 查询地址
address = Address.objects.get(id=address_id)
# 设置新的地址标题
address.title = title
address.save()
except Exception as e:
logger.error(e)
return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '设置地址标题失败'})
# 4.响应删除地址结果
return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '设置地址标题成功'})
data: {
edit_title_index: '',
new_title: '',
},
// 展示地址title编辑框
show_edit_title(index){
this.edit_title_index = index;
},
// 取消保存地址title
cancel_title(){
this.edit_title_index = '';
this.new_title = '';
},
// 修改地址title
save_title(index){
if (!this.new_title) {
alert("请填写标题后再保存!");
} else {
let url = '/addresses/' + this.addresses[index].id + '/title/';
axios.put(url, {
title: this.new_title
}, {
headers: {
'X-CSRFToken':getCookie('csrftoken')
},
responseType: 'json'
})
.then(response => {
if (response.data.code == '0') {
// 更新地址title
this.addresses[index].title = this.new_title;
this.cancel_title();
} else if (response.data.code == '4101') {
location.href = '/login/?next=/addresses/';
} else {
alert(response.data.errmsg);
}
})
.catch(error => {
console.log(error.response);
})
}
},
==============================
提示:
- 修改密码前需要校验原始密码是否正确,以校验修改密码的用户身份。
- 如果原始密码正确,再将新的密码赋值给用户。
class ChangePasswordView(LoginRequiredMixin, View):
"""修改密码"""
def get(self, request):
"""展示修改密码界面"""
return render(request, 'user_center_pass.html')
def post(self, request):
"""实现修改密码逻辑"""
# 接收参数
old_password = request.POST.get('old_password')
new_password = request.POST.get('new_password')
new_password2 = request.POST.get('new_password2')
# 校验参数
if not all([old_password, new_password, new_password2]):
return http.HttpResponseForbidden('缺少必传参数')
try:
request.user.check_password(old_password)
except Exception as e:
logger.error(e)
return render(request, 'user_center_pass.html', {'origin_pwd_errmsg':'原始密码错误'})
if not re.match(r'^[0-9A-Za-z]{8,20}$', new_password):
return http.HttpResponseForbidden('密码最少8位,最长20位')
if new_password != new_password2:
return http.HttpResponseForbidden('两次输入的密码不一致')
# 修改密码
try:
request.user.set_password(new_password)
request.user.save()
except Exception as e:
logger.error(e)
return render(request, 'user_center_pass.html', {'change_pwd_errmsg': '修改密码失败'})
# 清理状态保持信息
logout(request)
response = redirect(reverse('users:login'))
response.delete_cookie('username')
# # 响应密码修改结果:重定向到登录界面
return response