基于Django开发的电商购物平台(完整项目介绍 --> 项目环境 , 项目完整代码 , 项目服务器/虚拟机部署)

avatar
作者
猴君
阅读量:1

1-10_Django项目实战文档

本网站是基于Django+uwsgi+nginx+MySQL+redis+linux+requests开发的电商购物系统,
以及通过使用爬虫技术批量获取商品数据.
实现
客户端: 注册 , 登录 , 浏览记录保存, 购物车 , 订单等功能实现
管理端: 商品添加 , 用户管理等功能

项目内容较多 , 该博文只是对整体的大致思路介绍 , 如有疑问可以私信博主

项目的完整代码可见博主主页上传的资源
项目git地址: https://gitee.com/jixuonline/django_-shop-system
详细介绍: https://blog.csdn.net/xiugtt6141121/category_12658164.html
服务器部署教程: https://blog.csdn.net/xiugtt6141121/article/details/139497427

一、项目环境

python 3.8.10 django 3.2 mysql 5.7.40 redis 

二、项目环境的配置

1、创建 Django 项目 —— ShopSystem

2、配置 MySQL 的连接引擎

DATABASES = {     'default': {         'ENGINE': 'django.db.backends.mysql',         'NAME': 'shop_10',         'USER' : 'root',         'PASSWORD' : 'root',         'HOST' : '127.0.0.1'     } } 

3、配置静态文件项目检索路径

STATIC_URL = '/static/' STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] 

4、配置内存型数据库,配置 Redis 的连接引擎

# 配置 Redis 缓存数据库信息 CACHES = {     # 默认使用的 Redis 数据库     "default":{         # 配置数据库指定引擎         "BACKEND" : "django_redis.cache.RedisCache",         # 配置使用 Redis 的数据库名称         "LOCATION" : "redis://127.0.0.1:6379/0",         "OPTIONS":{             "CLIENT_CLASS" : "django_redis.client.DefaultClient"         }     },      # 将 session 的数据保存位置修改到 redis 中     "session":{         # 配置数据库指定引擎         "BACKEND" : "django_redis.cache.RedisCache",         # 配置使用 Redis 的数据库名称         "LOCATION" : "redis://127.0.0.1:6379/1",         "OPTIONS":{             "CLIENT_CLASS" : "django_redis.client.DefaultClient"         }     },  }  # 修改 session 默认的存储机制 SESSION_ENGINE = "django.contrib.sessions.backends.cache" # 配置 SESSION 要缓存的地方 SESSION_CACHE_ALIAS = "session" 

三、响应首页

创建响应首页的应用 —— contents

配置响应路由 ,实现响应的视图

# 响应首页 path('' , views.IndexView.as_view())  class IndexView(View):     '''     响应首页     '''     def get(self , request):         return render(request , 'index.html') 

四、用户注册

1、实现用户的数据模型类 2、响应注册页面 3、接收用户输入的数据 4、对用户输入的数据进行校验 5、保存用户数据,注册成功 

创建应用实现用户逻辑操作 —— users

1、响应注册页面视图

class RegisterView(View):     '''     用户注册     '''     def get(self , request):         return render(request , 'register.html') 

2、定义用户数据模型类

使用 auth 模块实现保存用户数据,自定义认证模型类别

from django.contrib.auth.models import AbstractUser  class User(AbstractUser):     mobile = models.CharField(max_length=11 , unique=True)          class Meta:         db_table = 'user' 

修改 Django 全局默认的认证模型类

# 配置自定义模型类 AUTH_USER_MODEL = 'users.User'  

3、后端数据校验

实现用户数据提交之后,Django 对数据校验是否合法,自定义 forms 表单类进行校验

在应用中创建 forms 模块

from django import forms  class RegisterForm(forms.Form):     '''     校验用户注册提交的数据     '''     username = forms.CharField(min_length= 5 , max_length= 15,                                error_messages={                                    "min_length":"用户名过短",                                    "max_length":"用户名过长",                                    "required":"用户名不允许为空"                                })     password = forms.CharField(min_length= 6 , max_length= 20,                                error_messages={                                    "min_length":"密码过短",                                    "max_length":"密码过长",                                    "required":"密码不允许为空"                                })     password2 = forms.CharField(min_length= 6 , max_length= 20,                                error_messages={                                    "min_length":"密码过短",                                    "max_length":"密码过长",                                    "required":"密码不允许为空"                                })     mobile = forms.CharField(min_length= 11 , max_length= 11,                                error_messages={                                    "min_length":"手机号过短",                                    "max_length":"手机号过长",                                    "required":"手机号不允许为空"                                })          # 使用全局钩子函数 , 检验两个密码是否一致     def clean(self):         clean_data = super().clean()         pw = clean_data.get('password')         pw2 = clean_data.get('password2')         if pw != pw2:             raise forms.ValidationError('两次密码不一致')         return clean_data  

在注册视图中,实现获取用户提交的数据, 进行校验数据,保存数据

    def post(self , request):         # 获取用户提交的数据,将数据传递给 forms 组件进行校验         register_form = RegisterForm(request.POST)          # 判断用户校验的数据是否合法         if register_form.is_valid():             return HttpResponse('注册成功')         return HttpResponse('注册失败')  

4、校验用户名重复

# 校验用户名重复 re_path('^username/(?P<username>[A-Za-z0-9_]{5,15})/count/$' , views.UsernameCountView.as_view())  class UsernameCountView(View):     '''     判断用户名是否重复     '''     def get(self , request , username):         # 根据参数从数据库获取数据         count = User.objects.filter(username=username).count()         return JsonResponse({'code':200 , 'errmsg':"OK" , 'count':count})  

5、图片验证码

创建一个应用实现验证码的功能 —— verfications

1、配置缓冲验证码的 Redis 数据库

# 缓冲 验证码     "ver_code":{         "BACKEND" : "django_redis.cache.RedisCache",         "LOCATION" : "redis://127.0.0.1:6379/2",         "OPTIONS":{             "CLIENT_CLASS" : "django_redis.client.DefaultClient"         }     },  

视图

# 响应图片验证码 re_path('^image_code/(?P<uuid>[\w-]+)/$' , views.ImageCodeView.as_view())  class ImageCodeView(View):     '''     响应图片验证码     '''     def get(self , request , uuid):         # 调用生成图片验证码的功能         image , text = CodeImg.create_img()         # 将验证码保存到数据库中         redis_conn = get_redis_connection('ver_code')         redis_conn.setex('image_%s'%uuid , 400 , text)         return HttpResponse(image , content_type='image/png')  

修改前端页面中的对应标签内容

<li>     <label>图形验证码:</label>     <input type="text" name="image_code" id="pic_code" class="msg_input"            v-model="image_code" @blur="check_image_code">     <img v-bind:src="image_code_url" alt="图形验证码" class="pic_code"          @click="generate_image_code">     <span class="error_tip" v-show="error_image_code">请填写图形验证码</span> </li>  

6、短信验证码

什么时候发送短信验证码 图片验证码校验成功之后发送  

发送短信验证码的功能使用:https://console.yuntongxun.com/member/main

安装:pip install ronglian_sms_sdk

在应用下创建一个包实现发送短信的功能 —— ronglianyun , 创建模块: ccp_sms

from ronglian_sms_sdk import SmsSDK import json  accId = '2c94811c88bf3503018900ca795012ba' accToken = '382b17b971884ddfad5c7ecadc07149b' appId = '2c94811c88bf3503018900ca7a9d12c1'  # 单例模式 class CCP:      _instance = None     def __new__(cls, *args, **kwargs):         # new 静态类方法,给对象分配内存空间         if cls._instance is None:             # 如果类属性数据为 None , 说明当前类中没有实例化对象             # 给对象创建一个新的对象内存空间             cls._instance = super().__new__(cls, *args, **kwargs)         return cls._instance      def send_message(self,mobile, datas):         sdk = SmsSDK(accId, accToken, appId)         tid = '1'         resp = sdk.sendMessage(tid, mobile, datas)         resp = json.loads(resp)         # 判断短信验证码是否发送成功         if resp["statusCode"] == "000000":             # 短信验证码发送成功             return 0         else:             return -1          send_code = CCP() <li>     <label>短信验证码:</label>     <input type="text" name="sms_code" id="msg_code" class="msg_input"            v-model="sms_code" @blur="check_sms_code">     <a @click="send_sms_code" class="get_msg_code">获取短信验证码</a>     <span class="error_tip" v-show="error_sms_code">请填写短信验证码</span> </li>  

定义发送短信验证码的视图

