### 5.4 用户信息
#### 1.用户基本信息
1. html页面中的模板替换(替换jinja2的数据)
1. request.user 可以获得用户对象(Django提供的方法),因为视图中已经添加了视图的判断,所以用户一定是已经登录的用户
```html
欢迎您:<em>{{ request.user.username }}</em>
<script type="text/javascript">
let username = "{{ request.user.username }}";
let mobile = "{{ request.user.mobile }}";
let email = "{{ request.user.email }}";
let email_active = "{{ request.user.email_active }}";
</script>
```
2. user表中添加字典 email_active 判断邮箱是否激活(models中添加字段后需要重新迁移)
user.models视图中添加
```python
class User(AbstractUser):
mobile = models.CharField(max_length=11)
email_active = models.BooleanField(default=0)
```
3. 用户填写邮箱后会发送ajax请求,发送请求后在邮箱中添加数据
#### 2.邮箱设置
1. 新增视图 由于接收添加邮箱的ajax请求
2. 需要继承之前添加的用户校验方式
3. 接收 将接收的数据撰文json格式 发送的是put请求
![1559024795469](assets/1559024795469.png)
4. 由于前端发送的是ajax请求 所以需要返回json格式
5. 判断邮箱是否为空
6. 判断邮箱格式是否正确
7. 处理
1. user = request.user
2. user.emil =emil
3. user.save()
8. 响应
```python
# 处理保存邮箱
class EmailSendView(login_requiredMIXIN,View):
#前端发送的是put请求
def put(self,request):
# 请求头发送的数据使用body接收 且发送的是二进制数据 需要解码 解码出来是json字符串
request_dict_json = request.body.decode()
#转换为字典
request_dict = json.loads(request_dict_json)
#用key取值
email = request_dict.get('email')
#非空验证
if not email:
return http.JsonResponse({
"code":RETCODE.DBERR,
"errmsg":"邮箱不能为空"
})
#格式验证
if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$',email):
return http.JsonResponse({
"code":RETCODE.DBERR,
"errmsg":"邮箱格式不正确"
})
try:
#尝试从数据库中查找该邮箱数据
user = User.objects.get(email=email)
except:
#如果没找到 那么将这个数据赋值给这个对象
user = request.user
user.email = email
else:
#如果
return http.HttpResponseForbidden('邮箱已存在,请重新输入')
#到此处已经成功验证了邮箱账号 准备发送邮件了
#把邮箱保存到数据库中放在这里
user.save()
return http.JsonResponse({
"code": RETCODE.OK,
"errmsg": "设置成功,请验证"
})
```
#### 3.异步发送邮件
##### 1.配置
python自带了发送邮件的方法:send_mall
> `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字符串
```
- message html和message二选一
- 由于本邮件准备发送带有链接的a标签,所以选择使用html_message
> **配置邮件服务器**
>
> 本段配置写在python的dev设置中
```python
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # 指定邮件后端
EMAIL_HOST = 'smtp.163.com' # 发邮件主机
EMAIL_PORT = 25 # 发邮件端口
EMAIL_HOST_USER = 'hmmeiduo@163.com' # 授权的邮箱
EMAIL_HOST_PASSWORD = 'hmmeiduo123' # 邮箱授权时获得的密码,非注册登录密码
EMAIL_FROM = '美多商城<hmmeiduo@163.com>' # 发件人抬头
```
内容中包含超链接 所以用html_message
##### 2.异步发送
将代码放到celery中执行
![1559028107565](assets/1559028107565.png)
发送邮件代码
配置服务器账号
定义celery任务:
1. 新建包
2. 新建tasks任务
3. 封装发送邮件的任务
4. main.app装饰 @app.task
5. 任务列表中添加任务
6. 调用任务:renwu.delay(参数)
```python
# 处理保存邮箱
class EmailSendView(login_requiredMIXIN,View):
........
#到此处已经成功验证了邮箱账号 准备发送邮件了
# 1. 邮件中添加字段 这个字段应该是每个用户专用的 所以使用用户id加密进行设置
value = user.id
#2.加密:
value_en = signature.dumps(value,constants.DUMPS_LOADS_EXPIRES)
#拼接网址
email_url = "http://www.meiduo.site:8000/info/emails/verification/"+value_en
# 加密数据
email_fun.delay(email,email_url)
#把邮箱保存到数据库中放在这里
user.save()
return http.JsonResponse({
"code": RETCODE.OK,
"errmsg": "设置成功,请验证"
})
```
tasks代码
```python
@app.task(bind=True, name='email', retry_backoff=3)
def email_fun(self,email,email_url):
html_message = '<p>尊敬的用户您好!</p>' \
'<p>感谢您的使用。</p>' \
'<p>您的邮箱为:%s 。请点击此链接激活您的邮箱:</p>' \
'<p><a href="%s">%s<a></p>' % (email, email_url, email_url)
try:
send_mail(
subject=settings.EMAIL_SUBJECT,
from_email=settings.EMAIL_FROM,
html_message=html_message,
recipient_list=[email],
message=""
)
except Exception as e:
print(e)
# 有异常自动重试三次
raise self.retry(exc=e, max_retries=3)
```
##### 4.邮箱激活
urls代码:
```python
url(r'^info/emails/verification/(?P<code>.*)',views.EmailResponseView.as_view())
```
视图代码:
```python
#接收用户点击的邮件
class EmailResponseView(View):
def get(self,request,code):
#解密
email_dict = signature.loads(code,constants.DUMPS_LOADS_EXPIRES)
#由于加密的是id 所以解密出来的就是id
email_id = email_dict.get('user.id')
try:
#获取需要操作的用户
user = User.objects.get(id = email_id)
except:
return http.HttpResponse('验证码超时,请重新请求')
#将验证邮箱项设为真
user.email_active = True
#保存到数据库
user.save()
#登陆保持
login(request,user)
#重定向到首页
response = redirect('/')
response.set_cookie('username',user)
return response
```
- 需要在虚拟机中打开连接,因为访问的是局域网 没有域名的
- pk字段名:primary key 可以缩写为pk 就是主键 默认主键就是id
#### 5.收货地址相关
##### 1.展示收货地址页面
定义收货地址省市区三级联动
1. 创建模型类
```python
class AresModel(models.Model):
name = models.CharField(max_length=20,verbose_name='名称')
parent = models.ForeignKey('self',null=True,blank=True,related_name='subs')
class Meta:
db_table = 'tb_areas'
verbose_name = '省市区'
verbose_name_plural = '省市区'
def __str__(self):
return self.name
```
1. 外键关联:应用.模型类 关联该表的主键,如果写self 则为自关联关联本表的主键
2. blank: True时,字段值可以为空。默认False。和null参数不同的是,null是纯数据库层面的,而blank是验证相关的,它与表单验证是否允许输入框内为空有关,与数据库无关。
3. related_name 关联查询的名称
对于两个关联表 有一对多 和多对一的关系 例如A为1表的实例对象 B为多表的实例对象
- 1查多:A.b(模型类的小写)_set.all() 返回所有的集合
- 多查1:B.b表中关联A表的关联字段 返回的是A表的对象(即A表的str方法) 本Model中则为a.subs.all()
- 所有使用related_name赋值可以代替`b(模型类的小写)_set`
##### 2.数据导入
> 导入省市区数据
```bash
mysql -h数据库ip地址 -u数据库用户名 -p数据库密码 数据库 < areas.sql
mysql -h127.0.0.1 -uroot -pmysql meiduo01 < areas.sql
```
##### 3.查询省市区数据
1.查询所有的省
根据前端的代码
- 如果前端没有传入`area_id`,表示用户需要省份数据
- 如果前端传入了`area_id`,表示用户需要市或区数据
```python
# 请求省地址的视图
class AreaView(View):
def get(self,request):
area_id = request.GET.get('area_id')
if area_id is None:
#表示没有制定地区,查询省市
city_list = AresModel.objects.filter(parent__isnull=True)
province_list = []
for city in city_list:
province_list.append({
"id":city.id,
"name":city.name
})
#返回值根据需求文档决定
return http.JsonResponse({
"code": "0",
"errmsg": "OK",
"province_list":province_list
})
```
请求市的视图 接着上面的书写
```python
else:
#表示有地区编号
try:
area = AresModel.objects.get(id=area_id)
except:
#如果地区编号不在列表中
return http.JsonResponse({
"code":'400',
"errmsg": "地区编号无效"
})
#将所有的对象取出来 一对多取出 方法为models中定义的related_name='subs'
area_list = area.subs.all()
subs_list = []
#根据需求文档要求,取出字符串
for i in area_list:
subs_list.append({
"id":i.id,
"name":i.name
})
print(subs_list)
return http.JsonResponse({
"code": "0",
"errmsg": "OK",
"sub_data":{
"id": area.id,
"name": area.name,
"subs":subs_list
}
})
```
##### 4.缓存省市区的数据
对于不变的数据,或者变化很小的数据,可以考虑缓存
![1559184513147](assets/1559184513147.png)
优点:速度快
> 缓存工具
- `from django.core.cache import cache`
- 存储缓存数据:`cache.set('key', 内容, 有效期)`
- 读取缓存数据:`cache.get('key')`
- 删除缓存数据:`cache.delete('key')`
缓存数据根据dev中配置可得:![1559066525078](assets/1559066525078.png)
由于没有指定储存位置 默认使用0号数据库
代码:
```python
# 请求省地址的视图
class AreaView(View):
def get(self,request):
area_id = request.GET.get('area_id')
#从redis中查找数据
result =cache.get('area_province')
........................
#将返回的值放在变量中
result = {
"code": "0",
"errmsg": "OK",
"province_list":province_list
}
#将变量存到数据库中
cache.set("area_province",result,contants.CACHE_AREA_EXPRIES)
#返回值根据需求文档决定
return http.JsonResponse(result)
```
地区的缓存大致相同,只是地区的缓存key需要增加area_id字段 方便区分
##### 5.定义收货地址模型类
![1559112139216](assets/1559112139216.png)
1.规划模型类字段:
关联user外键 一个用户有多个收货地址
收件人 省 市 区 详细地址 手机号 固定电话 邮箱
2.同时user表中需要添加一个外键 用于关联这张表
> **1.用户地址模型类**
```python
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.补充用户模型默认地址字段**
```python
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
```
- 两个表中都有对方的外键 原因:
- User中有address的外键,是为了设置默认地址
- address中有User的外键,是为了可以把地址和用户关联起来
![1559199428336](assets/1559199428336.png)
##### 6.添加收货地址
分析:
接收数据
- 前端发送的是ajax请求,为js数据,所以需要json.loads(request.body.decode())
验证数据
- 正则验证手机号
- 地址是否超过20个
- 用户是否有默认地址
处理数据(创建对象)
返回响应
##### 7.查询收货地址
分析:
接收请求 根据user查询所关联的地址
根据js请求 构建响应信息
##### 8.修改收货地址
##### 9.删除地址
获取操作对象(根据address_id)
删除对象(物理删除)/(逻辑删除)
10.修改标题
11.修改默认地址
12.修改密码
修改密码时要使用set_password方法 这样可以给密码加密
转自,昊文的笔记 |
|