一、天天生鲜项目分析及架构
1、电商模式
天天生鲜项目属于B2C(Business to Customer)电商模式,即企业对个人。
2、需求分析
2.1 用户模块
1) 注册页
- l 注册时校验用户名是否已被注册。
- l 完成用户信息的注册。
- l 给用户的注册邮箱发送邮件,用户点击邮件中的激活链接完成用户账户的激活。
2) 登录页
- l 实现用户的登录功能。
3) 用户中心
- l 用户中心信息页:显示登录用户的信息,包括用户名、电话和地址,同时页面下方显示出用户最近浏览的商品信息。
- l 用户中心地址页:显示登录用户的默认收件地址,页面下方的表单可以新增用户的收货地址。
- l 用户中心订单页:显示登录用户的订单信息。
4) 其他
- l 如果用户已经登录,页面顶部显示登录用户的信息。
2.2 商品相关
1) 首页
- l 动态指定首页轮播商品信息。
- l 动态指定首页活动信息。
- l 动态获取商品的种类信息并显示。
- l 动态指定首页显示的每个种类的商品(包括图片商品和文字商品)。
- l 点击某一个商品时跳转到商品的详情页面。
2) 商品详情页
- l 显示出某个商品的详情信息。
- l 页面的左下方显示出该种类商品的2个新品信息。
3)商品列表页
- l 显示出某一个种类商品的列表数据,分页显示并支持按照默认、价格、和人气进行排序。
- l 页面的左下方显示出该种类商品的2个新品信息。
4)其他
- l 通过页面搜索框搜索商品信息。
2.3 购物车相关
- l 列表页和详情页将商品添加到购物车。
- l 用户登录后,首页,详情页,列表页显示登录用户购物车中商品的数目。
- l 购物车页面:对用户购物车中商品的操作。如选择某件商品,增加或减少购物车中商品的数目。
2.4 订单相关
- l 提交订单页面:显示用户准备购买的商品信息。
- l 点击提交订单完成订单的创建。
- l 用户中心订单页显示用户的订单信息。
- l 点击支付完成订单的支付。
3、项目架构
4、数据表结构
5、富文本编辑器
借助富文本编辑器,网站的编辑人员能够像使用offfice一样编写出漂亮的、所见即所得的页面。此处以tinymce为例,其它富文本编辑器的使用也是类似的。
在虚拟环境中安装包。
pip install django-tinymce==2.6.0
安装完成后,可以使用在Admin管理中,也可以自定义表单使用。
示例
1)在test6/settings.py中为INSTALLED_APPS添加编辑器应用。
INSTALLED_APPS = ( ... 'tinymce', )配置setting.py
2)在test6/settings.py中添加编辑器配置。
TINYMCE_DEFAULT_CONFIG = { 'theme': 'advanced', 'width': 600, 'height': 400, }配置setting.py
3)在test6/urls.py中配置编辑器url。
urlpatterns = [ ... url(r'^tinymce/', include('tinymce.urls')), ]配置url
6、项目架构
二、各个模块功能的完成
用户模块--user
1、用户注册
用户注册的思路:用户通过注册模板文件提交了form表单到django后台,django中view函数接收这些参数,对参数进行校验,然后向用户发送邮件,用户通过点击邮件中的链接进行激活,view函数将数据保存到MySQL数据库中,其中调用celery异步队列处理发送邮件的任务。
- (1)注册模板文件中的form表单
<form action="/user/register" method="post"> {% 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> {{ errmsg }} </form>注册模板
- (2) 注册视图函数
1 from django.shortcuts import render,redirect 2 from django.http import HttpResponse 3 from django.urls import reverse 4 from django.views.generic import View 5 from itsdangerous import TimedJSONWebSignatureSerializer as Serializer 6 from itsdangerous import SignatureExpired 7 from django.conf import settings 8 from django.core.mail import send_mail 9 import re 10 from user.models import User 11 from celery_tasks.tasks import send_register_active_email 12 13 ''' 14 def register(request): 15 ## 注册 16 if request.method == 'GET': 17 ## 注册页面 18 return render(request,'register.html') 19 else: 20 ## 注册处理 21 ## 1、接收数据 22 username = request.POST.get('user_name') 23 password = request.POST.get('pwd') 24 email = request.POST.get('email') 25 allow = request.POST.get('allow') 26 27 ## 校验username在数据库中是否存在 28 try: 29 user = User.objects.get(username=username) 30 except Exception as e: 31 user = None 32 if user: 33 ## 用户名存在 34 return render(request, 'register.html', {'errmsg': '用户已存在'}) 35 36 ## 2、校验数据 37 if not all([username, password, email]): 38 ## 数据不完整 39 return render(request, 'register.html', {'errmsg': '数据不完整'}) 40 if not re.match('^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email): 41 return render(request, 'register.html', {'errmsg': '邮箱格式不正确'}) 42 if allow != 'on': 43 return render(request, 'register.html', {'errmsg': '请同意协议'}) 44 45 ## 3、保存进数据库 46 user = User.objects.create_user(username, email, password) 47 user.is_active = 0 48 user.save() 49 50 ## 4、返回应答 51 return redirect('goods:index') 52 53 def register_handle(request): 54 ## 注册处理 55 ## 1、接收数据 56 username = request.POST.get('user_name') 57 password = request.POST.get('pwd') 58 email = request.POST.get('email') 59 allow = request.POST.get('allow') 60 61 ## 校验username在数据库中是否存在 62 try: 63 user = User.objects.get(username=username) 64 except Exception as e: 65 user = None 66 if user: 67 ## 用户名存在 68 return render(request,'register.html',{'errmsg':'用户已存在'}) 69 70 ## 2、校验数据 71 if not all([username,password,email]): 72 ## 数据不完整 73 return render(request,'register.html',{'errmsg':'数据不完整'}) 74 if not re.match('^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$',email): 75 return render(request,'register.html',{'errmsg':'邮箱格式不正确'}) 76 if allow != 'on': 77 return render(request,'register.html',{'errmsg':'请同意协议'}) 78 79 ## 3、保存进数据库 80 user = User.objects.create_user(username,email,password) 81 user.is_active = 0 82 user.save() 83 84 ## 4、返回应答 85 return redirect('goods:index') 86 ''' 87 88 class RegisterView(View): 89 '''注册类视图''' 90 def get(self,request): 91 '''注册页面''' 92 return render(request,'register.html') 93 def post(self,request): 94 '''注册处理''' 95 ## 1、接收数据 96 username = request.POST.get('user_name') 97 password = request.POST.get('pwd') 98 email = request.POST.get('email') 99 allow = request.POST.get('allow') 100 101 ## 校验username在数据库中是否存在 102 try: 103 user = User.objects.get(username=username) 104 except Exception as e: 105 user = None 106 if user: 107 ## 用户名存在 108 return render(request, 'register.html', {'errmsg': '用户已存在'}) 109 110 ## 2、校验数据 111 if not all([username, password, email]): 112 ## 数据不完整 113 return render(request, 'register.html', {'errmsg': '数据不完整'}) 114 if not re.match('^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email): 115 return render(request, 'register.html', {'errmsg': '邮箱格式不正确'}) 116 if allow != 'on': 117 return render(request, 'register.html', {'errmsg': '请同意协议'}) 118 119 ## 3、保存进数据库 120 user = User.objects.create_user(username, email, password) 121 user.is_active = 0 122 user.save() 123 124 ## 4、发送邮件验证 http://127.0.0.1:8000/user/register/active/id 125 ## 对id 进行加密 126 serializer = Serializer(settings.SECRET_KEY,3600) 127 info = {'confirm':user.id} 128 token = serializer.dumps(info).decode('utf8') 129 130 ## 调用celery队列 131 send_register_active_email.delay(email, username, token) 132 133 ## 5、返回应答 134 return redirect(reverse('goods:index')) 135 136 class ActiveView(View): 137 '''邮件认证处理''' 138 def get(self,request,token): 139 try: 140 serializer = Serializer(settings.SECRET_KEY, 3600) 141 res = serializer.loads(token) 142 143 ## 获取用户名id 144 user_id = res['confirm'] 145 146 ## 修改数据库 147 user = User.objects.get(id=user_id) 148 user.is_active = 1 149 user.save() 150 151 ## 跳转到首页 152 return redirect(reverse('user:login')) 153 except SignatureExpired as e: 154 return HttpResponse('验证过期') 155 156 class LoginView(View): 157 '''登录页面''' 158 def get(self,request): 159 return render(request,'login.html')注册视图函数
-
(3)url配置
1 from django.conf.urls import url 2 from user.views import RegisterView,ActiveView,LoginView 3 4 urlpatterns = [ 5 # url(r'^register$',views.register,name='register'),## 显示注册页面 6 # url(r'^register_handle$',views.register_handle,name='register_handle'),## 注册处理 7 8 url(r'^register$',RegisterView.as_view(),name='register'),## 注册 9 url(r'^active/(?P<token>.*)',ActiveView.as_view(),name='active'),## 验证处理 10 url(r'^login$',LoginView.as_view(),name='login'),## 登录页面 11 ]url配置
-
(4)发送邮件的步骤
发送邮件需要使用SMTP服务器,常用的免费服务器有:163、126、QQ,下面以163邮件为例。
- 1)注册163邮箱itcast88,登录后设置。
- 2)在新页面中勾选“开启”,弹出新窗口扫码发送短信,记住授权密码。
- 3)打开dailyfresh/settings.py文件,进行下面配置配置。
1 # 发送邮件配置 2 EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 3 # smpt服务地址 4 EMAIL_HOST = 'smtp.163.com' 5 EMAIL_PORT = 25 6 # 发送邮件的邮箱 7 EMAIL_HOST_USER = 'a1240499170@163.com' 8 # 在邮箱中设置的客户端授权密码 9 EMAIL_HOST_PASSWORD = 'FDMQZVZOAILJZUVZ' 10 # 收件人看到的发件人 11 EMAIL_FROM = '天天生鲜<a1240499170@163.com>'settings配置
- 4)视图函数中发送邮件
1 from django.core.mail import send_mail 2 def send_register_active_email(to_email, username, token): 3 '''发送邮件''' 4 ## 发送邮件 5 subject = '天天生鲜欢迎信息' 6 message = '' 7 sender = settings.EMAIL_FROM 8 reciever = [to_email] 9 html_message = ''' 10 <h1>%s, 欢迎您成为天天生鲜注册会员</h1> 11 <h3>请点击下面链接激活您的账户</h3> 12 <a href="http://127.0.0.1:8000/user/active/%s"> 13 http://127.0.0.1:8000/user/active/%s 14 </a> 15 ''' % (username, token, token) 16 time.sleep(5) 17 send_mail(subject, message, sender, reciever, html_message=html_message)视图函数
-
(5)调用celery异步队列的步骤
详情见https://www.cnblogs.com/maoxinjueluo/p/12857651.html
2、用户登录
- (1)用户注册的form表单
<form method="post"> {% csrf_token %} <input type="text" name="username" value="{{ username }}" class="name_input" 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>用户注册form表单
-
(2)对应视图函数
1 from django.contrib.auth import authenticate,login 2 class LoginView(View): 3 '''登录页面''' 4 def get(self,request): 5 ## 判断是否记住密码 6 if 'username' in request.COOKIES: 7 username = request.COOKIES['username'] 8 checked = 'checked' 9 else: 10 username = '' 11 checked = '' 12 return render(request,'login.html',{'username':username,'checked':checked}) 13 14 def post(self,request): 15 '''登录校验''' 16 ## 1、接收数据 17 username = request.POST.get('username') 18 password = request.POST.get('pwd') 19 20 ## 2、数据校验 21 if not all([username,password]): 22 ## 数据不完整 23 return render(request,'login.html',{'errmsg':'用户名或密码不完整'}) 24 25 ## 3、业务处理 26 ## 校验数据库 27 user = authenticate(username=username, password=password) 28 print(user) 29 if user is not None: 30 ## 用户存在 31 if user.is_active: 32 ## 用户已激活 33 login(request,user) 34 response = redirect(reverse('goods:index')) 35 36 ## 判断是否记住用户名 37 remember = request.POST.get('remember') 38 if remember == 'on': 39 ## 记住用户名 40 response.set_cookie('username',username,max_age=7*24*3600) 41 else: 42 response.delete_cookie('username') 43 ## 返回应答 44 return response 45 else: 46 ## 用户未激活 47 return render(request,'login.html',{'errmsg':'用户未激活'}) 48 49 else: 50 ## 用户名或密码错误 51 return render(request,'login.html',{'errmsg':'用户名或密码错误'})登录视图函数
-
补充:
在登录时,如果登录的账号还未激活,可能会出现数据库校验一直未None的情况,使得用户登录信息判断失误
解决方法:
在django的setting文件中添加
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']
-
(3)用Redis设置session缓存
1)安装django-redis
pip install diango-redis
2)进行settings文件的配置
1 # Django的缓存配置 2 CACHES = { 3 "default": { 4 "BACKEND": "django_redis.cache.RedisCache", 5 "LOCATION": "redis://127.0.0.1:6379/4", 6 "OPTIONS": { 7 "CLIENT_CLASS": "django_redis.client.DefaultClient", 8 } 9 } 10 } 11 # 配置session存储 12 SESSION_ENGINE = "django.contrib.sessions.backends.cache" 13 SESSION_CACHE_ALIAS = "default"settings配置
3、父模板抽象
将各个模板*同有的模块抽取出来,抽象成父模板,子模板只需要继承父模板,改写父模板中不同的模块就可以了
{# 首页 注册 登录 #} <!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> <body> {# 网页顶部欢迎信息块 #} {% block header_con %} <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 %} <div class="user_link fl"> <span>|</span> <a href="{% url 'user:user' %}">用户中心</a> <span>|</span> <a href="cart.html">我的购物车</a> <span>|</span> <a href="{% url 'user:order' %}">我的订单</a> </div> </div> </div> </div> {% endblock header_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
{# 详情页 列表页 #} {% 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 %}base_detail_list.html
{# 购物车 提交订单 #} {% extends 'base.html' %} {% load staticfiles %} {# 网页顶部搜索框块 #} {% 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
{# 用户中心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 %}base_user_center.html
4、用户中心
-
1)显示用户中心的视图函数及url配置
1 class UserInfoView(View): 2 '''用户中心-详情页''' 3 def get(self,request): 4 '''显示用户详情页''' 5 return render(request,'user_center_info.html',{'page':'user'}) 6 7 class OrderView(View): 8 '''用户中心-订单页''' 9 def get(self,request): 10 '''显示用户订单页''' 11 return render(request,'user_center_order.html',{'page':'order'}) 12 13 class AddressView(View): 14 '''用户中心-地址页''' 15 def get(self,request): 16 '''显示用户地址页''' 17 return render(request,'user_center_site.html',{'page':'address'})用户中心视图函数
1 urlpatterns = [ 2 3 url(r'^$',UserInfoView.as_view(),name='user'),## 用户中心-详情页 4 url(r'^order$',OrderView.as_view(),name='order'),## 订单页 5 url(r'^address$',AddressView.as_view(),name='address'),## 地址页 6 7 ]对应的url文件
出现问题:用户即使没有登录也可以访问用户中心,我们需要借用django内置的login_required装饰器对用户中心的访问进行限制,没有进行登录时,访问用户中心则跳转到用户登录页面。
-
2)django内置的login_required装饰器
由于需要在访问用户中心前判断用户是否已登录,所以需要在url文件中使用 login_required装饰器
- 第一步,重新使用url文件
- 第二步,setting中添加设置LOGIN_URL
作用:当判断用户未登录时,会跳转至LOGIN_URL指定的路径,此时还会在跳转路径后添加用户访问的url,如:/user/login/?next=/user/info/,当用户登录后会自动跳转至/user/info/下。 LOGIN_URL = '/user/login/'
- 第三步,登录视图函数接收 request.GET.get("next")
1 class LoginView(View): 2 '''登录页面''' 3 def get(self,request): 4 ## 判断是否记住密码 5 if 'username' in request.COOKIES: 6 username = request.COOKIES['username'] 7 checked = 'checked' 8 else: 9 username = '' 10 checked = '' 11 return render(request,'login.html',{'username':username,'checked':checked}) 12 13 def post(self,request): 14 '''登录校验''' 15 ## 1、接收数据 16 username = request.POST.get('username') 17 password = request.POST.get('pwd') 18 19 ## 2、数据校验 20 if not all([username,password]): 21 ## 数据不完整 22 return render(request,'login.html',{'errmsg':'用户名或密码不完整'}) 23 24 ## 3、业务处理 25 ## 校验数据库 26 user = authenticate(username=username, password=password) 27 print(user) 28 if user is not None: 29 ## 用户存在 30 if user.is_active: 31 ## 用户已激活 32 33 ## 接收next 34 ## 指定默认值为'index' 35 ## 登录成功后跳转到next_url 36 next_url = request.GET.get('next',reverse('goods:index')) 37 login(request,user) 38 response = redirect(next_url) 39 40 ## 判断是否记住用户名 41 remember = request.POST.get('remember') 42 if remember == 'on': 43 ## 记住用户名 44 response.set_cookie('username',username,max_age=7*24*3600) 45 else: 46 response.delete_cookie('username') 47 ## 返回应答 48 return response 49 else: 50 ## 用户未激活 51 return render(request,'login.html',{'errmsg':'用户未激活'}) 52 53 else: 54 ## 用户名或密码错误 55 return render(request,'login.html',{'errmsg':'用户名或密码错误'})登录的视图函数
- 补充:
我们会发现,其实上面的url配置还是很麻烦,只要是需要判断是否已登录的我们都需要调用 login_required 方法来封装as_view方法,所以我们可以创建一个文件封装好 login_required方法,然后继承这个文件就好了。下面我们创建这个文件
1 from django.contrib.auth.decorators import login_required 2 from django.views.generic import View 3 4 class LoginRequiredMiXin(View): 5 @classmethod 6 def as_view(cls, **initkwargs): 7 as_view = super().as_view(**initkwargs) 8 return login_required(as_view)mixin.py
我们在视图函数中直接继承mixin.py 中的 LoginRequireMiXin 方法就可以了
1 from utils.mixin import LoginRequiredMiXin 2 3 class UserInfoView(LoginRequiredMiXin,View): 4 '''用户中心-详情页''' 5 def get(self,request): 6 '''显示用户详情页''' 7 return render(request,'user_center_info.html',{'page':'user'}) 8 9 class OrderView(LoginRequiredMiXin,View): 10 '''用户中心-订单页''' 11 def get(self,request): 12 '''显示用户订单页''' 13 return render(request,'user_center_order.html',{'page':'order'}) 14 15 class AddressView(LoginRequiredMiXin,View): 16 '''用户中心-地址页''' 17 def get(self,request): 18 '''显示用户地址页''' 19 return render(request,'user_center_site.html',{'page':'address'})视图函数
-
3)登录判断、登录和退出小结
from django.contrib.auth import authenticate,login,logout ## 判断数据库中是否存在此用户 authenticate(username,password) # 存在返回True,反之返回False ## 记录用户的登录状态 login(request,user) ## 退出登录 logout(request)
-
4)用户中心------地址页面
模板文件
{% 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" name="" value="提交" class="info_submit"> </form> </div> </div> {% endblock right_content %}地址的模板文件
视图函数
1 class AddressView(LoginRequiredMiXin,View): 2 '''用户中心-地址页''' 3 def get(self,request): 4 '''显示用户地址页''' 5 ## 显示数据 6 ## 判断是否为默认收货地址 7 user = request.user 8 try: 9 address = Address.objects.get(user=user, is_default=True) 10 except Address.DoesNotExist: 11 address = None 12 13 return render(request,'user_center_site.html',{'page':'address','address':address}) 14 15 def post(self,request): 16 '''接收地址数据''' 17 ## 接收数据 18 receiver = request.POST.get('receiver') 19 addr = request.POST.get('addr') 20 zip_code = request.POST.get('zip_code') 21 phone = request.POST.get('phone') 22 23 ## 数据校验 24 ## 校验数据是否完整 25 if not all([receiver,addr,phone]): 26 return render(request,'user_center_site.html',{'errmsg':'数据不完整'}) 27 ## 校验手机是否合法 28 if not re.match(r'^1[3|4|5|7|8][0-9]{9}$',phone): 29 return render(request,'user_center_site.html',{'errmsg':'手机格式不正确'}) 30 31 ## 业务处理 32 ## 判断是否为默认收货地址 33 user = request.user 34 try: 35 address = Address.objects.get(user=user,is_default=True) 36 except Address.DoesNotExist: 37 address = None 38 if address: 39 ## 已有默认收货地址 40 is_default = False 41 else: 42 is_default = True 43 44 ## 保存数据 45 Address.objects.create(user=user, 46 receiver=receiver, 47 addr=addr,zip_code=zip_code, 48 phone=phone, 49 is_default=is_default) 50 51 ## 返回应答 52 return redirect('user:address')地址视图函数
上面的视图函数中可以发现get函数和post函数中都需要判断是否为默认收货地址,我们可以将这一段代码封装在模型类中,在视图函数中直接调用模型类中的方法就可以了
1 class AddressManager(models.Manager): 2 '''地址模型管理器类''' 3 def get_default_address(self,user): 4 try: 5 address = self.get(user=user, is_default=True) 6 except self.model.DoesNotExist: 7 address = None 8 9 return address 10 11 class Address(BaseModel): 12 '''地址模型类''' 13 14 object = AddressManager()模型类
1 class AddressView(LoginRequiredMiXin,View): 2 '''用户中心-地址页''' 3 def get(self,request): 4 '''显示用户地址页''' 5 ## 显示数据 6 ## 判断是否为默认收货地址 7 user = request.user 8 # try: 9 # address = Address.objects.get(user=user, is_default=True) 10 # except Address.DoesNotExist: 11 # address = None 12 13 ## 调用模型类中封装的方法判断是否为默认收货地址 14 address = Address.objects.get_default_address(user) 15 16 return render(request,'user_center_site.html',{'page':'address','address':address}) 17 18 def post(self,request): 19 '''接收地址数据''' 20 ## 接收数据 21 receiver = request.POST.get('receiver') 22 addr = request.POST.get('addr') 23 zip_code = request.POST.get('zip_code') 24 phone = request.POST.get('phone') 25 26 ## 数据校验 27 ## 校验数据是否完整 28 if not all([receiver,addr,phone]): 29 return render(request,'user_center_site.html',{'errmsg':'数据不完整'}) 30 ## 校验手机是否合法 31 if not re.match(r'^1[3|4|5|7|8][0-9]{9}$',phone): 32 return render(request,'user_center_site.html',{'errmsg':'手机格式不正确'}) 33 34 ## 业务处理 35 ## 判断是否为默认收货地址 36 user = request.user 37 # try: 38 # address = Address.objects.get(user=user,is_default=True) 39 # except Address.DoesNotExist: 40 # address = None 41 42 ## 调用模型类中封装的方法判断是否为默认收货地址 43 address = Address.objects.get_default_address(user) 44 45 if address: 46 ## 已有默认收货地址 47 is_default = False 48 else: 49 is_default = True 50 51 ## 保存数据 52 Address.objects.create(user=user, 53 receiver=receiver, 54 addr=addr,zip_code=zip_code, 55 phone=phone, 56 is_default=is_default) 57 58 ## 返回应答 59 return redirect('user:address')修改后的视图函数
-
5)用户中心--------详情页
获取用户的历史浏览记录,由于历史浏览记录对于数据库的交互次数是很多的,所以这里使用Redis保存历史浏览记录
有两种方法可以进行Python与Redis的交互
- 第一种
1 from redis import StrictRedis 2 3 sr = StrictRedis(host='127.0.0.1',port=6379,db=4) ## 这里如果用到的是本机的Redis,括号里面的参数可以不填 4 5 数据库操作。。。
- 第二种使用diango-redis提供的方法
需要先配置settings文件
1 # Django的缓存配置 2 CACHES = { 3 "default": { 4 "BACKEND": "django_redis.cache.RedisCache", 5 "LOCATION": "redis://127.0.0.1:6379/4", 6 "OPTIONS": { 7 "CLIENT_CLASS": "django_redis.client.DefaultClient", 8 } 9 } 10 }settings配置
from django-redis import get_redis_connection con = get_redis_connection ## 效果和第一种方法是一样的,也是得到一个StrictRedis的实例对象
1 class UserInfoView(LoginRequiredMiXin,View): 2 '''用户中心-详情页''' 3 def get(self,request): 4 '''显示用户详情页''' 5 6 ## 显示用户个人信息 7 user = request.user 8 address = Address.objects.get_default_address(user) 9 10 ## 用Redis存储历史浏览记录,查询历史浏览记录 11 # from redis import StrictRedis 12 # sr = StrictRedis(host='127.0.0.1',port=6379,db=4) 13 14 ## 用django自带的get_redis_connection也可以做到 15 ## 得到一个StrictRedis对象 16 con = get_redis_connection('default') 17 ## 组织查询的key 18 history_key = 'history_%s'%user.id 19 ## 进行查询,得到查询集sku_id 20 sku_ids = con.lrange(history_key,0,4) 21 22 ## 向goods/models 中的GoodsSKU查询数据,遍历sku_ids 进行查询 23 goods_list = [] 24 for goods_id in sku_ids: 25 good = GoodsSKU.objects.get(id=goods_id) 26 goods_list.append(good) 27 28 ## 组织上下文 29 context = {'page':'user', 30 'address':address, 31 'goods_list':goods_list} 32 33 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 good in goods_list %} <li> <a href="detail.html"><img src="{{ good.image.url }}"></a> <h4><a href="detail.html">{{ good.name }}</a></h4> <div class="operate"> <span class="prize">¥{{ good.price }}</span> <span class="unit">{{ good.price }}/{{ good.unite }}g</span> <a href="#" class="add_goods" title="加入购物车"></a> </div> </li> {% empty %} <h1>无历史浏览记录</h1> {% endfor %} </ul> </div> </div> {% endblock right_content %}用户中心的模板文件
商品模块-------- goods
1、首页内容获取以及Redis存储购物车数据分析
首页view函数
1 from django.shortcuts import render 2 from django.views.generic import View 3 from django_redis import get_redis_connection 4 from django.core.cache import cache 5 from goods.models import GoodsType,IndexGoodsBanner,IndexPromotionBanner,IndexTypeGoodsBanner 6 7 # Create your views here. 8 9 class IndexView(View): 10 '''首页''' 11 def get(self,request): 12 '''显示首页''' 13 14 ## 获取缓存 15 context = cache.get('index_page_data') 16 if context is None: 17 print('设置缓存') 18 19 # 获取商品的种类信息 20 types = GoodsType.objects.all() 21 22 # 获取首页轮播商品信息 23 goods_banners = IndexGoodsBanner.objects.all().order_by('index') 24 25 # 获取首页促销活动信息 26 promotion_banners = IndexPromotionBanner.objects.all().order_by('index') 27 28 # 获取首页分类商品展示信息 29 for type in types: # GoodsType 30 # 获取type种类首页分类商品的图片展示信息 31 image_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=1).order_by('index') 32 # 获取type种类首页分类商品的文字展示信息 33 title_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=0).order_by('index') 34 35 # 动态给type增加属性,分别保存首页分类商品的图片展示信息和文字展示信息 36 type.image_banners = image_banners 37 type.title_banners = title_banners 38 39 context = {'types': types, 40 'goods_banners': goods_banners, 41 'promotion_banners': promotion_banners} 42 43 ## 设置缓存 44 cache.set('index_page_data', context, 3600) 45 46 # 获取用户购物车中商品的数目 47 user = request.user 48 cart_count = 0 49 if user.is_authenticated: 50 # 用户已登录 51 conn = get_redis_connection('default') 52 cart_key = 'cart_%d' % user.id 53 cart_count = conn.hlen(cart_key) 54 55 56 57 # 组织模板上下文 58 context.update(cart_count=cart_count) 59 60 61 # 使用模板 62 return render(request, 'index.html', context)首页view视图
{% 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/jquery-ui.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/slide.js' %}"></script> {% endblock topfiles %} {% block body %} <div class="navbar_con"> <div class="navbar"> <h1 class="fl">全部商品分类</h1> <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> <div class="center_con clearfix"> <ul class="subnav fl"> {% for type in types %} <li><a href="#model0{{ forloop.counter }}" class="{{ type.logo }}">{{ type.name }}</a></li> {% endfor %} </ul> <div class="slide fl"> <ul class="slide_pics"> {% for banner in goods_banners %} <li><a href="{% url 'goods:detail' banner.sku.id %}"><img src="{{ banner.image.url }}" alt="幻灯片"></a></li> {% endfor %} </ul> <div class="prev"></div> <div class="next"></div> <ul class="points"></ul> </div> <div class="adv fl"> {% for banner in promotion_banners %} <a href="{{ banner.url }}"><img src="{{ banner.image.url }}"></a> {% endfor %} </div> </div> {% for type in types %} <div class="list_model"> <div class="list_title clearfix"> <h3 class="fl" id="model0{{ forloop.counter }}">{{ type.name }}</h3> <div class="subtitle fl"> <span>|</span> {% for banner in type.title_banners %} <a href="{% url 'goods:detail' banner.sku.id %}">{{ banner.sku.name }}</a> {% endfor %} </div> <a href="#" class="goods_more fr" id="fruit_more">查看更多 ></a> </div> <div class="goods_con clearfix"> <div class="goods_banner fl"><img src="{{ type.image.url }}"></div> <ul class="goods_list fl"> {% for banner in type.image_banners %} <li> <h4><a href="{% url 'goods:detail' banner.sku.id %}">{{ banner.sku.name }}</a></h4> <a href="{% url 'goods:detail' banner.sku.id %}"><img src="{{ banner.sku.image.url }}"></a> <div class="prize">¥ {{ banner.sku.price }}</div> </li> {% endfor %} </ul> </div> </div> {% endfor %} {% endblock body %}模板文件
这里Redis存储用户信息用的数据形式是hash
参考文档:http://doc.redisfans.com/hash/hlen.html
2、FastDFS和Nignx上传商品图片
-
1)分布式文件系统FastDFS
在本次项目中商品的图片将存储在分布式文件新系统FastDFS中,FastDFS暂时还不支持window系统,所以安装使用都是在Linux系统中,FastDFS的安装配置见https://www.cnblogs.com/maoxinjueluo/p/12842719.html
-
2)项目中上传和使用图片的流程(重点)
使用FastDFS和Nignx的好处:
海量存储,存储容量扩展方便。
文件内容重复。
结合nginx提高网站访问图片的效率。
-
3)自定义文件存储类
因为django默认后台文件上传的文件会存储在本地电脑中,所以我们要自定义文件存储类,修改文件存储的路径,将文件存储到远程FastDFS分布式文件存储服务器中。
(1)创建一个文件管理storage文件(storage编写自定义文件存储类)
(2)改写client.conf的内容
详情见https://www.cnblogs.com/maoxinjueluo/p/12842719.html 中的第6条,这里将client.conf存在在fdfs文件中。
(3)编写storeage.py中的自定义文件存储类以及设置settings文件
1 # 设置Django的文件存储类 2 DEFAULT_FILE_STORAGE='utils.fdfs.storage.FDFSStorage' 3 4 # 设置fdfs使用的client.conf文件路径 5 FDFS_CLIENT_CONF='./utils/fdfs/client.conf' 6 7 # 设置fdfs存储服务器上nginx的IP和端口号 8 FDFS_URL='http://192.168.43.62:8888/'settings文件配置
1 from django.core.files.storage import Storage 2 from fdfs_client.client import Fdfs_client 3 from django.conf import settings 4 5 class FDFSStorage(Storage): 6 '''fast dfs 文件存储类''' 7 def __init__(self, client_conf=None, base_url=None): 8 '''初始化''' 9 if client_conf is None: 10 client_conf = settings.FDFS_CLIENT_CONF 11 self.client_conf = client_conf 12 13 if base_url is None: 14 base_url = settings.FDFS_URL 15 self.base_url = base_url 16 17 def _open(self, name, mode='rb'): 18 '''打开文件时使用''' 19 pass 20 21 def _save(self, name, content): 22 '''保存文件时使用''' 23 ## name:上传的文件名 24 ## content:包含上传文件内容的file对象 25 26 ## 创建一个client对象 27 client = Fdfs_client(self.client_conf) 28 29 ## 上传文件到fast dfs系统中 30 res = client.upload_by_buffer(content.read()) 31 32 # dict 33 # { 34 # 'Group name': group_name, 35 # 'Remote file_id': remote_file_id, 36 # 'Status': 'Upload successed.', 37 # 'Local file name': '', 38 # 'Uploaded size': upload_size, 39 # 'Storage IP': storage_ip 40 # } 41 if res.get('Status') != 'Upload successed.': 42 # 上传失败 43 raise Exception('上传文件到fast dfs失败') 44 45 # 获取返回的文件ID 46 filename = res.get('Remote file_id') 47 48 return filename 49 50 def exists(self, name): 51 '''Django判断文件名是否可用''' 52 return False 53 54 def url(self,name): 55 '''返回访问文件的url路径''' 56 return self.base_url+namestorage.py中的自定义文件存储类
fdfs上传图片小结:
3、首页
-
1)celery生成首页静态页面
由于首页无论是否登录都可以访问,并且首页是相对不变的,所以可以将首页生成静态页面,减少数据库的访问,当管理员登录后台管理页面修改商品时再重新生成静态页面,可以提高首页的访问速度。
详情见 https://www.cnblogs.com/maoxinjueluo/p/12857651.html 第3条。
-
2)admin管理更新首页数据表数据时重新生成index静态页面
详情见 https://www.cnblogs.com/maoxinjueluo/p/12857651.html 第4条。
-
3)首页数据缓存设置和获取
1 from django.shortcuts import render 2 from django.views.generic import View 3 from django_redis import get_redis_connection 4 from django.core.cache import cache 5 from goods.models import GoodsType,IndexGoodsBanner,IndexPromotionBanner,IndexTypeGoodsBanner 6 7 # Create your views here. 8 9 class IndexView(View): 10 '''首页''' 11 def get(self,request): 12 '''显示首页''' 13 14 ## 获取缓存 15 context = cache.get('index_page_data') 16 if context is None: 17 print('设置缓存') 18 19 # 获取商品的种类信息 20 types = GoodsType.objects.all() 21 22 # 获取首页轮播商品信息 23 goods_banners = IndexGoodsBanner.objects.all().order_by('index') 24 25 # 获取首页促销活动信息 26 promotion_banners = IndexPromotionBanner.objects.all().order_by('index') 27 28 # 获取首页分类商品展示信息 29 for type in types: # GoodsType 30 # 获取type种类首页分类商品的图片展示信息 31 image_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=1).order_by('index') 32 # 获取type种类首页分类商品的文字展示信息 33 title_banners = IndexTypeGoodsBanner.objects.filter(type=type, display_type=0).order_by('index') 34 35 # 动态给type增加属性,分别保存首页分类商品的图片展示信息和文字展示信息 36 type.image_banners = image_banners 37 type.title_banners = title_banners 38 39 context = {'types': types, 40 'goods_banners': goods_banners, 41 'promotion_banners': promotion_banners} 42 43 ## 设置缓存 44 cache.set('index_page_data', context, 3600) 45 46 # 获取用户购物车中商品的数目 47 user = request.user 48 cart_count = 0 49 if user.is_authenticated: 50 # 用户已登录 51 conn = get_redis_connection('default') 52 cart_key = 'cart_%d' % user.id 53 cart_count = conn.hlen(cart_key) 54 55 56 57 # 组织模板上下文 58 context.update(cart_count=cart_count) 59 60 61 # 使用模板 62 return render(request, 'index.html', context) 63 64 class DetailView(View): 65 '''详情页''' 66 def get(self,request,a): 67 '''显示详情页''' 68 return render(request,'detail.html')商品的视图函数
当管理员修改后台商品时,应该要删除缓存
1 from django.contrib import admin 2 from django.core.cache import cache 3 from goods.models import GoodsType,GoodsSKU,Goods,GoodsImage,IndexGoodsBanner,IndexTypeGoodsBanner,IndexPromotionBanner 4 5 # Register your models here. 6 7 class BaseModelAdmin(admin.ModelAdmin): 8 '''更新或删除生成首页静态页面模型类''' 9 10 def save_model(self, request, obj, form, change): 11 '''更新时调用''' 12 super().save_model(request,obj,form,change) 13 ## 发送任务队列 14 from celery_tasks.tasks import generate_static_index_html 15 generate_static_index_html.delay() 16 17 ## 删除缓存 18 cache.delete('index_page_data') 19 20 21 22 def delete_model(self, request, obj): 23 '''删除时调用''' 24 super().delete_model(request, obj) 25 ## 发送任务队列 26 from celery_tasks.tasks import generate_static_index_html 27 generate_static_index_html.delay() 28 29 ## 删除缓存 30 cache.delete('index_page_data')goods中的admin.py
参考文档:https://yiyibooks.cn/xx/django_182/topics/cache.html
-
4)页面静态化和缓存数据小结:
4、商品详情页
视图函数:
1 ## /goods/detailid 2 class DetailView(View): 3 '''详情页''' 4 def get(self,request,goods_id): 5 '''显示详情页''' 6 ## 1、查询数据 7 ## 查询商品 8 try: 9 ## 商品存在 10 sku = GoodsSKU.objects.get(id=goods_id) 11 except Exception: 12 return redirect(reverse('goods:index')) 13 14 ## 查询商品种类 15 types = GoodsType.objects.all() 16 17 ## 商品评论 18 goods_comments = OrderGoods.objects.filter(sku=sku).order_by('create_time').exclude(comment='') 19 20 ## 新品推荐 21 new_skus = GoodsSKU.objects.filter(type=sku.type).exclude(id=sku.id).order_by('-create_time')[:2] 22 23 ## 查询同一个SPU下的不同商品 24 same_spu_skus = GoodsSKU.objects.filter(goods=sku.goods).exclude(id=sku.id) 25 26 ## 获取购物车数量 27 user = request.user 28 cart_count = 0 29 if user.is_authenticated: 30 ## 用户已登录 31 conn = get_redis_connection('default') ## 连接Redis 32 cart_key = 'cart_%d'%user.id 33 cart_count = conn.hlen(cart_key) 34 35 ## 添加历史浏览记录 36 history_id = 'history_%d'%user.id 37 conn = get_redis_connection('default') 38 ## 删除列表中的history_id 39 conn.lrem(history_id,0,sku.id) 40 ## 从左侧插入 41 conn.lpush(history_id,sku.id) 42 ## 只保留5条记录 43 conn.ltrim(history_id,0,4) 44 45 46 ## 2、组织模板上下文 47 context = { 48 'sku':sku, 49 'types':types, 50 'goods_comments':goods_comments, 51 'new_skus':new_skus, 52 'same_spu_skus':same_spu_skus, 53 'cart_count':cart_count 54 } 55 56 ## 3、返回应答 57 return render(request,'detail.html',context)视图函数
模板文件:
{% extends 'base_detail_list.html' %} {% load static %} {% block title %}天天生鲜-商品详情{% endblock %} {% block main_content %} <div class="breadcrumb"> <a href="#">全部分类</a> <span>></span> <a href="#">{{ sku.type.name }}</a> <span>></span> <a href="#">商品详情</a> </div> <div class="goods_detail_con clearfix"> <div class="goods_detail_pic fl"><img src="{{ sku.image.url }}"></div> <div class="goods_detail_list fr"> <h3>{{ sku.name }}</h3> <p>{{ sku.desc }}</p> <div class="prize_bar"> <span class="show_pirze">¥<em>{{ sku.price }}</em></span> <span class="show_unit">单 位:{{ sku.unite }}</span> </div> <div class="goods_num clearfix"> <div class="num_name fl">数 量:</div> <div class="num_add fl"> <input type="text" class="num_show fl" value="1"> <a href="javascript:;" class="add fr">+</a> <a href="javascript:;" class="minus fr">-</a> </div> </div> <div class="total">总价:<em>16.80元</em></div> <div class="operate_btn"> <a href="javascript:;" class="buy_btn">立即购买</a> <a href="javascript:;" class="add_cart" id="add_cart">加入购物车</a> </div> <div> <p>其它规格:</p> <ul> {% for sku in same_spu_skus %} <li><a href="{% url 'goods:detail' sku.id %}">{{ sku.name }}</a></li> {% endfor %} </ul> </div> </div> </div> <div class="main_wrap clearfix"> <div class="l_wrap fl clearfix"> <div class="new_goods"> <h3>新品推荐</h3> <ul> {% for new_sku in new_skus %} <li> <a href="{% url 'goods:detail' new_sku.id %}"><img src="{{ new_sku.image.url }}"></a> <h4><a href="{% url 'goods:detail' new_sku.id %}">{{ new_sku.name }}</a></h4> <div class="prize">¥{{ new_sku.price }}</div> </li> {% endfor %} </ul> </div> </div> <div class="r_wrap fr clearfix"> <ul class="detail_tab clearfix"> <li class="active">商品介绍</li> <li>评论</li> </ul> <div class="tab_content"> <dl> <dt>商品详情:</dt> <dd>{{ sku.goods.detail|safe }}</dd> </dl> </div> <div class="tab_content"> <dl> {% for order in sku_orders %} <dt>评论时间:{{ order.update_time }} 用户名:{{ order.order.user.username }}</dt> <dd>评论内容:{{ order.comment }}</dd> {% endfor %} </dl> </div> </div> </div> {% endblock main_content %} {% block bottom %} <div class="add_jump"></div> {% endblock bottom %} {% block bottomfiles %} <script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script> <script type="text/javascript"> update_goods_amount() // 计算商品的总价格 function update_goods_amount() { // 获取商品的单价和数量 price = $('.show_pirze').children('em').text() count = $('.num_show').val() // 计算商品的总价 price = parseFloat(price) count = parseInt(count) amount = price*count // 设置商品的总价 $('.total').children('em').text(amount.toFixed(2)+'元') } // 增加商品的数量 $('.add').click(function () { // 获取商品原有的数目 count = $('.num_show').val() // 加1 count = parseInt(count)+1 // 重新设置商品的数目 $('.num_show').val(count) // 更新商品的总价 update_goods_amount() }) // 减少商品的数量 $('.minus').click(function () { // 获取商品原有的数目 count = $('.num_show').val() // 减1 count = parseInt(count)-1 if (count <= 0){ count = 1 } // 重新设置商品的数目 $('.num_show').val(count) // 更新商品的总价 update_goods_amount() }) // 手动输入商品的数量 $('.num_show').blur(function () { // 获取用户输入的数目 count = $(this).val() // 校验count是否合法 if (isNaN(count) || count.trim().length==0 || parseInt(count) <=0){ count = 1 } // 重新设置商品的数目 $(this).val(parseInt(count)) // 更新商品的总价 update_goods_amount() }) // 获取add_cart div元素左上角的坐标 var $add_x = $('#add_cart').offset().top; var $add_y = $('#add_cart').offset().left; // 获取show_count div元素左上角的坐标 var $to_x = $('#show_count').offset().top; var $to_y = $('#show_count').offset().left; $('#add_cart').click(function(){ // 获取商品id和商品数量 sku_id = $(this).attr('sku_id') // attr prop count = $('.num_show').val() csrf = $('input[name="csrfmiddlewaretoken"]').val() // 组织参数 params = {'sku_id':sku_id, 'count':count, 'csrfmiddlewaretoken':csrf} // 发起ajax post请求,访问/cart/add, 传递参数:sku_id count $.post('/cart/add', params, function (data) { if (data.res == 5){ // 添加成功 $(".add_jump").css({'left':$add_y+80,'top':$add_x+10,'display':'block'}) $(".add_jump").stop().animate({ 'left': $to_y+7, 'top': $to_x+7}, "fast", function() { $(".add_jump").fadeOut('fast',function(){ // 重新设置用户购物车中商品的条目数 $('#show_count').html(data.total_count); }); }); } else{ // 添加失败 alert(data.errmsg) } }) }) </script> {% endblock bottomfiles %} </body> </html>详情页的模板文件
5、商品列表页
视图函数:
1 ## /goods/list/type/page?sort=1 2 class ListView(View): 3 '''商品列表页''' 4 def get(self,request,type_id,page): 5 '''显示商品列表''' 6 7 ## 获取商品类型 8 try: 9 type = GoodsType.objects.get(id=type_id) 10 except GoodsType.DoesNotExist: 11 ## 种类不存在 12 return redirect(reverse('goods:index')) 13 14 ## 获取商品的分类信息 15 types = GoodsType.objects.all() 16 17 ## 获取排序方式 18 sort = request.GET.get('sort') 19 if sort == 'price': 20 ## 获取种类下的所有商品信息 21 goods_skus = GoodsSKU.objects.filter(type=type).order_by('price') 22 elif sort == 'hot': 23 goods_skus = GoodsSKU.objects.filter(type=type).order_by('-sales') 24 else: 25 goods_skus = GoodsSKU.objects.filter(type=type).order_by('-id') 26 sort = 'default' 27 28 29 ## 获取所有商品的页码 30 paginator = Paginator(goods_skus,1) 31 # 获取传过来的页码,并且校验 32 try: 33 page = int(page) 34 except Exception: 35 page = 1 36 if page > paginator.num_pages: 37 page = 1 38 39 ## 页面上最多显示5页页码 40 ## 总页数小于5,显示全部页数 41 ## 当前页小于等于3,显示1-5页 42 ## 当前页大于等于倒数第3,显示后5页 43 ## 其它情况,显示当前页的前两页,当前页,后两页 44 if paginator.num_pages < 6: 45 pages = paginator.page_range 46 elif page <= 3: 47 pages = range(1,6) 48 elif (paginator.num_pages-page) <= 2: 49 pages = range(paginator.num_pages-4,paginator.num_pages+1) 50 else: 51 pages = range(page-2,page+3) 52 53 ## 获取page页的page实例对象 54 skus_page = paginator.page(page) 55 56 ## 新品推荐 57 new_skus = GoodsSKU.objects.filter(type=type).order_by('-create_time')[:2] 58 59 # 获取用户购物车中商品的数目 60 user = request.user 61 cart_count = 0 62 if user.is_authenticated: 63 # 用户已登录 64 conn = get_redis_connection('default') 65 cart_key = 'cart_%d' % user.id 66 cart_count = conn.hlen(cart_key) 67 68 ## 组织模板上下文 69 context = { 70 'type':type, 71 'types':types, 72 'sort':sort, 73 'paginator':paginator, 74 'skus_page':skus_page, 75 'new_skus':new_skus, 76 'cart_count':cart_count, 77 'pages':pages 78 } 79 80 return render(request,'list.html',context)商品列表的视图函数
模板文件:
{% extends 'base_detail_list.html' %} {% block title %}天天生鲜-商品列表{% endblock title %} {% block main_content %} <div class="breadcrumb"> <a href="#">全部分类</a> <span>></span> <a href="{% url 'goods:list' type.id 1 %}?sort=default">{{ type.name }}</a> <span>></span> <a href="#">商品详情</a> </div> <div class="main_wrap clearfix"> <div class="l_wrap fl clearfix"> <div class="new_goods"> <h3>新品推荐</h3> <ul> {% for sku in new_skus %} <li> <a href="{% url 'goods:detail' sku.id %}"><img src="{{ sku.image.url }}"></a> <h4><a href="{% url 'goods:detail' sku.id %}">{{sku.name}}</a></h4> <div class="prize">¥{{ sku.price }}</div> </li> {% endfor %} </ul> </div> </div> <div class="r_wrap fr clearfix"> <div class="sort_bar"> <a href="{% url 'goods:list' type.id 1 %}" {% if sort == 'default' %}class="active"{% endif %}>默认</a> <a href="{% url 'goods:list' type.id 1 %}?sort=price" {% if sort == 'price' %}class="active"{% endif %}>价格</a> <a href="{% url 'goods:list' type.id 1 %}?sort=hot" {% if sort == 'hot' %}class="active"{% endif %}>人气</a> </div> <ul class="goods_type_list clearfix"> {% for sku in skus_page %} <li> <a href="{% url 'goods:detail' sku.id %}"><img src="{{ sku.image.url }}"></a> <h4><a href="{% url 'goods:detail' sku.id %}">{{ sku.name }}</a></h4> <div class="operate"> <span class="prize">¥{{ sku.price }}</span> <span class="unit">{{ sku.price }}/{{ sku.unite }}</span> <a href="#" class="add_goods" title="加入购物车"></a> </div> </li> {% endfor %} </ul> <div class="pagenation"> {% if skus_page.has_previous %} <a href="#"><上一页</a> {% endif %} {% for pindex in pages %} {% if pindex == skus_page.number %} <a href="{% url 'goods:list' type.id pindex %}?sort={{sort}}" class="active">{{pindex}}</a> {% else %} <a href="{% url 'goods:list' type.id pindex %}?sort={{sort}}">{{pindex}}</a> {% endif %} {% endfor %} {% if skus_page.has_next %} <a href="#">下一页></a> {% endif %} </div> </div> </div> {% endblock main_content %}模板文件
其中页码控制的参考文档:https://yiyibooks.cn/xx/django_182/topics/pagination.html
6、搜索框的设计
搜索框的设计参考:
购物车模块------cart
1、购物车记录的添加
前端商品页面中购物车的添加,由于前端页面只需要刷新购物车的数量,不需要刷新整个页面,所以采用ajax请求向后端传递数据
后端视图函数:
1 from django.shortcuts import render 2 from django.http import JsonResponse 3 from django_redis import get_redis_connection 4 from django.views.generic import View 5 from goods.models import GoodsSKU 6 from utils.mixin import LoginRequiredMiXin 7 8 # Create your views here. 9 10 class CartAddView(View): 11 '''添加购物车''' 12 def post(self,request): 13 '''处理ajax请求''' 14 ## 获取用户,判断用户是否登录,如果未登录则不能添加购物车 15 user = request.user 16 if not user.is_authenticated: 17 ## 用户未登录 18 return JsonResponse({'res':0,'errmsg':'用户未登录'}) 19 20 ## 1、接收数据 传过来的参数: sku_id count 21 sku_id = request.POST.get('sku_id') 22 count = request.POST.get('count') 23 24 25 ## 2、数据校验 26 ## 校验数据的完整性 27 if not all([sku_id,count]): 28 return JsonResponse({'res':1,'errmsg':'数据不完整'}) 29 ## 校验商品是否存在 30 try: 31 sku = GoodsSKU.objects.get(id=sku_id) 32 except GoodsSKU.DoesNoExist: 33 return JsonResponse({'res':2,'errmsg':'商品不存在'}) 34 ## 校验数目是否正确 35 try: 36 count = int(count) 37 except Exception: 38 return JsonResponse({'res':3,'errmsg':'添加的数目格式有误'}) 39 40 41 ## 3、业务处理 42 ## 将数据添加到Redis数据库中,hash格式: cart_key : sku_id count 43 conn = get_redis_connection('default') 44 cart_key= 'cart_%d'%user.id 45 ## 判断cart_key中是否存在值sku_id ---> 存在返回count的字符串类型,不存在返回None 46 cart_count = conn.hget(cart_key,sku_id) 47 if cart_count: 48 count += int(cart_count) 49 50 ## 校验商品的库存,如果添加的数目大于库存则报错 51 if count>sku.stock: 52 return JsonResponse({'res':4,'errmsg':'库存不足'}) 53 54 ## 添加或更新到Redis数据库 55 conn.hset(cart_key,sku_id,count) 56 ## 计算购物车中的商品总条数 57 total_count = conn.hlen(cart_key) 58 return JsonResponse({'res':5,'message':'购物车添加成功','total_count':total_count}) 59 60 ## 使用LoginRequiredMiXin判断用户是否登录 61 class CartInfoView(LoginRequiredMiXin,View): 62 '''购物车视图''' 63 def get(self,request): 64 '''显示购物车视图''' 65 ## 获取用户信息 66 user = request.user 67 ## 从Redis数据库中查询用户的购物车信息 68 cart_key = 'cart_%d'%user.id 69 conn = get_redis_connection('default') 70 ## 查询用户的所有购物车信息 {商品sku_id:数目} 71 cart_dict = conn.hgetall(cart_key) 72 73 skus = [] ## sku列表 74 total_count = 0 ## 商品总数目 75 total_price = 0 ## 所有商品的总价格 76 ## 遍历查询到的字典 77 for sku_id,count in cart_dict.items(): 78 ## 根据sku_id从商品模块中查询相应的商品 79 sku = GoodsSKU.objects.get(id=sku_id) 80 count = int(count) 81 ## 动态给sku增加商品数目的属性 82 sku.count = count 83 ## 动态给sku增加商品总价的属性 84 sku.amount = sku.price*count 85 ## 将sku加入skus列表中 86 skus.append(sku) 87 ## 累加商品数目和价格 88 total_count += sku.count 89 total_price += sku.amount 90 91 ## 组织模板上下文 92 context = { 93 'skus':skus, 94 'total_count':total_count, 95 'total_price':total_price 96 } 97 98 ## 返回应答 99 return render(request,'cart.html',context)购物车添加的视图函数处理
商品详情页的模板文件:
{% extends 'base_detail_list.html' %} {% load staticfiles %} {% block title %}天天生鲜-商品详情{% endblock title %} {% block main_content %} <div class="breadcrumb"> <a href="#">全部分类</a> <span>></span> <a href="#">{{ sku.type.name }}</a> <span>></span> <a href="#">商品详情</a> </div> <div class="goods_detail_con clearfix"> <div class="goods_detail_pic fl"><img src="{{ sku.image.url }}"></div> <div class="goods_detail_list fr"> <h3>{{ sku.name }}</h3> <p>{{ sku.desc }}</p> <div class="prize_bar"> <span class="show_pirze">¥<em>{{ sku.price }}</em></span> <span class="show_unit">单 位:{{ sku.unite }}</span> </div> <div class="goods_num clearfix"> <div class="num_name fl">数 量:</div> <div class="num_add fl"> <input type="text" class="num_show fl" value="1"> <a href="javascript:;" class="add fr">+</a> <a href="javascript:;" class="minus fr">-</a> </div> </div> <div> <p>其他规格:</p> <ul> {% for sku in same_spu_skus %} <li><a href="{% url 'goods:detail' sku.id %}">{{ sku.name }}</a></li> {% endfor %} </ul> </div> <div class="total">总价:<em>16.80元</em></div> <div class="operate_btn"> {% csrf_token %} <a href="javascript:;" class="buy_btn">立即购买</a> <a href="javascript:;" sku_id="{{ sku.id }}" class="add_cart" id="add_cart">加入购物车</a> </div> </div> </div> <div class="main_wrap clearfix"> <div class="l_wrap fl clearfix"> <div class="new_goods"> <h3>新品推荐</h3> <ul> {% for sku in new_skus %} <li> <a href="{% url 'goods:detail' sku.id %}"><img src="{{ sku.image.url }}"></a> <h4><a href="{% url 'goods:detail' sku.id %}">{{ sku.name }}</a></h4> <div class="prize">¥{{ sku.price }}</div> </li> {% endfor %} </ul> </div> </div> <div class="r_wrap fr clearfix"> <ul class="detail_tab clearfix"> <li class="active">商品介绍</li> <li>评论</li> </ul> <div class="tab_content"> <dl> <dt>商品详情:</dt> <dd>{{ sku.goods.detail|safe }}</dd> </dl> </div> <div class="tab_content"> <dl> {% for order in sku_orders %} <dt>评论时间:{{ order.update_time }} 用户名:{{ order.order.user.username }}</dt> <dd>评论内容:{{ order.comment }}</dd> {% endfor %} </dl> </div> </div> </div> {% endblock main_content %} {% block bottom %} <div class="add_jump"></div> {% endblock bottom %} {% block bottomfiles %} <script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script> <script type="text/javascript"> update_goods_amount() // 计算商品的总价格 function update_goods_amount() { // 获取商品的单价和数量 price = $('.show_pirze').children('em').text() count = $('.num_show').val() // 计算商品的总价 price = parseFloat(price) count = parseInt(count) amount = price*count // 设置商品的总价 $('.total').children('em').text(amount.toFixed(2)+'元') } // 增加商品的数量 $('.add').click(function () { // 获取商品原有的数目 count = $('.num_show').val() // 加1 count = parseInt(count)+1 // 重新设置商品的数目 $('.num_show').val(count) // 更新商品的总价 update_goods_amount() }) // 减少商品的数量 $('.minus').click(function () { // 获取商品原有的数目 count = $('.num_show').val() // 减1 count = parseInt(count)-1 if (count <= 0){ count = 1 } // 重新设置商品的数目 $('.num_show').val(count) // 更新商品的总价 update_goods_amount() }) // 手动输入商品的数量 $('.num_show').blur(function () { // 获取用户输入的数目 count = $(this).val() // 校验count是否合法 if (isNaN(count) || count.trim().length==0 || parseInt(count) <=0){ count = 1 } // 重新设置商品的数目 $(this).val(parseInt(count)) // 更新商品的总价 update_goods_amount() }) // 获取add_cart div元素左上角的坐标 var $add_x = $('#add_cart').offset().top; var $add_y = $('#add_cart').offset().left; // 获取show_count div元素左上角的坐标 var $to_x = $('#show_count').offset().top; var $to_y = $('#show_count').offset().left; $('#add_cart').click(function(){ // 获取商品id和商品数量 sku_id = $(this).attr('sku_id') // attr prop count = $('.num_show').val() csrf = $('input[name="csrfmiddlewaretoken"]').val() // 组织参数 params = {'sku_id':sku_id, 'count':count, 'csrfmiddlewaretoken':csrf} // 发起ajax post请求,访问/cart/add, 传递参数:sku_id count $.post('/cart/add', params, function (data) { if (data.res == 5){ // 添加成功 $(".add_jump").css({'left':$add_y+80,'top':$add_x+10,'display':'block'}) $(".add_jump").stop().animate({ 'left': $to_y+7, 'top': $to_x+7}, "fast", function() { $(".add_jump").fadeOut('fast',function(){ // 重新设置用户购物车中商品的条目数 $('#show_count').html(data.total_count); }); }); } else{ // 添加失败 alert(data.errmsg) } }) }) </script> {% endblock bottomfiles %}模板文件
2、购物车页面记录的更新
后端视图函数:
1 from django.shortcuts import render 2 from django.http import JsonResponse 3 from django_redis import get_redis_connection 4 from django.views.generic import View 5 from goods.models import GoodsSKU 6 from utils.mixin import LoginRequiredMiXin 7 8 # Create your views here. 9 10 11 ## 使用LoginRequiredMiXin判断用户是否登录 12 class CartInfoView(LoginRequiredMiXin,View): 13 '''购物车视图''' 14 def get(self,request): 15 '''显示购物车视图''' 16 ## 获取用户信息 17 user = request.user 18 ## 从Redis数据库中查询用户的购物车信息 19 cart_key = 'cart_%d'%user.id 20 conn = get_redis_connection('default') 21 ## 查询用户的所有购物车信息 {商品sku_id:数目} 22 cart_dict = conn.hgetall(cart_key) 23 24 skus = [] ## sku列表 25 total_count = 0 ## 商品总数目 26 total_price = 0 ## 所有商品的总价格 27 ## 遍历查询到的字典 28 for sku_id,count in cart_dict.items(): 29 ## 根据sku_id从商品模块中查询相应的商品 30 sku = GoodsSKU.objects.get(id=sku_id) 31 count = int(count) 32 ## 动态给sku增加商品数目的属性 33 sku.count = count 34 ## 动态给sku增加商品总价的属性 35 sku.amount = sku.price*count 36 ## 将sku加入skus列表中 37 skus.append(sku) 38 ## 累加商品数目和价格 39 total_count += sku.count 40 total_price += sku.amount 41 42 ## 组织模板上下文 43 context = { 44 'skus':skus, 45 'total_count':total_count, 46 'total_price':total_price 47 } 48 49 ## 返回应答 50 return render(request,'cart.html',context) 51 52 ## ajax post 提交数据,接收的参数 sku_id count 53 class CartUpdateView(View): 54 '''更新购物车''' 55 def post(self,request): 56 '''更新购物车''' 57 ## 判断用户是否登录 58 user = request.user 59 if not user.is_authenticated: 60 ## 用户未登录 61 return JsonResponse({'res': 0, 'errmsg': '用户未登录'}) 62 63 ## 1、接收数据 传过来的参数: sku_id count 64 sku_id = request.POST.get('sku_id') 65 count = request.POST.get('count') 66 67 ## 2、数据校验 68 ## 校验数据的完整性 69 if not all([sku_id, count]): 70 return JsonResponse({'res': 1, 'errmsg': '数据不完整'}) 71 ## 校验商品是否存在 72 try: 73 sku = GoodsSKU.objects.get(id=sku_id) 74 except GoodsSKU.DoesNoExist: 75 return JsonResponse({'res': 2, 'errmsg': '商品不存在'}) 76 ## 校验数目是否正确 77 try: 78 count = int(count) 79 except Exception: 80 return JsonResponse({'res': 3, 'errmsg': '添加的数目格式有误'}) 81 82 ## 页面处理,更新Redis中购物车的数据 83 conn = get_redis_connection('default') 84 cart_key = 'cart_%d'%user.id 85 86 ## 校验库存 87 stock = sku.stock 88 if count>stock: 89 return JsonResponse({'res':4,'errmsg':'商品库存不足'}) 90 91 ## 更新Redis 92 conn.hset(cart_key,sku.id,count) 93 94 ## 计算商品的总数量 95 total_count = 0 96 vals = conn.hvals(cart_key) 97 for val in vals: 98 total_count += int(val) 99 100 ## 返回应答 101 return JsonResponse({'res':5,'massage':'购物车更新成功','total_count':total_count}) 102 103 104 class CartDeleteView(View): 105 '''购物车记录删除''' 106 def post(self,request): 107 '''购物车记录删除''' 108 ## 获取用户,判断用户是否登录,如果未登录则不能添加购物车 109 user = request.user 110 if not user.is_authenticated: 111 ## 用户未登录 112 return JsonResponse({'res': 0, 'errmsg': '用户未登录'}) 113 114 ## 数据接收 115 sku_id = request.POST.get('sku_id') 116 117 ## 数据校验 118 try: 119 sku_id = int(sku_id) 120 except Exception: 121 return JsonResponse({'res':0,'errmsg':'数据格式不正确'}) 122 123 try: 124 sku = GoodsSKU.objects.get(id=sku_id) 125 except GoodsSKU.DoesNoExist: 126 return JsonResponse({'res':1,'errmsg':'无效的商品编号'}) 127 128 ## 业务处理 129 cart_key = 'cart_%d'%user.id 130 conn = get_redis_connection('default') 131 ## 删除sku.id 这个商品 132 conn.hdel(cart_key,sku.id) 133 134 ## 计算商品的总数量 135 total_count = 0 136 vals = conn.hvals(cart_key) 137 for val in vals: 138 total_count += int(val) 139 140 ## 返回应答 141 return JsonResponse({'res':2,'massage':'删除成功','total_count':total_count})购物车页面记录更新的视图函数
购物车页面模板文件:
模板文件购物车页面中:后端主要涉及到Redis的查询与修改,前端主要涉及到ajax post请求
参考文档:Redis文档
订单模块------order
1、订单页面的显示
逻辑分析:
当购物车页面点击结算时,需要向后端用户订单页面 传递的参数: sku_id 组成的列表,request.POST.getlist()获取
而后端的订单页面视图函数需要向订单页面前端传递的参数:skus sku数量和小计 总数量 总价格 用户关联的地址
1 from django.shortcuts import render,redirect,reverse 2 from django.views.generic import View 3 from django.http import JsonResponse 4 from django_redis import get_redis_connection 5 from datetime import datetime 6 from utils.mixin import LoginRequiredMiXin 7 from goods.models import GoodsSKU 8 from user.models import Address 9 10 11 # Create your views here. 12 13 ## 用户订单页面 传递的参数: sku_id 组成的列表,request.POST.getlist()获取 14 ## 需要向订单页面前端传递的参数:skus sku数量和小计 总数量 总价格 用户关联的地址 15 ## 需要先校验用户是否登录 16 class OrderPlaceView(LoginRequiredMiXin,View): 17 '''用户订单页面''' 18 def post(self,request): 19 '''用户订单页面''' 20 ## 获取用户信息 21 user = request.user 22 ## 获取用户关联的收货地址 23 addrs = Address.objects.filter(user=user) 24 25 ## 1、接收参数 26 skus_id = request.POST.getlist('sku_id') 27 28 ## 2、校验参数 29 if not skus_id: 30 return redirect(reverse('cart:cart_info')) 31 32 ## 3、业务处理 33 ## 初始化商品总数量和总价格 34 total_count = 0 35 total_price = 0 36 ## 将sku加入skus列表中 37 skus = [] 38 ## 连接到Redis数据库 39 conn = get_redis_connection('default') 40 cart_key = 'cart_%d' % user.id 41 ## 遍历获取sku 42 for sku_id in skus_id: 43 sku = GoodsSKU.objects.get(id=sku_id) 44 skus.append(sku) 45 46 ## 获取sku的数量 47 count = conn.hget(cart_key,sku.id) 48 count = int(count) 49 ## 获取sku的小计 50 amount = count*sku.price 51 ## 动态给sku增加数量属性和小计属性 52 sku.count = count 53 sku.amount = amount 54 ## 累加总数量和总价格 55 total_count += count 56 total_price += amount 57 58 59 60 ## 设置运费 61 trans_money = 10 62 63 ## 实付款 64 total_pay = total_price + trans_money 65 66 ## 将skus_id拼接成一个 2,5 之类的字符串传到前端 67 skus_id = ','.join(skus_id) 68 69 70 ## 组织模板上下文 71 context = { 72 'skus':skus, 73 'total_count':total_count, 74 'total_price':total_price, 75 'addrs':addrs, 76 'trans_money':trans_money, 77 'total_pay':total_pay, 78 'skus_id':skus_id 79 } 80 81 ## 4、返回应答 82 return render(request,'place_order.html',context)订单页面视图函数
{% extends 'base_no_cart.html' %} {% load static %} {% block title %}天天生鲜-提交订单{% endblock title %} {% block page_title %}提交订单{%endblock page_title %} {% block body %} <h3 class="common_title">确认收货地址</h3> <div class="common_list_con clearfix"> <dl> <dt>寄送到:</dt> {% for addr in addrs %} <dd><input type="radio" name="addr_id" value="{{addr.id}}" {% if addr.is_default %} checked {% endif %}>{{ addr.addr }} ({{ addr.receiver }} 收) {{ addr.phone }}</dd> {% endfor %} </dl> <a href="user_center_site.html" class="edit_site">编辑收货地址</a> </div> <h3 class="common_title">支付方式</h3> <div class="common_list_con clearfix"> <div class="pay_style_con clearfix"> <input type="radio" name="pay_style" checked value="1"> <label class="cash">货到付款</label> <input type="radio" name="pay_style" value="2"> <label class="weixin">微信支付</label> <input type="radio" name="pay_style" value="3"> <label class="zhifubao"></label> <input type="radio" name="pay_style" value="4"> <label class="bank">银行卡支付</label> </div> </div> <h3 class="common_title">商品列表</h3> <div class="common_list_con clearfix"> <ul class="goods_list_th clearfix"> <li class="col01">商品名称</li> <li class="col02">商品单位</li> <li class="col03">商品价格</li> <li class="col04">数量</li> <li class="col05">小计</li> </ul> {% for sku in skus %} <ul class="goods_list_td clearfix"> <li class="col01">{{ forloop.counter }}</li> <li class="col02"><img src="{{sku.image.url}}"></li> <li class="col03">{{ sku.name }}</li> <li class="col04">{{ sku.unite }}</li> <li class="col05">{{ sku.price }}元</li> <li class="col06">{{ sku.count }}</li> <li class="col07">{{ sku.amount }}元</li> </ul> {% endfor %} </div> <h3 class="common_title">总金额结算</h3> <div class="common_list_con clearfix"> <div class="settle_con"> <div class="total_goods_count">共<em>{{ total_count }}</em>件商品,总金额<b>{{ total_price }}元</b></div> <div class="transit">运费:<b>{{ trans_money }}元</b></div> <div class="total_pay">实付款:<b>{{ total_pay }}元</b></div> </div> </div> <div class="order_submit clearfix"> {% csrf_token %} <a href="javascript:;" skus_id={{ skus_id }} id="order_btn">提交订单</a> </div> {% endblock body %} {% block bottom %} <div class="popup_con"> <div class="popup"> <p>订单提交成功!</p> </div> <div class="mask"></div> </div> {% endblock bottom %} {% block bottomfiles %} <script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script> <script type="text/javascript"> // 当提交按钮被点击时,需要向后台传递的参数有:支付方式 用户选择的收货地址id skus_id $('#order_btn').click(function(){ var addr_id = $('input[name="addr_id"]:checked').val(); var pay_method = $('input[name="pay_style"]:checked').val(); var skus_id = $(this).attr('skus_id'); var csrf = $('input[name="csrfmiddlewaretoken"]').val(); // 组织模板上下文 var context = { 'addr_id':addr_id, 'pay_method':pay_method, 'skus_id':skus_id, 'csrfmiddlewaretoken':csrf }; // 发去ajax post请求 $.post('/order/commit',context,function(data){ if(data.res == 5){ // 订单创建成功 alert(data.message); } else{ alert(data.errmsg); } }); }); $('#order_btn').click(function() { localStorage.setItem('order_finish',2); $('.popup_con').fadeIn('fast', function() { setTimeout(function(){ $('.popup_con').fadeOut('fast',function(){ // window.location.href = 'index.html'; }); },3000) }); }); </script> {% endblock bottomfiles %}订单页面的模板文件
2、创建新订单
逻辑分析:
创建新订单的视图函数需要接收的参数: 支付方式 pay_method 收货地址id addr_id 购买的商品id skus_id
接收参数之后,需要先从df_order_info(订单数据)中插入一条数据,再向df_goods_info(商品订单)中插入相应的商品数据
最后更新MySQL和Redis数据库中的数据
1 ## `/order/commit 接收的参数: 支付方式 pay_method 收货地址id addr_id 购买的商品id skus_id 2 class OrderCommitView(View): 3 '''订单创建''' 4 def post(self,request): 5 '''订单创建''' 6 ## 判断用户是否登录 7 user = request.user 8 if not user: 9 return JsonResponse({'res':0,'errmsg':'用户未登录'}) 10 11 ## 1、接收数据 12 pay_method = request.POST.get('pay_method') 13 addr_id = request.POST.get('addr_id') 14 skus_id = request.POST.get('skus_id') 15 ## 将skus_id的格式转化成列表形式 16 skus_id = skus_id.split(',') 17 18 19 ## 2、校验数据 20 if not all([pay_method,addr_id,skus_id]): 21 return JsonResponse({'res':1,'errmsg':'数据不完整'}) 22 skus = [] 23 for sku_id in skus_id: 24 ## 遍历skus_id,从数据库中查询sku 25 try: 26 sku = GoodsSKU.objects.get(id=sku_id) 27 skus.append(sku) 28 except GoodsSKU.DoesNoExist: 29 return JsonResponse({'res':2,'errmsg':'商品不存在'}) 30 try: 31 addr = Address.objects.get(id=addr_id) 32 except Address.DoesNoExist: 33 return JsonResponse({'res':3,'errmsg':'地址不存在'}) 34 ## 校验支付方式 35 if str(pay_method) not in OrderInfo.PAY_METHODS.keys(): 36 return JsonResponse({'res':4,'errmsg':'错误的支付方式'}) 37 38 ## 3、业务处理 39 ########## (1)先向订单数据库中添加一条数据,准备数据:order_id、user、addr、pay_method、total_count、total_price、transit_price 40 ## 计算total_count和total_price,先初始化为0,后面查询各个商品的数量和价格之后再进行累加,以及重新保存 41 total_count = 0 42 total_price = 0 43 ## 生成order_id:格式:当前的时间+sku_id 44 order_id = datetime.now().strftime('%Y%m%d%H%M%S') + str(user.id) 45 ## 添加数据,创建新的订单数据 46 order = OrderInfo.objects.create(order_id=order_id, 47 user=user, 48 addr=addr, 49 pay_method=pay_method, 50 total_count=total_count, 51 total_price=total_price, 52 transit_price=10) 53 54 ########## (2)向商品订单中添加多条数据,准备数据:order、sku、count、price 55 conn = get_redis_connection('default') 56 cart_key = 'cart_%d'%user.id 57 for sku in skus: 58 count = conn.hget(cart_key,sku.id) 59 price = sku.price 60 OrderGoods.objects.create(order=order, 61 sku=sku, 62 count=count, 63 price=price) 64 ## MySQL数据库的库存减少,销量增加 65 sku.stock -= int(count) 66 sku.sales += int(count) 67 sku.save() 68 ## 删除Redis数据库中添加到订单中的数据 69 conn.hdel(cart_key,sku.id) 70 ## 累加total_count 和total_price 71 total_count += int(count) 72 total_price += int(count)*int(price) 73 ## 重新更新订单中的total_count和total_price 74 order.total_count = total_count 75 order.total_price = total_price 76 order.save() 77 78 ## 4、返回应答 79 return JsonResponse({'res':5,'message':'创建订单成功'})订单创建的视图函数
{% extends 'base_no_cart.html' %} {% load static %} {% block title %}天天生鲜-提交订单{% endblock title %} {% block page_title %}提交订单{%endblock page_title %} {% block body %} <h3 class="common_title">确认收货地址</h3> <div class="common_list_con clearfix"> <dl> <dt>寄送到:</dt> {% for addr in addrs %} <dd><input type="radio" name="addr_id" value="{{addr.id}}" {% if addr.is_default %} checked {% endif %}>{{ addr.addr }} ({{ addr.receiver }} 收) {{ addr.phone }}</dd> {% endfor %} </dl> <a href="user_center_site.html" class="edit_site">编辑收货地址</a> </div> <h3 class="common_title">支付方式</h3> <div class="common_list_con clearfix"> <div class="pay_style_con clearfix"> <input type="radio" name="pay_style" checked value="1"> <label class="cash">货到付款</label> <input type="radio" name="pay_style" value="2"> <label class="weixin">微信支付</label> <input type="radio" name="pay_style" value="3"> <label class="zhifubao"></label> <input type="radio" name="pay_style" value="4"> <label class="bank">银行卡支付</label> </div> </div> <h3 class="common_title">商品列表</h3> <div class="common_list_con clearfix"> <ul class="goods_list_th clearfix"> <li class="col01">商品名称</li> <li class="col02">商品单位</li> <li class="col03">商品价格</li> <li class="col04">数量</li> <li class="col05">小计</li> </ul> {% for sku in skus %} <ul class="goods_list_td clearfix"> <li class="col01">{{ forloop.counter }}</li> <li class="col02"><img src="{{sku.image.url}}"></li> <li class="col03">{{ sku.name }}</li> <li class="col04">{{ sku.unite }}</li> <li class="col05">{{ sku.price }}元</li> <li class="col06">{{ sku.count }}</li> <li class="col07">{{ sku.amount }}元</li> </ul> {% endfor %} </div> <h3 class="common_title">总金额结算</h3> <div class="common_list_con clearfix"> <div class="settle_con"> <div class="total_goods_count">共<em>{{ total_count }}</em>件商品,总金额<b>{{ total_price }}元</b></div> <div class="transit">运费:<b>{{ trans_money }}元</b></div> <div class="total_pay">实付款:<b>{{ total_pay }}元</b></div> </div> </div> <div class="order_submit clearfix"> {% csrf_token %} <a href="javascript:;" skus_id={{ skus_id }} id="order_btn">提交订单</a> </div> {% endblock body %} {% block bottom %} <div class="popup_con"> <div class="popup"> <p>订单提交成功!</p> </div> <div class="mask"></div> </div> {% endblock bottom %} {% block bottomfiles %} <script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script> <script type="text/javascript"> // 当提交按钮被点击时,需要向后台传递的参数有:支付方式 用户选择的收货地址id skus_id $('#order_btn').click(function(){ var addr_id = $('input[name="addr_id"]:checked').val(); var pay_method = $('input[name="pay_style"]:checked').val(); var skus_id = $(this).attr('skus_id'); var csrf = $('input[name="csrfmiddlewaretoken"]').val(); // 组织模板上下文 var context = { 'addr_id':addr_id, 'pay_method':pay_method, 'skus_id':skus_id, 'csrfmiddlewaretoken':csrf }; // 发去ajax post请求 $.post('/order/commit',context,function(data){ if(data.res == 5){ // 订单创建成功 alert(data.message); } else{ alert(data.errmsg); } }); }); $('#order_btn').click(function() { localStorage.setItem('order_finish',2); $('.popup_con').fadeIn('fast', function() { setTimeout(function(){ $('.popup_con').fadeOut('fast',function(){ // window.location.href = 'index.html'; }); },3000) }); }); </script> {% endblock bottomfiles %}模板文件
补充:
当添加的商品数量大于数据库中的库存时,就不应该让订单创建成功,但是这里会有一个问题,订单虽然创建失败了,数据库中的df_order_goods虽然没有修改数据,但是df_order_info仍然添加了一条数据,显然这是不行的,所以就必须要使用到MySQL中的事务,订单页面的事务使用具体参考:https://www.cnblogs.com/maoxinjueluo/p/12883162.html
3、订单并发与处理
订单并发的处理详情见:订单并发处理
用户模块追加------用户订单与订单的支付
1、用户订单中心页面的显示:
用户订单中心显示的视图函数:
1 class OrderView(LoginRequiredMiXin,View): 2 '''用户中心-订单页''' 3 def get(self,request,page): 4 '''显示用户订单页''' 5 ## 1、准备参数:根据用户查询相应的订单列表 orders,每个订单中的商品sku 数量 ,价格 以及每个订单的各个商品小计 6 user = request.user 7 ## 获取用户的所有订单 8 orders = OrderInfo.objects.filter(user=user).order_by('-create_time') 9 ## 遍历所有的订单,获取每个订单里面每个商品的小计,动态添加 10 for order in orders: 11 order_skus = OrderGoods.objects.filter(order_id=order.order_id) 12 for order_sku in order_skus: 13 ## 动态给每个订单中的每类商品增加小计的属性 14 order_sku.amount = int(order_sku.count)*int(order_sku.price) 15 ## 动态给每个订单增加order_skus属性 16 order.order_skus = order_skus 17 ## 动态给每个订单增加支付状态 18 order.status = OrderInfo.ORDER_STATUS[order.order_status] 19 20 ## 分页显示 21 ## 获取Paginator对象 22 paginator = Paginator(orders,1) 23 # 获取传过来的页码,并且校验 24 try: 25 page = int(page) 26 except Exception: 27 page = 1 28 if page > paginator.num_pages: 29 page = 1 30 31 ## 页面上最多显示5页页码 32 ## 总页数小于5,显示全部页数 33 ## 当前页小于等于3,显示1-5页 34 ## 当前页大于等于倒数第3,显示后5页 35 ## 其它情况,显示当前页的前两页,当前页,后两页 36 if paginator.num_pages < 6: 37 pages = paginator.page_range 38 elif page <= 3: 39 pages = range(1, 6) 40 elif (paginator.num_pages - page) <= 2: 41 pages = range(paginator.num_pages - 4, paginator.num_pages + 1) 42 else: 43 pages = range(page - 2, page + 3) 44 45 ## 获取page页的page实例对象 46 order_pages = paginator.page(page) 47 48 ## 2、组织上下文 49 context = { 'pages':pages, 50 'order_pages':order_pages, 51 'page':'order'} 52 53 54 return render(request,'user_center_order.html',context)视图函数
用户订单中心的模板文件:
{% extends 'base_user_center.html' %} {% load static %} {% block title %}天天生鲜-用户中心{% endblock title %} {% block right_content %} <div class="right_content clearfix"> <h3 class="common_title2">全部订单</h3> {% for order in order_pages %} <ul class="order_list_th w978 clearfix"> <li class="col01">{{ order.create_time }}</li> <li class="col02">订单号:{{ order.order_id }}</li> <li class="col02 stress">{{ order.status }}</li> </ul> <table class="order_list_table w980"> <tbody> <tr> <td width="55%"> {% for order_sku in order.order_skus %} <ul class="order_goods_list clearfix"> <li class="col01"><a href="{% url 'goods:detail' order_sku.sku.id %}"><img src="{{ order_sku.sku.image.url }}" ></a></li> <li class="col02">{{ order_sku.sku.name }}<em>{{ order_sku.price }}元/{{ order_sku.sku.unite }}</em></li> <li class="col03">{{ order_sku.count }}</li> <li class="col04">{{ order_sku.amount }}元</li> </ul> {% endfor %} </td> <td width="15%">{{ order.total_price|add:order.transit_price }}(含运费:{{ order.transit_price }})元</td> <td width="15%">{{ order.status }}</td> <td width="15%"><a href="#" class="oper_btn">去付款</a></td> </tr> </tbody> </table> {% endfor %} <div class="pagenation"> {% if order_pages.has_previous %} <a href="{% url 'user:order' order_pages.previous_page_number %}"><上一页</a> {% endif %} {% for pindex in pages %} {% if pindex == order_pages.number %} <a href="{% url 'user:order' pindex %}" class="active">{{ pindex }}</a> {% else %} <a href="{% url 'user:order' pindex %}">{{ pindex }}</a> {% endif %} {% endfor %} {% if order_pages.has_next %} <a href="{% url 'user:order' order_pages.next_page_number %}">下一页></a> {% endif %} </div> </div> {% endblock right_content %}模板文件
2、订单支付与支付结果的查询
订单支付与支付结果的查询参考:https://www.cnblogs.com/maoxinjueluo/p/12889863.html
3、订单评价
订单评价的视图函数:
1 class OrderCommentView(LoginRequiredMiXin,View): 2 '''订单评论''' 3 def get(self, request, order_id): 4 """提供评论页面""" 5 user = request.user 6 7 # 校验数据 8 if not order_id: 9 return redirect(reverse('user:order')) 10 11 try: 12 order = OrderInfo.objects.get(order_id=order_id, user=user) 13 except OrderInfo.DoesNotExist: 14 return redirect(reverse("user:order")) 15 16 # 根据订单的状态获取订单的状态标题 17 order.status_name = OrderInfo.ORDER_STATUS[order.order_status] 18 19 # 获取订单商品信息 20 order_skus = OrderGoods.objects.filter(order_id=order_id) 21 for order_sku in order_skus: 22 # 计算商品的小计 23 amount = order_sku.count*order_sku.price 24 # 动态给order_sku增加属性amount,保存商品小计 25 order_sku.amount = amount 26 # 动态给order增加属性order_skus, 保存订单商品信息 27 order.order_skus = order_skus 28 29 # 使用模板 30 return render(request, "order_comment.html", {"order": order}) 31 32 def post(self, request, order_id): 33 """处理评论内容""" 34 user = request.user 35 # 校验数据 36 if not order_id: 37 return redirect(reverse('user:order')) 38 39 try: 40 order = OrderInfo.objects.get(order_id=order_id, user=user) 41 except OrderInfo.DoesNotExist: 42 return redirect(reverse("user:order")) 43 44 # 获取评论条数 45 total_count = request.POST.get("total_count") 46 total_count = int(total_count) 47 48 # 循环获取订单中商品的评论内容 49 for i in range(1, total_count + 1): 50 # 获取评论的商品的id 51 sku_id = request.POST.get("sku_%d" % i) # sku_1 sku_2 52 # 获取评论的商品的内容 53 content = request.POST.get('content_%d' % i, '') # cotent_1 content_2 content_3 54 try: 55 order_goods = OrderGoods.objects.get(order=order, sku_id=sku_id) 56 except OrderGoods.DoesNotExist: 57 continue 58 59 order_goods.comment = content 60 order_goods.save() 61 62 order.order_status = 5 # 已完成 63 order.save() 64 65 return redirect(reverse("user:order", kwargs={"page": 1}))订单评价视图函数
模板文件:
{% extends 'base_user_center.html' %} {% load staticfiles %} {% block title %}天天生鲜-用户中心{% endblock %} {% block page_title %}用户中心{% endblock page_title %} {% block right_content %} <div class="right_content clearfix"> <h3 class="common_title2">订单评价</h3> <ul class="order_list_th w978 clearfix"> <li class="col01">{{order.create_time}}</li> <li class="col02">订单号:{{order.order_id}}</li> <li class="col02 stress">{{order.status_name}}</li> </ul> <form method="post"> {% csrf_token %} {# 订单id #} <input type="hidden" name="order_id" value="{{order.order_id}}"> {# 订单中有几个商品 #} <input type="hidden" name="total_count" value="{{order.order_skus|length}}"> {% for order_sku in order.order_skus %} <table class="order_list_table w980"> <tbody> <tr> <td width="80%"> <ul class="order_goods_list clearfix"> <li class="col01"><img src="{{ order_sku.sku.image.url }}"></li> <li class="col02">{{order_sku.sku.name}}<em>{{order_sku.price}}/{{order_sku.sku.unite}}</em></li> <li class="col03">{{order_sku.count}}</li> </ul> </td> <td width="20%">{{order_sku.amount}}元</td> </tr> </tbody> </table> <div class="site_con"> <input type="hidden" name="sku_{{forloop.counter}}" value="{{order_sku.sku.id}}"> <div class="form_group form_group2"> <label>评价内容:</label> <textarea class="site_area" name="content_{{forloop.counter}}"></textarea> </div> </div> {% endfor %} <input type="submit" name="" value="提交" class="info_submit"> </form> </div> {% endblock right_content %}订单评价模板文件
三、项目部署
详情见:https://www.cnblogs.com/maoxinjueluo/p/12898164.html