# 发送短信验证码 re_path('^sms_code/(?P<mobile>1[3-9]\d{9})/$' , views.SmsCodeView.as_view()),   class SmsCodeView(View):     '''     发送短信验证码     1、校验图片验证码     在接收发送短信验证码期间内,不允许重复的调用该视图     '''     def get(self , request , mobile):         # 接收参数:uuid , 用户输入图片验证码         uuid = request.GET.get('uuid')         image_code_client = request.GET.get('image_code')          # 检验请求中的数据是否完整         if not all([uuid , image_code_client]):             return HttpResponse("缺少不要的参数")          # 校验图片验证码         # 从 Redis 数据库中获取该用户生成的图片验证码         redis_conn = get_redis_connection('ver_code')         image_code_server = redis_conn.get('image_%s'%uuid)          # 从数据库中获取手机号标记变量         sand_flag = redis_conn.get('sand_%s' % mobile)         # 判断标记变量是否有值         if sand_flag:             return JsonResponse({'code':RETCODE.THROTTLINGERR , 'errmsg':'发送短信验证码过于频繁'})          # 判断图片验证码是否在有效期内         if image_code_server is None:             return JsonResponse({'code':RETCODE.IMAGECODEERR , 'errmsg':'图片验证码失效'})         # 将图片验证码删除         redis_conn.delete('image_%s'%uuid)         # 判断图片验证码是否正确         image_code_server = image_code_server.decode()         if image_code_client.lower() != image_code_server.lower():             return JsonResponse({'code': RETCODE.IMAGECODEERR, 'errmsg': '图片验证码输入错误'})         # 图片验证码正确 , 发送短信验证码         # 生成短信验证码         sms_code = '%05d'%random.randint(0,99999)         # 保存短信验证码         redis_conn.setex('code_%s' % mobile, 400, sms_code)         # 保存该手机的标记变量到数据库         redis_conn.setex('sand_%s' % mobile, 60, 1)         # 发送短信验证码         send_code.send_message(mobile , (sms_code , 5))         return JsonResponse({'code':RETCODE.OK , 'errmsg':'短信验证码发送成功'}) 在项目中 ajax 响应的状态很多种。同一讲状态保存到一个文件包中,需要的时候进行调用。 在项目中创建一个共用包:utils 将 response_code 模块文本保存进去  

7、完善注册视图

在 users 应用中的 forms 内对短信验证码的字段进行校验

sms_code = forms.CharField(max_length=5 , min_length=5) class RegisterView(View):     '''     用户注册     '''     def get(self , request):         return render(request , 'register.html')      def post(self , request):         # 获取用户提交的数据,将数据传递给 forms 组件进行校验         register_form = RegisterForm(request.POST)          # 判断用户校验的数据是否合法         if register_form.is_valid():             # 数据合法             username = register_form.cleaned_data.get('username')             password = register_form.cleaned_data.get('password')             mobile = register_form.cleaned_data.get('mobile')             sms_code_client = register_form.cleaned_data.get('sms_code')              # 从 redis 中获取生成短信验证码的             redis_conn = get_redis_connection('ver_code')             sms_code_server = redis_conn.get('code_%s' % mobile)             # 判断短信验证码是否有效             if sms_code_server is None:                 return render(request , 'register.html' , {'sms_code_errmsg':'短信验证码失效'})              if sms_code_client != sms_code_server.decode():                 return render(request, 'register.html', {'sms_code_errmsg': '短信验证码输入错误'})             # 将数据保存到数据库中             user = User.objects.create_user(username=username , password=password , mobile=mobile)             # 做状态保持             login(request , user)             # 注册成功响应到登录页面             return redirect('login')         else:             # 用户数据不合法             # 从 forms 组件中获取数据异常信息             context = {'forms_errmsg':register_form.errors}             return render(request ,'register.html' , context=context)  

修改前端注册页面中对应的部分标签 , 获取后端的校验用户数据的异常信息

<li class="reg_sub">     <input type="submit" value="注 册">     <!-- 获取后端校验表单数据的异常信息 -->     {% if forms_errmsg %}     <span style="color: red">{{ forms_errmsg }}</span>     {% endif %} </li> <li>     <label>短信验证码:</label>     <input type="text" name="sms_code" id="msg_code" class="msg_input"            v-model="sms_code" @blur="check_sms_code">     <a @click="send_sms_code" class="get_msg_code">[[ sms_code_tip ]]</a>     <span class="error_tip" v-show="error_sms_code">请填写短信验证码</span>     <!-- 获取后端校验短信验证码的异常信息 -->     {% if sms_code_errmsg %}     <span style="color: red">{{ sms_code_errmsg }}</span>     {% endif %} </li>  

五、用户登录

1、响应登录页面的视图

# 用户登录 path('login/' , views.LoginView.as_view() , name='login'),  class LoginView(View):     '''     用户登录视图     '''     def get(self , request):         return render(request , 'login.html')  

2、用户名登录

1、校验用户数据

class LoginForm(forms.Form):     username = forms.CharField(min_length=5, max_length=15,                                error_messages={                                    "min_length": "用户名过短",                                    "max_length": "用户名过长",                                    "required": "用户名不允许为空"                                })     password = forms.CharField(min_length=6, max_length=20,                                error_messages={                                    "min_length": "密码过短",                                    "max_length": "密码过长",                                    "required": "密码不允许为空"                                })     remembered = forms.BooleanField(required=False) class LoginView(View):     '''     用户登录视图     '''     def get(self , request):         return render(request , 'login.html')      def post(self , request):         login_form = LoginForm(request.POST)          if login_form.is_valid():             username = login_form.cleaned_data.get('username')             password = login_form.cleaned_data.get('password')             remembered = login_form.cleaned_data.get('remembered')              if not all([username , password]):                 return HttpResponse('缺少必要的参数')              # 通过认证模块到数据库中进行获取用户数据             user = authenticate(username=username , password=password)              # 判断在数据库中是否能够查询到用户数据             if user is None:                 return render(request , 'login.html' , {'account_errmsg':'用户名或者密码错误'})              # 状态保持             login(request , user)              # 判断用户是否选择记住登录的状态             if remembered:                 # 用户选择记住登录状态 , 状态默认保存14天                 request.session.set_expiry(None)             else:                 # 用户状态不保存 , 关闭浏览器 , 数据销毁                 request.session.set_expiry(0)             # 响应首页             return redirect('index')         else:             context = {'forms_errors':login_form.errors}             return render(request , 'login.html' , context=context)  

3、手机号登录

Django 的 auth 认证系统中 默认是使用 用户名认证,使用其他数据进行认证 , 需要重新定义认证系统

1、实现一个类 , 这个类继承 ModelBackend 2、重写认证方法 authenticate  

在 users 的应用下 创建一个文件 —— utils

from django.contrib.auth.backends import ModelBackend from users.models import User import re  # 定义一个方法可以用手机号或者用户名查询数据的 def get_user(account):     try:         if re.match(r'1[3-9]\d{9}' , account):             user = User.objects.get(mobile=account)         else:             user = User.objects.get(username=account)     except Exception:         return None     else:         return user   class UsernameMobileBackend(ModelBackend):     # 重写用户认证方法     def authenticate(self, request, username=None, password=None, **kwargs):         # 调用查询用户数据的方法         user = get_user(username)         # 判断密码是否正确         if user.check_password(password) and user:             return user         else:             return None  

修改 Django 项目中的配置文件的全局认证

# 配置自定义认证的方法 AUTHENTICATION_BACKENDS = ['users.utils.UsernameMobileBackend']  

4、首页显示用户名

在后端的登录请求的视图中,在用户登录成功之后将用户名写到 Cookie 中。

    def post(self , request):         login_form = LoginForm(request.POST)          if login_form.is_valid():             username = login_form.cleaned_data.get('username')             password = login_form.cleaned_data.get('password')             remembered = login_form.cleaned_data.get('remembered')              if not all([username , password]):                 return HttpResponse('缺少必要的参数')              # 通过认证模块到数据库中进行获取用户数据             user = authenticate(username=username , password=password)              # 判断在数据库中是否能够查询到用户数据             if user is None:                 return render(request , 'login.html' , {'account_errmsg':'用户名或者密码错误'})              # 状态保持             login(request , user)              # 判断用户是否选择记住登录的状态             if remembered:                 # 用户选择记住登录状态 , 状态默认保存14天                 request.session.set_expiry(None)             else:                 # 用户状态不保存 , 关闭浏览器 , 数据销毁                 request.session.set_expiry(0)             # 响应首页             response = redirect('index')             # 将获取到的 用户名写入到 Cookie 中             response.set_cookie('username' , user.username , 3600)             return response         else:             context = {'forms_errors':login_form.errors}             return render(request , 'login.html' , context=context)  

5、用户退出登录

退出登录:将用户的数据从 SESSION 会话中删除掉。

logout方法: 清除 session 会话的数据

<div class="login_btn fl">     欢迎您:<em>[[ username ]]</em>     <span>|</span>     <a href="{% url 'logout' %}">退出</a> </div> # 退出登录 path('logout/' , views.LogoutView.as_view() , name='logout')  class LogoutView(View):     '''     用户退出登录     '''     def get(self , request):         # 清除用户保存的数据         logout(request)         response = redirect('index')         # 清除保存的 Cookie 数据         response.delete_cookie('username')         return response  

