概览
- 开发user_center_info、user_center_order、user_center_site三个页面,三个页面都属于apps中的user模块,其中user_center_info默认第一个显示,三个页面可以通过页面左侧的选项卡随时切换。
- 上述的三个应该在用户登录后才能继续访问,应该需要对用户是否登录进行校验,若未登录,则返回login页面登录。
- templates中页面代码重复的部分用模板继承进行改写,简化代码。
- 浏览记录的设计:用redis存储用户的访问信息,提高效率。
注销当前用户
apps/user/urls.py
url(r'^logout$', LogoutView.as_view(), name='logout'), # 注销
apps/user/views.py
class LogoutView(View):
def get(self, request):
logout(request) # 认证系统的logout的方法,不需要手动注销
return redirect(reverse('goods:index'))
用户中心的登录校验
登录校验的方法一:在urls.py相关的函数前面,用login_required修饰。
# 用户中心登录校验的方式一:在前面添加login_required
# 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'), # 用户中心-地址页
方法二:定义登录校验的相关类,urls中保持不变,在类中继承相关类。
# 方式二,装饰类的方式处理,定义在utils包中
url(r'^$', UserInfoView.as_view(), name='user'), # 用户中心-信息页
url(r'^order$', UserOrderView.as_view(), name='order'), # 用户中心-订单页
url(r'^address$', AddressView.as_view(), name='address'), # 用户中心-地址页
新建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)
视图代码中的类继承这个类:
class UserOrderView(LoginRequiredMixin, View):
用户中心视图层代码
当未登录访问用户中心页面时,跳转到登录页面,url后面会带上一个next参数,我们需要把next参数设置成自己的路径,便于登录后直接访问之前想访问的地址。
项目settings文件配置:
# 配置登录url地址
LOGIN_URL='/user/login' # /accounts/login
为了能够登录后跳转到用户中心相关页面,需要对登录函数进行修改:
# /user/login
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})
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':'数据不完整'})
# 业务处理:登录校验 authenticate是认证系统提供的方法
user = authenticate(username=username, password=password)
if user is not None:
# 用户名密码正确
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
else:
# 用户未激活
return render(request, 'login.html', {'errmsg':'账户未激活'})
else:
# 用户名或密码错误
return render(request, 'login.html', {'errmsg':'用户名或密码错误'})
用户中心三个视图层代码如下,其中的page参数是为了根据页面的不同,在左侧给对应的选项卡加上class:active
class UserInfoView(View):
'''用户中心-信息页'''
def get(self, request):
# Django会给request对象添加一个属性request.user
# 如果用户未登录->user是AnonymousUser类的一个实例对象
# 如果用户登录->user是User类的一个实例对象
# request.user.is_authenticated()
# 获取用户个人信息
user = request.user
address = Address.objects.get_default_address(user)
# 获取用户的历史浏览记录
# from redis import StrictRedis
# sr = StrictRedis(host='172.16.179.130', port='6379', db=9)
con = get_redis_connection('default')
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)
#
# goods_res = []
# for a_id in sku_ids:
# for goods in goods_li:
# if a_id == goods.id:
# goods_res.append(goods)
# 遍历获取用户浏览的商品信息,这里之所以要这样的做的原因是因为,即使查询的是[2,3,1],但是数据库返回的顺序是ID从小到大的
goods_li = []
for id in sku_ids:
goods = GoodsSKU.objects.get(id=id)
goods_li.append(goods)
# 用户中心的三个页面,info/order/site只有右侧的内容不同,但是左侧选项卡需要
# class:active来指定,所以这里给模板的templates需要带上page属性,便于分辨是哪一页
context = {'page': 'user',
'address': address,
'goods_li': goods_li,
}
return render(request, 'user_center_info.html', context)
class UserOrderView(LoginRequiredMixin, View):
'''用户中心-订单页'''
def get(self, request):
'''显示'''
# 获取用户的订单信息
return render(request, 'user_center_order.html', {'page': 'order'})
class AddressView(LoginRequiredMixin, View):
'''用户中心-地址页'''
def get(self, request):
'''显示'''
# 获取登录用户对应User对象
user = request.user
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
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请求方式
其中地址中的get_default_address方法,是创建了模型管理器类后的方法,代码如下:
from django.db import models
from django.contrib.auth.models import AbstractUser
from db.base_model import BaseModel
# Create your models here.
class User(AbstractUser, BaseModel):
'''用户模型类'''
class Meta:
db_table = 'df_user'
verbose_name = '用户'
verbose_name_plural = verbose_name
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)
except self.model.DoesNotExist:
# 不存在默认收获地址
address = None
return address
class Address(BaseModel):
'''地址模型类'''
user = models.ForeignKey('User', verbose_name='所属账户')
receiver = models.CharField(max_length=20, verbose_name='收件人')
addr = models.CharField(max_length=256, verbose_name='收件地址')
zip_code = models.CharField(max_length=6, null=True, verbose_name='邮政编码')
phone = models.CharField(max_length=11, verbose_name='联系电话')
is_default = models.BooleanField(default=False, verbose_name='是否默认')
# 自定义的模型管理器对象
objects = AddressManager()
class Meta:
db_table = 'df_address'
verbose_name = '地址'
verbose_name_plural = verbose_name
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:user' %}" {% if page == 'user' %}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 %}
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>{{ user.username }}</li>
{% if address %}
<li><span>联系方式:</span>{{ address.phone }}</li>
<li><span>联系地址:</span>{{ address.addr }}</li>
{% else %}
<li><span>联系方式:</span>无默认</li>
<li><span>联系地址:</span>无默认</li>
{% endif %}
</ul>
</div>
<h3 class="common_title2">最近浏览</h3>
<div class="has_view_list">
<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>
</div>
</div>
{% endblock right_content %}
user_center_order.html
{% 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 %}
user_center_cite.html
注意点:
- 表单提交记得加上{% csrf_token %}
- 表单提交没有指定提交地址时,会给当前地址栏中的url提交。
{% 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>
{% if address %}
<dd>{{ address.addr }} ({{ address.receiver }} 收) {{ address.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" value="提交" class="info_submit">
</form>
</div>
</div>
{% endblock right_content %}
历史记录的实现
- 采用第三方包django-redis包来是实现,注意两个版本4.7.0和3.8.4,安装时候很坑,会把相关的django和redis包安装到最新版本,这里采用3.8.4的版本,安装过程中发现了一个现象,电脑中的redis版本和python环境的中的redis版本不一定要统一,如:电脑中的是redis2.8.0,而为了兼容django-redis,pip安装了redis2.10.0,发现功能还是可以正常使用的。
settings.py
# Django的缓存配置
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/9",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
# 配置session存储
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
历史记录实现:
- 最新访问的页面肯定第一个显示,因此需要保证数据库查询的顺序,同时在插入的时候,需要保证在左侧插入,需要用lpush方法,记录的过程在后面实现。
- 采用单用户对应访问列表的方式,键是用户,值是商品的id。
# 获取用户的历史浏览记录
# from redis import StrictRedis
# sr = StrictRedis(host='172.16.179.130', port='6379', db=9)
con = get_redis_connection('default')
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)
#
# goods_res = []
# for a_id in sku_ids:
# for goods in goods_li:
# if a_id == goods.id:
# goods_res.append(goods)
# 遍历获取用户浏览的商品信息,这里之所以要这样的做的原因是因为,即使查询的是[2,3,1],但是数据库返回的顺序是ID从小到大的
goods_li = []
for id in sku_ids:
goods = GoodsSKU.objects.get(id=id)
goods_li.append(goods)
# 用户中心的三个页面,info/order/site只有右侧的内容不同,但是左侧选项卡需要
# class:active来指定,所以这里给模板的templates需要带上page属性,便于分辨是哪一页
context = {'page': 'user',
'address': address,
'goods_li': goods_li,
}
return render(request, 'user_center_info.html', context)
对应的template:
<h3 class="common_title2">最近浏览</h3>
<div class="has_view_list">
<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>
</div>