21、抽取父模板
怎样抽取父模板:找一个典型的html(如主页index.html),把每个页面都一样的部分抽取出来(保留);把每个页面都有但不一样的部分预留出块;有的页面有有的页面没有的部分可以只预留出块也可以在块中写默认内容;只有一个页面有的也就是特例的删掉;剩下的就是父模板;
在index.html基础上抽取父模板base.html,作为首页、注册、登录页的父模板:
{# templates/base.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 static %}
<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>
<body>
{# 页面顶部欢迎信息块 #}
{% block head_con %}
<div class="header_con">
<div class="header">
<div class="welcome fl">欢迎来到天天生鲜!</div>
<div class="fr">
<div class="login_info fl">
欢迎您:<em>张 山</em>
</div>
<div class="login_btn fl">
<a href="login.html">登录</a>
<span>|</span>
<a href="register.html">注册</a>
</div>
<div class="user_link fl">
<span>|</span>
<a href="user_center_info.html">用户中心</a>
<span>|</span>
<a href="cart.html">我的购物车</a>
<span>|</span>
<a href="user_center_order.html">我的订单</a>
</div>
</div>
</div>
</div>
{% endblock head_con %}
{# 页面顶部搜索框块 #}
{% block search_bar %}
<div class="search_bar clearfix">
<a href="index.html" class="logo fl"><img src="images/logo.png"></a>
<div class="search_con fl">
<input type="text" class="input_text fl" name="" placeholder="搜索商品">
<input type="button" class="input_btn fr" name="" value="搜索">
</div>
<div class="guest_cart fr">
<a href="#" class="cart_name fl">我的购物车</a>
<div class="goods_count fl" id="show_count">1</div>
</div>
</div>
{% endblock search_bar %}
{# 网页主体内容块 #}
{% block body %}{% endblock body %}
<div class="footer">
<div class="foot_link">
<a href="#">关于我们</a>
<span>|</span>
<a href="#">联系我们</a>
<span>|</span>
<a href="#">招聘人才</a>
<span>|</span>
<a href="#">友情链接</a>
</div>
<p>CopyRight © 2016 北京天天生鲜信息技术有限公司 All Rights Reserved</p>
<p>电话:010-****888 京ICP备*******8号</p>
</div>
{# 底部html元素块 #}
{% block bottom %}{% endblock bottom %}
{# 底部引入文件块 #}
{% block bottomfiles %}{% endblock bottomfiles %}
</body>
</html>
在base.html的基础上抽取模板base_no_cart.html,作为购物车、提交订单页的父模板:(根据搜索框的特征提取)
{# templates/base_no_cart #}
{# 购物车,提交订单 #}
{% extends 'base.html' %}
{% load static %}
{# 网页顶部搜索框块 #}
{% block search_bar %}
<div class="search_bar clearfix">
<a href="index.html" class="logo fl"><img src="{% static 'images/logo.png' %}"></a>
<div class="sub_page_name fl">| {% block page_title %}{% endblock page_title %}</div>
<div class="search_con fr">
<input type="text" class="input_text fl" name="" placeholder="搜索商品">
<input type="button" class="input_btn fr" name="" value="搜索">
</div>
</div>
{% endblock search_bar %}
在base_no_cart.html的基础上抽取模板base_user_center.html,作为用户中心三个页面的父模板:(根据左侧栏提取)
{# templates/base_user_center.html #}
{# 用户中心×3 #}
{% extends 'base_no_cart.html' %}
{% block title %}天天生鲜-用户中心{% endblock title %}
{% block page_title %}用户中心{% endblock page_title %}
{% block body %}
<div class="main_con clearfix">
<div class="left_menu_con clearfix">
<h3>用户中心</h3>
<ul>
<li><a href="{% url 'user:info' %}" {% if page == 'info' %}class="active"{% endif %}>· 个人信息</a></li>
<li><a href="{% url 'user:order' %}"{% if page == 'order' %}class="active"{% endif %}>· 全部订单</a></li>
<li><a href="{% url 'user:address' %}"{% if page == 'address' %}class="active"{% endif %}>· 收货地址</a></li>
</ul>
</div>
{# 用户中心右侧内容块 #}
{% block right_content %}{% endblock right_content %}
</div>
{% endblock body %}
在base.html的基础上抽取模板base_detail_list.html,作为详情页、列表页的父模板:(根据顶部菜单栏提取)
{# templates/base_detail_list.html #}
{# 详情页,列表页 #}
{% extends 'base.html' %}
{% block body %}
<div class="navbar_con">
<div class="navbar clearfix">
<div class="subnav_con fl">
<h1>全部商品分类</h1>
<span></span>
<ul class="subnav">
<li><a href="#" class="fruit">新鲜水果</a></li>
<li><a href="#" class="seafood">海鲜水产</a></li>
<li><a href="#" class="meet">猪牛羊肉</a></li>
<li><a href="#" class="egg">禽类蛋品</a></li>
<li><a href="#" class="vegetables">新鲜蔬菜</a></li>
<li><a href="#" class="ice">速冻食品</a></li>
</ul>
</div>
<ul class="navlist fl">
<li><a href="">首页</a></li>
<li class="interval">|</li>
<li><a href="">手机生鲜</a></li>
<li class="interval">|</li>
<li><a href="">抽奖</a></li>
</ul>
</div>
</div>
{# 详情页,列表页主题内容块 #}
{% block main_content %}{% endblock main_content %}
{% endblock body %}
22、继承父模板改写页面:
注册页,标题块、顶部引入文件块、顶部欢迎信息块、搜索框块、主题内容块要改:
{# templates/register.html #}
{% extends 'base.html' %}
{% load static %}
{% block title %}天天生鲜-注册{% endblock title %}
{% block topfiles %}
<script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/register.js' %}"></script>
{% endblock topfiles %}
{% block head_con %}{% endblock head_con %}
{% block search_bar %}{% endblock search_bar %}
{% block body %}
<div class="register_con">
<div class="l_con fl">
<a class="reg_logo"><img src="{% static 'images/logo02.png' %}"></a>
<div class="reg_slogan">足不出户 · 新鲜每一天</div>
<div class="reg_banner"></div>
</div>
<div class="r_con fr">
<div class="reg_title clearfix">
<h1>用户注册</h1>
<a href="#">登录</a>
</div>
<div class="reg_form clearfix">
<form method="post" action="/user/register">
{% csrf_token %}
<ul>
<li>
<label>用户名:</label>
<input type="text" name="user_name" id="user_name">
<span class="error_tip">提示信息</span>
</li>
<li>
<label>密码:</label>
<input type="password" name="pwd" id="pwd">
<span class="error_tip">提示信息</span>
</li>
<li>
<label>确认密码:</label>
<input type="password" name="cpwd" id="cpwd">
<span class="error_tip">提示信息</span>
</li>
<li>
<label>邮箱:</label>
<input type="text" name="email" id="email">
<span class="error_tip">提示信息</span>
</li>
<li class="agreement">
<input type="checkbox" name="allow" id="allow" checked="checked">
<label>同意”天天生鲜用户使用协议“</label>
<span class="error_tip2">提示信息</span>
</li>
<li class="reg_sub">
<input type="submit" value="注 册" name="">
</li>
</ul>
</form>
</div>
</div>
</div>
{% endblock body %}
登录页,同注册页:
{# templates/login.html #}
{% extends 'base.html' %}
{% load static %}
{% block title %}天天生鲜-登录{% endblock title %}
{% block head_con %}{% endblock head_con %}
{% block search_bar %}{% endblock search_bar %}
{% block body %}
<div class="login_top clearfix">
<a href="index.html" class="login_logo"><img src="{% static 'images/logo02.png'%}"></a>
</div>
<div class="login_form_bg">
<div class="login_form_wrap clearfix">
<div class="login_banner fl"></div>
<div class="slogan fl">日夜兼程 · 急速送达</div>
<div class="login_form fr">
<div class="login_title clearfix">
<h1>用户登录</h1>
<a href="#">立即注册</a>
</div>
<div class="form_input">
<form method="post">
{% csrf_token %}
<input type="text" name="username" class="name_input" value="{{ username }}" placeholder="请输入用户名">
<div class="user_error">输入错误</div>
<input type="password" name="pwd" class="pass_input" placeholder="请输入密码">
<div class="pwd_error">输入错误</div>
<div class="more_input clearfix">
<input type="checkbox" name="remember" {{ checked }}>
<label>记住用户名</label>
<a href="#">忘记密码</a>
</div>
<input type="submit" name="" value="登录" class="input_submit">
</form>
</div>
</div>
</div>
</div>
{{ errmsg }}
{% endblock body %}
用户中心-信息页,右侧栏要改:
{# templates/user_center_info.html #}
{% extends 'base_user_center.html' %}
{% block right_content %}
<div class="right_content clearfix">
<div class="info_con clearfix">
<h3 class="common_title2">基本信息</h3>
<ul class="user_info_list">
<li><span>用户名:</span>18210569700</li>
<li><span>联系方式:</span>18210569700</li>
<li><span>联系地址:</span>北京市昌平区</li>
</ul>
</div>
<h3 class="common_title2">最近浏览</h3>
<div class="has_view_list">
<ul class="goods_type_list clearfix">
<li>
<a href="detail.html"><img src="images/goods/goods003.jpg"></a>
<h4><a href="detail.html">大兴大棚草莓</a></h4>
<div class="operate">
<span class="prize">¥16.80</span>
<span class="unit">16.80/500g</span>
<a href="#" class="add_goods" title="加入购物车"></a>
</div>
</li>
<li>
<a href="#"><img src="images/goods/goods004.jpg"></a>
<h4><a href="#">吐鲁番梨光杏</a></h4>
<div class="operate">
<span class="prize">¥5.50</span>
<span class="unit">5.50/500g</span>
<a href="#" class="add_goods" title="加入购物车"></a>
</div>
</li>
<li>
<a href="#"><img src="images/goods/goods005.jpg"></a>
<h4><a href="#">黄肉桃</a></h4>
<div class="operate">
<span class="prize">¥10.00</span>
<span class="unit">10.00/500g</span>
<a href="#" class="add_goods" title="加入购物车"></a>
</div>
</li>
<li>
<a href="#"><img src="images/goods/goods006.jpg"></a>
<h4><a href="#">进口西梅</a></h4>
<div class="operate">
<span class="prize">¥28.80</span>
<span class="unit">28.8/500g</span>
<a href="#" class="add_goods" title="加入购物车"></a>
</div>
</li>
<li>
<a href="#"><img src="images/goods/goods007.jpg"></a>
<h4><a href="#">香梨</a></h4>
<div class="operate">
<span class="prize">¥6.45</span>
<span class="unit">6.45/500g</span>
<a href="#" class="add_goods" title="加入购物车"></a>
</div>
</li>
</ul>
</div>
</div>
{% endblock right_content %}
用户中心-订单页,同上:
{# templates/user_center_order #}
{% extends 'base_user_center.html' %}
{% block right_content %}
<div class="right_content clearfix">
<h3 class="common_title2">全部订单</h3>
<ul class="order_list_th w978 clearfix">
<li class="col01">2016-8-21 17:36:24</li>
<li class="col02">订单号:56872934</li>
<li class="col02 stress">未支付</li>
</ul>
<table class="order_list_table w980">
<tbody>
<tr>
<td width="55%">
<ul class="order_goods_list clearfix">
<li class="col01"><img src="images/goods02.jpg"></li>
<li class="col02">嘎啦苹果嘎啦苹果<em>11.80元/500g</em></li>
<li class="col03">1</li>
<li class="col04">11.80元</li>
</ul>
<ul class="order_goods_list clearfix">
<li class="col01"><img src="images/goods02.jpg"></li>
<li class="col02">嘎啦苹果嘎啦苹果<em>11.80元/500g</em></li>
<li class="col03">1</li>
<li class="col04">11.80元</li>
</ul>
</td>
<td width="15%">33.60元</td>
<td width="15%">待付款</td>
<td width="15%"><a href="#" class="oper_btn">去付款</a></td>
</tr>
</tbody>
</table>
<ul class="order_list_th w978 clearfix">
<li class="col01">2016-8-21 17:36:24</li>
<li class="col02">订单号:56872934</li>
<li class="col02 stress">已支付</li>
</ul>
<table class="order_list_table w980">
<tbody>
<tr>
<td width="55%">
<ul class="order_goods_list clearfix">
<li class="col01"><img src="images/goods02.jpg"></li>
<li class="col02">嘎啦苹果嘎啦苹果<em>11.80元/500g</em></li>
<li class="col03">1</li>
<li class="col04">11.80元</li>
</ul>
<ul class="order_goods_list clearfix">
<li class="col01"><img src="images/goods02.jpg"></li>
<li class="col02">嘎啦苹果嘎啦苹果<em>11.80元/500g</em></li>
<li class="col03">1</li>
<li class="col04">11.80元</li>
</ul>
</td>
<td width="15%">33.60元</td>
<td width="15%">已付款</td>
<td width="15%"><a href="#" class="oper_btn">查看物流</a></td>
</tr>
</tbody>
</table>
<div class="pagenation">
<a href="#">《上一页</a>
<a href="#" class="active">1</a>
<a href="#">2</a>
<a href="#">3</a>
<a href="#">4</a>
<a href="#">5</a>
<a href="#">下一页》</a>
</div>
</div>
{% endblock right_content %}
用户中心-地址页,同上:
{# templates/user_center_site.html #}
{% extends 'base_user_center.html' %}
{% block right_content %}
<div class="right_content clearfix">
<h3 class="common_title2">收货地址</h3>
<div class="site_con">
<dl>
<dt>当前地址:</dt>
<dd>北京市 海淀区 东北旺西路8号中关村软件园 (李思 收) 182****7528</dd>
</dl>
</div>
<h3 class="common_title2">编辑地址</h3>
<div class="site_con">
<form>
<div class="form_group">
<label>收件人:</label>
<input type="text" name="">
</div>
<div class="form_group form_group2">
<label>详细地址:</label>
<textarea class="site_area"></textarea>
</div>
<div class="form_group">
<label>邮编:</label>
<input type="text" name="">
</div>
<div class="form_group">
<label>手机:</label>
<input type="text" name="">
</div>
<input type="submit" name="" value="提交" class="info_submit">
</form>
</div>
</div>
{% endblock right_content %}
23、显示用户中心:
定义视图:
# user.views.py追加
# /user
class UserInfoView(View):
'''用户中心-信息页'''
def get(self, request):
'''显示'''
return render(request, 'user_center_info.html', {'page': 'info'})
# /user/order
class UserOrderView(View):
'''用户中心-信息页'''
def get(self, request):
'''显示'''
return render(request, 'user_center_order.html', {'page': 'order'})
# user/address
class UserAddressView(View):
'''用户中心-信息页'''
def get(self, request):
'''显示'''
return render(request, 'user_center_site.html', {'page': 'address'})
配置路由:
# user/urls.py添加url
path('address', views.UserAddressView.as_view(), name='address'), # 用户中心-地址页
path('order', views.UserOrderView.as_view(), name='order'), # 用户中心-订单页
path('', views.UserInfoView.as_view(), name='info') # 用户中心-信息页
父模板中已经做了相应修改,如{% if %}判断页面参数决定左侧标签是否高亮显示、<a>标签的href换成{% url 'namespace:name' %}
测试:
23、登录装饰器
用户中心3个页面必须登录之后才能访问,Django内置的登录装饰器可以检测登录状态,如果没有登录,就跳转到登录页并在地址栏url后面加上一个参数?next=刚要访问的url;
方案一:作用于视图函数
# 按理应该
from django.contrib.auth.decorators import login_required
@login_required
def user_info(request):
pass
由于我们用的是视图类,视图函数只会出现在配置url时as_view()的返回结果,因此:
# user/urls.py修改url
from django.urls import path
from apps.user import views
from django.contrib.auth.decorators import login_required
app_name = 'user'
urlpatterns = [
path('register', views.RegisterView.as_view(), name='register'), # 注册
path('active/<token>', views.ActiveView.as_view(), name='active'), # 激活
path('login', views.LoginView.as_view(), name='login'), # 登录
path('address', login_required(views.UserAddressView.as_view()), name='address'), # 用户中心-地址页
path('order', login_required(views.UserOrderView.as_view()), name='order'), # 用户中心-订单页
path('', login_required(views.UserInfoView.as_view()), name='info') # 用户中心-信息页
]
# 作为路径的最后一节,path('register')不能带/,否则报错Page Not Found
# 路径中用<>捕获参数
默认跳转到的登录页不是我们用的,要配置跳转的登录页:
# settings.py追加
# login_required跳转的登录url
LOGIN_URL = '/user/login'
测试,清楚浏览器数据后访问:
结果跳到了登录页,url后面跟上了刚要访问的页面:
因为登录后要跳到刚要访问的页面,所以post方法中要获取next参数并跳转:
# user/views.py post方法 定义response那一句改成:
next_url = request.GET.get('next', reverse('goods:index')) # 参数2是如果没有next参数的话返回的默认值
response = redirect(next_url)
登录完就跳转了:
方案二:作用于视图类,Minxin
方案一使得配置url的代码太冗杂;
新建python package,python file:utils.mixin:
# utils/mixin.py
from django.contrib.auth.decorators import login_required
class LoginRequiredMixin(object):
@classmethod
def as_view(cls, **initkwargs):
view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
return login_required(view)
# user/views.py为需要登录验证的视图类添加继承
from utils.mixin import LoginRequiredMixin
class UserInfoView(LoginRequiredMixin, View):
pass
class UserOrderView(LoginRequiredMixin, View):
pass
class UserAddressView(LoginRequiredMixin, View):
pass
# user/urls.py用户中心3个url清除包装,恢复原样
path('address', views.UserAddressView.as_view(), name='address'), # 用户中心-地址页
path('order', views.UserOrderView.as_view(), name='order'), # 用户中心-订单页
path('', views.UserInfoView.as_view(), name='info') # 用户中心-信息页
原理:
执行views.UserAddressView.as_view()时,由于我们定义的UserAddressView里并没有as_view()方法,所以到父类中找,先继承的LoginRequiredMixin所以先到LoginRequiredMixin里找,我们定义了,所以UserAddressView调用as_view()方法,而方法内部super(LoginRequiredMixin, cls).as_view(**initkwargs)又调用父类方法,因此UserAddressView又到父类中找,由于LoginRequiredMixin已经找了,所以去View里找,当然能找到,调用,返回的结果用login_required()包装并返回,这样一来,跟直接在配置url时用login_required()包装其实一模一样。
24、判断用户登录,显示欢迎信息
Django通过中间件和回话拦截请求(request),每来一个请求,Django都给它设置一个user属性(request.user)如果已登录,此user是User类型的实例(User就是settings.AUTH_USER_MODEL指定的模型类,也就是我们自定义的User),如果未登录,此user是AnonymousUser类的实例,前者user.is_authenticated属性返回True,后者user.is_authenticated属性返回False,而这个user属性会在request.render()时自动传给模板,也就是在模板里可以直接用,即:
{# templates/base.html 修改欢迎信息块中的欢迎信息 #}
{% if user.is_authenticated %}
<div class="login_btn fl"> {# 修改class改成跟下面一样,否则不显示 #}
欢迎您:<em>{{ user.username }}</em>
</div>
{% else %}
<div class="login_btn fl">
<a href="login.html">登录</a>
<span>|</span>
<a href="register.html">注册</a>
</div>
{% endif %}
25、退出登录
前端做出退出按钮:
{# templates/base.html在欢迎信息处添加退出按钮 #}
{% if user.is_authenticated %}
<div class="login_btn fl">
欢迎您:<em>{{ user.username }}</em>
<span>|</span>
<a href="{% url 'user:logout' %}">退出</a>
</div>
定义视图:
# user/views.py追加
# /user/logout
class LogoutView(View):
'''退出登录'''
def get(self, request):
logout(request)
return redirect(reverse('goods:index'))
配置路由:
# user/urls.py
from django.urls import path
from apps.user.views import *
app_name = 'user'
urlpatterns = [
path('register', RegisterView.as_view(), name='register'), # 注册
path('active/<token>', ActiveView.as_view(), name='active'), # 激活
path('login', LoginView.as_view(), name='login'), # 登录
path('logout', LogoutView.as_view(), name='logout'), # 退出登录
path('address', UserAddressView.as_view(), name='address'), # 用户中心-地址页
path('order', UserOrderView.as_view(), name='order'), # 用户中心-订单页
path('', UserInfoView.as_view(), name='info') # 用户中心-信息页
]
# 作为路径的最后一节,path('register')不能带/,否则报错Page Not Found
# 路径中用<>捕获参数
顺便给其它按钮修改链接:
{# templates/base.html修改模板中的链接 #}
{% else %}
<div class="login_btn fl">
<a href="{% url 'user:login' %}">登录</a>
<span>|</span>
<a href="{% url 'user:register' %}">注册</a>
</div>
{% endif %}
<div class="user_link fl">
<span>|</span>
<a href="{% url 'user:info' %}">用户中心</a>
<span>|</span>
<a href="cart.html">我的购物车</a>
<span>|</span>
<a href="{% url 'user:order' %}">我的订单</a>
</div>
26、用户中心-地址页
{# templates/user_center_site.html右侧内容块 #}
{% block right_content %}
<div class="right_content clearfix">
<h3 class="common_title2">收货地址</h3>
<div class="site_con">
<dl>
<dt>当前地址:</dt>
{% if addr_default %}
<dd>{{ addr_default.addr }} ({{ addr_default.receiver }} 收) {{ addr_default.phone }}</dd>
{% else %}
<dd>无默认收货地址</dd>
{% endif %}
</dl>
</div>
<h3 class="common_title2">编辑地址</h3>
<div class="site_con">
<form method="post">
{% csrf_token %}
<div class="form_group">
<label>收件人:</label>
<input type="text" name="receiver">
</div>
<div class="form_group form_group2">
<label>详细地址:</label>
<textarea class="site_area" name="addr"></textarea>
</div>
<div class="form_group">
<label>邮编:</label>
<input type="text" name="zip_code">
</div>
<div class="form_group">
<label>手机:</label>
<input type="text" name="phone">
</div>
<input type="submit" name="" value="提交" class="info_submit">
</form>
</div>
</div>
{% endblock right_content %}
# user/views.py完善地址视图
# user/address
class UserAddressView(LoginRequiredMixin, View):
'''用户中心-信息页'''
def get(self, request):
'''显示'''
try:
addr_default = Address.objects.get(user=request.user, is_default=True)
except Address.DoesNotExist:
addr_default = None
return render(request, 'user_center_site.html', {'page': 'address', 'addr_default': addr_default})
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 = request.user
try:
addr_default = Address.objects.get(user=user, is_default=True)
except Address.DoesNotExist:
addr_default = None
if addr_default:
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'))
添加地址:
查看数据库:
27、自定义模型管理器对象,封装方法
地址视图里每次获取默认地址都要try--except,本着复用的原则,应该封装起来这个方法:
# user/models.py追加
class AddressManager(models.Manager):
'''自定义地址模型管理器类'''
def get_default_address(self, user):
'''获取默认收货地址'''
try:
addr_default = self.get(user=user, is_default=True)
except Address.DoesNotExist:
addr_default = None
return addr_default
# 为什么能直接self.get()?
# 原来用Address.object.get()是因为Address.objects是models.Manager类对象,有get()方法,
# 而这里self是AddressManager类对象,继承了models.Manager,也有get()方法,所以没毛病,
# 这里的self就是Address.object(新)
# user/models.py Address类添加成员属性
objects = AddressManager()
# 查询默认收货地址时只需要:
addr_default = Address.objects.get_default_address(request.user)
28、用户信息页
也是简单的模板操作:
# user/views.py 用户信息视图
# /user
class UserInfoView(LoginRequiredMixin, View):
'''用户中心-信息页'''
def get(self, request):
'''显示'''
addr_default = Address.objects.get_default_address(request.user)
return render(request, 'user_center_info.html', {'page': 'info', 'addr_default': addr_default})
{# templates/user_center_info.html修改模板 #}
<ul class="user_info_list">
<li><span>用户名:</span>{{ user.username }}</li>
{% if addr_default %}
<li><span>联系方式:</span>{{ addr_default.phone }}</li>
<li><span>联系地址:</span>{{ addr_default.addr }}</li>
{% else %}
<li><span>联系方式:</span>无默认</li>
<li><span>联系地址:</span>无默认</li>
{% endif %}
</ul>
29、浏览记录
什么时候存储浏览记录:访问商品详情页的时候;
什么时候获取浏览记录:访问用户中心-个人信息页时;
浏览记录以什么形式存储:redis数据库(快),每个用户用一条list类型数据保存,左侧添加;
获取浏览记录:
# user/views.py 用户信息视图
# /user
class UserInfoView(LoginRequiredMixin, View):
'''用户中心-信息页'''
def get(self, request):
'''显示'''
user = request.user
addr_default = Address.objects.get_default_address(user)
# 浏览记录
conn = get_redis_connection('default')
history_key = 'history_%d' % user.id
sku_ids = conn.lrange(history_key, 0, 4) # 获取最前面的5个商品id
# goods_li = GoodsSKU.objects.filter(id__in=sku_ids) # 顺序不可控
goods_li = []
# 为了顺序,一个一个查
for id in sku_ids:
goods_li.append(GoodsSKU.objects.get(id=id))
# 组织上下文
context = {'page': 'user', 'addr_default': addr_default, 'goods_li': goods_li}
return render(request, 'user_center_info.html', context)
{# templates/user_center_info.html修改模板 #}
<ul class="goods_type_list clearfix">
{% for goods in goods_li %}
<li>
<a href="detail.html"><img src="{{ goods.image.url }}"></a>
<h4><a href="detail.html">{{ goods.name }}</a></h4>
<div class="operate">
<span class="prize">¥{{ goods.price }}</span>
<span class="unit">{{ goods.price }}/{{ goods.unite }}</span>
<a href="#" class="add_goods" title="加入购物车"></a>
</div>
</li>
{% empty %}
无历史浏览记录
{% endfor %}
</ul>
29、分布式文件存储系统FastDFS
FastDFS分为TrackerServer和StorageServer,二者都可以是集群;存储文件时,客户端访问TrackerServer,返回应该存储到哪个StorageServer,然后客户端请求该StorageServer,返回一个唯一的文件id;下载文件也是这个流程;FastDFS存储的特点:StorageServer收到一个文件后,对文件内容取哈希值,称作文件指纹,如果已经存储过相同的文件,就不会再保存第二次,直接把该文件id返回,这也是某盘存储文件的特点。
安装、配置、启动:
# 解压后进入libfastcommon-master
sudo sh make.sh
sudo sh make.sh install
# 解压后进入fastdfs-master
sudo sh make.sh
sudo sh make.sh install
# 改配置文件
cd /etc/fdfs
sudo cp tracker.conf.sample tracker.conf
sudo dedit tracker.conf
bind_addr=192.168.0.72
# 存一些日志之类的
base_path=/home/deepon/FastDFS/tracker
sudo mkdir /home/deepon/FastDFS/tracker
sudo cp storage.conf.sample storage.conf
sudo dedit storage.conf
base_path=/home/deepon/FastDFS/storage
# 数据最终存储的路径
store_path0=/home/deepon/FastDFS/storage
# 由哪个tracker管理
tracker_server=192.168.0.72:22122
sudo mkdir /home/deepon/FastDFS/storage
# 启动tracker服务
sudo fdfs_trackerd /etc/fdfs/tracker.conf
# 启动storage服务
sudo fdfs_storaged /etc/fdfs/storage.conf
检查启动了没:
创建客户端配置文件:
# 客户端配置文件
cd /etc/fdfs
sudo cp client.conf.sample client.conf
sudo dedit client.conf
base_path=/home/deepon/FastDFS/tracker
tracker_server=192.168.0.72:22122
上传文件测试,返回文件id:
到配置文件指定的路径下找00/00,果然保存了:
30、FastDFS和Nginx配合使用
当用户量大的时候,用FastDFS下载文件比较慢,而Nginx适合大用户量的静态文件请求,Nginx底层用epoll实现,效率高;要装一个fastdfs-nginx-module-master才能配合使用;
安装:
# --prefix指定要安装的路径
# --add-module指定fastdfs-nginx-module-master解压后的路径/src
cd nginx-1.8.1
sudo ./configure --prefix=/usr/local/nginx/ --add-module=/home/deepon/baidunetdiskdownload/FastDFS/fastdfs-nginx-module-master/src
sudo make
# make报错this statement may fall through [-Werror=implicit-fallthrough=]
# 解决:cd nginx-1.8.1/objs sudo dedit Makefile 第三行删除-Werror 重新sudo make
sudo make install
配置文件:
# 进入fastdfs-nginx-module-master/src
sudo cp mod_fastdfs.conf /etc/fdfs/
sudo dedit /etc/fdfs/mod_fastdfs.conf
connect_timeout=10
tracker_server=192.168.43.45:22122
url_have_group_name = true
store_path0=/home/deepon/FastDFS/storage
到fastdfs-master(FastDFS解压目录)/conf下,复制http.conf、mime.types到/etc/fdfs/下;
修改Nginx配置文件:
sudo vim /usr/local/nginx/conf/nginx.conf
# 在http{}中第一个server{}前添加一个server{}:
# 只要碰到group[0-9]就去fastdfs找
server {
listen 8888;
server_name localhost;
location ~/group[0-9]/ {
ngx_fastdfs_module;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
启动Nginx(停止:sudo ./nginx -s stop):
上传文件测试:
复制文件id,请求文件测试(访问Nginx):
成功了;
31、FastDFS与Python交互
安装py包,直接进入虚拟环境后pip3 install 压缩包:
32、项目中上传和使用图片
Django默认在admin页面上传文件后,保存到MEDIA_ROOT指定的目录下,由FileSystemStorage类的save()方法实现;
我们要让文件保存在FastDFS,不需要改他源代码,Django已经准备好了扩展的方式:自定义一个文件存储类,继承Storage类(FileSystomStorage的父类),重写相应的方法;
utils目录下新建fdfs目录:
client.conf和配置FastDFS时用的客户端配置文件一样,python里创建FastDFS客户端也要用客户端配置文件
# utils/fdfs/storage.py
from django.core.files.storage import Storage
from fdfs_client.client import Fdfs_client
class FDFSStorage(Storage):
'''FastDFS文件存储类'''
def _open(self, name, mode='rb'):
'''打开文件'''
# 我们用不到打开,但是自定义文件存储类必须实现_open()_save(),所以pass
pass
def _save(self, name, content):
'''保存文件'''
# name:要上传的文件的名字
# content:包含上传文件内容的File对象
# 创建一个FastDFS客户端
client = Fdfs_client('./utils/fdfs/client.conf')
# 上传后返回一个字典,有Status(上传状态),Remote file_id(文件id)等属性
res = client.upload_by_buffer(content.read())
if res.get('Status') != 'Upload successed.':
# 上传失败
raise Exception('上传文件到FastDFS失败')
filename = res.get('Remote file_id')
# _save()返回什么,表df_goods_type.image就保存什么,我们要保存文件id
return filename
def exists(self, name):
'''判断Django内置的文件存储系统中是否已存在这个文件名,存在就不可用,而我们根本没用他的系统,当然不会存在'''
return False
def url(self, name):
'''返回'''
# url()返回什么,goods.image.url就返回什么
return 'http:127.0.0.1:8888/' + name