六、用户中心

1、响应用户中心

# 用户中心 path('info/' , views.UserInfoView.as_view() , name='info')  class UserInfoView(View):     '''     用户中心     '''     def get(self , request):         return render(request , 'user_center_info.html')  

2、判断登录状态

使用 Django 用户认证提供 :LoginRequiredMixin 进行用户登录判断并且可以配置重定向到原来的请求页面 , 实现效果直接继承即可

class UserInfoView(LoginRequiredMixin , View):     '''     用户中心     '''     def get(self , request):         return render(request , 'user_center_info.html')  

到配置文件中重新定义认证登录重定向的 url

# 配置项目认证登录的重定向 LOGIN_URL = '/login/'  

修改登录的视图

    def post(self , request):         login_form = LoginForm(request.POST)          if login_form.is_valid():             username = login_form.cleaned_data.get('username')             password = login_form.cleaned_data.get('password')             remembered = login_form.cleaned_data.get('remembered')              if not all([username , password]):                 return HttpResponse('缺少必要的参数')              # 通过认证模块到数据库中进行获取用户数据             user = authenticate(username=username , password=password)              # 判断在数据库中是否能够查询到用户数据             if user is None:                 return render(request , 'login.html' , {'account_errmsg':'用户名或者密码错误'})              # 状态保持             login(request , user)              # 判断用户是否选择记住登录的状态             if remembered:                 # 用户选择记住登录状态 , 状态默认保存14天                 request.session.set_expiry(None)             else:                 # 用户状态不保存 , 关闭浏览器 , 数据销毁                 request.session.set_expiry(0)                              next = request.GET.get('next')             if next:                 # next 有值,重定向到指定的 url                  response = redirect(next)             else:                 # 响应首页                 response = redirect('index')             # 将获取到的 用户名写入到 Cookie 中             response.set_cookie('username' , user.username , 3600)             return response         else:             context = {'forms_errors':login_form.errors}             return render(request , 'login.html' , context=context)  

3、显示获取用户信息

class UserInfoView(LoginRequiredMixin , View):     '''     用户中心     '''     def get(self , request):         # 从 request 中获取用户信息         context = {             "username" : request.user.username,             "mobile" : request.user.mobile,             "email" : request.user.email,         }         return render(request , 'user_center_info.html' , context=context)  

4、添加邮箱

1、在用户数据模型类中补充:邮箱验证状态的字段

# 邮箱验证字段 email_active = models.BooleanField(default=False)  

验证登录的: LoginRequiredMixin 要求的返回值是 HttpResponse 对象 , 如果返回值的是 json 类型必须重写类方法

在项目全局的 utlis 包创建一个 view 模块

from django.contrib.auth.mixins import LoginRequiredMixin from django.http import JsonResponse from utils.response_code import RETCODE  class LoginRequiredJSONMixin(LoginRequiredMixin):          def handle_no_permission(self):         # 让这个返回可以返回 json 类型对象即可         return JsonResponse({'code':RETCODE.SESSIONERR , 'errmsg':'用户未登录'}) class EmailView(LoginRequiredJSONMixin , View):     '''     用户添加邮箱     '''     def put(self , request):         # put 请求的参数放在 request 的 body 中,并且一个字节传输的数据         # b'{'email':'123@com'}'         json_str = request.body.decode()         # '{'email':'123@com'}'         # 使用 json 进行反序列化         json_dict = json.loads(json_str)         email = json_dict.get('email')          # 校验数据         if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$' , email):             return HttpResponseForbidden('邮箱参数有误')          # 保存邮箱到数据库中         request.user.email = email         request.user.save()          # 添加成功         return JsonResponse({'code':RETCODE.OK , 'errmsg':'OK'})  

5、验证邮箱

让 Django 发送邮件 , 是无法直接发送,需要借助SMTP服务器进行中转

需要到 Django 项目的配置文件中配置邮箱的需要的信息

# 发送邮件的配置参数 EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' 	# 指定邮件后端 EMAIL_HOST = 'smtp.163.com'	 	# 发邮件主机 EMAIL_PORT = 25		# 发邮件的端口 EMAIL_HOST_USER = '17841687578@163.com'		# 授权邮箱 EMAIL_HOST_PASSWORD = 'BHPRRXBTMTCTGHVU'		# 邮箱授权时获取的密码,非登录邮箱的密码 EMAIL_FROM = 'AC-<17841687578@163.com>'		# 发件人抬头  # 设置邮箱的激活连接 EMAIL_VERIFY_URL = 'http://127.0.0.1:8000/verification/' 以网易云为例:在设置打开 SMTP/POP3 开启 IMAP/SMTP 和 POP3/SMTP 获取授权码  

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

发送邮件

from django.core.mail import send_mail  subject = '邮件验证' message = '阿宸真的超级帅' from_email = 'AC-<17841687578@163.com>' recipient_list = ['17841687578@163.com',] html_message = '<h1>阿宸真的超级帅</h1>' send_mail(subject, message, from_email, recipient_list , html_message=html_message) '''     subject: 邮件标题     message: 邮件正文(普通的文本文件,字符串)     from_email: 发件人抬头     recipient_list: 收件人邮箱 (列表格式)     html_message: 邮件正文(文件可以带渲染格式)     '''  

生成邮件激活连接 , 在 users 应用下的 utils 中操作

下载模块 itsdangerou==1.1.0 from itsdangerous import TimedJSONWebSignatureSerializer as TJWSS from ShopSystem import settings  def generate_verify_email_url(user):     '''     生成邮箱激活连接     '''     # 调用加密的方法     s = TJWSS(settings.SECRET_KEY , 600)     # 获取用户的基本数据     data = {'user_id':user.id , 'email':user.email}     token = s.dumps(data)     return settings.EMAIL_VERIFY_URL+'?token='+token.decode()  

在保存邮箱的视图中 , 进行对邮箱发送一个验证连接

class EmailView(LoginRequiredJSONMixin , View):     '''     用户添加邮箱     '''     def put(self , request):         # put 请求的参数放在 request 的 body 中,并且一个字节传输的数据         # b'{'email':'123@com'}'         json_str = request.body.decode()         # '{'email':'123@com'}'         # 使用 json 进行反序列化         json_dict = json.loads(json_str)         email = json_dict.get('email')          # 校验数据         if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$' , email):             return HttpResponseForbidden('邮箱参数有误')          # 保存邮箱到数据库中         request.user.email = email         request.user.save()          # 发送验证邮件         subject = 'AC商城邮箱验证'         # 调用生成加密验证邮箱连接         veerify_url = generate_verify_email_url(request.user)         html_message = f'<p>您的邮箱为:{email} , 请点击链接进行验证激活邮箱</p>' \                        f'<p><a href="{veerify_url}">{veerify_url}</p>'         send_mail(subject , '' , from_email=settings.EMAIL_FROM ,                   recipient_list=[email],html_message=html_message)          # 添加成功         return JsonResponse({'code':RETCODE.OK , 'errmsg':'OK'})  

在 Django 中实现邮箱的数据验证,需要接收用户邮箱连接发送过来的参数

进行对参数解码,校验

def check_verify_email_token(token):     '''     校验邮箱连接中的参数     '''     s = TJWSS(settings.SECRET_KEY, 600)     data = s.loads(token)     # 获取解码好之后的参数     user_id = data.get('user_id')     email = data.get('email')     # 从数据库中查询是否有该用户     try:         user = User.objects.get(id=user_id ,email=email)     except Exception:         return None     else:         return user  

验证邮箱的视图

# 验证邮箱 path('verification/' , views.VerifyEmailView.as_view()),  class VerifyEmailView(View):     '''     邮箱验证     '''     def get(self , request):         token = request.GET.get('token')         if not token:             return HttpResponseForbidden('缺少必要参数')          # 调用解码的方法         user = check_verify_email_token(token)          if not user:             return HttpResponseForbidden('用户不存在')          # 判断用户优先是否已经验证         if user.email_active == 0:             # 邮箱没有验证             user.email_active = 1             user.save()         else:             return HttpResponseForbidden('用户邮箱已验证')         # 验证成功,重定向到用户中心         return redirect('info')  

6、收货地址

class AddressView(View):     '''     用户收货地址     '''     def get(self , request):         return render(request , 'user_center_site.html')  

7、实现全国省市区名称数据

创建一个应用来操作实现地区数据 —— areas

设计地区数据模型类

from django.db import models  # 自关联 # id   name   -id #  1  广东省   null #  2  湖北省 #  3  广州市    1 #  4  天河区    3  class Area(models.Model):     name = models.CharField(max_length=20)     # 自关联 : self     # SET_NULL: 删除被关联的数据 , 对应链接的数据字段值会设置为 NULL     parent = models.ForeignKey('self' , on_delete=models.SET_NULL , null=True,blank=True,related_name='subs')          class Meta:         db_table = 'areas'  

