A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© wuyutaott 初级黑马   /  2019-5-31 18:51  /  805 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

### 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方法 这样可以给密码加密

转自,昊文的笔记

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马