电商概念: SPU 和 SKU
- SPU(Standard Product Unit): 标准产品单位
- 可以理解为: 就是'类对象'
- 比如'iPhone X'
- SKU(Standard Keeping Unit): 库存量单位
- 可以理解为: 就是'类实例对象'
- 比如'iPhone X 黑色', 'iPhone X 白色'
商品部分
-
新建两个app
-
goods(商品)
-
contents(广告)
-
-
模型如下:
# goods.models
from django.db import models
from utils.models import BaseModel
class GoodsCategory(BaseModel):
"""
商品类别(分组)
"""
name = models.CharField(max_length=10, verbose_name='名称')
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, verbose_name='父类别')
class Meta:
db_table = 'tb_goods_category'
verbose_name = '商品类别'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class GoodsChannel(BaseModel):
"""
商品频道
"""
group_id = models.IntegerField(verbose_name='组号')
category = models.ForeignKey(GoodsCategory, on_delete=models.CASCADE, verbose_name='顶级商品类别')
url = models.CharField(max_length=50, verbose_name='频道页面链接')
sequence = models.IntegerField(verbose_name='组内顺序')
class Meta:
db_table = 'tb_goods_channel'
verbose_name = '商品频道'
verbose_name_plural = verbose_name
def __str__(self):
return self.category.name
class Brand(BaseModel):
"""
品牌
"""
name = models.CharField(max_length=20, verbose_name='名称')
logo = models.ImageField(verbose_name='Logo图片')
first_letter = models.CharField(max_length=1, verbose_name='品牌首字母')
class Meta:
db_table = 'tb_brand'
verbose_name = '品牌'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Goods(BaseModel):
"""
商品SPU
"""
name = models.CharField(max_length=50, verbose_name='名称')
brand = models.ForeignKey(Brand, on_delete=models.PROTECT, verbose_name='品牌')
category1 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat1_goods', verbose_name='一级类别')
category2 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat2_goods', verbose_name='二级类别')
category3 = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, related_name='cat3_goods', verbose_name='三级类别')
sales = models.IntegerField(default=0, verbose_name='销量')
comments = models.IntegerField(default=0, verbose_name='评价数')
class Meta:
db_table = 'tb_goods'
verbose_name = '商品'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class GoodsSpecification(BaseModel):
"""
商品规格
"""
goods = models.ForeignKey(Goods, on_delete=models.CASCADE, verbose_name='商品')
name = models.CharField(max_length=20, verbose_name='规格名称')
class Meta:
db_table = 'tb_goods_specification'
verbose_name = '商品规格'
verbose_name_plural = verbose_name
def __str__(self):
return '%s: %s' % (self.goods.name, self.name)
class SpecificationOption(BaseModel):
"""
规格选项
"""
spec = models.ForeignKey(GoodsSpecification, on_delete=models.CASCADE, verbose_name='规格')
value = models.CharField(max_length=20, verbose_name='选项值')
class Meta:
db_table = 'tb_specification_option'
verbose_name = '规格选项'
verbose_name_plural = verbose_name
def __str__(self):
return '%s - %s' % (self.spec, self.value)
class SKU(BaseModel):
"""
商品SKU
"""
name = models.CharField(max_length=50, verbose_name='名称')
caption = models.CharField(max_length=100, verbose_name='副标题')
goods = models.ForeignKey(Goods, on_delete=models.CASCADE, verbose_name='商品')
category = models.ForeignKey(GoodsCategory, on_delete=models.PROTECT, verbose_name='从属类别')
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='单价')
cost_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='进价')
market_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='市场价')
stock = models.IntegerField(default=0, verbose_name='库存')
sales = models.IntegerField(default=0, verbose_name='销量')
comments = models.IntegerField(default=0, verbose_name='评价数')
is_launched = models.BooleanField(default=True, verbose_name='是否上架销售')
default_image_url = models.CharField(max_length=200, default='', null=True, blank=True, verbose_name='默认图片')
class Meta:
db_table = 'tb_sku'
verbose_name = '商品SKU'
verbose_name_plural = verbose_name
def __str__(self):
return '%s: %s' % (self.id, self.name)
class SKUImage(BaseModel):
"""
SKU图片
"""
sku = models.ForeignKey(SKU, on_delete=models.CASCADE, verbose_name='sku')
image = models.ImageField(verbose_name='图片')
class Meta:
db_table = 'tb_sku_image'
verbose_name = 'SKU图片'
verbose_name_plural = verbose_name
def __str__(self):
return '%s %s' % (self.sku.name, self.id)
class SKUSpecification(BaseModel):
"""
SKU具体规格
"""
sku = models.ForeignKey(SKU, on_delete=models.CASCADE, verbose_name='sku')
spec = models.ForeignKey(GoodsSpecification, on_delete=models.PROTECT, verbose_name='规格名称')
option = models.ForeignKey(SpecificationOption, on_delete=models.PROTECT, verbose_name='规格值')
class Meta:
db_table = 'tb_sku_specification'
verbose_name = 'SKU规格'
verbose_name_plural = verbose_name
def __str__(self):
return '%s: %s - %s' % (self.sku, self.spec.name, self.option.value)
# contents.models
from django.db import models
from utils.models import BaseModel
class ContentCategory(BaseModel):
"""
广告内容类别(分组)
"""
name = models.CharField(max_length=50, verbose_name='名称')
key = models.CharField(max_length=50, verbose_name='类别键名')
class Meta:
db_table = 'tb_content_category'
verbose_name = '广告内容类别'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Content(BaseModel):
"""
广告内容(主表)
"""
category = models.ForeignKey(ContentCategory, on_delete=models.PROTECT, verbose_name='类别')
title = models.CharField(max_length=100, verbose_name='标题')
url = models.CharField(max_length=300, verbose_name='内容链接')
image = models.ImageField(null=True, blank=True, verbose_name='图片')
text = models.TextField(null=True, blank=True, verbose_name='内容')
sequence = models.IntegerField(verbose_name='排序')
status = models.BooleanField(default=True, verbose_name='是否展示')
class Meta:
db_table = 'tb_content'
verbose_name = '广告内容'
verbose_name_plural = verbose_name
def __str__(self):
return self.category.name + ': ' + self.title
FastDFS分布式文件系统
- 基于B/S 架构,工作流程详情见图
Docker组件
-
Docker客户端和服务器(或者称为'守护进程')
-
镜像:用户的应用程序,比如以前xp系统的'xxx.gho'
-
Registry(注册中心)相当于github的仓库,用来存放镜像
- 公有Registry和私有Registry
-
容器: 存放用户的应用程序/服务(是一个环境,'集装箱')
- 先启动容器,然后登录到容器,安装自己的应用
-
安装,参考网址,拷贝步骤如下
http://121.5.151.41/mylesson/Django/%E7%BE%8E%E5%A4%9A%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE%E8%AF%BE%E4%BB%B6/C03-Goods/Docker/InstallAndOperations.html
- 更新ubuntu的apt源索引
sudo apt-get update
- 安装包允许apt通过HTTPS使用仓库
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common
- 添加Docker官方GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
- 设置Docker稳定版仓库
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
- 添加仓库后,更新apt源索引
sudo apt-get update
- 安装最新版Docker CE(社区版)
sudo apt-get install docker-ce
- 检查Docker CE是否安装正确
sudo docker run hello-world
- 出现如下信息,表示安装成功
Hello from Docker!......
- (选做)为了避免每次命令都输入sudo,可以设置用户权限,注意执行后须注销重新登录
sudo usermod -a -G docker $USER
启动与停止
- 安装完成Docker后,默认已经启动了docker服务,如需手动控制docker服务的启停,可执行如下命令
# 启动docker
sudo service docker start
# 停止docker
sudo service docker stop
# 重启docker
sudo service docker restart
Docker镜像操作
-
Docker镜像: docker自己本身的底层文件,一般无需去关注
我们只操作 Docker镜像的'应用层',无需理会底层 -
image文件: 打包应用程序及其依赖
-
作用: 生成 docker 容器(实例)
-
同一个 image 文件,可以生成多个同时运行的容器实例
-
-
列出镜像
docker image ls
- 拉取镜像
- docker image pull library/hello-world
- docker image pull hello-world # 简写
- 删除镜像
docker image rm 镜像名或镜像id # docker image rm hello-world
- 注意事项:如果提示容器被占用,删除失败,可以这么解决,先删容器,再删镜像
- docker ps -a # 查看所有的容器(包括未运行的容器)
- docker rm 容器ID
- docker rm 镜像ID
Docker容器的操作
- 创建容器,把镜像扔进去并运行起来
- docker run [option] 镜像名 [向启动容器中传入的命令]
常用可选参数说明:
-i 表示以“交互模式”运行容器
-t 表示容器启动后会进入其命令行。加入这两个参数后,容器创建就能登录进去。即 分配一个伪终端。
--name 为创建的容器命名
-v 表示目录映射关系(前者是宿主机目录,后者是映射到宿主机上的目录,即 宿主机目录:容器中目录),可以使 用多个-v 做多个目录或文件映射。注意:最好做目录映射,在宿主机上做修改,然后 共享到容器上。
-d 在run后面加上-d参数,则会创建一个守护式容器在后台运行(这样创建容器后不 会自动登录容器,如果只加-i -t 两个参数,创建后就会自动进去容器)。
-p 表示端口映射,前者是宿主机端口,后者是容器内的映射端口。可以使用多个-p 做多个端口映射
-e 为容器设置环境变量
--network=host 表示将主机的网络环境映射到容器中,容器的网络与主机相同
- 交互式容器
# 创建一个交互式容器,并命名为myubuntu
docker run -it --name=myubuntu ubuntu /bin/bash
- 在容器中可以随意执行linux命令,就是一个ubuntu的环境,当执行exit命令退出时,该容器也随之停止
-
守护式容器
- 如果对于一个需要长期运行的容器来说,我们可以创建一个守护式容器
在容器内部exit退出时,容器也不会停止
- 如果对于一个需要长期运行的容器来说,我们可以创建一个守护式容器
docker run -dit --name=myubuntu2 ubuntu
- 进入已运行的容器
- docker exec -it 容器名或容器id 进入后执行的第一个命令
- docker exec -it myubuntu2 /bin/bash
- 查看容器
# 列出本机正在运行的容器
docker container ls
# 列出本机所有容器,包括已经终止运行的
docker container ls --all
- 停止与启动容器
# 停止一个已经在运行的容器
docker container stop 容器名或容器id
# 启动一个已经停止的容器
docker container start 容器名或容器id
# kill掉一个已经在运行的容器
docker container kill 容器名或容器id
- 删除容器
docker container rm 容器名或容器id
- 将容器保存为镜像
我们可以通过如下命令将容器保存为镜像
docker commit 容器名 镜像名
- 镜像备份与迁移
我们可以通过save命令将镜像打包成文件,拷贝给别人使用
- docker save -o 保存的文件名 镜像名
- docker save -o ./ubuntu.tar ubuntu
- 在拿到镜像文件后,可以通过load方法,将镜像加载到本地
- docker load -i ./ubuntu.tar
使用Docker安装FastDFS
-
获取镜像
- 获取镜像可以通过下载
docker image pull delron/fastdfs
- 也可以直接使用提供给大家的镜像备份文件
docker load -i 文件路径/fastdfs_docker.tar
- 加载好镜像后,就可以开启运行FastDFS的tracker和storage了
-
运行tracker
- 执行如下命令开启tracker 服务
# 我们将fastDFS tracker运行目录映射到本机的 /var/fdfs/tracker目录中
docker run -dti --network=host --name tracker -v /var/fdfs/tracker:/var/fdfs delron/fastdfs tracker
-
执行如下命令查看tracker是否运行起来
docker container ls
-
如果想停止tracker服务,可以执行如下命令
docker container stop tracker
-
停止后,重新运行tracker,可以执行如下命令
docker container start tracker
-
运行storage,执行如下命令开启storage服务
docker run -dti --network=host --name storage -e TRACKER_SERVER=10.211.55.5:22122 -v /var/fdfs/storage:/var/fdfs delron/fastdfs storage
- 注意事项
- TRACKER_SERVER=本机的ip地址:22122 本机ip地址不要使用127.0.0.1
- 我们将fastDFS storage运行目录映射到本机的/var/fdfs/storage目录中
-
执行如下命令查看storage是否运行起来
docker container ls
-
如果想停止storage服务,可以执行如下命令
docker container stop storage
-
停止后,重新运行storage,可以执行如下命令
docker container start storage
- 如果无法重新运行,可以删除/var/fdfs/storage/data目录下的fdfs_storaged.pid 文件,然后重新运行storage
安装FastDFS的Python客户端
pip install fdfs_client-py-master.zip
pip install mutagen # 两个依赖库
pip isntall requests
- 安装报错踩坑记录
- 坑1: 报C++安装错误(装了,还是不能解决问题),解决办法
- 参考网址: https://blog.csdn.net/weixin_43796109/article/details/108194248
- 解压文件,执行 python setup.py install
- 进入 setup.py 文件,注释掉以下两行代码
sdict = {
......
'classifiers': [
'Development Status :: 1 - Production/Beta',
'Environment :: Console',
'Intended Audience :: Developers',
'License :: GPLV3',
'Operating System :: OS Independent',
'Programming Language :: Python'],
# 'ext_modules': [Extension('fdfs_client.sendfile',
# sources=['fdfs_client/sendfilemodule.c'])],
}
- 找到fdfs_client/storage_client.py文件打开注释下面的一行代码
# from fdfs_client.sendfile import *
- 一定要把 坑2 先改了,再执行 python setup.py install
- 会生成后缀为'xxx.egg'的文件,就对了
- 坑2:导入from fdfs_client.client import Fdfs_client报错: No module named 'mutagen._compat'
- 错误原因:导包时mutang._compat不存在,查看源码发现_compat在_senf文件夹下,
- 解决方式:需要将fdfs_client的utils.py的导包路径进行修改(使用压缩包zip去编辑保存即可)
# from mutagen._compat import StringIO
from mutagen._senf._compat import StringIO
- 坑3: client = Fdfs_client(conf_path='utils.fastdfs.client.conf')
报错: configparser.NoOptionError: No option 'connect_timeout' in section: '__config__'
- 解决办法:配置文件的路径,必须修改成绝对路径
client = Fdfs_client(conf_path=r'D:\Python\new_django\meiduo_mail\meiduo\meiduo\utils\fastdfs\client.conf')
-
项目utils目录新建'fastdfs包',把配置文件 client.conf复制进去
重要的配置就两个,其他默认即可- base_path(日志文件)
- tracker_server(服务器IP)
- 解压以后,python setup.py install 安装报错: #error: platfom not supported
### client.conf
# connect timeout in seconds
# default value is 30s
connect_timeout=30
# network timeout in seconds
# default value is 30s
network_timeout=120
# the base path to store log files
# base_path=/home/python/Desktop/python_Django/fdfs/log_files
# 配置fastdfs日志文件存放位置
base_path=/home/anning/Desktop/fastdfs/log_files
# tracker_server can ocur more than once, and tracker_server format is
# "host:port", host can be hostname or ip address
# 配置服务器IP地址
tracker_server=192.168.11.39:22122
#standard log level as syslog, case insensitive, value list:
### emerg for emergency
### alert
### crit for critical
### error
### warn for warning
### notice
### info
### debug
log_level=info
# if use connection pool
# default value is false
# since V4.05
use_connection_pool = false
# connections whose the idle time exceeds this time will be closed
# unit: second
# default value is 3600
# since V4.05
connection_pool_max_idle_time = 3600
# if load FastDFS parameters from tracker server
# since V4.05
# default value is false
load_fdfs_parameters_from_tracker=false
# if use storage ID instead of IP address
# same as tracker.conf
# valid only when load_fdfs_parameters_from_tracker is false
# default value is false
# since V4.05
use_storage_id = false
# specify storage ids filename, can use relative or absolute path
# same as tracker.conf
# valid only when load_fdfs_parameters_from_tracker is false
# since V4.05
storage_ids_filename = storage_ids.conf
#HTTP settings
http.tracker_server_port=80
#use "#include" directive to include HTTP other settiongs
##include http.conf
- fastdfs 客户端demo测试
- 测试demo之前,先把ubuntu虚拟机的tracker&storage服务开启
- docker container start tracker
- docker container start storage
from fdfs_client.client import Fdfs_client,get_tracker_conf
client = Fdfs_client(conf_path=r'D:\Python\new_django\meiduo_mail\meiduo\meiduo\utils\fastdfs\client.conf')
# res 返回值是一个字典,包裹反馈信息
res = client.upload_by_filename('demo.png')
'''
{'Group name': 'group1', 'Remote file_id': 'group1\\M00/00/00/wKgLJ2Oqat2AQsPXAAAMcih_5Rk631.png', 'Status': 'Upload successed.', 'Local file name': 'demo.png', 'Uploaded size': '3.00KB', 'Storage IP': '192.168.11.39'}
- 访问地址: http://192.168.11.39:8888/group1/M00/00/00/wKgLJ2Oqat2AQsPXAAAMcih_5Rk631.png
'''
- 如果出现远程服务器没有响应的问题,十有八九是远程服务器没有开放端口(图片访问的是8888端口也要开启)
- sudo ufw status # 查看端口开放情况
- sudo ufw all <端口号> # 允许某个端口
- sudo ufw enable # 开启防火墙
自定义Django文件存储系统
-
Django自带文件存储系统,但是默认文件存储在本地,在本项目中,我们需要将文件保存到FastDFS服务器上
所以需要自定义文件存储系统 -
存储类中必须实现_open()和_save()方法
以及任何后续使用中可能用到的其他方法(参照django文档) -
子类必须可以不用传任何参数(django规定)
from django.conf import settings
from django.core.files.storage import Storage
class FastDFSStorage(Storage):
def __init__(self):
pass
def _open(self,name,mode='rb'):
# 打开文件,而项目不需要打开,只上传而已,这里啥都不做
pass
def _save(self,name,content):
# 默认文件存储到本地,重写这个方法实现上传到fastdfs
# name是文件名,content是以'rb'模式打开的文件对象
# content.read()就是读取文件的二进制数据
# return file_id
pass
def exists(self, name):
pass
def url(self,name):
pass
from django.conf import settings
from django.core.files.storage import Storage
from fdfs_client.client import Fdfs_client
class FastDFSStorage(Storage):
def __init__(self):
pass
def _open(self,name,mode='rb'):
pass
def _save(self,name,content):
client = Fdfs_client(conf_path=r'D:\Python\new_django\meiduo_mail\meiduo\meiduo\utils\fastdfs\client.conf')
# upload_by_filename('xxx.png') 必须传入文件的绝对路径(上传的文件带有后缀),这里不满足需求
# 这个方法,可以传入文件的二进制数据(上传的文件不带后缀)
res = client.upload_by_buffer(content.read())
if res.get('Status') != 'Upload successed.':
raise Exception('Upload file failed')
file_id = res.get('Remote file_id')
return file_id
def exists(self, name):
'''
- 上传的时候,调用此方法判断文件是否已上传,如果没有上传则调用 save()方法进行上传
'''
# 标明文件需要上传,若返回True则表明文件不需要上传
return False
def url(self,name):
'''
- 要访问图片时,调用此方法获取图片文件的绝对路径
- name: 要访问的图片的 file_id
- return 完整图片的访问路径: storage_server IP:8888 + file_id
'''
return 'http://192.168.11.39:8888/' + name
- 最后,settings配置
#-----------文件存储配置-------#
DEFAULT_FILE_STORAGE = 'utils.fastdfs.fdfs_storage.FastDFSStorage'
富文本编辑器 CKEditor
- 参考网址
https://blog.csdn.net/qq_35709559/article/details/86544093
- 安装
pip install django-ckeditor
- 配置
### settings
......
NSTALLED_APPS = [
...
'ckeditor', # 富文本编辑器
'ckeditor_uploader', # 富文本编辑器上传图片模块
...
]
......
# 富文本编辑器ckeditor配置
CKEDITOR_CONFIGS = {
'default':{
'toolbar':'full', # 完整工具条
'height': 300, # 编辑高度
# 'woidth': 300, # 编辑宽度
},
}
CKEDITOR_UPLOAD_PATH = '' # 上传图片保存路径,使用了fastDFS,设置为''
### 总路由urls.py
urlpatterns = [
...
url(r'^ckeditor/', include('ckeditor_uploader.urls')),
]
-
为模型类添加字段
- ckeditor提供了两种类型的Django模型类字段
- ckeditor.fields.RichTextField 不支持上传文件的富文本字段
- ckeditor_uploader.fields.RichTextUploadingField 支持上传文件的富文本字段
# models.py
...
class Goods(BaseModel):
"""
商品SPU
"""
...
desc_detail = RichTextUploadingField(default='', verbose_name='详细介绍')
desc_pack = RichTextField(default='', verbose_name='包装信息')
desc_service = RichTextUploadingField(default='', verbose_name='售后服务')
...
- 添加FastDFS保存的测试图片数据
- 将/var/fdfs/storage中的data目录删除
- 将data.tar.gz文件拷贝到/var/fdfs/storage中,并解压缩
sudo cp -i data.tar.gz /local/arm # 拷贝
sudo tar -zxvf data.tar.gz # 解压
- 添加对应的数据库测试数据
mysql -h127.0.0.1 -uroot -pmysql meiduo_mall < goods_data.sql
页面静态化
- 参考网址
https://blog.csdn.net/qq_35709559/article/details/86582028
- 商场首页被频繁访问,为了提高访问速度,除了使用缓存技术外,还可以使用页面静态化技术
页面静态化即动态渲染生成的页面结果保存成html文件,放到静态服务器中
用户访问的时候访问的直接是处理好的html静态文件
对于页面中属于每个用户展示不同数据内容的部分, 可以在用户请求完静态化页面之后
再页面中向后端发起请求,获取数据属于用户的特殊的数据
### contents.crons.py
'''
- 从db获取数据,然后渲染模板,最后放到前端文件
'''
from collections import OrderedDict
from django.conf import settings
from django.template import loader
import os
import time
from goods.models import GoodsChannel
from .models import ContentCategory
def generate_static_index_html():
"""
生成静态的主页html文件
"""
print('%s: generate_static_index_html' % time.ctime())
# 商品频道及分类菜单
# 使用有序字典保存类别的顺序
# categories = {
# 1: { # 组1
# 'channels': [{'id':, 'name':, 'url':},{}, {}...],
# 'sub_cats': [{'id':, 'name':, 'sub_cats':[{},{}]}, {}, {}, ..]
# },
# 2: { # 组2
#
# }
# }
categories = OrderedDict()
channels = GoodsChannel.objects.order_by('group_id', 'sequence')
for channel in channels:
group_id = channel.group_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
})
# 构建当前类别的子类别
for cat2 in cat1.goodscategory_set.all():
cat2.sub_cats = []
for cat3 in cat2.goodscategory_set.all():
cat2.sub_cats.append(cat3)
categories[group_id]['sub_cats'].append(cat2)
# 广告内容
contents = {}
content_categories = ContentCategory.objects.all()
for cat in content_categories:
contents[cat.key] = cat.content_set.filter(status=True).order_by('sequence')
# 渲染模板
context = {
'categories': categories,
'contents': contents
}
template = loader.get_template('index.html')
html_text = template.render(context)
file_path = os.path.join(settings.GENERATED_STATIC_HTML_FILES_DIR, 'index.html')
with open(file_path, 'w', encoding='utf-8') as f:
f.write(html_text)
- 在配置文件中添加生成静态文件的保存路径
### settings
......
# 静态化主页存储路径
GENERATED_STATIC_HTML_FILES_DIR = os.path.join((os.path.dirname(BASE_DIR)), 'front_end')
- 在项目中新建templates模板目录,配置模板目录
......
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # 新增
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
...
-
在模板目录中新建index.html模板文件
-
定时任务,需要用到 'django-crontab'插件(Linux系统才可以用,windows不能用)
使用 redis 存储商品的浏览记录
-
本质存储的是 商品ID
-
为什么不存在MySQL
- 因为这种数据没有必要作'持久化'
-
redis 实现流程
-
先去重
-
添加记录
-
最多存5条记录(切片)
-
-
后端代码
### views
# 增加一条记录,使用 CreateAPIView
class UserBrowserHistoryView(CreateAPIView):
serializer_class = UserBrowserHistorySerializer # 自定义序列化器
permission_classes = [IsAuthenticated] # 校验不能丢
### serializers
# 只对sku_id进行校验
class UserBrowserHistorySerializer(serializers.Serializer):
sku_id = serializers.IntegerField(min_value=1, label='商品ID')
# 校验 sku_id是否存在
def validate_sku_id(self, value):
try:
SKU.objects.get(id=value)
except SKU.DoesNotExist:
raise serializers.ValidationError('sku_id不存在!')
return value
# 为每个用户在 redis中单独创建一个list,存储sku_id
def create(self, validated_data):
sku_id = validated_data.get('sku_id')
user = self.context['request'].user
conn = get_redis_connection('history')
pl = conn.pipeline()
user_list_name = 'history_{}'.format(user.id)
pl.lrem(user_list_name, 0, sku_id)
pl.lpush(user_list_name, sku_id)
pl.ltrim(user_list_name, 0, 4)
pl.execute()
return validated_data
### urls
......
urlpatterns = [
......
# 商品浏览记录
url(r'^browse_histories/$', views.UserBrowserHistoryView.as_view()),
]
- 前端代码
......
// 页面加载完毕后,立即把 sku_id 发给后端
mounted: function(){
// 添加用户浏览历史记录
this.get_sku_id();
if (this.user_id) {
axios.post(this.host+'/browse_histories/', {
sku_id: this.sku_id
}, {
headers: {
'Authorization': 'JWT ' + this.token
}
})
}
this.get_cart();
this.get_hot_goods();
this.get_comments();
},
- 浏览商品记录,redis测试
- select 3
- keys * # 啥都没
- keys * # 点击商品浏览记录
- "history_1"
- lrange history_1 0 -1
- "16"
商品浏览记录的展示
-
是否可以用 ListAPIView
- 不可以,使用 ListAPIView,涉及到 queryset
涉及到模型,而我们是把数据存入redis,没有模型这种概念
- 不可以,使用 ListAPIView,涉及到 queryset
-
为了节省路由,把查询商品记录的逻辑,写在之前的View
### views
......
class UserBrowserHistoryView(CreateAPIView):
serializer_class = UserBrowserHistorySerializer
permission_classes = [IsAuthenticated]
# 展示商品记录
def get(self,request):
conn = get_redis_connection('history')
user = request.user
user_list_name = 'history_{}'.format(user.id)
sku_ids = conn.lrange(user_list_name,0,-1)
# 以下写法,顺序有可能会乱掉,所以不这么写
# sku_list = SKU.objects.filter(id__in=sku_ids)
sku_list = []
for sku_id in sku_ids:
obj = SKU.objects.get(id=sku_id)
sku_list.append(obj)
# 自定义序列化器
serializer = SKUSerializer(sku_list,many=True)
return Response(serializer.data)
### serializers
......
class SKUSerializer(serializers.ModelSerializer):
class Meta:
model = SKU
fields = ('id','name','price','default_image_url','comments')
- 前端代码
......
mounted: function () {
// 判断用户的登录状态
......
// 补充请求浏览历史
axios.get(this.host + '/browse_histories/', {
headers: {
'Authorization': 'JWT ' + this.token
},
responseType: 'json'
})
.then(response => {
// 渲染数据
this.histories = response.data;
for (var i = 0; i < this.histories.length; i++) {
this.histories[i].url = '/goods/' + this.histories[i].id + '.html';
}
})
})
.catch(error => {
if (error.response.status == 401 || error.response.status == 403) {
location.href = '/login.html?next=/user_center_info.html';
}
});
......
},