在前端中使用 ajax 发送 url = /areas/ ; 获取地区数据 , 判断当请求路由中没有携带参数,则获取的是省份的数据

携带了 area_id=1,获取的是市或者区的数据

from django.shortcuts import render from django.views import View from areas.models import Area from utils.response_code import RETCODE from django.http import JsonResponse from django.core.cache import cache  class AreasView(View):     '''     响应地区数据     '''     def get(self , request):         area_id = request.GET.get('area_id')         # 判断是否存在 area_id 参数         if not area_id:             # 判断这个数据在内存中是否存在             province_list = cache.get('province_list')             if not province_list:                 # 获取省份的数据                 province_model_list = Area.objects.filter(parent_id__isnull=True)                 '''                 响应 json 数据                 {                     'code' : 200                     'errmsg' : OK                     'province_list':[                         {id:110000 ; name:北京市},                         {id:120000 ; name:天津市},                         ……                     ]                 }                 '''                 province_list = []                 for province_model in province_model_list:                     province_dict = {                         "id" : province_model.id,                         "name" : province_model.name,                     }                     province_list.append(province_dict)                 # 将数据缓存到内存中                 cache.set('province_list',province_list , 3600)             return JsonResponse({'code':RETCODE.OK , 'errmsg':"OK" , 'province_list':province_list})         else:             # 获取 市 或者 区 的数据             '''             {                 'code' : 200                 'errmsg' : OK                 sub_data : {                     id : 省110000                     name : 广东省                     subs : [                         {id , name},                         {id , name},                         {id , name},                         ……                     ]                 }             }             '''             sub_data = cache.get('sub_data_%s'%area_id)             if not sub_data:                 parent_model = Area.objects.get(id=area_id)                 # 获取关联 area_id 的对象数据                 sub_model_list = parent_model.subs.all()                 subs = []                 for sub_model in sub_model_list:                     sub_dict = {                         'id' : sub_model.id,                         'name' : sub_model.name,                     }                     subs.append(sub_dict)                 sub_data = {                     'id' : parent_model.id,                     'name' : parent_model.name,                     'subs' : subs                 }                 cache.set('sub_data_%s'%area_id , sub_data , 3600)             return JsonResponse({'code':RETCODE.OK , 'errmsg':"OK" , 'sub_data':sub_data})  

修改前端 user_center_site.html 中对应标签的内容

<div class="form_group">     <label>*所在地区:</label>     <select v-model="form_address.province_id">         <option value="0">请选择</option>         <option :value="province.id" v-for="province in provinces">[[ province.name ]]</option>     </select>     <select v-model="form_address.city_id">         <option value="0">请选择</option>         <option :value="city.id" v-for="city in cities">[[ city.name ]]</option>     </select>     <select v-model="form_address.district_id">         <option value="0">请选择</option>         <option :value="district.id" v-for="district in districts">[[ district.name ]]</option>     </select> </div>  

8、创建收货地址模型类

让其他模型类可以共用时间的字段,在项目中的 utils 包内创建一个 model 文件

from django.db import models  class BaseModel(models.Model):     # 创建时间     create_time = models.DateTimeField(auto_now_add=True)     # 更新时间       update_time = models.DateTimeField(auto_now=True)          class Meta:         # 在迁移数据库的时候不为该模型类单独创建一张表         abstract = True  

需要使用时间的模型类继承上面该类即可。

创建用户收货地址模型类

class Address(BaseModel):     # 用户收货地址     # 关联用户     user = models.ForeignKey(User , on_delete=models.CASCADE , related_name='address')     receiver = models.CharField(max_length=20)     province = models.ForeignKey('areas.Area' , on_delete=models.PROTECT , related_name='province_address')     city = models.ForeignKey('areas.Area' , on_delete=models.PROTECT , related_name='city_address')     district = models.ForeignKey('areas.Area' , on_delete=models.PROTECT , related_name='district_address')     palce = models.CharField(max_length=50)     mobile = models.CharField(max_length=11)     tel = models.CharField(max_length=20 , null=True , blank=True , default='')     email = models.CharField(max_length=20 , null=True , blank=True , default='')     is_delete = models.BooleanField(default=False)      class Meta:         db_table = 'address'  

在用户个人数据模型类中保存默认收货地址 , 默认地址只有一个

default_address = models.ForeignKey('Address' , on_delete=models.SET_NULL , null=True, blank=True , related_name='users')  

9、修改密码

# 修改密码 path('changepwd/' , views.ChangePasswordView.as_view(), name='changepwd'),  class ChangePasswordView(View):     '''     用户修改密码     '''     def get(self , request):         return render(request,'user_center_pass.html')      def post(self , request):         # 接收用户输入的密码         old_password = request.POST.get('old_password')         new_password = request.POST.get('new_password')         new_password2 = request.POST.get('new_password2')          # 校验数据 , 数据是否完整         if not all([old_password , new_password , new_password2]):             return HttpResponseForbidden('缺少必要的数据')          # 校验旧密码是否正确         if not request.user.check_password(old_password):             return render(request , 'user_center_pass.html' , {'origin_password_errmsg':'旧密码不正确'})          # 校验新密码中的数据是否合法         if not re.match(r'^[0-9A-Za-z]{6,20}$' , new_password):             return render(request, 'user_center_pass.html', {'change_password_errmsg': '密码格式不正确'})          # 校验两次新密码是否一致         if new_password != new_password2:             return render(request, 'user_center_pass.html', {'change_password_errmsg': '两次密码不一致'})          # 密码数据正确合法,将新的密码重新保存         request.user.set_password(new_password)         request.user.save()          # 跟新状态保持 , 清理原有的密码数据         logout(request)         response = redirect('login')         response.delete_cookie('username')         return response  

10、新增收货地址

# 新增收货地址 path('addresses/create/', views.AddressCreateView.as_view()),  class AddressCreateView(View):     '''     用户新增收货地址     '''     def post(self , request):         json_str = request.body.decode()         json_dict = json.loads(json_str)         receiver = json_dict.get('receiver')         province_id = json_dict.get('province_id')         city_id = json_dict.get('city_id')         district_id = json_dict.get('district_id')         place = json_dict.get('place')         mobile = json_dict.get('mobile')         tel = json_dict.get('tel')         email = json_dict.get('email')          # 校验数据 , 数据完整性         if not all([receiver , province_id , city_id , district_id , place , mobile]):             return HttpResponseForbidden('缺少不要数据')         if not re.match(r'^1[3-9]\d{9}$' , mobile):             return HttpResponseForbidden('手机号输入有误')         if tel:             if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):                 return HttpResponseForbidden('固定电话输入有误')             if email:                 if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):                     return HttpResponseForbidden('邮箱输入有误')                  # 将数据保存到数据库中                 address = Address.objects.create(                     user=request.user,                     receiver = receiver,                     province_id = province_id,                     city_id = city_id,                     district_id = district_id,                     palce = place,                     mobile = mobile,                     tel = tel,                     email = email,                 )                 address_dict = {                     'id' : address.id,                     'receiver': address.receiver,                     'province': address.province.name,                     'city': address.city.name,                     'district': address.district.name,                     'place': address.palce,                     'mobile': address.mobile,                     'tel': address.tel,                     'email': address.email,                 }                 return  JsonResponse({'code':RETCODE.OK , 'errmsg':'新增地址成功' , 'address':address_dict})  

11、渲染收货地址

class AddressView(View):     '''     用户收货地址     '''     def get(self , request):         # 获取当前登录的用户信息         login_user = request.user         # 根据当前登录的用户信息,获取对应的地址数据         addresses = Address.objects.filter(user=login_user , is_delete=False)          address_list = []         for address in addresses:             address_dict = {                 'id': address.id,                 'receiver': address.receiver,                 'province': address.province.name,                 'city': address.city.name,                 'district': address.district.name,                 'place': address.palce,                 'mobile': address.mobile,                 'tel': address.tel,                 'email': address.email,             }             address_list.append(address_dict)         context = {             'addresses' : address_list,             # 获取用户的默认收货地址             'default_address_id' : login_user.default_address_id,             # 计算用户的收货地址个数             'count':addresses.count()         }         return render(request , 'user_center_site.html' , context=context)  

修改前端对应标签:user_center_site.html

<div class="right_content clearfix" v-cloak>     <div class="site_top_con">         <a @click="show_add_site">新增收货地址</a>         <span>你已创建了<b>{{ count }}</b>个收货地址,最多可创建<b>20</b></span>     </div>     <div class="site_con" v-for="(address , index) in addresses" :key="address.id">         <div class="site_title">             <h3>[[ address.receiver ]]</h3>             <a @click="show_edit_title(index)" class="edit_icon"></a>             <em v-if="address.id === default_address_id">默认地址</em>             <span class="del_site" @click="delete_address(index)">×</span>         </div>         <ul class="site_list">             <li><span>收货人:</span><b>[[ address.receiver ]]</b></li>             <li><span>所在地区:</span><b>[[ address.province ]] [[ address.city ]] [[ address.district ]]</b></li>             <li><span>地址:</span><b>[[ address.place ]]</b></li>             <li><span>手机:</span><b>[[ address.mobile ]]</b></li>             <li><span>固定电话:</span><b>[[ address.tel ]]</b></li>             <li><span>电子邮箱:</span><b>[[ address.email ]]</b></li>         </ul>         <div class="down_btn">             <a v-if="address.id != default_address_id" @click="set_default(index)">设置默认地址</a>             <a class="edit_icon" @click="show_edit_site(index)" >编辑</a>         </div>     </div> </div>  

