登陆页
1 实现用户的登录功能
用户中心
用户中心信息页,显示登录用户的信息,包括用户名、电话和地址,同时页面下方显示出用户最近浏览的商品信息。
用户中心地址页:显示登陆用户的默认收件地址,页面下方的表单可以新增用户的收货地址。
用户中心订单页:显示登录用户的订单信息。
首页
1.动态指定首页轮播商品信息。
2.动态指定首页活动信息。
3.动态获取商品的种类信息并显示。
4.动态指定首页显示的每个种类的商品(包括图片商品的文字商品)。
5.点击某一个商品时跳转到商品的详情页面。
商品详情页
1.显示出某个商品的详细信息。
2.页面下方显示出该商品的两个新品信息。
3.商品列表页
显示出某一个种类的商品的列表数据,分页显示并支持按照默认、价格和人气进行排序。
页面下方显示出该商品的两个新品信息。
4.其他
通过搜索框搜索商品信息。
列表页和详情页将商品添加到购物车。
用户登录后,首页,详情页,列表页显示用户购物车中的商品数目。
购物车页面:对用户购物车中的商品操作。如选择某件商品,增加或减少购物车中的商品数目。
提交订单页面:显示用户准备购买的商品信息。
点击提交订单完成订单的创建。
用户中心订单页显示用户的订单信息。
点击支付完成订单的支付。
点击评价完成订单的评价。
遇到的问题集合
当使用makemigrations时报错No changes detected
在修改了models.py后,有些用户会喜欢用python manage.py makemigrations生成对应的py代码。
但有时执行python manage.py makemigrations命令(也可能人比较皮,把migrations文件夹给删了),会提示"No changes detected." 可能有用的解决方式如下:
先 python manage.py makemigrations --empty yourappname 生成一个空的initial.py
再 python manage.py makemigrations 生成原先的model对应的migration file
逐行迁移文件
https://stackoverflow.com/questions/41398949/django-related-model-users-userprofile-cannot-be-resolved
as_view()最终干的事情就是根据request请求方式来执行视图类的不同请求方法
settings.py 设置静态文件路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
{% load staticfiles %}
类视图的使用
from django.views.generic import View
class RegisterView(View):
"""注册"""
def get(self, request):
"""显示注册页面"""
return render(request, 'register.html')
def post(self, request):
"""进行注册处理"""
return register_handle(request)
urls.py
re_path(r'^register$', RegisterView.as_view(), name='register'),
re_path(r'^active/(?P.*)$' , ActiveView.as_view(), name='active'),
视图views.py
生成token
def register_handle(request):
"""进行注册的处理"""
# 接收数据
username = request.POST.get('user_name')
password = request.POST.get('pwd')
email = request.POST.get('email')
allow = request.POST.get('allow')
# 进行数据校验
if not all([username, password, email]):
# 数据有误
return render(request, 'register.html', {'errmsg': '数据不完整'})
# 校验邮箱
if not re.match(r'^[a-z0-9][\w.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
# 数据有误
return render(request, 'register.html', {'errmsg': '邮箱格式不对'})
if allow != 'on':
return render(request, 'register.html', {'errmsg': '请同意协议条款再进行注册'})
# 校验用户是否重复
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = None
if user:
return render(request, 'register.html', {'errmsg': '用户名已存在'})
# 进行业务处理:进行用户注册
user = User.objects.create_user(username, email, password)
user.is_active = 0 #不激活
user.save()
# 发送激活邮件,包含激活链接
# 激活链接中需要包含用户的身份信息,并且需要将身份信息进行加密处理
# 加密用户身份信息,生成激活Token。过期时间的单位为秒
serializer = Serializer(settings.SECRET_KEY, 3600*72)
info = {'confirm': user.id}
token = serializer.dumps(info) # 此处返回是数据类型是 bytes,将字节流转换成字符串需要对其进行解码。
token = token.decode() # 解码变成字符串
# 发邮件
send_register_active_email.delay(email, username, token)
# 返回应答 反向解析,跳转到首页
return redirect(reverse('goods:index'))
激活处理
class ActiveView(View):
"""用户激活"""
def get(self, request, token):
"""进行用户激活"""
# 进行解密,获取要激活的用户信息
serializer = Serializer(settings.SECRET_KEY, 3600*72)
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('激活链接已过期')
Celery分为3个部分
(1)worker部分负责任务的处理,即工作进程(我的理解工作进程就是你写的python代码,当然还包括python调用系统工具功能)
(2)broker部分负责任务消息的分发以及任务结果的存储,这部分任务主要由中间数据存储系统完成,比如消息队列服务器RabbitMQ、redis、
Amazon SQS、MongoDB、IronMQ等或者关系型数据库,使用关系型数据库依赖sqlalchemy或者django的ORM
(3)Celery主类,进行任务最开始的指派与执行控制,他可以是单独的python脚本,也可以和其他程序结合,应用到django或者flask等web框架里面以及你能想到的任何应用
# 在任务处理者一端加这几句
# import os
# import django
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyfresh.settings")
# django.setup()
# 创建一个Celery类的实例对象
app = Celery('celery_tasks.tasks', broker='redis://127.0.0.1:6379/8')
tips:与异步文件夹处于同一目录下
linux:
celery -A celery_tasks.tasks worker -l info
windows:
celery -A celery_tasks.tasks worker --loglevel=info --pool=solo
# 使用celery
from django.core.mail import send_mail
from django.conf import settings
from celery import Celery
import time
# 在任务处理者worker一端加这几句
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mydailyfresh.settings")
django.setup()
# 创建一个Celery类的实例对象
app = Celery('celery_tasks.tasks', broker='redis://127.0.0.1:6379/8')
# 定义任务函数
@app.task
def send_register_active_email(to_email, username, token):
'''发送激活邮件'''
# 组织邮件信息
subject = '天天生鲜欢迎信息'
message = ''
sender = settings.EMAIL_FROM
receiver = [to_email]
html_message = '%s, 欢迎您成为天天生鲜注册会员
请点击下面链接激活您的账户
http://127.0.0.1:8000/user/active/%s' % (username, token, token)
send_mail(subject, message, sender, receiver, html_message=html_message)
time.sleep(5)
send_register_active_email.delay(email, username, token)
# 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"
Django 2.1 用户认证系统authenticate()一直返回None
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': '数据不完整'})
# 业务处理:登录校验
try:
user=User.objects.get(username=username)
pwd=user.password
# user = authenticate(username=username, password=password)
if check_password(password,pwd) and user is not None:
# if user is not None:
if user.is_active:
# 记录用户登录状态
login(request, user)
# 判断是否需要记住用户名
remember = request.POST.get('remember')
# 获取登录后所要跳转的地址
# 获取get上请求的路径,默认跳转到首页
# response = redirect(reverse('goods:index')) # #HttpResponseRedirect
next_url = request.GET.get('next', reverse('goods:index'))
response = redirect(next_url)
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': '用户名或密码错误'})
except:
return render(request, 'login.html', {'errmsg': '无此用户'})
用户中心页面显示
urls.py
re_path(r'^$', UserInfoView.as_view(), name='user'), # 用户中心-信息页
re_path(r'^order$', UserOrderView.as_view(), name='order'), # 用户中心-订单页
re_path(r'^address$', AddressView.as_view(), name='address'), # 用户中心-地址页
老版的Django使用装饰器login_required来限制用户登录
新版的Dajngo通过继承LoginRequiredMixin类来限制用户登录,必须是第一个继承,在继承列表最左侧位置
utils.mixin.py
from django.contrib.auth.decorators import login_required
# 只有在使用了内置的登录信息认证系统的时候,才能使用内置的装饰器函数login_required()
class LoginRequiredMixin(object):
@classmethod
def as_view(cls, **initkwargs):
# 调用父类的as_view,以下两种方法都可调用super的方法
# view = super().as_view(**initkwargs)
view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
return login_required(view)
from utils.mixin import LoginRequiredMixin # 自定义认证检查
class UserInfoView(LoginRequiredMixin, View):
def get(self, request):
# 方法一,使用函数检查登录装饰器 login_required(UserInfoView.as_view()),
# re_path(r'^$', login_required(UserInfoView.as_view()), name='user'), # 用户中心-信息页
# re_path(r'^order$', login_required(UserOrderView.as_view()), name='order'), # 用户中心-订单页
# re_path(r'^address$', login_required(AddressView.as_view()), name='address'), # 用户中心-地址页
# 方法二,继承LoginRequiredMixin对象,进行检查。
re_path(r'^$', UserInfoView.as_view(), name='user'), # 用户中心-信息页
re_path(r'^order$', UserOrderView.as_view(), name='order'), # 用户中心-订单页
re_path(r'^address$', AddressView.as_view(), name='address'), # 用户中心-地址页
如果用户还没有登录,默认会跳转到‘/accounts/login/’。这个值可以在settings文件中通过LOGIN_URL参数来设定。(后面还会自动加上你请求的url作为登录后跳转的地址,如: /accounts/login/?next=/polls/3/ 登录完成之后,会去请求/poll/3)
因此,settings.py设置默认值
# 配置登录url地址,当认证检查不通过时,会自动跳转到指定的界面
LOGIN_URL = '/user/login' # 默认的地址是 /accounts/login,需要重新设置来修改默认值
未登录时自动跳转http://127.0.0.1:8000/user/login?next=/user/
登录页
不设置表单action时,提交表单时,会向浏览器地址栏中的地址提交数据,因此,登录逻辑改变,获取next的地址
# 获取登录后所要跳转的地址
# 获取get上请求的路径,默认跳转到首页
# response = redirect(reverse('goods:index')) # #HttpResponseRedirect
next_url = request.GET.get('next', reverse('goods:index')) # 无next时返回默认值reverse('goods:index')
response = redirect(next_url)
登录后应该不显示登录和注册按钮
模板中可以直接使用request对象的,比如request.user。
如果不能的话需要settings中进行配置TEMPLATES的OPTIONS.context_processors增加
django.template.context_processors.request:
request.user.is_authenticated()
除了你给模板文件传递的模板变量之外,Django框架会自动把request.user也传递给模板文件,这样就可以直接在模板文件中只用这个user对象。
如果用户未登录,那么request.user则是一个AnonymousUser(匿名用户)对象。is_authenticated()返回false。
如果用户已登录,那么request.user则是一个真实的User实例对象。is_authenticated()返回true。
<div class="header_con">
<div class="header">
<div class="welcome fl">欢迎来到天天生鲜!</div>
<div class="fr">
{% 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 %}
# /user/logout
class LogoutView(View):
"""退出登录"""
def get(self, request):
"""退出登录"""
# 清除用户的session信息
logout(request)
# 跳转到首页
return redirect(reverse('goods:index'))
model.py 添加 地址模型管理器类
class AddressManager(models.Manager):
"""地址模型管理器类"""
# 1.改变原有查询的结果集:all()
# 2.封装方法:用户操作模型类对应的数据表(增删改查)
def get_default_address(self, user):
# print(self) 这里的self就是user.Address.objects
"""获取用户默认收货地址"""
# self.model:获取self对象所在的模型类
try:
address = self.get(user=user, is_default=True) # models.Manager
except self.model.DoesNotExist:
# 不存在默认收货地址
address = None
return address
# /user/address
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
# 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请求方式
{% 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 %}
class UserInfoView(LoginRequiredMixin, View):
def get(self, request):
# request.user.is_authenticated()
# 除了你给模板文件传递的模板变量之外,Django框架会自动把request.user也传递给模板文件,这样就可以直接在模板文件中只用这个user对象。
# 如果用户未登录,那么request.user则是一个AnonymousUser(匿名用户)对象。is_authenticated()返回false。
# 如果用户已登录,那么request.user则是一个真实的User实例对象。is_authenticated()返回true。
# 获取用户的个人信息
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)
# 遍历获取用户浏览的商品信息
goods_li = []
for id in sku_ids:
goods = GoodsSKU.objects.get(id=id)
goods_li.append(goods)
# 组织上下文
context = {'page': 'user',
'address': address,
'goods_li': goods_li}
return render(request, 'user_center_info.html', context)
{% 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 %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<img src="http://47.98.126.56:8888/group1/M00/00/00/L2J-OF45VlqAR9y7AADGU6re_xM3605706">
</body>
</html>
天天生鲜Django项目②