zoukankan      html  css  js  c++  java
  • django-dailyfresh

    Hold on ,learn by myself!

        redis
        
    nosql
        - 不支持sql语法
        - 存储数据都是KV形式
        - Mongodb
        - Redis
        - Hbase hadoop
        - Cassandra hadoop
        
    关系型数据库
        mysql/oracle/sql server/关系型数据库
        通用的操作语言
        
    关系型比非关系数据库:
        - sql适用关系特别复杂的数据查询场景
        - sql对事务支持非常完善
        - 两者不断取长补短
    redis对比其他nosql产品:
        - 支持数据持久化
        - 支持多样数据结构,list,set,zset,hash等
        - 支持数据备份,即master-slave模式的数据备份
        - 所有操作都是原子性的,指多线程没有抢数据的过程
        
    redis 应用场景:
        用来做缓存(echcache/memcached),redis所有数据放在内存中
        社交应用
    
    redis-server redis
    redis-cli redis
    测试是否通信:
        ping  ------------> pang
    默认数据库16,通过0-15标示,select n 切换
    
    常用通用命令
        命令集  http://doc.redisfans.com
        - keys *
            keys a*   #查询以a开头的key
        - exists key1  #返回1,0
        - type key
        - del key1
        - expire key seconds #设置过期时间
        - ttl key #查看过期时间
    1. string    
        - 二进制,可以接受任何格式的数据,如JPEG或JSON对象描述信息
        - set name value
            - get name
        - mset key1 python key2 linux
            - get key1  ,get key2
            - mget key1 key2
        - append a1 haha  追加字符串
            - get a1    #a1+'haha'
        - setex key seconds value
    2. hash
        - 用于存储对象,对象结果是属性、值
        - 值的类型为string
        - hset user name itheima
        - hmset key field1 value1 field2 value2
        - hkeys key
        - hget key field
        - hmget key field1 field2
        - hvals key  #获取所有的属性
        - del key1 #删除整个hash键值,
        - hdel key field1 field2 #删除field1 field2的属性
    3. list 
        - 列表中元素类型为string
        - lpush key value1 value2
        - lrange key 0 2  #start stop 返回列表里指定范围的元素
            - lrange key 0 -1 #查询整列元素
        - rpush key value1 value2
        - linsert key before/after b 3
            - b 现有元素
            - 3 插入元素
        - lset key index value  #设置指定元素的值
        - lrem key count value
            - count > 0 从左向右移除
            - count < 0 从右向左移除
            - count = 0 移除所有
    4. set 
        - 元素为string类型
        - 无序集合
        - sadd key zhangsan lisi wangwu 
        - smembers key
        - srem key wangwu 
    
    5. zset
        - 有序集合
        - 元素唯一性、不重复
        - 每个元素都关联一个double类型的score权重,通常
        从小到大排序
        - zadd key score1 member1 score2 member2
        - zrange key start stop
        - zrangebyscore key min max
        - zscore key member
        - zrem key member1 member2
        - zremrangebyscore key min max 
    
    python 操作 redis
    
    pip install redis 
    from redis import StrictRedis
    
    redis 存储session
    而session默认存储在django-session表里
    pip install django-redis-sessions==0.5.6
    
    open django工程,改setting配置redis
    
    SESSION_ENGINE = 'redis_sessions.session'
    SESSION_REDIS_HOST = 'localhost'
    SESSION_REDIS_PORT = 6379
    SESSION_REDIS_DB = 2
    SESSION_REDIS_PASSWORD = ''
    SESSION_REDIS_PREFIX = 'session'
    
    
    通过redis-cli客户端查看
    最后在Base64在线解码
        
    主从配置实现读写分离
    一个master可以拥有多个slave,一个slave可以有多个slave
        - 实现读写分离
        - 备份主服务、防止主服务挂掉后数据丢失
    bind 192.168.26.128
    slaveof 192.168.26.128 6379
    port 6378
        
        
    redis集群
    集群:一群通过网络连接的计算机,共同对外提交服务,想一个独立的服务器
    主服务、从服务
    集群:
        - 软件层面
            - 只有一台电脑,在这一台电脑上启动了多个redis服务。
        - 硬件层面
            - 存在多台实体的电脑,每台电脑上都启动了一个redis或者多个redis服务。
        
    集群和python交互:
        pip install redis-by-cluster
        from rediscluster import *
        if __name == 'main':
            try:
                startup_nodes = [
                {'host':'192..','port':'700'},...]
                src = StricRedisCluster(startup_nodes=startup_nodes,
                decode_response = True)
                result = src.set('name','di')
                print(result)
                name = src.geg('name')
                print(name)
            except exception as e:
                print(e)
        
        
    ---------------mongodb-----------
    
    not only sql
    有点:
    - 易扩展
    - 大数据量、高性能
    - 灵活的数据模型    
    缺点:
        - 占据的内存比之mysql要多
        
    mongodb
    mongo
    show databases
    use douban
    db #查看当前数据路
    db.dropDatabase()
    
    不手动创建集合(类似mysql的表)
    db.createCollection(name,options)
    db.createCollection('sub',{capped:true,size:10})
    show collections
    db.xxx.drop()
    
    Object ID
    String
    Boolean
    Integer   false true ---类似json里的小写false
    Double
    Arrays
    Object
    Null
    Timestamp
    Date
    
    
    -------------------------flask 单元测试------------
    单元测试
        - 程序员自测
        - 一般用于测试一些实现某功能的代码
    集成测试
    系统测试
    
    def num_div(num1num2):
        #断言为真、则成功,为假,则失败抛出异常/终止程序执行
        #assert num1 int
        assert isinstance(num1,int)
        assert isinstance(num2,int)
        assert num2 != 0
    
        print(num1/num2)
        
    if __name__ == '__main__'
        num_div('a','b')  #AssertionError
            
    assertEqual
    assertNotEqual
    assertTrue
    assertFalse
    assertIsNone
    assertIsNotNone
    
    必须以test_开头
    
    classs LoginTest(unittest.TestCase):
        def test_empty_user_name_password(self):
            client = app.test_client()
            ret = client.post('/login',data={})
            resp = ret.data
            resp = json.loads(resp)
            
            self.assertIn('code',resp)
            self.assertEqual(resp['code'],1)
    
    if __name__ == '__main__':
        unittest.main()
        
    cmd
    python test.python
    
    
    
    class LoginTest(unittest.TestCase):
        def setup(self):
            #相当于 __init__
            self.client = app.test_client()
            #开启测试模式,获取错误信息
          1    app.config['TESTING'] = True 
          2 app.testing = True
        
        def test_xxxx(self):
            .....
     
    
     
    简单单元测试
    网络接口测试(视图)
    数据库测试
    import unittest
    from author_book import Author,db,app
    
    class DatabaseTest(unittest.TestCase):
        
        def setUp(self):
            app.testing = True
            #构建测试数据库
            app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:mysql@127.0.0.1:3306/flask_test"
            db.create_all()
        
        def test_add_user(self):
            
            author = Author(name="zhang",email='xxx'..)
            db.session.add(author)
            db.session.commit()
            
            result_author =  Author.query.filter_by(name="zhang").first()
            self.assertNotNone(result_author)
        
        def tearDown(self):
            db.session.remove()
            db.drop_all()
            
            
    ------------部署-------------
    
    django  uwsgi nginx
    
    
    用户
    Nginx   负载均衡  提供静态文件
    业务服务器 flask + Gunicorn        
    mysql/redis
            
            
    pip install gunicorn
    gunicorn -w 4 -b 127.0.0.1:5000 --access-logfile ./logs/og main:app
     
    if self.server_version_info < (5,7,20):
        cursor.execute(”SELECT @@tx_isolation")
    else:
        cursor.execute("SELECT @@transaction_isolation")
            
            
    --------------- 测试-----------------
    
    jekins
        - Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目
    jmeter
        - 软件做压力测试
    postman
        - 
    
    
            创建天天生鲜 B2C 大型网站
    
    1. 电商概念
    B2B    Alibaba
    C2C    瓜子二手车、淘宝、易趣
    B2C    唯品会
    C2B    尚品宅配
    O2O    美团
    F2C    戴尔
    
    2. 开发流程
    产品原型的设计 --- 产品经理-----axure
    
    非常关键:
        - 架构设计
        - 数据库设计
    
    3. 数据库分析
    mysql
        - 
    redis 
        - 若用户多,session服务器     
        - 对于经常访问的如首页,则用缓存服务器
    xxx    
        -异步任务处理celery (注册页面发邮件之类的)
    
    分布式文件存储系统fastdfs(不用django默认的media上传文件方式)
        - 
    
    
    4. 数据库设计:
    
    a.用户模块、商品模块
    用户表
        - ID
        - 用户名
        - 密码
        - 邮箱
        - 激活标识
        - 权限标识
    地址表(一个用户可能有多个地址)
        - ID
        - 收件人
        - 地址
        - 邮编
        - 联系方式
        - 是否默认
        - 用户ID
    商品SKU表
        - ID
        - 名称
        - 简介
        - 价格
        - 单位
        - 库存
        - 销量
        - 详情
        - *图片(就放一张,以空间换取时间)
        - 状态
        - 种类ID
        - sup ID
    商品SPU表
        - ID
        - 名称
        - 详情
    商品种类表
        - ID
        - 种类名称
        - logo
        - 图片
    商品图片表
        - ID
        - 图片
        - sku ID
    首页轮播商品表
        - ID
        - sku 
        - 图片
        - index
    首页促销表
        - ID
        - 图片
        - 活动url
        - index
    首页分类商品展示表
        - ID
        - sku ID
        - 种类ID
        - 展示标识
        - index
        
    b. 购物车模块  redis实现
        - redis保存用户历史浏览记录
    c. 订单模块
    
    订单信息表
        - 订单ID
        - 地址ID
        - 用户ID
        - 支付方式
        - *总金额
        - *总数目  
        - 运费
        - 支付状态
        - 创建时间
    订单商品表
        - ID
        - sku ID
        - 商品数量
        - 商品价格
    
    健表时须知:
        - 此时用的是Ubanto的mysql数据库,需要
            - grant all on test2.* to 'root'@'1.2.3.4' identified
    by 'root'
            - flush privileges    
            - migrate
        - choices
        - 富文本编辑器
            - tinymce
            - pip install django-tinymce==2.6.0
        - LANGUAGE_CODE = 'zh-hans'
        - TIME_ZONE = 'Asia/Shanghai'
        - url(r'^',include('goods.urls',namespace='goods'))
        - vervose_name
        - 项目框架搭建
            - 四个app
            - user
            - goods
            - cart
            - order
        - 使用Abstractuser时,settings里需要
        AUTH_USER_MODEL = 'user.User'
        
        
        
    class BaseModel(models.Model):
        '''模型类抽象基类’''
        create_time = models.DatetimeField(auto_now_add=True,verbose_name='创建时间')
        cupdate_time = models.DatetimeField(auto_now=True,verbose_name='更新时间')
        is_delete = models.BooleanField(default=False,verbose_name='删除标记')
        
        class Meta:
            #说明是一个抽象类模型
            abstract = True
    
    
    开始设计前后端
    1. 如何设计四个app
    2. register.html
        - 动态导入静态文件,{% load staticfiles %}
            link ....  href="{% static 'css/reset.css' %}"
        - 前端post一个勾选按钮(阅读同意),后端收到是
            if allow !== 'on':
                pass
        -  几乎每一个URL都有namespace,注册成功后跳转首页用
            反向解析 return redirect(reverse('goods:index'))
                - goods 是app域名的别名
                - index 是goods里面的别名
        - 在mysql里输入 select * from df_user G
            信息会竖排显示
        - 数据完整性校验
            if not all([username,password,email]):
                pass
        - 在表里发现 is_active 已经为1激活了,但是我们不想注册即激活,
            需要,在创建用户的时候加入如下:
            user=User.objects.create_user(username,email,password)
            user.is_active = 0
            user.save()
        - 在注册之前先进性校验,register_handle
            判断用户名是否重复,
            try:
            #get有的话只返回一个,没有的话会包异常
                user=User.objects.get(username=username)
            except User.DoesNotExist:
                user = None
            if user:
                return ...
        - 类视图的使用
            - 原理关键在dispatch,getattr
            - from django.view.generic import View
             class RegisterView(View):
                def get(self,request):
                    pass
                def post(self,request):
                    pass
            url(r'^register$', RegisterView.as_view(), name='register'), # 注册
        - 发送激活邮件,包含激活连接
            - 激活连接中需包含用户身份信息,并加密
            - pip install itsdangerous
            from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
            from itsdangerous import SignatureExpired
            
             # 加密用户的身份信息,生成激活token
                serializer = Serializer(settings.SECRET_KEY, 3600)
                info = {'confirm':user.id}
                token = serializer.dumps(info) # bytes
                token = token.decode()
            #发邮件
                1. django本身有秘钥、
                    subject
                    message
                    sender
                    receiver
                    html_message
                    dend_mail(subject,message,sender,receiver,html_message
                    =html_message)  #发送html格式的
            - django网站 -(阻塞执行)-->SMTP服务器 -->目的邮箱
                2.celery使用
                    任务发出者 -- 任务队列(broker)-- 任务处理者(worker)        
                            发出任务             监听任务队列
                    pip install celery
                    任务队列是一种跨线程、跨机器工作的一种机制.
                    celery通过消息进行通信,通常使用一个叫Broker(中间人)来协client(任务的发出者)
                    和worker(任务的处理者). clients发出消息到队列中,broker将队列中的信息派发给
                    worker来处理用于处理些IO操作耗时的事务,如上传下载文件、发邮件
                    
                    from celery import Celery
                    # 创建一个Celery类的实例对象
                    app = Celery('celery_tasks.tasks', broker='redis://172.16.179.130:6379/8')
                    # 定义任务函数
                    @app.task
                i.    def send_register_active_email(to_email, username, token):
                        '''发送激活邮件'''
                        # 组织邮件信息
                        subject = '天天生鲜欢迎信息'
                        message = ''
                        sender = settings.EMAIL_FROM
                        receiver = [to_email]
                        html_message = '<h1>%s, 欢迎您成为天天生鲜注册会员</h1>请点击下面链接激活您的账户<br/><a href="http://127.0.0.1:8000/user/active/%s">http://127.0.0.1:8000/user/active/%s</a>' % (username, token, token)
    
                        send_mail(subject, message, sender, receiver, html_message=html_message)
                        time.sleep(5)
                    
                ii.    # 发邮件
                    send_register_active_email.delay(email, username, token)
                    
                iii.# Ubuntu虚拟机启动worker
                    1.只是启动celery里的worker进程,配置信息需要与django里的task.py文件
                    一样,否则django里的变动(time.sleep),Ubuntu不会执行,以当前为准
                    2.vi celery_tasks/tasks.py
                        django环境的初始化
                        # 在任务处理者一端加这几句
                        # import os
                        # import django
                        # os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings")
                        # django.setup()
                    3.启动
                        celery -A celery_tasks.tasks worker -l info
        -  激活成功后返回登陆页面
            class ActiveView(View):
                '''用户激活'''
                def get(self, request, token):
                    '''进行用户激活'''
                    # 进行解密,获取要激活的用户信息
                    serializer = Serializer(settings.SECRET_KEY, 3600)
                    try:
                        info = serializer.loads(token)
                        # 获取待激活用户的id
                        user_id = info['confirm']
    
                        # 根据id获取用户信息
                        user = User.objects.get(id=user_id)
                        user.is_active = 1
                        user.save()
    
                        # 跳转到登录页面
                        return redirect(reverse('user:login'))
                    except SignatureExpired as e:
                        # 激活链接已过期
                        return HttpResponse('激活链接已过期')
    
    3. login
        - 因为用户多,不能经常调用数据库,使用redis存储session
            https://django-redis-chs.readthedocs.io/zh_CN/latest/
            - pip install django-redis 
            - django缓存配置
                - 指定ip的redis数据库
            - 配置session存储
            - redis-cli -h 192.169.12.1
        - 是否记住用户名    
        i.     class LoginView(View):
            '''登录'''
            def get(self, request):
                '''显示登录页面'''
                # 判断是否记住了用户名
                if 'username' in request.COOKIES:
                    username = request.COOKIES.get('username')
                    checked = 'checked'
                else:
                    username = ''
                    checked = ''
    
                # 使用模板
                return render(request, 'login.html', {'username':username, 'checked':checked})
    
        ii.    def post(self, request):
                '''登录校验'''
                # 接收数据
                username = request.POST.get('username')
                password = request.POST.get('pwd')
                # 校验数据
                if not all([username, password]):
                    return render(request, 'login.html', {'errmsg':'数据不完整'})
                # 业务处理:登录校验
                user = authenticate(username=username, password=password)
                if user is not None:
                    # 用户名密码正确
                    if user.is_active:
                        # 用户已激活
                        # 记录用户的登录状态
                        login(request, user)
                        # 跳转到首页
                        response = redirect(reverse('goods:index')) # HttpResponseRedirect
                        # 判断是否需要记住用户名
                        remember = request.POST.get('remember')
                        if remember == 'on':
                            # 记住用户名
                            response.set_cookie('username', username, max_age=7*24*3600)
                        else:
                            response.delete_cookie('username')
                        # 返回response
                        return response
                    else:
                        # 用户未激活
                        return render(request, 'login.html', {'errmsg':'账户未激活'})
                else:
                    # 用户名或密码错误
                    return render(request, 'login.html', {'errmsg':'用户名或密码错误'})
        
        iii.<input type="text" name="username" class="name_input" value="{{ username }}" placeholder="请输入用户名">    
            <input type="checkbox" name="remember" {{ checked }}>
    4. 用户中心
        - base模板的设计,分base.html  base_no_cart.html,非常重要
            {# 首页 注册 登录 #}
            <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
            <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
            {% load staticfiles %}
            <head>
                <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
                {# 网页标题内容块 #}
                <title>{% block title %}{% endblock title %}</title>
                <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
                <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
                {# 网页顶部引入文件块 #}
                {% block topfiles %}{% endblock topfiles %}
            </head>
            。。。。。。
    
        - 一个用户中心页面可以点三个页面  (用户/订单/收货地址)
            <li><a href="{% url 'user:user' %}" {% if page == 'user' %}class="active"{% endif %}>· 个人信息</a></li>
        - 登录装饰器(如用户界面需要登录)
            i.    login_required   ?next=xxxx
                    from django.contrib.auth.decorators import login_required
                settings
                    # 配置登录url地址
                    LOGIN_URL='/user/login' # /accounts/login
                login.html
                    <div class="form_input">
                    {# 不设置表单action时,提交表单时,会向浏览器地址栏中的地址提交数据 #}
                    .....
                url
                    url(r'^$', login_required(UserInfoView.as_view()), name='user'), # 用户中心-信息页
                    url(r'^order$', login_required(UserOrderView.as_view()), name='order'), # 用户中心-订单页
                    url(r'^address$', login_required(AddressView.as_view()), name='address'), # 用户中心-地址页
                登录视图里的logic处理
                    if user.is_active:
                        # 用户已激活
                        # 记录用户的登录状态
                        login(request, user)
                        # 获取登录后所要跳转到的地址
                        # 默认跳转到首页
                        next_url = request.GET.get('next', reverse('goods:index'))
                        # 跳转到next_url
                        response = redirect(next_url) # HttpResponseRedirect
    
                        # 判断是否需要记住用户名
                        remember = request.POST.get('remember')
                        if remember == 'on':
                            # 记住用户名
                            response.set_cookie('username', username, max_age=7*24*3600)
                        else:
                            response.delete_cookie('username')
                        # 返回response
                        return response
            ii. login_required    
                    - 一些经常用的python_package放在utils文件夹里,如mixin.py
                            from django.contrib.auth.decorators import login_required
                            class LoginRequiredMixin(object):
                                @classmethod
                                def as_view(cls, **initkwargs):
                                    # 调用父类的as_view
                                    view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
                                    return login_required(view)
                    - 同时类视图调用
                        from utils.mixin import LoginRequiredMixin
                        
                        class UserInfoView(LoginRequiredMixin, View):pass
                        class UserOrderView(LoginRequiredMixin, View):pass
    
                settings(同上)        
                url     (不需要做处理了)
                        url(r'^$', UserInfoView.as_view(), name='user'), # 用户中心-信息页
                        url(r'^order$', UserOrderView.as_view(), name='order'), # 用户中心-订单页
                        url(r'^address$', AddressView.as_view(), name='address'), # 用户中心-地址页
                登录视图里的logic处理(同上)    
        
        - 用户登录欢迎信息(head)
            - 考点  
              # Django会给request对象添加一个属性request.user
              # 如果用户未登录->user是AnonymousUser类的一个实例对象
              # 如果用户登录->user是User类的一个实例对象
              # request.user.is_authenticated()
            - base.html
              {% if user.is_authenticated %}
                <div class="login_btn fl">
                    欢迎您:<em>{{ user.username }}</em>
                    <span>|</span>
                    <a href="{% url 'user:logout' %}">退出</a>
                </div>
                {% else %}
                <div class="login_btn fl">
                    <a href="{% url 'user:login' %}">登录</a>
                    <span>|</span>
                    <a href="{% url 'user:register' %}">注册</a>
                </div>
                {% endif %}
        - logout
             url
                url(r'^logout$', LogoutView.as_view(), name='logout'), # 注销登录
            views
                from django.contrib.auth import authenticate, login, logout
                
                class LogoutView(View):
                '''退出登录'''
                    def get(self, request):
                        '''退出登录'''
                        # 清除用户的session信息
                        logout(request)
    
                        # 跳转到首页
                        return redirect(reverse('goods:index'))    
                    
        - 用户中心地址页(默认地址和新添地址的设计)
            i.  post 新上传地址数据,从数据库里查找是否有默认地址
                get 在页面上显示是否有默认地址
                class AddressView(LoginRequiredMixin, View):
                    '''用户中心-地址页'''
                    def get(self, request):
                        '''显示'''
                        # 获取登录用户对应User对象
                        user = request.user
    
                        # 获取用户的默认收货地址
                        # try:
                        #     address = Address.objects.get(user=user, is_default=True) # models.Manager
                        # except Address.DoesNotExist:
                        #     # 不存在默认收货地址
                        #     address = None
                        address = Address.objects.get_default_address(user)
    
                        # 使用模板
                        return render(request, 'user_center_site.html', {'page':'address', 'address':address})
    
                    def post(self, request):
                        '''地址的添加'''
                        # 接收数据
                        receiver = request.POST.get('receiver')
                        addr = request.POST.get('addr')
                        zip_code = request.POST.get('zip_code')
                        phone = request.POST.get('phone')
    
                        # 校验数据
                        if not all([receiver, addr, phone]):
                            return render(request, 'user_center_site.html', {'errmsg':'数据不完整'})
    
                        # 校验手机号
                        if not re.match(r'^1[3|4|5|7|8][0-9]{9}$', phone):
                            return render(request, 'user_center_site.html', {'errmsg':'手机格式不正确'})
    
                        # 业务处理:地址添加
                        # 如果用户已存在默认收货地址,添加的地址不作为默认收货地址,否则作为默认收货地址
                        # 获取登录用户对应User对象
                        user = request.user
    
                        # try:
                        #     address = Address.objects.get(user=user, is_default=True)
                        # except Address.DoesNotExist:
                        #     # 不存在默认收货地址
                        #     address = None
    
                        address = Address.objects.get_default_address(user)
    
                        if address:
                            is_default = False
                        else:
                            is_default = True
    
                        # 添加地址
                        Address.objects.create(user=user,
                                               receiver=receiver,
                                               addr=addr,
                                               zip_code=zip_code,
                                               phone=phone,
                                               is_default=is_default)
    
                        # 返回应答,刷新地址页面
                        return redirect(reverse('user:address')) # get请求方式        
            ii. 因为get.post里都用到去models里查询默认数据,可以优化
                - 模型管理器类方法封装
                    每个models里都有models.Manager
                                
                1. class AddressManager(models.Manager):
                    '''地址模型管理器类'''
                    # 1.改变原有查询的结果集:all()
                    # 2.封装方法:用户操作模型类对应的数据表(增删改查)
                    def get_default_address(self, user):
                        '''获取用户默认收货地址'''
                        # self.model:获取self对象所在的模型类
                        try:
                            address = self.get(user=user, is_default=True)  # models.Manager
                        except self.model.DoesNotExist:
                            # 不存在默认收货地址
                            address = None
    
                        return address
                2. class Address(BaseModel):
                    '''地址模型类'''
                    ....
                    # 自定义一个模型管理器对象
                    objects = AddressManager()
                    ..
                3. views调用
                    address = Address.objects.get_default_address(user)
                    
        - 用户中心个人信息页历史浏览记录            
            - 在用户访问详情页面(SKU),需要添加历史浏览记录
            - 存在表中要经常增删改查不放方便,所以存redis
                - redis数据库->内存性的数据库
                - 表格设计
                    1. 所有用户历史记录用一条数据保存
                        hash
                        history:'user_id':'1,2,3'
                    2. 一个用户历史记录用一条数据保存
                        list
                        history_user_id:1,2,3
                        添加记录时,用户最新浏览的商品id从列表左侧插入
            - 实际使用
                1. StrictRedis
                  i.# Django的缓存配置
                        CACHES = {
                            "default": {
                                "BACKEND": "django_redis.cache.RedisCache",
                                "LOCATION": "redis://172.16.179.130:6379/9",
                                "OPTIONS": {
                                    "CLIENT_CLASS": "django_redis.client.DefaultClient",
                                }
                            }
                        }
                  ii.form redis import StricRedis
                    # 获取用户的历史浏览记录
                    # from redis import StrictRedis
                    # sr = StrictRedis(host='172.16.179.130', port='6379', db=9)
                      history_key = 'history_%d'%user.id
    
                    # 获取用户最新浏览的5个商品的id
                    sku_ids = con.lrange(history_key, 0, 4) # [2,3,1]
            
    ********     # 从数据库中查询用户浏览的商品的具体信息
                    # goods_li = GoodsSKU.objects.filter(id__in=sku_ids)
                    # 数据库查询时按遍历的方式,只要id in里面,则查询出来
                    #这样就违背了用户真实历史浏览记录了
                 i.    # goods_res = []
                    # for a_id in sku_ids:
                    #     for goods in goods_li:
                    #         if a_id == goods.id:
                    #             goods_res.append(goods)
    
                    # 遍历获取用户浏览的商品信息
                 ii.goods_li = []
                    for id in sku_ids:
                        goods = GoodsSKU.objects.get(id=id)
                        goods_li.append(goods)
                
                iii.user_info.html
                        使用{% empty %}标签  ,相当于elseif
                            {% for athlete in athlete_list %}
                                <p>{{ athlete.name }}</p>
                            {% empty %}
                                <p>There are no athletes. Only computer programmers.</p>
                            {% endfor %}
                                        
                2. from django_redis import get_redis_connection
                        con = get_redis_connection('default')    
                        其它同上    
                        
    5. fastdfs
        - 概念
            - 分布式文件系统,使用 FastDFS 很容易搭建一套高性能的
              文件服务器集群提供文件上传、下载等服务。 
            - 架构包括 Tracker server 和 Storage server。        
            - 客户端请求 Tracker server 进行文件上传、下载,通过
              Tracker server 调度最终由 Storage server 完成文件上传和下载。         
            - Tracker server 作用是负载均衡和调度
            - Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上
            - 优势:海量存储、存储容量扩展方便、文件内容重复          
                    
    6. 商品搜索引擎            
        - 搜索引擎
            1. 可以对表中的某些字段进行关键词分析,建立关键词对应的索引数据
                - 如 select * from xxx where name like '%草莓%' or desc like
                    '%草莓%'
                - 很
                  好吃
                  的
                  草莓:sku_id1 sku_id2 sku_id5
                  字典
            2. 全文检索框架
                可以帮助用户使用搜索引擎
                    用户->全文检索框架(haystack)->搜索引擎(whoosh)
        - 安装即使用
            - pip isntall django-haystack
              pip install whoosh
            - 在settings里注册haystack并配置
                - INSTALLED_APPS = (
                        'django.contrib.admin',
                        ...
                        'tinymce', # 富文本编辑器
                        'haystack', # 注册全文检索框架
                        'user', # 用户模块
                        'goods', # 商品模块
                        'cart', # 购物车模块
                        'order', # 订单模块
                    )
                - # 全文检索框架的配置
                        HAYSTACK_CONNECTIONS = {
                            'default': {
                                # 使用whoosh引擎
                                # 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
                                'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine',
                                # 索引文件路径
                                'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
                            }
                        }
    
                        # 当添加、修改、删除数据时,自动生成索引
                        HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
                - 索引文件的生成
                    - 在goods应用目录下新建一个search_indexes.py文件,在其中定义一个商品索引类
                        # 定义索引类
                        from haystack import indexes
                        # 导入你的模型类
                        from goods.models import GoodsSKU
    
                        # 指定对于某个类的某些数据建立索引
                        # 索引类名格式:模型类名+Index
                        class GoodsSKUIndex(indexes.SearchIndex, indexes.Indexable):
                            # 索引字段 use_template=True指定根据表中的哪些字段建立索引文件的说明放在一个文件中
                            text = indexes.CharField(document=True, use_template=True)
    
                            def get_model(self):
                                # 返回你的模型类
                                return GoodsSKU
    
                            # 建立索引的数据
                            def index_queryset(self, using=None):
                                return self.get_model().objects.all()    
                    - 在templates下面新建目录search/indexes/goods。
                        templates
                            search        固定
                                indexes      固定
                                    goods        模型类所在应用app
                                        goodssku_text.txt        模型类名小写_text.txt
                    - 在此目录下面新建一个文件goodssku_text.txt并编辑内容如下。
                        # 指定根据表中的哪些字段建立索引数据
                        {{ object.name }} # 根据商品的名称建立索引
                        {{ object.desc }} # 根据商品的简介建立索引
                        {{ object.goods.detail }} # 根据商品的详情建立索引,根据外键跨表查询
                    - 使用命令生成索引文件    
                        python manage.py rebuild_index        
                            - 会在whoosh_index文件夹里建立xx个商品索引(所有)
                - 全文检索的使用
                    - 配置url
                        url(r'^search', include('haystack.urls')), # 全文检索框架
                    - 表单搜索时设置表单内容如下
                        base.html    get和q固定
                            <div class="search_con fl">
                                <form method="get" action="/search">
                                    <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
                                    <input type="submit" class="input_btn fr" name="" value="搜索">
                                </form>
                            </div>
                    - 全文检索结果
                        搜索出结果后,haystack会把搜索出的结果传递给templates/search
                        目录下的search.html,传递的上下文包括:
                            query:搜索关键字
                            page:当前页的page对象 –>遍历page对象,获取到的是SearchResult类的实例对象,
                                    对象的属性object才是模型类的对象。
                            paginator:分页paginator对象
                        通过HAYSTACK_SEARCH_RESULTS_PER_PAGE 可以控制每页显示数量。
                    - search.html分页器的经典使用
                         <div class="pagenation">
                            {% if page.has_previous %}
                            <a href="/search?q={{ query }}&page={{ page.previous_page_number }}"><上一页</a>
                            {% endif %}
                            {% for pindex in paginator.page_range %}
                                {% if pindex == page.number %}
                                    <a href="/search?q={{ query }}&page={{ pindex }}" class="active">{{ pindex }}</a>
                                {% else %}
                                    <a href="/search?q={{ query }}&page={{ pindex }}">{{ pindex }}</a>
                                {% endif %}
                            {% endfor %}
                            {% if spage.has_next %}
                            <a href="/search?q={{ query }}&page={{ page.next_page_number }}">下一页></a>
                            {% endif %}
                            </div>
                        </div>
                
                    - 完成上面步奏之后,可以简单搜索,但是无法根据商品详情里的中文字段搜索
                        需要优化,商品搜索、改变分词方式
                        - 安装jieba分词模块
                            pip install jieba
                                str = '很不错的草莓'
                                res = jieba.cut(str,cut_all=True)
                                for val in res:
                                    print(val)
                        - 。。。。。
                        - settings里配置需要更改
                        - 最后重新创建索引数据
                            python manage.py rebuild_index
    
    7. 订单并发处理
        - 悲观锁
            - select * from xx where id=2 for update
            - 应用场景:
                try:
                    #select * from df_goods_sku where id=sku_id for update;
                    sku=GoodsSKU.object.select_for_update().get(id=sku_id)
                except:
                    transaction.savepoint_rollback(save_id)
                    return ...                                                
        - 乐观锁
            - 查询数据时不加锁,在更新时进行判断
            - 判断更新时的库存和之前查出的库存是否一致
            - 操作
                -
                for i in range(3):
                    #update df_goods_sku set tock=stock,sales=new_sales where id=
                        sku_id and stock=origin_stock
                      res=GoodsSKU.object.filter(id=sku_id,stock=stock).update(
                      stock=new_stock,sales=new_sales)
                      if res==0:  库存为0
                        if i==2:    #尝试第3次查询
                            transaction.savepoint_rollback(save_id)
                - mysql事务隔离性
                    事务隔离级别
                        - Read Uncommitted
                        - Read Committed(大多数数据库默认)
                        - Repeatable Read(mysql默认,产生幻读)
                        - serializable(可串行化,解决幻读问题,但容易引发竞争)
                - 重新配置mysql.conf为read committed
        总结:
            - 在冲突较少的时候使用乐观锁,因为省去了加锁、减锁的时间
            - 在冲突多的时候、乐观锁重复操作的代价比较大时使用悲观锁
            - 判断的时候,更新失败不一定是库存不足,需要再去尝试
    
    
                        
    SKU & SPU
        - SPU
            - Standard product unittest
            - 商品信息聚合的最小单位
            - 如iphone,
        - SKU
            - STOCK keeping unittest
            - 库存量进出计量单位
            - 如纺织品中一个SKU表示:
                规格、颜色、款式
            
    
    
    
    
    
    
    
    
    
    
    
        
        
        
        
    View Code

    单元测试

    单元测试
    
    pip install unittest
    assert xxx xxx
    false  true
    
    xitong
    jicheng 
    
    一些功能基础测试
    网络报文测试
    数据库测试
    
    def test_div(num1,num2):
        assert num1 int
        assert num1 int
        assert isinstance(num1,int)
        assert num2 != 0
        
        AssertionError
    
    assertEqual
    assertNotEqual
    assertIn
    assertNotIn
    
    assertTrue
    assertFalse
    assertNone
    assertNotNone
    
    class LoginTest(unittest.TestCase):
        
        def test_empty(self):
            client = app.test_client()
            rep = client.post('/login',data={})
            rep = rep.data
            rep = json.loads(rep)
            
            self.assertIn('code',resp)
            self.assertEqual(rep['code'],1)
            
    if __name__ == '__main__':
        unittest.main()
    
    python test.py
    
    class LoginTest(unittest.TestCase):
        
        def setUp(self):
            self.client = app.test_client()
            app.testing = True
        
        def test_empty_user(self):
        
        
        def tearDown(self):
            sss
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    View Code

    flask

    回顾:
        1.谈谈你对django和flask的认识。
        
        2.flask和django最大的不同点:request/session 
        
        3.flask知识点
            - 模板+静态文件,app= Flask(__name__,....)
            - 路由 
                @app.route('/index',methods=["GET"])
            - 请求 
                request.form 
                request.args
                request.method 
            - 响应 
                ""
                render
                redirect
            - session 
                session['xx'] = 123
                session.get('xx')
        4. 路飞总共有几个项目
            - 管理后台
            - 导师后台
            - 主站
            
        5. 路飞主站业务
            - 课程
                - 课程列表
                - 课程详细
                - 大纲、导师、推荐课程
                - 价格策略
                - 章节和课时
                - 常见问题
            - 深科技
                - 文章列表
                - 文章详细
                - 收藏
                - 评论
                - 点赞
            - 支付
                - 购物车(4- 结算中心(3- 立即支付(1)
                知识点:
                    - redis
                    - 支付宝
                    - 消息推送
                    - 构建数据结构
                    - 优惠券+贝里+支付宝
            - 个人中心
            - 课程中心
            
        6. 播放视频:CC视频
            - 加密
            - 非加密
    
    
    今日内容:
        1. 配置文件
        2. 路由系统 
        3. 视图
        4. 请求相关
        5. 响应 
        6. 模板渲染
        7. session 
        8. 闪现
        9. 中间件 
        10. 蓝图(blueprint)
        11. 特殊装饰器
        
    内容详细:
        知识点:
            - 给你一个路径 “settings.Foo”,可以找到类并获取去其中的大写的静态字段。
            
                settings.py
                    class Foo:
                        DEBUG = True
                        TEST = True
                    
                xx.py 
                    import importlib
    
                    path = "settings.Foo"
    
                    p,c = path.rsplit('.',maxsplit=1)
                    m = importlib.import_module(p)
                    cls = getattr(m,c)
    
                    # 如果找到这个类?
                    for key in dir(cls):
                        if key.isupper():
                            print(key,getattr(cls,key))
        1. 配置文件
            
            app.config.from_object("settings.DevelopmentConfig")
            
            
            class Config(object):
                DEBUG = False
                TESTING = False
                DATABASE_URI = 'sqlite://:memory:'
    
    
            class ProductionConfig(Config):
                DATABASE_URI = 'mysql://user@localhost/foo'
    
    
            class DevelopmentConfig(Config):
                DEBUG = True
    
    
            class TestingConfig(Config):
                TESTING = True
        2. 路由系统 
            - endpoint,反向生成URL,默认函数名
            - url_for('endpoint') / url_for("index",nid=777)
            - 动态路由:
                @app.route('/index/<int:nid>',methods=['GET','POST'])
                def index(nid):
                    print(nid)
                    return "Index"
            
        3. FBV
        
        
        4. 请求相关 
            # 请求相关信息
            # request.method
            # request.args
            # request.form
            # request.values
            # request.cookies
            # request.headers
            # request.path
            # request.full_path
            # request.script_root
            # request.url
            # request.base_url
            # request.url_root
            # request.host_url
            # request.host
            # request.files
            # obj = request.files['the_file_name']
            # obj.save('/var/www/uploads/' + secure_filename(f.filename))
            
        5. 响应:
                响应体:
                    return “asdf”
                    return jsonify({'k1':'v1'})
                    return render_template('xxx.html')
                    return redirect()
                
                定制响应头:   
                    obj = make_response("asdf")
                    obj.headers['xxxxxxx'] = '123'
                    obj.set_cookie('key', 'value')
                    return obj
            
            
        示例程序:学生管理
            
            版本一:
                @app.route('/index')
                def index():
                    if not session.get('user'):
                        return redirect(url_for('login'))
                    return render_template('index.html',stu_dic=STUDENT_DICT)
            版本二:
                import functools
                def auth(func):
                    @functools.wraps(func)
                    def inner(*args,**kwargs):
                        if not session.get('user'):
                            return redirect(url_for('login'))
                        ret = func(*args,**kwargs)
                        return ret
                    return inner
            
                @app.route('/index')
                @auth
                def index():
                    return render_template('index.html',stu_dic=STUDENT_DICT)
            
                应用场景:比较少的函数中需要额外添加功能。
                
            版本三:before_request
                @app.before_request
                def xxxxxx():
                    if request.path == '/login':
                        return None
    
                    if session.get('user'):
                        return None
    
                    return redirect('/login')
    
            
        6. 模板渲染 
            - 基本数据类型:可以执行python语法,如:dict.get()  list['xx']
            - 传入函数
                - django,自动执行
                - flask,不自动执行
            - 全局定义函数
                @app.template_global()
                def sb(a1, a2):
                    # {{sb(1,9)}}
                    return a1 + a2
    
                @app.template_filter()
                def db(a1, a2, a3):
                    # {{ 1|db(2,3) }}
                    return a1 + a2 + a3
            - 模板继承
                layout.html
                    <!DOCTYPE html>
                    <html lang="zh-CN">
                    <head>
                        <meta charset="UTF-8">
                        <title>Title</title>
                        <meta name="viewport" content="width=device-width, initial-scale=1">
                    </head>
                    <body>
                        <h1>模板</h1>
                        {% block content %}{% endblock %}
                    </body>
                    </html>
                
                tpl.html
                    {% extends "layout.html"%}
    
    
                    {% block content %}
                        {{users.0}}
                        
    
                    {% endblock %}    
            - include 
        
        
                {% include "form.html" %}
                
                
                form.html 
                    <form>
                        asdfasdf
                        asdfasdf
                        asdf
                        asdf
                    </form>
            - 宏
                {% macro ccccc(name, type='text', value='') %}
                    <h1>宏</h1>
                    <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
                    <input type="submit" value="提交">
                {% endmacro %}
    
                {{ ccccc('n1') }}
    
                {{ ccccc('n2') }}
                
            - 安全
                - 前端: {{u|safe}}
                - 前端: MarkUp("asdf")
            
        
        7. session 
            当请求刚到来:flask读取cookie中session对应的值:eyJrMiI6NDU2LCJ1c2VyIjoib2xkYm95,将该值解密并反序列化成字典,放入内存以便视图函数使用。
            视图函数:
                @app.route('/ses')
                def ses():
                    session['k1'] = 123
                    session['k2'] = 456
                    del session['k1']
    
                    return "Session"
    
                        
                            
                        session['xxx'] = 123
                        session['xxx']
                        
            当请求结束时,flask会读取内存中字典的值,进行序列化+加密,写入到用户cookie中。
        
        
        8. 闪现,在session中存储一个数据,读取时通过pop将数据移除。
            from flask import Flask,flash,get_flashed_messages
            @app.route('/page1')
            def page1():
    
                flash('临时数据存储','error')
                flash('sdfsdf234234','error')
                flash('adasdfasdf','info')
    
                return "Session"
    
            @app.route('/page2')
            def page2():
                print(get_flashed_messages(category_filter=['error']))
                return "Session"
            
        
        9. 中间件 
            - call方法什么时候出发?
                - 用户发起请求时,才执行。
            - 任务:在执行call方法之前,做一个操作,call方法执行之后做一个操作。
                class Middleware(object):
                    def __init__(self,old):
                        self.old = old
    
                    def __call__(self, *args, **kwargs):
                        ret = self.old(*args, **kwargs)
                        return ret
    
    
                if __name__ == '__main__':
                    app.wsgi_app = Middleware(app.wsgi_app)
                    app.run()
                
        
        10. 特殊装饰器 
        
            1. before_request
            
            2. after_request
            
                示例:
                    from flask import Flask
                    app = Flask(__name__)
    
    
                    @app.before_request
                    def x1():
                        print('before:x1')
                        return ''
    
                    @app.before_request
                    def xx1():
                        print('before:xx1')
    
    
                    @app.after_request
                    def x2(response):
                        print('after:x2')
                        return response
    
                    @app.after_request
                    def xx2(response):
                        print('after:xx2')
                        return response
    
    
    
                    @app.route('/index')
                    def index():
                        print('index')
                        return "Index"
    
    
                    @app.route('/order')
                    def order():
                        print('order')
                        return "order"
    
    
                    if __name__ == '__main__':
    
                        app.run()
            
            3. before_first_request
            
                from flask import Flask
                app = Flask(__name__)
    
                @app.before_first_request
                def x1():
                    print('123123')
    
    
                @app.route('/index')
                def index():
                    print('index')
                    return "Index"
    
    
                @app.route('/order')
                def order():
                    print('order')
                    return "order"
    
    
                if __name__ == '__main__':
    
                    app.run()
    
            
            4. template_global
            
            5. template_filter
            
            6. errorhandler
                @app.errorhandler(404)
                def not_found(arg):
                    print(arg)
                    return "没找到"
    
        
    总结:
        - 配置文件
        - 路由 
        - 视图:FBV
        - 请求 
        - 响应 
            obj = make_response("adfasdf")
            obj.headers['x'] = asdfasdf 
            return obj 
        - 模板 
        - session
        - flash 
        - 中间件
        - 特殊装饰器 
    
    
    1. 上下文
        - request在django和flask中不一样
          全局变量-->线程局部变量,使用起来就像线程的局部
            变量一样
        - 如用户A,B..同时访问/index,name=XX,在视图里
        request.form.get('name')是多少呢,
            由此才有上下文的概念将之隔开处理
                {
                    “线程A”:{
                        form:{'name':"zhangsan"}
                        args:
                    },
                    “线程A”:{
                            form:{'name':"lisi"}
                            args:
                        },                    
                }
            - 并发(处理多少个线程资源)
            
        - 请求上下文 
            - request/session(每个用户独有的session信息)
        - 应用上下文
            - current_app
                表示当前运行程序文件的程序实例
            - g
                处理请求时,用于临时存储的对象,每次
                请求都会重设这个变量
            
    2. 请求钩子
            - before_first_request
                在第一次请求处理之前先被执行
            - before request
                在每次请求之前都被执行
            - after_request
                在每次请求之后执行前提是视图无异常
            - teaddown_request
                在每次请求之后都被执行
    
    3. flask_script  类似django的manage.py起管理作用
        pipn install Flask-Script
        使用
            - 在xx.py 里需要
                from flask_script import Manager  #启动命令的管理类
                app = Flask(__name__)
                manager = Manager(app)    
                
                @app.route()"/index"
                def index():
                    return ccccc
                    
                if __name__=='__main__':
                    #通过管理对象来启动app
                    manager.run()
            - python xx.py runserver -h -p....
            - python xx.py shell
    4.sqlalchemy
        - 其它框架都能用的关系型数据库
        - pip install flask-sqlalchemy
    View Code

     flask-sqlacchemy

    4.sqlalchemy
        - 其它框架都能用的关系型数据库
        - pip install flask-sqlalchemy
        - 要连接mysql数据库,仍需要安装flask-mysqldb
            pip install flask-mysqldb    
        - 初始化配置
            from flask import Flask
            from flask_sqlalchemy import SQLAlchemy
    
            app = Flask(__name__)
            class Config(object):
                """配置参数"""
                # sqlalchemy的配置参数
                SQLALCHEMY_DATABASE_URI = "mysql://root:mysql@127.0.0.1:3306/db_python04"
                # 设置sqlalchemy自动更跟踪数据库
                SQLALCHEMY_TRACK_MODIFICATIONS = True
    
            app.config.from_object(Config)
            # 创建数据库sqlalchemy工具对象
            db = SQLAlchemy(app)
            。。。。
            
        - 创建数据库模型表models
            表名常见规范
                数据库缩写_表名  ihome--> ih_user
                class Role(db.Model):
                    """用户角色/身份表"""
                    __tablename__ = "tbl_roles"
    
                    id = db.Column(db.Integer, primary_key=True)
                    name = db.Column(db.String(32), unique=True)
                    users = db.relationship("User", backref="role")    
                class User(db.Model):
                    """用户表"""
                    __tablename__ = "tbl_users"  # 指明数据库的表名
    
                    id = db.Column(db.Integer, primary_key=True)  # 整型的主键,会默认设置为自增主键
                    name = db.Column(db.String(64), unique=True)
                    email = db.Column(db.String(128), unique=True)
                    password = db.Column(db.String(128))
                    role_id = db.Column(db.Integer, db.ForeignKey("tbl_roles.id"))        
            - 分析
                - db.Column在表中都是真实存在的数据,relationship非真
                - 有了外键,可以User.role_id.name正向查询,但不能表名小写_set反向查询
                - 有了relationship,可以直接Role.users.name,对象查询
                - 但是只有外键的话,如User.role_id仅仅是数字,若想为对象,需要加个
                    backref的方法,则User.role.name就可以直接查询了
                    
            -  # 清除数据库里的所有数据(第一次才用)
                db.drop_all()
    
                # 创建所有的表
                db.create_all()    
        - 保存(增加)数据
            - 增加单条数据
                  role1 = Role(name="admin")
                  # session记录对象任务
                  db.session.add(role1)
                  # 提交任务到数据库中
                  db.session.commit()
            
            - 增加多条数据        
                  us1 = User(name='wang', email='wang@163.com', password='123456', role_id=role1.id)
                  us2 = User(name='zhang', email='zhang@189.com', password='201512', role_id=role2.id)
                  us3 = User(name='chen', email='chen@126.com', password='987654', role_id=role2.id)
                  us4 = User(name='zhou', email='zhou@163.com', password='456789', role_id=role1.id)
                  
                  # 一次保存多条数据
                  db.session.add_all([us1, us2, us3, us4])
                  db.session.commit()
    
        - 查询数据
            - 查询多条、查询一条
                Role.query.all()
                Out[2]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>]
    
                In [3]: li = Role.query.all()
    
                In [4]: li
                Out[4]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>]
    
                In [5]: r = li[0]
    
                In [6]: type(r)
                Out[6]: db_demo.Role
    
                In [7]: r.name
                Out[7]: u'admin'
    
                In [8]: Role.query.first()
                Out[8]: <db_demo.Role at 0x10388d190>
    
                In [9]: r = Role.query.first()
    
                In [10]: r.name
                Out[10]: u'admin'
    
                #  根据主键id获取对象
                In [11]: r = Role.query.get(2)
    
                In [12]: r
                Out[12]: <db_demo.Role at 0x10388d310>
    
                In [13]: r.name
                Out[13]: u'stuff'
    
                In [14]:
    
    
                # 另一种查询方式
                In [15]: db.session.query(Role).all()
                Out[15]: [<db_demo.Role at 0x10388d190>, <db_demo.Role at 0x10388d310>]
    
                In [16]: db.session.query(Role).get(2)
                Out[16]: <db_demo.Role at 0x10388d310>
    
                In [17]: db.session.query(Role).first()
                Out[17]: <db_demo.Role at 0x10388d190>
    
                In [18]:
    
                In [18]: User.query.filter_by(name="wang")
                Out[18]: <flask_sqlalchemy.BaseQuery at 0x1038c90d0>
    
                In [19]: User.query.filter_by(name="wang").all()
                Out[19]: [<db_demo.User at 0x1038c87d0>]
    
                In [20]: User.query.filter_by(name="wang").first()
                Out[20]: <db_demo.User at 0x1038c87d0>
    
                In [21]: user = User.query.filter_by(name="wang").first()
    
                In [22]: user.name
                Out[22]: u'wang'
    
                In [23]: user.email
                Out[23]: u'wang@163.com'
    
                In [24]: User.query.filter_by(name="wang", role_id=1).first()
                Out[24]: <db_demo.User at 0x1038c87d0>
    
                In [25]: User.query.filter_by(name="wang", role_id=2).first()
    
                In [26]: user = User.query.filter_by(name="wang", role_id=2).first()
    
                In [27]: type(user)
                Out[27]: NoneType
    
                In [28]:
    
    
                In [28]: user = User.query.filter(User.name=="wang", User.role_id==1).first
                    ...: ()
    
                In [29]: user
                Out[29]: <db_demo.User at 0x1038c87d0>
    
                In [30]: user.name
                Out[30]: u'wang'
    
                In [31]: from sqlalchemy import or_
    
                In [32]: User.query.filter(or_(User.name=="wang", User.email.endswith("163.com")
                    ...: )).all()
                Out[32]: [<db_demo.User at 0x1038c87d0>, <db_demo.User at 0x1038ef310>]
    
                In [33]: li = User.query.filter(or_(User.name=="wang", User.email.endswith("163.
                    ...: com"))).all()
    
                In [34]: li[0].name
                Out[34]: u'wang'
    
                In [35]: li[1].name
                Out[35]: u'zhou'
    
                In [36]:
    
    
                # offset偏移  跳过几条
                In [36]: User.query.offset(2).all()
                Out[36]: [<db_demo.User at 0x1038c0950>, <db_demo.User at 0x1038ef310>]
    
                In [37]: li = User.query.offset(2).all()
    
                In [38]: li[0].name
                Out[38]: u'chen'
    
                In [39]: li[1].name
                Out[39]: u'zhou'
    
                In [40]:
    
    
                In [42]: li = User.query.offset(1).limit(2).all()
    
                In [43]: li
                Out[43]: [<db_demo.User at 0x1038fd990>, <db_demo.User at 0x1038c0950>]
    
                In [44]: li[0].name
                Out[44]: u'zhang'
    
                In [45]: li[1].name
                Out[45]: u'chen'
    
                In [46]:
    
                In [50]: User.query.order_by("-id").all()
                Out[50]:
                [<db_demo.User at 0x1038ef310>,
                 <db_demo.User at 0x1038c0950>,
                 <db_demo.User at 0x1038fd990>,
                 <db_demo.User at 0x1038c87d0>]
    
                In [51]:
    
                In [51]: li = User.query.order_by(User.id.desc()).all()
    
                In [52]: li
                Out[52]:
                [<db_demo.User at 0x1038ef310>,
                 <db_demo.User at 0x1038c0950>,
                 <db_demo.User at 0x1038fd990>,
                 <db_demo.User at 0x1038c87d0>]
    
                In [53]: li[0].name
                Out[53]: u'zhou'
    
                In [54]: li[3].name
                Out[54]: u'wang'
    
                In [55]:
    
    
                In [55]: from sqlalchemy import func
    
                In [56]: db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_i
                    ...: d)
                Out[56]: <flask_sqlalchemy.BaseQuery at 0x103a38050>
    
                In [57]: db.session.query(User.role_id, func.count(User.role_id)).group_by(User.role_i
                    ...: d).all()
                Out[57]: [(1L, 2L), (2L, 2L)]
    
                In [58]:
    
            - 跨表查询数据    
                - 关联查询
                - 定义显示信息
                    def __repr__(self):
                        return "User object: name=%s" % self.name
    
                In [61]: ro = Role.query.get(1)
    
                In [62]: type(ro)
                Out[62]: db_demo.Role
    
                In [63]: ro.users
                Out[63]: [<db_demo.User at 0x1038c87d0>, <db_demo.User at 0x1038ef310>]
    
                In [64]: ro.users[0].name
                Out[64]: u'wang'
    
                In [65]: ro.users[1].name
                Out[65]: u'zhou'
    
                In [66]:
    
    
    
                In [67]: user
                Out[67]: <db_demo.User at 0x1038c87d0>
    
                In [68]: user.role_id
                Out[68]: 1L
    
                In [69]: Role.query.get(user.role_id)
                Out[69]: <db_demo.Role at 0x10388d190>
    
                In [70]: user.role
                Out[70]: <db_demo.Role at 0x10388d190>
    
                In [71]: user.role.name
                Out[71]: u'admin'
    
                In [72]:
    
            - 数据的修改与删除
                # 更新
                In [14]: User.query.filter_by(name="zhou").update({"name": "python", "emai
                    ...: l": "python@itast.cn"})
                Out[14]: 1L
    
                In [15]: db.session.commit()
    
                In [16]:
                # 删除
                In [16]: user = User.query.get(3)
    
                In [17]: db.session.delete(user)
    
                In [18]: db.session.commit()
    
                In [19]:
                 
    5.flask migrate  数据库迁移
        - 安装
            pip install flask-migrate
    View Code

     add daily

        redis
        
    nosql
        - 不支持sql语法
        - 存储数据都是KV形式
        - Mongodb
        - Redis
        - Hbase hadoop
        - Cassandra hadoop
        
    关系型数据库
        mysql/oracle/sql server/关系型数据库
        通用的操作语言
        
    关系型比非关系数据库:
        - sql适用关系特别复杂的数据查询场景
        - sql对事务支持非常完善
        - 两者不断取长补短
    redis对比其他nosql产品:
        - 支持数据持久化
        - 支持多样数据结构,list,set,zset,hash等
        - 支持数据备份,即master-slave模式的数据备份
        - 所有操作都是原子性的,指多线程没有抢数据的过程
        
    redis 应用场景:
        用来做缓存(echcache/memcached),redis所有数据放在内存中
        社交应用
    
    redis-server redis
    redis-cli redis
    测试是否通信:
        ping  ------------> pang
    默认数据库16,通过0-15标示,select n 切换
    
    常用通用命令
        命令集  http://doc.redisfans.com
        - keys *
            keys a*   #查询以a开头的key
        - exists key1  #返回1,0
        - type key
        - del key1
        - expire key seconds #设置过期时间
        - ttl key #查看过期时间
    1. string    
        - 二进制,可以接受任何格式的数据,如JPEG或JSON对象描述信息
        - set name value
            - get name
        - mset key1 python key2 linux
            - get key1  ,get key2
            - mget key1 key2
        - append a1 haha  追加字符串
            - get a1    #a1+'haha'
        - setex key seconds value
    2. hash
        - 用于存储对象,对象结果是属性、值
        - 值的类型为string
        - hset user name itheima
        - hmset key field1 value1 field2 value2
        - hkeys key
        - hget key field
        - hmget key field1 field2
        - hvals key  #获取所有的属性
        - del key1 #删除整个hash键值,
        - hdel key field1 field2 #删除field1 field2的属性
    3. list 
        - 列表中元素类型为string
        - lpush key value1 value2
        - lrange key 0 2  #start stop 返回列表里指定范围的元素
            - lrange key 0 -1 #查询整列元素
        - rpush key value1 value2
        - linsert key before/after b 3
            - b 现有元素
            - 3 插入元素
        - lset key index value  #设置指定元素的值
        - lrem key count value
            - count > 0 从左向右移除
            - count < 0 从右向左移除
            - count = 0 移除所有
    4. set 
        - 元素为string类型
        - 无序集合
        - sadd key zhangsan lisi wangwu 
        - smembers key
        - srem key wangwu 
    
    5. zset
        - 有序集合
        - 元素唯一性、不重复
        - 每个元素都关联一个double类型的score权重,通常
        从小到大排序
        - zadd key score1 member1 score2 member2
        - zrange key start stop
        - zrangebyscore key min max
        - zscore key member
        - zrem key member1 member2
        - zremrangebyscore key min max 
    
    python 操作 redis
    
    pip install redis 
    from redis import StrictRedis
    
    redis 存储session
    而session默认存储在django-session表里
    pip install django-redis-sessions==0.5.6
    
    open django工程,改setting配置redis
    
    SESSION_ENGINE = 'redis_sessions.session'
    SESSION_REDIS_HOST = 'localhost'
    SESSION_REDIS_PORT = 6379
    SESSION_REDIS_DB = 2
    SESSION_REDIS_PASSWORD = ''
    SESSION_REDIS_PREFIX = 'session'
    
    
    通过redis-cli客户端查看
    最后在Base64在线解码
        
    主从配置实现读写分离
    一个master可以拥有多个slave,一个slave可以有多个slave
        - 实现读写分离
        - 备份主服务、防止主服务挂掉后数据丢失
    bind 192.168.26.128
    slaveof 192.168.26.128 6379
    port 6378
        
        
    redis集群
    集群:一群通过网络连接的计算机,共同对外提交服务,想一个独立的服务器
    主服务、从服务
    集群:
        - 软件层面
            - 只有一台电脑,在这一台电脑上启动了多个redis服务。
        - 硬件层面
            - 存在多台实体的电脑,每台电脑上都启动了一个redis或者多个redis服务。
        
    集群和python交互:
        pip install redis-by-cluster
        from rediscluster import *
        if __name == 'main':
            try:
                startup_nodes = [
                {'host':'192..','port':'700'},...]
                src = StricRedisCluster(startup_nodes=startup_nodes,
                decode_response = True)
                result = src.set('name','di')
                print(result)
                name = src.geg('name')
                print(name)
            except exception as e:
                print(e)
        
        
    ---------------mongodb-----------
    
    not only sql
    有点:
    - 易扩展
    - 大数据量、高性能
    - 灵活的数据模型    
    缺点:
        - 占据的内存比之mysql要多
        
    mongodb
    mongo
    show databases
    use douban
    db #查看当前数据路
    db.dropDatabase()
    
    不手动创建集合(类似mysql的表)
    db.createCollection(name,options)
    db.createCollection('sub',{capped:true,size:10})
    show collections
    db.xxx.drop()
    
    Object ID
    String
    Boolean
    Integer   false true ---类似json里的小写false
    Double
    Arrays
    Object
    Null
    Timestamp
    Date
    
    
    -------------------------flask 单元测试------------
    单元测试
        - 程序员自测
        - 一般用于测试一些实现某功能的代码
    集成测试
    系统测试
    
    def num_div(num1num2):
        #断言为真、则成功,为假,则失败抛出异常/终止程序执行
        #assert num1 int
        assert isinstance(num1,int)
        assert isinstance(num2,int)
        assert num2 != 0
    
        print(num1/num2)
        
    if __name__ == '__main__'
        num_div('a','b')  #AssertionError
            
    assertEqual
    assertNotEqual
    assertTrue
    assertFalse
    assertIsNone
    assertIsNotNone
    
    必须以test_开头
    
    classs LoginTest(unittest.TestCase):
        def test_empty_user_name_password(self):
            client = app.test_client()
            ret = client.post('/login',data={})
            resp = ret.data
            resp = json.loads(resp)
            
            self.assertIn('code',resp)
            self.assertEqual(resp['code'],1)
    
    if __name__ == '__main__':
        unittest.main()
        
    cmd
    python test.python
    
    
    
    class LoginTest(unittest.TestCase):
        def setup(self):
            #相当于 __init__
            self.client = app.test_client()
            #开启测试模式,获取错误信息
          1    app.config['TESTING'] = True 
          2 app.testing = True
        
        def test_xxxx(self):
            .....
     
    
     
    简单单元测试
    网络接口测试(视图)
    数据库测试
    import unittest
    from author_book import Author,db,app
    
    class DatabaseTest(unittest.TestCase):
        
        def setUp(self):
            app.testing = True
            #构建测试数据库
            app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:mysql@127.0.0.1:3306/flask_test"
            db.create_all()
        
        def test_add_user(self):
            
            author = Author(name="zhang",email='xxx'..)
            db.session.add(author)
            db.session.commit()
            
            result_author =  Author.query.filter_by(name="zhang").first()
            self.assertNotNone(result_author)
        
        def tearDown(self):
            db.session.remove()
            db.drop_all()
            
            
    ------------部署-------------
    
    django  uwsgi nginx
    
    
    用户
    Nginx   负载均衡  提供静态文件
    业务服务器 flask + Gunicorn        
    mysql/redis
            
            
    pip install gunicorn
    gunicorn -w 4 -b 127.0.0.1:5000 --access-logfile ./logs/og main:app
     
    if self.server_version_info < (5,7,20):
        cursor.execute(”SELECT @@tx_isolation")
    else:
        cursor.execute("SELECT @@transaction_isolation")
            
            
    --------------- 测试-----------------
    
    jekins
        - Jenkins是一个功能强大的应用程序,允许持续集成和持续交付项目
    jmeter
        - 软件做压力测试
    postman
        - 
    
    
            创建天天生鲜 B2C 大型网站
    
    1. 电商概念
    B2B    Alibaba
    C2C    瓜子二手车、淘宝、易趣
    B2C    唯品会
    C2B    尚品宅配
    O2O    美团
    F2C    戴尔
    
    2. 开发流程
    产品原型的设计 --- 产品经理-----axure
    
    非常关键:
        - 架构设计
        - 数据库设计
    
    3. 数据库分析
    mysql
        - 
    redis 
        - 若用户多,session服务器     
        - 对于经常访问的如首页,则用缓存服务器
    xxx    
        -异步任务处理celery (注册页面发邮件之类的)
    
    分布式文件存储系统fastdfs(不用django默认的media上传文件方式)
        - 
    
    
    4. 数据库设计:
    
    a.用户模块、商品模块
    用户表
        - ID
        - 用户名
        - 密码
        - 邮箱
        - 激活标识
        - 权限标识
    地址表(一个用户可能有多个地址)
        - ID
        - 收件人
        - 地址
        - 邮编
        - 联系方式
        - 是否默认
        - 用户ID
    商品SKU表
        - ID
        - 名称
        - 简介
        - 价格
        - 单位
        - 库存
        - 销量
        - 详情
        - *图片(就放一张,以空间换取时间)
        - 状态
        - 种类ID
        - sup ID
    商品SPU表
        - ID
        - 名称
        - 详情
    商品种类表
        - ID
        - 种类名称
        - logo
        - 图片
    商品图片表
        - ID
        - 图片
        - sku ID
    首页轮播商品表
        - ID
        - sku 
        - 图片
        - index
    首页促销表
        - ID
        - 图片
        - 活动url
        - index
    首页分类商品展示表
        - ID
        - sku ID
        - 种类ID
        - 展示标识
        - index
        
    b. 购物车模块  redis实现
        - redis保存用户历史浏览记录
    c. 订单模块
    
    订单信息表
        - 订单ID
        - 地址ID
        - 用户ID
        - 支付方式
        - *总金额
        - *总数目  
        - 运费
        - 支付状态
        - 创建时间
    订单商品表
        - ID
        - sku ID
        - 商品数量
        - 商品价格
    
    健表时须知:
        - 此时用的是Ubanto的mysql数据库,需要
            - grant all on test2.* to 'root'@'1.2.3.4' identified
    by 'root'
            - flush privileges    
            - migrate
        - choices
        - 富文本编辑器
            - tinymce
            - pip install django-tinymce==2.6.0
        - LANGUAGE_CODE = 'zh-hans'
        - TIME_ZONE = 'Asia/Shanghai'
        - url(r'^',include('goods.urls',namespace='goods'))
        - vervose_name
        - 项目框架搭建
            - 四个app
            - user
            - goods
            - cart
            - order
        - 使用Abstractuser时,settings里需要
        AUTH_USER_MODEL = 'user.User'
        
        
        
    class BaseModel(models.Model):
        '''模型类抽象基类’''
        create_time = models.DatetimeField(auto_now_add=True,verbose_name='创建时间')
        cupdate_time = models.DatetimeField(auto_now=True,verbose_name='更新时间')
        is_delete = models.BooleanField(default=False,verbose_name='删除标记')
        
        class Meta:
            #说明是一个抽象类模型
            abstract = True
    
    
    开始设计前后端
    1. 如何设计四个app
    2. register.html
        - 动态导入静态文件,{% load staticfiles %}
            link ....  href="{% static 'css/reset.css' %}"
        - 前端post一个勾选按钮(阅读同意),后端收到是
            if allow !== 'on':
                pass
        -  几乎每一个URL都有namespace,注册成功后跳转首页用
            反向解析 return redirect(reverse('goods:index'))
                - goods 是app域名的别名
                - index 是goods里面的别名
        - 在mysql里输入 select * from df_user G
            信息会竖排显示
        - 数据完整性校验
            if not all([username,password,email]):
                pass
        - 在表里发现 is_active 已经为1激活了,但是我们不想注册即激活,
            需要,在创建用户的时候加入如下:
            user=User.objects.create_user(username,email,password)
            user.is_active = 0
            user.save()
        - 在注册之前先进性校验,register_handle
            判断用户名是否重复,
            try:
            #get有的话只返回一个,没有的话会包异常
                user=User.objects.get(username=username)
            except User.DoesNotExist:
                user = None
            if user:
                return ...
        - 类视图的使用
            - 原理关键在dispatch,getattr
            - from django.view.generic import View
             class RegisterView(View):
                def get(self,request):
                    pass
                def post(self,request):
                    pass
            url(r'^register$', RegisterView.as_view(), name='register'), # 注册
        - 发送激活邮件,包含激活连接
            - 激活连接中需包含用户身份信息,并加密
            - pip install itsdangerous
            from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
            from itsdangerous import SignatureExpired
            
             # 加密用户的身份信息,生成激活token
                serializer = Serializer(settings.SECRET_KEY, 3600)
                info = {'confirm':user.id}
                token = serializer.dumps(info) # bytes
                token = token.decode()
            #发邮件
                1. django本身有秘钥、
                    subject
                    message
                    sender
                    receiver
                    html_message
                    dend_mail(subject,message,sender,receiver,html_message
                    =html_message)  #发送html格式的
            - django网站 -(阻塞执行)-->SMTP服务器 -->目的邮箱
                2.celery使用
                    任务发出者 -- 任务队列(broker)-- 任务处理者(worker)        
                            发出任务             监听任务队列
                    pip install celery
                    任务队列是一种跨线程、跨机器工作的一种机制.
                    celery通过消息进行通信,通常使用一个叫Broker(中间人)来协client(任务的发出者)
                    和worker(任务的处理者). clients发出消息到队列中,broker将队列中的信息派发给
                    worker来处理用于处理些IO操作耗时的事务,如上传下载文件、发邮件
                    
                    from celery import Celery
                    # 创建一个Celery类的实例对象
                    app = Celery('celery_tasks.tasks', broker='redis://172.16.179.130:6379/8')
                    # 定义任务函数
                    @app.task
                i.    def send_register_active_email(to_email, username, token):
                        '''发送激活邮件'''
                        # 组织邮件信息
                        subject = '天天生鲜欢迎信息'
                        message = ''
                        sender = settings.EMAIL_FROM
                        receiver = [to_email]
                        html_message = '<h1>%s, 欢迎您成为天天生鲜注册会员</h1>请点击下面链接激活您的账户<br/><a href="http://127.0.0.1:8000/user/active/%s">http://127.0.0.1:8000/user/active/%s</a>' % (username, token, token)
    
                        send_mail(subject, message, sender, receiver, html_message=html_message)
                        time.sleep(5)
                    
                ii.    # 发邮件
                    send_register_active_email.delay(email, username, token)
                    
                iii.# Ubuntu虚拟机启动worker
                    1.只是启动celery里的worker进程,配置信息需要与django里的task.py文件
                    一样,否则django里的变动(time.sleep),Ubuntu不会执行,以当前为准
                    2.vi celery_tasks/tasks.py
                        django环境的初始化
                        # 在任务处理者一端加这几句
                        # import os
                        # import django
                        # os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings")
                        # django.setup()
                    3.启动
                        celery -A celery_tasks.tasks worker -l info
        -  激活成功后返回登陆页面
            class ActiveView(View):
                '''用户激活'''
                def get(self, request, token):
                    '''进行用户激活'''
                    # 进行解密,获取要激活的用户信息
                    serializer = Serializer(settings.SECRET_KEY, 3600)
                    try:
                        info = serializer.loads(token)
                        # 获取待激活用户的id
                        user_id = info['confirm']
    
                        # 根据id获取用户信息
                        user = User.objects.get(id=user_id)
                        user.is_active = 1
                        user.save()
    
                        # 跳转到登录页面
                        return redirect(reverse('user:login'))
                    except SignatureExpired as e:
                        # 激活链接已过期
                        return HttpResponse('激活链接已过期')
    
    3. login
        - 因为用户多,不能经常调用数据库,使用redis存储session
            https://django-redis-chs.readthedocs.io/zh_CN/latest/
            - pip install django-redis 
            - django缓存配置
                - 指定ip的redis数据库
            - 配置session存储
            - redis-cli -h 192.169.12.1
        - 是否记住用户名    
        i.     class LoginView(View):
            '''登录'''
            def get(self, request):
                '''显示登录页面'''
                # 判断是否记住了用户名
                if 'username' in request.COOKIES:
                    username = request.COOKIES.get('username')
                    checked = 'checked'
                else:
                    username = ''
                    checked = ''
    
                # 使用模板
                return render(request, 'login.html', {'username':username, 'checked':checked})
    
        ii.    def post(self, request):
                '''登录校验'''
                # 接收数据
                username = request.POST.get('username')
                password = request.POST.get('pwd')
                # 校验数据
                if not all([username, password]):
                    return render(request, 'login.html', {'errmsg':'数据不完整'})
                # 业务处理:登录校验
                user = authenticate(username=username, password=password)
                if user is not None:
                    # 用户名密码正确
                    if user.is_active:
                        # 用户已激活
                        # 记录用户的登录状态
                        login(request, user)
                        # 跳转到首页
                        response = redirect(reverse('goods:index')) # HttpResponseRedirect
                        # 判断是否需要记住用户名
                        remember = request.POST.get('remember')
                        if remember == 'on':
                            # 记住用户名
                            response.set_cookie('username', username, max_age=7*24*3600)
                        else:
                            response.delete_cookie('username')
                        # 返回response
                        return response
                    else:
                        # 用户未激活
                        return render(request, 'login.html', {'errmsg':'账户未激活'})
                else:
                    # 用户名或密码错误
                    return render(request, 'login.html', {'errmsg':'用户名或密码错误'})
        
        iii.<input type="text" name="username" class="name_input" value="{{ username }}" placeholder="请输入用户名">    
            <input type="checkbox" name="remember" {{ checked }}>
    4. 用户中心
        - base模板的设计,分base.html  base_no_cart.html,非常重要
            {# 首页 注册 登录 #}
            <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
            <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
            {% load staticfiles %}
            <head>
                <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
                {# 网页标题内容块 #}
                <title>{% block title %}{% endblock title %}</title>
                <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
                <link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
                {# 网页顶部引入文件块 #}
                {% block topfiles %}{% endblock topfiles %}
            </head>
            。。。。。。
    
        - 一个用户中心页面可以点三个页面  (用户/订单/收货地址)
            <li><a href="{% url 'user:user' %}" {% if page == 'user' %}class="active"{% endif %}>· 个人信息</a></li>
        - 登录装饰器(如用户界面需要登录)
            i.    login_required   ?next=xxxx
                    from django.contrib.auth.decorators import login_required
                settings
                    # 配置登录url地址
                    LOGIN_URL='/user/login' # /accounts/login
                login.html
                    <div class="form_input">
                    {# 不设置表单action时,提交表单时,会向浏览器地址栏中的地址提交数据 #}
                    .....
                url
                    url(r'^$', login_required(UserInfoView.as_view()), name='user'), # 用户中心-信息页
                    url(r'^order$', login_required(UserOrderView.as_view()), name='order'), # 用户中心-订单页
                    url(r'^address$', login_required(AddressView.as_view()), name='address'), # 用户中心-地址页
                登录视图里的logic处理
                    if user.is_active:
                        # 用户已激活
                        # 记录用户的登录状态
                        login(request, user)
                        # 获取登录后所要跳转到的地址
                        # 默认跳转到首页
                        next_url = request.GET.get('next', reverse('goods:index'))
                        # 跳转到next_url
                        response = redirect(next_url) # HttpResponseRedirect
    
                        # 判断是否需要记住用户名
                        remember = request.POST.get('remember')
                        if remember == 'on':
                            # 记住用户名
                            response.set_cookie('username', username, max_age=7*24*3600)
                        else:
                            response.delete_cookie('username')
                        # 返回response
                        return response
            ii. login_required    
                    - 一些经常用的python_package放在utils文件夹里,如mixin.py
                            from django.contrib.auth.decorators import login_required
                            class LoginRequiredMixin(object):
                                @classmethod
                                def as_view(cls, **initkwargs):
                                    # 调用父类的as_view
                                    view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
                                    return login_required(view)
                    - 同时类视图调用
                        from utils.mixin import LoginRequiredMixin
                        
                        class UserInfoView(LoginRequiredMixin, View):pass
                        class UserOrderView(LoginRequiredMixin, View):pass
    
                settings(同上)        
                url     (不需要做处理了)
                        url(r'^$', UserInfoView.as_view(), name='user'), # 用户中心-信息页
                        url(r'^order$', UserOrderView.as_view(), name='order'), # 用户中心-订单页
                        url(r'^address$', AddressView.as_view(), name='address'), # 用户中心-地址页
                登录视图里的logic处理(同上)    
        
        - 用户登录欢迎信息(head)
            - 考点  
              # Django会给request对象添加一个属性request.user
              # 如果用户未登录->user是AnonymousUser类的一个实例对象
              # 如果用户登录->user是User类的一个实例对象
              # request.user.is_authenticated()
            - base.html
              {% if user.is_authenticated %}
                <div class="login_btn fl">
                    欢迎您:<em>{{ user.username }}</em>
                    <span>|</span>
                    <a href="{% url 'user:logout' %}">退出</a>
                </div>
                {% else %}
                <div class="login_btn fl">
                    <a href="{% url 'user:login' %}">登录</a>
                    <span>|</span>
                    <a href="{% url 'user:register' %}">注册</a>
                </div>
                {% endif %}
        - logout
             url
                url(r'^logout$', LogoutView.as_view(), name='logout'), # 注销登录
            views
                from django.contrib.auth import authenticate, login, logout
                
                class LogoutView(View):
                '''退出登录'''
                    def get(self, request):
                        '''退出登录'''
                        # 清除用户的session信息
                        logout(request)
    
                        # 跳转到首页
                        return redirect(reverse('goods:index'))    
                    
        - 用户中心地址页(默认地址和新添地址的设计)
            i.  post 新上传地址数据,从数据库里查找是否有默认地址
                get 在页面上显示是否有默认地址
                class AddressView(LoginRequiredMixin, View):
                    '''用户中心-地址页'''
                    def get(self, request):
                        '''显示'''
                        # 获取登录用户对应User对象
                        user = request.user
    
                        # 获取用户的默认收货地址
                        # try:
                        #     address = Address.objects.get(user=user, is_default=True) # models.Manager
                        # except Address.DoesNotExist:
                        #     # 不存在默认收货地址
                        #     address = None
                        address = Address.objects.get_default_address(user)
    
                        # 使用模板
                        return render(request, 'user_center_site.html', {'page':'address', 'address':address})
    
                    def post(self, request):
                        '''地址的添加'''
                        # 接收数据
                        receiver = request.POST.get('receiver')
                        addr = request.POST.get('addr')
                        zip_code = request.POST.get('zip_code')
                        phone = request.POST.get('phone')
    
                        # 校验数据
                        if not all([receiver, addr, phone]):
                            return render(request, 'user_center_site.html', {'errmsg':'数据不完整'})
    
                        # 校验手机号
                        if not re.match(r'^1[3|4|5|7|8][0-9]{9}$', phone):
                            return render(request, 'user_center_site.html', {'errmsg':'手机格式不正确'})
    
                        # 业务处理:地址添加
                        # 如果用户已存在默认收货地址,添加的地址不作为默认收货地址,否则作为默认收货地址
                        # 获取登录用户对应User对象
                        user = request.user
    
                        # try:
                        #     address = Address.objects.get(user=user, is_default=True)
                        # except Address.DoesNotExist:
                        #     # 不存在默认收货地址
                        #     address = None
    
                        address = Address.objects.get_default_address(user)
    
                        if address:
                            is_default = False
                        else:
                            is_default = True
    
                        # 添加地址
                        Address.objects.create(user=user,
                                               receiver=receiver,
                                               addr=addr,
                                               zip_code=zip_code,
                                               phone=phone,
                                               is_default=is_default)
    
                        # 返回应答,刷新地址页面
                        return redirect(reverse('user:address')) # get请求方式        
            ii. 因为get.post里都用到去models里查询默认数据,可以优化
                - 模型管理器类方法封装
                    每个models里都有models.Manager
                                
                1. class AddressManager(models.Manager):
                    '''地址模型管理器类'''
                    # 1.改变原有查询的结果集:all()
                    # 2.封装方法:用户操作模型类对应的数据表(增删改查)
                    def get_default_address(self, user):
                        '''获取用户默认收货地址'''
                        # self.model:获取self对象所在的模型类
                        try:
                            address = self.get(user=user, is_default=True)  # models.Manager
                        except self.model.DoesNotExist:
                            # 不存在默认收货地址
                            address = None
    
                        return address
                2. class Address(BaseModel):
                    '''地址模型类'''
                    ....
                    # 自定义一个模型管理器对象
                    objects = AddressManager()
                    ..
                3. views调用
                    address = Address.objects.get_default_address(user)
                    
        - 用户中心个人信息页历史浏览记录            
            - 在用户访问详情页面(SKU),需要添加历史浏览记录
            - 存在表中要经常增删改查不放方便,所以存redis
                - redis数据库->内存性的数据库
                - 表格设计
                    1. 所有用户历史记录用一条数据保存
                        hash
                        history:'user_id':'1,2,3'
                    2. 一个用户历史记录用一条数据保存
                        list
                        history_user_id:1,2,3
                        添加记录时,用户最新浏览的商品id从列表左侧插入
            - 实际使用
                1. StrictRedis
                  i.# Django的缓存配置
                        CACHES = {
                            "default": {
                                "BACKEND": "django_redis.cache.RedisCache",
                                "LOCATION": "redis://172.16.179.130:6379/9",
                                "OPTIONS": {
                                    "CLIENT_CLASS": "django_redis.client.DefaultClient",
                                }
                            }
                        }
                  ii.form redis import StricRedis
                    # 获取用户的历史浏览记录
                    # from redis import StrictRedis
                    # sr = StrictRedis(host='172.16.179.130', port='6379', db=9)
                      history_key = 'history_%d'%user.id
    
                    # 获取用户最新浏览的5个商品的id
                    sku_ids = con.lrange(history_key, 0, 4) # [2,3,1]
            
    ********     # 从数据库中查询用户浏览的商品的具体信息
                    # goods_li = GoodsSKU.objects.filter(id__in=sku_ids)
                    # 数据库查询时按遍历的方式,只要id in里面,则查询出来
                    #这样就违背了用户真实历史浏览记录了
                 i.    # goods_res = []
                    # for a_id in sku_ids:
                    #     for goods in goods_li:
                    #         if a_id == goods.id:
                    #             goods_res.append(goods)
    
                    # 遍历获取用户浏览的商品信息
                 ii.goods_li = []
                    for id in sku_ids:
                        goods = GoodsSKU.objects.get(id=id)
                        goods_li.append(goods)
                
                iii.user_info.html
                        使用{% empty %}标签  ,相当于elseif
                            {% for athlete in athlete_list %}
                                <p>{{ athlete.name }}</p>
                            {% empty %}
                                <p>There are no athletes. Only computer programmers.</p>
                            {% endfor %}
                                        
                2. from django_redis import get_redis_connection
                        con = get_redis_connection('default')    
                        其它同上    
                        
    5. fastdfs
        - 概念
            - 分布式文件系统,使用 FastDFS 很容易搭建一套高性能的
              文件服务器集群提供文件上传、下载等服务。 
            - 架构包括 Tracker server 和 Storage server。        
            - 客户端请求 Tracker server 进行文件上传、下载,通过
              Tracker server 调度最终由 Storage server 完成文件上传和下载。         
            - Tracker server 作用是负载均衡和调度
            - Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上
            - 优势:海量存储、存储容量扩展方便、文件内容重复,结合nginx提高网站提供图片效率         
        - 项目上传图片和使用图片的过程
            (通过admin页面上传图片)浏览器 --请求上传文件----django服务器(修改django默认的上传行为)
            --->Fastdfs文件存储服务器
            随后返回文件/group/文件id--->在django上保存对应的image表
            
            用户请求页面时,显示页面,<img src='127.2.2.2:80'>,浏览器请求nginx获取图片
            返回图片内容到浏览器上
    ****- 修改django默认文件上传方式,
            - python.usyiyi.cn    
            - 自定义文件存储类,使得django存储在fastdfs存储器上
                class FDFSStorage(Storage):
                    '''fast dfs文件存储类'''
                    def __init__(self, client_conf=None, base_url=None):
                        '''初始化'''
                        if client_conf is None:
                            client_conf = settings.FDFS_CLIENT_CONF
                        self.client_conf = client_conf
    
                        if base_url is None:
                            base_url = settings.FDFS_URL
                        self.base_url = base_url
    
                    def _open(self, name, mode='rb'):
                        '''打开文件时使用'''
                        pass
    
                    def _save(self, name, content):
                        '''保存文件时使用'''
                        # name:你选择上传文件的名字
                        # content:包含你上传文件内容的File对象
    
                        # 创建一个Fdfs_client对象
                        client = Fdfs_client(self.client_conf)
    
                        # 上传文件到fast dfs系统中
                        res = client.upload_by_buffer(content.read())
    
                        # dict
                        # {
                        #     'Group name': group_name,
                        #     'Remote file_id': remote_file_id,
                        #     'Status': 'Upload successed.',
                        #     'Local file name': '',
                        #     'Uploaded size': upload_size,
                        #     'Storage IP': storage_ip
                        # }
                        if res.get('Status') != 'Upload successed.':
                            # 上传失败
                            raise Exception('上传文件到fast dfs失败')
    
                        # 获取返回的文件ID
                        filename = res.get('Remote file_id')
    
                        return filename
    
                    def exists(self, name):
                        '''Django判断文件名是否可用'''
                        return False
    
                    def url(self, name):
                        '''返回访问文件的url路径'''
                        return self.base_url+name
    
    
            
            
            
            
    6. 商品搜索引擎            
        - 搜索引擎
            1. 可以对表中的某些字段进行关键词分析,建立关键词对应的索引数据
                - 如 select * from xxx where name like '%草莓%' or desc like
                    '%草莓%'
                - 很
                  好吃
                  的
                  草莓:sku_id1 sku_id2 sku_id5
                  字典
            2. 全文检索框架
                可以帮助用户使用搜索引擎
                    用户->全文检索框架(haystack)->搜索引擎(whoosh)
        - 安装即使用
            - pip isntall django-haystack
              pip install whoosh
            - 在settings里注册haystack并配置
                - INSTALLED_APPS = (
                        'django.contrib.admin',
                        ...
                        'tinymce', # 富文本编辑器
                        'haystack', # 注册全文检索框架
                        'user', # 用户模块
                        'goods', # 商品模块
                        'cart', # 购物车模块
                        'order', # 订单模块
                    )
                - # 全文检索框架的配置
                        HAYSTACK_CONNECTIONS = {
                            'default': {
                                # 使用whoosh引擎
                                # 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
                                'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine',
                                # 索引文件路径
                                'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
                            }
                        }
    
                        # 当添加、修改、删除数据时,自动生成索引
                        HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
                - 索引文件的生成
                    - 在goods应用目录下新建一个search_indexes.py文件,在其中定义一个商品索引类
                        # 定义索引类
                        from haystack import indexes
                        # 导入你的模型类
                        from goods.models import GoodsSKU
    
                        # 指定对于某个类的某些数据建立索引
                        # 索引类名格式:模型类名+Index
                        class GoodsSKUIndex(indexes.SearchIndex, indexes.Indexable):
                            # 索引字段 use_template=True指定根据表中的哪些字段建立索引文件的说明放在一个文件中
                            text = indexes.CharField(document=True, use_template=True)
    
                            def get_model(self):
                                # 返回你的模型类
                                return GoodsSKU
    
                            # 建立索引的数据
                            def index_queryset(self, using=None):
                                return self.get_model().objects.all()    
                    - 在templates下面新建目录search/indexes/goods。
                        templates
                            search        固定
                                indexes      固定
                                    goods        模型类所在应用app
                                        goodssku_text.txt        模型类名小写_text.txt
                    - 在此目录下面新建一个文件goodssku_text.txt并编辑内容如下。
                        # 指定根据表中的哪些字段建立索引数据
                        {{ object.name }} # 根据商品的名称建立索引
                        {{ object.desc }} # 根据商品的简介建立索引
                        {{ object.goods.detail }} # 根据商品的详情建立索引,根据外键跨表查询
                    - 使用命令生成索引文件    
                        python manage.py rebuild_index        
                            - 会在whoosh_index文件夹里建立xx个商品索引(所有)
                - 全文检索的使用
                    - 配置url
                        url(r'^search', include('haystack.urls')), # 全文检索框架
                    - 表单搜索时设置表单内容如下
                        base.html    get和q固定
                            <div class="search_con fl">
                                <form method="get" action="/search">
                                    <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
                                    <input type="submit" class="input_btn fr" name="" value="搜索">
                                </form>
                            </div>
                    - 全文检索结果
                        搜索出结果后,haystack会把搜索出的结果传递给templates/search
                        目录下的search.html,传递的上下文包括:
                            query:搜索关键字
                            page:当前页的page对象 –>遍历page对象,获取到的是SearchResult类的实例对象,
                                    对象的属性object才是模型类的对象。
                            paginator:分页paginator对象
                        通过HAYSTACK_SEARCH_RESULTS_PER_PAGE 可以控制每页显示数量。
                    - search.html分页器的经典使用
                         <div class="pagenation">
                            {% if page.has_previous %}
                            <a href="/search?q={{ query }}&page={{ page.previous_page_number }}"><上一页</a>
                            {% endif %}
                            {% for pindex in paginator.page_range %}
                                {% if pindex == page.number %}
                                    <a href="/search?q={{ query }}&page={{ pindex }}" class="active">{{ pindex }}</a>
                                {% else %}
                                    <a href="/search?q={{ query }}&page={{ pindex }}">{{ pindex }}</a>
                                {% endif %}
                            {% endfor %}
                            {% if spage.has_next %}
                            <a href="/search?q={{ query }}&page={{ page.next_page_number }}">下一页></a>
                            {% endif %}
                            </div>
                        </div>
                
                    - 完成上面步奏之后,可以简单搜索,但是无法根据商品详情里的中文字段搜索
                        需要优化,商品搜索、改变分词方式
                        - 安装jieba分词模块
                            pip install jieba
                                str = '很不错的草莓'
                                res = jieba.cut(str,cut_all=True)
                                for val in res:
                                    print(val)
                        - 。。。。。
                        - settings里配置需要更改
                        - 最后重新创建索引数据
                            python manage.py rebuild_index
    
    7. 订单并发处理
        - 悲观锁
            - select * from xx where id=2 for update
            - 应用场景:
                try:
                    #select * from df_goods_sku where id=sku_id for update;
                    sku=GoodsSKU.object.select_for_update().get(id=sku_id)
                except:
                    transaction.savepoint_rollback(save_id)
                    return ...                                                
        - 乐观锁
            - 查询数据时不加锁,在更新时进行判断
            - 判断更新时的库存和之前查出的库存是否一致
            - 操作
                -
                for i in range(3):
                    #update df_goods_sku set tock=stock,sales=new_sales where id=
                        sku_id and stock=origin_stock
                      res=GoodsSKU.object.filter(id=sku_id,stock=stock).update(
                      stock=new_stock,sales=new_sales)
                      if res==0:  库存为0
                        if i==2:    #尝试第3次查询
                            transaction.savepoint_rollback(save_id)
                - mysql事务隔离性
                    事务隔离级别
                        - Read Uncommitted
                        - Read Committed(大多数数据库默认)
                        - Repeatable Read(mysql默认,产生幻读)
                        - serializable(可串行化,解决幻读问题,但容易引发竞争)
                - 重新配置mysql.conf为read committed
        总结:
            - 在冲突较少的时候使用乐观锁,因为省去了加锁、减锁的时间
            - 在冲突多的时候、乐观锁重复操作的代价比较大时使用悲观锁
            - 判断的时候,更新失败不一定是库存不足,需要再去尝试
    
    8. 首页静态页面优化
        - 将动态请求的数据保存为静态的HTML文本,供访问用户下次直接返回。
        - celery
        - 什么时候首页的静态页面需要重新生成?
            当管理员后台修改了首页信息对应的表格中的数据的时候,需要重新生成首页静态页
        - 配置nginx提交静态页面
            - ps aux | grep nginx
         
        - admin管理更新数据表时重新生成index静态页面
            后台管理员操作(重写类,admin.ModelAdmin)-修改首页中表的数--》celery任务函数
                -----》celery服务器生成index.html
            用户访问时直接访问aginx的80端口,即index.html
        - 页面数据的缓存
            把页面中使用的数据存储在缓存中,当再次使用这些数据时,先从缓存中获取,若获取
            不到,再去数据库查询。减少数据库查询的次数
                - 设置缓存
                    - 先判断缓存中是否有数据,cache.get('xx')
                    - views里设置cache.set(key,zidian,time)
                - 获取缓存
            - 什么时候需要更新首页的缓存数据
                当管理员修改首页信息对应的表格中的数据的时候,需要
                - 在BaseModelAdmin里清除cache
    ****- 网站本身性能的优化,减少数据查询的次数,防止恶意的攻击,
            DDOS功能
            nginx在提供静态文件方面效率很高,采用epoll
    
    
    
                
    SKU & SPU
        - SPU
            - Standard product unittest
            - 商品信息聚合的最小单位
            - 如iphone,
        - SKU
            - STOCK keeping unittest
            - 库存量进出计量单位
            - 如纺织品中一个SKU表示:
                规格、颜色、款式
            
    
    
    
    
    
    
    
    
    
    
    
        
        
        
        
    View Code
  • 相关阅读:
    Jenkins插件管理及汉化
    rpm安装Jenkins报错
    Codeforces Round #572 (Div. 2) A.
    [kuangbin带你飞]专题一 简单搜索 A棋盘问题
    北京信息科技大学第十一届程序设计竞赛(重现赛)I
    北京信息科技大学第十一届程序设计竞赛(重现赛)H
    北京信息科技大学第十一届程序设计竞赛(重现赛)B
    nyoj 206-矩形的个数 (a*b*(a+1)*(b+1)/4)
    nyoj 241-字母统计 (python count)
    nyoj 242-计算球体积 (pi*r*r*r*4/3)
  • 原文地址:https://www.cnblogs.com/di2wu/p/10328659.html
Copyright © 2011-2022 走看看