12、修改\删除收货地址

# 修改、删除收货地址 re_path('^addresses/(?P<address_id>\d+)/$', views.UpdateAddressView.as_view()),  class UpdateAddressView(View):     '''     修改/删除收货地址     '''     def put(self , request , address_id):         # 用户修改收货地址         json_str = request.body.decode()         json_dict = json.loads(json_str)         receiver = json_dict.get('receiver')         province_id = json_dict.get('province_id')         city_id = json_dict.get('city_id')         district_id = json_dict.get('district_id')         place = json_dict.get('place')         mobile = json_dict.get('mobile')         tel = json_dict.get('tel')         email = json_dict.get('email')          # 校验数据 , 数据完整性         if not all([receiver, province_id, city_id, district_id, place, mobile]):             return HttpResponseForbidden('缺少不要数据')         if not re.match(r'^1[3-9]\d{9}$', mobile):             return HttpResponseForbidden('手机号输入有误')         if tel:             if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):                 return HttpResponseForbidden('固定电话输入有误')             if email:                 if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):                     return HttpResponseForbidden('邮箱输入有误')                  Address.objects.filter(id=address_id).update(                     user=request.user,                     receiver=receiver,                     province_id=province_id,                     city_id=city_id,                     district_id=district_id,                     palce=place,                     mobile=mobile,                     tel=tel,                     email=email,                 )                 address = Address.objects.get(id=address_id)                 address_dict = {                     'id': address.id,                     'receiver': address.receiver,                     'province': address.province.name,                     'city': address.city.name,                     'district': address.district.name,                     'place': address.palce,                     'mobile': address.mobile,                     'tel': address.tel,                     'email': address.email,                 }                 return JsonResponse({'code': RETCODE.OK, 'errmsg': '修改地址成功', 'address': address_dict})              def delete(self , request , address_id):                 # 删除地址                 address = Address.objects.get(id=address_id)                 address.is_delete = True                 address.save()                 return JsonResponse({'code': RETCODE.OK, 'errmsg': '删除地址成功'})  

13、设置默认收货地址

# 设置用户默认收货地址 re_path('^addresses/(?P<address_id>\d+)/default/$', views.DefaultAddressView.as_view()),  class DefaultAddressView(View):     '''     设置默认收货地址     '''     def put(self , request , address_id):         # 获取默认地址的数据对象         address = Address.objects.get(id=address_id)         request.user.default_address = address         request.user.save()         return JsonResponse({'code': RETCODE.OK, 'errmsg': '默认收货地址设置成功'})  

七、商品数据

SPU:标准产品单位 , 表示一组类似属性或者特征的商品集合。【商品的基本信息和属性:名称,描述,品牌】 , 对商品的基本定义  SKU:库存单位,表示具体的商品或者库存【商品的规格 , 价格,尺码,版本】  

创建一个应用来操作商品 相关数据 —— goods

迁移数据库 , 执行 goods_data.sql 插入商品数据

1、首页的商品分类

{ 1:{ 	channels:[ 		{id:1 , name:手机 , url:}, 		{} 		{} 	], 	sub_cats:[ 		{id:500 		name:手机通讯, 		sub_cat:[ 			{id:520 , name:华为}, 			{},…… 		]}, 		{}…… 	] }, 2:{}, 3:{}, …… } class IndexView(View):     '''     响应首页     '''     def get(self , request):          # 定义一个空的字典 , 存放商品频道分类的数据         categories = {}         # 查询商品分组频道的所有数据         channels = GoodsChannel.objects.all()         # 获取到所有的商品频道组         for channel in channels:             # 获取商品频道组的 id ,作为分组的 key             group_id = channel.group_id             # 判断获取到的分组 id 在字典中是否存在 , 存在则不添加             if group_id not in categories:                 categories[group_id] = {'channels':[] , 'sub_cats':[]}              # 查询一级商品的数据信息             # 根据商品的外键数据 , 判断是否为一级商品类别             cat1 = channel.category             categories[group_id]['channels'].append(                 {                     'id':cat1.id,                     'name':cat1.name,                     'url': channel.url                 }             )              # 获取二级的商品类别数据             # 二级的数据根据一级的类别 id 进行获取:cat1.subs.all()             for cat2 in cat1.subs.all():                 cat2.sub_cats = []                 categories[group_id]['sub_cats'].append(                     {                         'id':cat2.id,                         'name':cat2.name,                         'sub_cat':cat2.sub_cats                     }                 )                 # 获取三级的数据                 for cat3 in cat2.subs.all():                     cat2.sub_cats.append(                         {                             'id':cat3.id,                             'name':cat3.name                         }                     )          # 首页商品推荐广告数据         # 获取所有的推荐商品广告类别         content_categories = ContentCategory.objects.all()         contents = {}         for content_category in content_categories:             contents[content_category.key] = Content.objects.filter(                 category_id=content_category.id,                 status=True             ).all().order_by('sequence')          context = {'categories':categories , 'contents':contents}         print(contents)         return render(request , 'index.html' , context=context)  

修改index.html页面中对应的标签内容

<ul class="slide">     {% for content in contents.index_lbt %}     <li><a href="{{ content.url }}"><img src="/static/images/goods/{{ content.image }}.jpg" alt="幻灯片"></a></li>     {% endfor %} </ul> <div class="news">     <div class="news_title">         <h3>快讯</h3>         <a href="#">更多 &gt;</a>     </div>     <ul class="news_list">         {% for content in contents.index_kx %}         <li><a href="{{ content.url }}">{{ content.title }}</a></li>         {% endfor %}     </ul>     {% for content in contents.index_ytgg %}     <a href="{{ content.url }}" class="advs"><img src="/static/images/goods/{{ content.image }}.jpg"></a>     {% endfor %} </div>  

2、商品列表页

因为商品列表中页需要商品分类的功能 , 将首页中的商品分类功能进行抽取出来单独定义一个模块中,需要导入调用。

在 contents 应用中。创建 utils 模块 , 实现商品分类模块功能。

制作列表页中列表导航栏(面包屑) , 在应用中创建 utils 模块 , 列表导航栏(面包屑) 。

from goods.models import GoodsCategory  def get_breadcrumb(category):     # 一级:breadcrumb = {cat1:''}     # 二级:breadcrumb = {cat1:'',cat2:''}     # 三级:breadcrumb = {cat1:'',cat2:'',cat3:''}     breadcrumb = {'cat1':'','cat2':'','cat3':''}      if category.parent == None:         # 没有外键数据 , 说明类别属于一级         breadcrumb['cat1'] = category     elif GoodsCategory.objects.filter(parent_id = category.id).count() == 0:         # 判断是否有外键被链接对象 , 如果没有说明这个是三级的数据         # 三级是通过二级间接连接到一级的数据 , 无法直接拿到一级的名称         cat2 = category.parent         breadcrumb['cat1'] = cat2.parent         breadcrumb['cat2'] = cat2         breadcrumb['cat3'] = category     else:         # 二级         breadcrumb['cat1'] = category.parent         breadcrumb['cat2'] = category     return breadcrumb class GoodsListView(View):     '''     商品列表页     '''     def get(self ,request ,  category_id , pag_num):         categories = get_categories()         # 获取到当前列表的商品类别对象         category = GoodsCategory.objects.get(id=category_id)         # 调用生成面包屑的功能         breadcrumb = get_breadcrumb(category)          # 商品排序         # 获取请求的参数:sort , 进行判断商品排序的方式         # 如果没有 sort 参数 , 则按照默认排序         sort = request.GET.get('sort' , 'default')         if sort == 'price':             sort_field = 'price'         elif sort == 'hot':             sort_field = 'sales'         else:             sort = 'default'             sort_field = 'create_time'          skus = SKU.objects.filter(is_launched=True , category_id=category_id).order_by(sort_field)          # 对商品进行分页         paginator = Paginator(skus , 5)         # 获取当前页面的数据         page_skus = paginator.page(pag_num)         # 获取分页的总数         total_num = paginator.num_pages          context = {             'categories':categories,             'breadcrumb':breadcrumb,             'page_skus':page_skus,             'sort':sort,             'pag_num':pag_num,             'category_id':category_id,             'total_num':total_num         }         return render(request , 'list.html' , context=context)  

修改前端list.html对应的标签内容

<div class="breadcrumb">     <a href="http://shouji.jd.com/">{{ breadcrumb.cat1.name }}</a>     <span>></span>     <a href="javascript:;">{{ breadcrumb.cat2.name }}</a>     <span>></span>     <a href="javascript:;">{{ breadcrumb.cat3.name }}</a> </div> <div class="r_wrap fr clearfix">     <div class="sort_bar">         <a href="{% url 'list' category_id pag_num %}?sort=default"             {% if sort == 'default' %} class="active"{% endif %}>默认</a>         <a href="{% url 'list' category_id pag_num %}?sort=price"            {% if sort == 'price' %} class="active"{% endif %}>价格</a>         <a href="{% url 'list' category_id pag_num %}?sort=hot"            {% if sort == 'hot' %} class="active"{% endif %}>人气</a>     </div>     <ul class="goods_type_list clearfix">         {% for sku in page_skus %}         <li>             <a href="detail.html"><img src="/static/images/goods{{ sku.default_image.url }}.jpg"></a>             <h4><a href="detail.html">{{ sku.name }}</a></h4>             <div class="operate">                 <span class="price">¥{{ sku.price }}</span>                 <span class="unit"></span>                 <a href="#" class="add_goods" title="加入购物车"></a>             </div>         </li>         {% endfor %}     </ul> </div> <script>     $(function () {         $('#pagination').pagination({             currentPage: {{ pag_num }},                                     totalPage: {{ total_num }},       callback:function (current) {         location.href = '/list/{{ category_id }}/' + current + '/?sort={{ sort }}';     }     })     }); </script>  

在页面的底部

<!-- 分页器盒子 --> <div class="pagenation">     <div id="pagination" class="page"></div> </div>  

3、热销商品排行

# 热销商品排行 re_path('^hot/(?P<category_id>\d+)/$' , views.HotGoodsView.as_view()),  class HotGoodsView(View):     '''     热销商品排行     '''     def get(self , request , category_id):         # 获取该类别商品的数据         skus = SKU.objects.filter(is_launched=True, category_id=category_id).order_by('-sales')[:2]         hot_skus = []         for sku in skus:             sku_dict = {                 'id': sku.id,                 'name': sku.name,                 'price':sku.price,                 'default_image_url': settings.STATIC_URL+'images/goods/'+sku.default_image.url+'.jpg'             }             hot_skus.append(sku_dict)             return JsonResponse({'code':RETCODE.OK , 'errmsg':'OK' , 'hot_skus':hot_skus})  

修改前端 list.html 页面中对应的标签内容

<script type="text/javascript">     let category_id = {{ category_id }}; </script> <div class="new_goods">     <h3>热销排行</h3>     <ul>         <li v-for="sku in hot_skus" :key="sku.id">             <a href="detail.html"><img :src="sku.default_image_url"></a>             <h4><a href="detail.html">[[ sku.name ]]</a></h4>             <div class="price">¥[[ sku.price ]]</div>         </li>     </ul> </div>  

4、商品详情页

# 商品详情页 # 这个是哪一个商品:sku.id re_path('^detail/(?P<sku_id>\d+)/$', views.DetailGoodsView.as_view() ,name='detail'),  class DetailGoodsView(View):     '''     商品详情页     '''     def get(self , request , sku_id):         categories = get_categories()         sku = SKU.objects.get(id=sku_id)         breadcrumb = get_breadcrumb(sku.category)          # 通过 sku.id 获取商品对象的对应规格信息的选项         sku_specs = SKUSpecification.objects.filter(sku_id=sku_id).order_by('spec_id')         # 创建一个空的列表 用来存储当前 sku 对应的规格选项数据         sku_key = []         # 遍历当前 sku 的规格选项         for spec in sku_specs:             # 将每个规格选项的 ID 添加到 sku_key 列表中             sku_key.append(spec.option.id)             # [8, 11]  颜色:金色 ,内存:64GB             # [1, 4, 7]  屏幕尺寸:13.3英寸 颜色:银色 ,内存:core i5/8G内存/512G存储              # 获取当前商品的所有 sku             # 保证选择不同的规格的情况下 , 商品不变             spu_id = sku.spu_id             skus = SKU.objects.filter(spu_id=spu_id)             # 构建商品的不同规格参数,sku的选项字段             spec_sku_map = {}             for i in skus:                 # 获取sku规格的参数                 s_pecs = i.specs.order_by('spec_id')                 # 创建一个空列表 , 用于存储 sku 规格参数                 key = []                 # 遍历当前 sku 规格参数列表                 for spec in s_pecs:                     key.append(spec.option.id)                     spec_sku_map[tuple(key)] = i.id                      # 获取当前商品的规格名称                     # 根据商品的 ID 获取当前商品的所有规格名称                     goods_specs = SPUSpecification.objects.filter(spu_id=spu_id).order_by('id')                      # 前端渲染                     # 实现根据规格选项生成对应 sku.id, 更新规格对象个规格选项信息。                     # 为了给用户展示所有的规格参数                     for index , spec in enumerate(goods_specs):                         # 复制 sku_key 列表中的数据,避免直接 sku_key 列表中的内容                         key = sku_key[:]                         # 获取当前 specs 对象的规格名称                         spec_options = spec.options.all()                         # 遍历当前商品的规格名称                         for spec_option in spec_options:                             # 将当前规格选项对象 spec_option 的 id 赋值给 key列表中 index 的位置,用于查询对应 sku 参数内容                             key[index] = spec_option.id                             # 根据列表中的值, 在 spec_sku_map 字典中查询对应的 sku 数据                             spec_option.sku_id = spec_sku_map.get(tuple(key))                             # 更新每个规格对象的选项内容                             spec.spec_options = spec_options                              context = {                                 'categories' : categories,                                 'breadcrumb' : breadcrumb,                                 'sku':sku,                                 'specs' : goods_specs                             }                             return render(request , 'detail.html' , context=context) <script type="text/javascript">     let category_id = {{ sku.category_id }};     let sku_price = {{ sku.price }};     let sku_id = {{ sku.id }}; </script>  

5、统计分类商品的访问量

# 统计分类商品访问量 re_path('^detail/visit/(?P<category_id>\d+)/$' , views.DetailVisitView.as_view()),  class DetailVisitView(View):     '''     分类商品访问量     '''     def post(self , request , category_id):         # 校验数据         try:             category = GoodsCategory.objects.get(id=category_id)         except Exception:             return HttpResponseForbidden('商品参数类别不存在')          t = timezone.localtime()         # yyyy-mm-dd , 格式化当前时间         today = '%d-%02d-%02d'%(t.year , t.month , t.day)          try:             # 获取当前类别在数据库是否存在 , 如果修改时间以及访问量即可             count_data = GoodsVisitCount.objects.get(category=category_id , date=today)         except GoodsVisitCount.DoesNotExist:             # DoesNotExist: 模型类数据不存在             # 创建一个空的数据对象             count_data = GoodsVisitCount()          count_data.category = category         count_data.date = today         count_data.count += 1         count_data.save()          return JsonResponse({'code':RETCODE.OK , 'errmsg':"OK"})  

6、商品搜索

全文搜索引擎

需要下载这两个框架 django_haystack whoosh  

可以对表中的某些字段进行关键字分析 , 建立关键词对应的索引数据

在配置文件中 INSTALLED_APPS 的列表中添加: haystack;

在配置文件末尾添加

# 配置 haystack  HAYSTACK_CONNECTIONS = {     'default': {         # 设置搜索引擎         'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',         'PATH':os.path.join(BASE_DIR,'whoosh_index'),     }, } # 当数据库改变时,自动更新新引擎 HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'  

在 goods 应用中创建 search_indexes.py 文件

from haystack import indexes from goods.models import SKU # 类名必须为模型类名+Index class SKUIndex(indexes.SearchIndex, indexes.Indexable):     # document=True 代表搜索引擎将使用此字段的内容作为引擎进行检索     # use_template=True 代表使用索引模板建立索引文件     text = indexes.CharField(document=True, use_template=True)     # 将索引类与模型类进行绑定     def get_model(self):         return SKU     # 设置索引的查询范围     def index_queryset(self, using=None):         return self.get_model().objects.all()  

在templates 目录下创建搜索引擎文件的

templates  |---- search 	|---- indexes 		|---- goods(这个目录的名称是指定搜索模型类所在的应用名称) 			|---- sku_text.txt	(这个名称根据小写模型类名称_text.txt)  

在 sku_text.txt 中配置搜索索引字段

# 指定根据表中的字段建立索引 {{ object.name }} {{ object.caption }}  

在终端执行创建全文搜索索引文件

python manage.py rebuild_index  

实现分页,在 goods 应用的视图下

from haystack.query import SearchQuerySet  def search_view(request):      query = request.GET.get('q', '')     page = request.GET.get('page', 1)     # 使用 Haystack 的 SearchQuerySet 进行搜索,过滤出包含搜索关键词的结果集     search_results = SearchQuerySet().filter(content=query)     paginator = Paginator(search_results, 6)  # 每页显示10条搜索结果      try:         results = paginator.page(page)     except PageNotAnInteger:	# 处理用户在 URL 中输入的页数不是整数的情况,将当前页设为第一页         results = paginator.page(1)     except EmptyPage:	# 处理用户请求的页面超出搜索结果范围的情况,将当前页设为最后一页。         results = paginator.page(paginator.num_pages)     return render(request, 'search.html', {'results': results, 'query': query})  

在应用下配置url

path('search/' , views.search_view ,  name='search') <div class=" clearfix">     <ul class="goods_type_list clearfix">         {% for result in results %}         <li>             {# object取得才是sku对象 #}             <a href="/detail/{{ result.object.id }}/"><img src="/static/images/goods/{{ result.object.default_image.url }}.jpg"></a>             <h4><a href="/detail/{{ result.object.id }}/">{{ result.object.name }}</a></h4>             <div class="operate">                 <span class="price">{{ result.object.price }}</span>                 <span>{{ result.object.comments }}评价</span>             </div>         </li>         {% empty %}         <p>没有找到您要查询的商品。</p>         {% endfor %}     </ul>     <div class="pagination">         {% if results.has_previous %}         <a href="?q={{ query }}&page=1">&laquo; 首页</a>         <a href="?q={{ query }}&page={{ results.previous_page_number }}">上一页</a>         {% endif %}         当前页: {{ results.number }} of 总页数: {{ results.paginator.num_pages }}         {% if results.has_next %}         <a href="?q={{ query }}&page={{ results.next_page_number }}">下一页</a>         <a href="?q={{ query }}&page={{ results.paginator.num_pages }}">尾页 &raquo;</a>         {% endif %}     </div> </div>  

八、购物车

1、响应添加购物车数据

创建应用实现购物车的功能操作 —— carts

配置 redis 数据库缓存购物车中的商品数据

# 缓存 购物车商品id     "carts":{         "BACKEND" : "django_redis.cache.RedisCache",         "LOCATION" : "redis://127.0.0.1:6379/3",         "OPTIONS":{             "CLIENT_CLASS" : "django_redis.client.DefaultClient"         }     }, # 购物车页面 path('carts/' , views.CartsView.as_view() , name='carts'),  class CartsView(View):     '''     响应购物车视图     '''     def get(self , request):         # 响应购物车页面         return render(request , 'cart.html')     def post(self , request):         # 商品添加购物车         json_str = request.body.decode()         json_dict = json.loads(json_str)         sku_id = json_dict.get('sku_id')         count = json_dict.get('count')         selected = json_dict.get('selected' , True)          try:             SKU.objects.get(id=sku_id)         except Exception:             return HttpResponseForbidden('sku_id 商品数据不存在')          user = request.user         redis_conn = get_redis_connection('carts')         # user_id = {sku_id:count}         redis_conn.hincrby('cart_%s'%user.id , sku_id , count)         if selected:             # 结果为 True , 勾选的进行保存             redis_conn.sadd('selected_%s'%user.id , sku_id)         return JsonResponse({'code': RETCODE.OK, 'errmsg':'OK'})  

2、响应渲染购物车页面

class CartsView(View):     '''     响应购物车视图     '''     def get(self , request):         # 响应购物车页面         user = request.user         redis_conn = get_redis_connection('carts')         redis_cart = redis_conn.hgetall('cart_%s' % user.id)         redis_selected = redis_conn.smembers('selected_%s'%user.id)         # cart_dict = {sku1:{count:200 , selected:true},{}……}         cart_dict = {}         for sku_id , count in redis_cart.items():             cart_dict[int(sku_id)] = {                 'count': int(count),                 'selected' : sku_id in redis_selected             }         # 获取购物车所有的商品数据         sku_ids = cart_dict.keys()         skus = SKU.objects.filter(id__in=sku_ids)         cart_skus = []         for sku in skus:             cart_skus_dict = {                 'id' : sku.id,                 'name':sku.name,                 'price' : str(sku.price),                 'count' : cart_dict.get(sku.id).get('count'),                 'selected' : str(cart_dict.get(sku.id).get('selected')),                 'amount':str(sku.price * cart_dict.get(sku.id).get('count')),                 'default_image_url':settings.STATIC_URL+'images/goods/'+sku.default_image.url+'.jpg'             }             cart_skus.append(cart_skus_dict)         context = {'cart_skus':cart_skus}         return render(request , 'cart.html' , context=context) <script type="text/javascript">     let carts = {{ cart_skus|safe }}; </script>  

3、修改购物车商品数据

    def put(self , request):         # 修改购物车商品数据         # {sku_id: 1, count: 2, selected: true}         json_str = request.body.decode()         json_dict = json.loads(json_str)         sku_id = json_dict.get('sku_id')         count = json_dict.get('count')         selected = json_dict.get('selected', True)         try:             sku = SKU.objects.get(id=sku_id)         except Exception:             return HttpResponseForbidden('sku_id 商品数据不存在')          user = request.user         redis_conn = get_redis_connection('carts')         redis_conn.hincrby('cart_%s' % user.id, sku_id, count)         if selected:             redis_conn.sadd('selected_%s' % user.id, sku_id)         else:             redis_conn.srem('selected_%s' % user.id, sku_id)         cart_skus_dict = {             'id': sku.id,             'name':  sku.name,             'price': sku.price,             'count': count,             'selected': selected,             'amount': sku.price * count,             'default_image_url': settings.STATIC_URL + 'images/goods/' + sku.default_image.url + '.jpg'         }         return JsonResponse({'code':RETCODE.OK , 'errmsg':"OK" , 'cart_sku':cart_skus_dict})  

4、删除购物车商品

    def delete(self , request):         # 删除购物车商品         json_str = request.body.decode()         json_dict = json.loads(json_str)         sku_id = json_dict.get('sku_id')         user = request.user         redis_conn = get_redis_connection('carts')         redis_conn.hdel('cart_%s' % user.id, sku_id)         redis_conn.srem('selected_%s' % user.id, sku_id)         return JsonResponse({'code': RETCODE.OK, 'errmsg': "OK"})  

5、全选购物车商品

# 全选购物车 path('carts/selection/' , views.CratSelectAllView.as_view()),  class CratSelectAllView(View):     '''     全选购物车商品     '''     def put(self , request):         json_dict = json.loads(request.body.decode())         selected = json_dict.get('selected')         user = request.user         redis_conn = get_redis_connection('carts')         redis_cart = redis_conn.hgetall('cart_%s' % user.id)         redis_sku_id = redis_cart.keys()         if selected:             redis_conn.sadd('selected_%s' % user.id, *redis_sku_id)         else:             for sku_id in redis_sku_id:                 redis_conn.srem('selected_%s' % user.id, sku_id)         return JsonResponse({'code': RETCODE.OK, 'errmsg': "OK"})  

5、浏览记录

用户浏览记录临时数据 , 数据读写频繁 , 存放在 redis

# 缓存 浏览记录商品 id     "history":{         "BACKEND" : "django_redis.cache.RedisCache",         "LOCATION" : "redis://127.0.0.1:6379/4",         "OPTIONS":{             "CLIENT_CLASS" : "django_redis.client.DefaultClient"         }     }, # 用户浏览记录 path('browse_histories/' , views.UserBrowerHistoryView.as_view()),  class UserBrowerHistoryView(View):      def get(self , request):         redis_conn = get_redis_connection('history')         user = request.user         sku_ids = redis_conn.lrange('history_%s'%user.id , 0 , -1)          skus = []         for sku_id in sku_ids:             sku = SKU.objects.get(id = sku_id)             sku_dict = {                 'id':sku.id,                 'name': sku.name,                 'price' : sku.price,                 'default_image_url':settings.STATIC_URL+'images/goods/'+sku.default_image.url+'.jpg'             }             skus.append(sku_dict)         return JsonResponse({'code':RETCODE.OK , 'errmsg':"OK" , 'skus':skus})      def post(self , request):         json_dict = json.loads(request.body.decode())         sku_id = json_dict.get('sku_id')          try:             SKU.objects.get(id=sku_id)         except Exception:             return HttpResponseForbidden('sku_id 商品数据不存在')          redis_conn = get_redis_connection('history')         user = request.user         # 去重         redis_conn.lrem('history_%s'%user.id , 0 ,  sku_id)         redis_conn.lpush('history_%s'%user.id , sku_id)         # 截取         redis_conn.ltrim('history_%s'%user.id , 0 , 20)          return JsonResponse({'code':RETCODE.OK , 'errmsg':"OK"})  

九、订单

1、响应订单

创建应用实现订单的功能 —— orders

# 订单页面 path('settlement/' , views.OrderSettlementView.as_view() , name='settlement'),  class OrderSettlementView(View):     '''     购物车结算订单页面     '''     def get(self , request):         user = request.user          # 获取用户收货地址         try:             addresses = Address.objects.filter(is_delete=False , user=user)         except Exception:             addresses = None          # 获取购物车商品数据         redis_conn = get_redis_connection('carts')         redis_cart = redis_conn.hgetall('cart_%s'%user.id)         redis_selected = redis_conn.smembers('selected_%s'%user.id)          # 获取勾选中的商品 id         new_cart_dict = {}         for sku_id in redis_selected:             new_cart_dict[int(sku_id)] = int(redis_cart[sku_id])          sku_ids = new_cart_dict.keys()         skus = SKU.objects.filter(id__in=sku_ids)         # 总件数 , 金额         total_number = 0         total_amount = 0         for sku in skus:             sku.count = new_cart_dict[sku.id]             sku.amount = sku.price * sku.count             total_number += sku.count             total_amount += sku.amount          freight = 35         context = {             'addresses': addresses,             'skus' : skus,             'total_amount' : total_amount,             'total_number' : total_number,             'freight' : freight,             'payment_amount':total_amount + freight         }          return render(request , 'place_order.html' , context=context) <script type="text/javascript">     let default_address_id = {{ user.default_address.id }};     let payment_amount = {{ payment_amount }}; </script>  

2、提交订单

# 提交订单 path('orders/commit/' , views.OrderCommitView.as_view())  class OrderCommitView(View):     '''     提交订单     '''     def post(self , request):         json_dict = json.loads(request.body.decode())         address_id = json_dict.get('address_id')         pay_method = json_dict.get('pay_method')          try:             address = Address.objects.get(id=address_id)         except Exception:             return HttpResponseForbidden('用户收货地址数据不存在')         if pay_method not in [OrderInfo.PAY_METHODS_ENUM['CASH'] , OrderInfo.PAY_METHODS_ENUM['ALIPAY']]:             return HttpResponseForbidden('支付方式不正确')         user = request.user         order_id = timezone.localdate().strftime('%Y%m%d%H%M%S')+('%05d'%user.id)          # 创建一个事务,要么全部操作成功 , 要么全部操作失败         with transaction.atomic():             # 获取数据库最初的状态             save_id = transaction.savepoint()             try:                 order = OrderInfo.objects.create(                     order_id= order_id,                     user = user,                     address=address,                     total_count = 0,                     total_amount = 0,                     freight = 35,                     pay_method = pay_method,                     status= OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY'] else OrderInfo.ORDER_STATUS_ENUM['UNSEND']                 )                  # 获取购物车商品数据                 redis_conn = get_redis_connection('carts')                 redis_cart = redis_conn.hgetall('cart_%s' % user.id)                 redis_selected = redis_conn.smembers('selected_%s' % user.id)                  # 获取购物车中勾选的状态数据                 new_cart_dict = {}                 for sku_id in redis_selected:                     new_cart_dict[int(sku_id)] = int(redis_cart[sku_id])                 skus = SKU.objects.filter(id__in = new_cart_dict.keys())                 #进行单件商品的计算                 for sku in skus:                     sku_count = new_cart_dict[sku.id]                      # 获取商品的销量和库存                     origin_stock= sku.stock                     origin_sales= sku.sales                      # 判断商品的库存是否足够                     if sku_count > origin_stock:                         transaction.savepoint_rollback(save_id)                         return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '商品库存不足'})                      # 对库存进行减少 , 销量进行增加                     sku.stock -= sku_count                     sku.sales += sku_count                     sku.save()                      # 保存商品订单信息                     OrderGoods.objects.create(                         order=order,                         sku=sku,                         count=sku_count,                         price = sku.price                     )                      # 计算单间商品的购买数量和总金额                     order.total_count += sku_count                     order.total_amount += sku_count * sku.price                  # 对总金额加入运费                 order.total_amount += order.freight                 order.save()              except Exception:                 # 数据库操作异常 , 事务回滚                 transaction.savepoint_rollback(save_id)                 return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '订单提交失败'})          # 提交事务         transaction.savepoint_commit(save_id)         return JsonResponse({'code':RETCODE.OK , 'errmsg':'OK' , 'order_id':order_id})  

3、提交订单成功页面

# 我的订单 path('orders/info/' , views.UserOrderInfoView.as_view() , name='myorder')  class UserOrderInfoView(View):     def get(self , request):         page_num = request.GET.get('page_num')         user = request.user         orders = user.orderinfo_set.all()         # 获取 商品订单数据         for order in orders:             # 支付方式 , 1 , 2             order.pay_method_name = OrderInfo.PAY_METHOD_CHOICES[order.pay_method - 1][1]             # 订单状态             order.status_name = OrderInfo.PAY_METHOD_CHOICES[order.status - 1][1]              order.sku_list = []             order_goods = order.skus.all()             for order_good in order_goods:                 sku = order_good.sku                 sku.count = order_good.count                 sku.amount = sku.price * sku.count                 order.sku_list.append(sku)              # 制作分页             if not page_num:                 page_num = 1             page_num = int(page_num)             paginatot = Paginator(orders , 5)             page_orders = paginatot.page(page_num)             total_page = paginatot.num_pages              context = {                 'page_orders' : page_orders,                 'total_page':total_page,                 'page_num':page_num             }          return render(request , 'user_center_order.html' , context=context)  

十、项目部署

1、Nginx

Nginx:开源的高性能的HTTP和反向代理服务器

反向代理:服务器做出逆向操作 , 代理服务器接收用户发送的请求,解析转发给内部服务器,返回Response的响应。

WAF功能:阻止 web 攻击

Nginx特点:内存小 , 并发能力强 , 灵活好扩展

2、配置Linux环境

1、要有 Python 环境

2、要有 MySQL 数据库

3、下载 redis 数据库

sudo yum install redis  

4、下载 Nginx

sudo yum install epel-release yum install -y nginx  

5、下载 UWSGI

sudo yum install epel-release yum install python3-devel pip3.8 install uwsgi==2.0.19.1  

6、下载项目需要的所有模块

pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple mysqlclient==2.1.0 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple django==3.2 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pymysql pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pillow==8.3.0 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple ronglian_sms_sdk pip install -i https://pypi.tuna.tsinghua.edu.cn/simple itsdangerous==1.1.0 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple urllib3==1.26.15 pip2 install -i https://pypi.tuna.tsinghua.edu.cn/simple django_redis pip install -i https://pypi.tuna.tsinghua.edu.cn/simple django_haystack pip install -i https://pypi.tuna.tsinghua.edu.cn/simple whoosh pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests  更新 pip版本: pip3 install --upgrade pip  在下载模块前,先下载需要的依赖 yum install python3-devel mysql-devel  项目中需要的模块 mysqlclient==2.1.1 django==3.2 pymysql pillow==8.3.0 ronglian_sms_sdk itsdangerous==1.1.0 urllib3==1.26.15 django_redis django_haystack whoosh requests 出现: Aonther app is ***** exit *** 另一个应用程序*****  执行: rm -f /var/rum/yum.pid  

3、项目部署

1、在项目上传到 Linux 之前 , 修改 settings.py 文件 , 允许所有主机访问

ALLOWED_HOSTS = ['*']  

2、将搜索索引目录: whoosh_index 删除

3、将整个项目的数据迁移数据库记录文件全部删除

4、通过 Xftp 上传到 Linux 中:opt目录中

5、配置 uwsgi 的配置信息

到 etc 目录下创建 uwsg.d目录 mkdir uwsgi.d

进入创建的目录中,创建 uwsgi.ini 配置文件: vim uwsgi.ini

[uwsgi] socket= 120.55.47.111:8080	 chdir=/opt/ShopSystem	 module=JiXuShopSystem/wsgi.py   processes=2 threads=2 master=True pidfile=uwsgi.pid buffer-size = 65535  

6、配置 Nginx

到 etc/nginx/nginx.conf

server {         listen       8080;	         # listen       [::]:80;         server_name  120.55.47.111:10056;	         # root         /usr/share/nginx/html;          # Load configuration files for the default server block.         # include /etc/nginx/default.d/*.conf;         charset utf-8;         location /static {                 alias /opt/www/django_-shop-system/static;	         }          location / {                 include uwsgi_params;                 uwsgi_pass 0.0.0.0:8005;                 uwsgi_param UWSGI_SCRITP django_-shop-system.wsgi;	                 uwsgi_param UWSGI_CHDIR /opt/www/django_-shop-system;	         }  

7、进入MySQL数据库 , 创建项目需要的数据库。

8、启动

启动 nginx : nginx 启动uwsgi:进入uwsgi.d目录下执行: uwsgi --ini uwsgi.ini 启动redis : systemctl start redis  关闭防火墙:systemctl stop firewalld.service  

9、迁移数据库,生成全文搜索索引

python3 manage.py rebuild_index

10、启动项目

python3.8 manage.py runserver 0.0.0.0:8000 一定要有ip和端口号 , 否则外部设备无法访问  

遭周文而舒志

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!