商品详情页依然采用页面静态化技术。
商品详情页的静态化由运营人员在编辑商品信息时触发生成静态化页面。
先来实现静态化异步任务,在celery_tasks中新建html/tasks.py任务
from celery_tasks.main import app
from django.template import loader
from django.conf import settings import os from goods.utils import get_categories from goods.models import SKU @app.task(name='generate_static_sku_detail_html') def generate_static_sku_detail_html(sku_id): """ 生成静态商品详情页面 :param sku_id: 商品sku id """ # 商品分类菜单 categories = get_categories() # 获取当前sku的信息 sku = SKU.objects.get(id=sku_id) sku.images = sku.skuimage_set.all() # 面包屑导航信息中的频道 goods = sku.goods goods.channel = goods.category1.goodschannel_set.all()[0] # 构建当前商品的规格键 # sku_key = [规格1参数id, 规格2参数id, 规格3参数id, ...] sku_specs = sku.skuspecification_set.order_by('spec_id') sku_key = [] for spec in sku_specs: sku_key.append(spec.option.id) # 获取当前商品的所有SKU skus = goods.sku_set.all() # 构建不同规格参数(选项)的sku字典 # spec_sku_map = { # (规格1参数id, 规格2参数id, 规格3参数id, ...): sku_id, # (规格1参数id, 规格2参数id, 规格3参数id, ...): sku_id, # ... # } spec_sku_map = {} for s in skus: # 获取sku的规格参数 s_specs = s.skuspecification_set.order_by('spec_id') # 用于形成规格参数-sku字典的键 key = [] for spec in s_specs: key.append(spec.option.id) # 向规格参数-sku字典添加记录 spec_sku_map[tuple(key)] = s.id # 获取当前商品的规格信息 #specs = [ # { # 'name': '屏幕尺寸', # 'options': [ # {'value': '13.3寸', 'sku_id': xxx}, # {'value': '15.4寸', 'sku_id': xxx}, # ] # }, # { # 'name': '颜色', # 'options': [ # {'value': '银色', 'sku_id': xxx}, # {'value': '黑色', 'sku_id': xxx} # ] # }, # ... #] specs = goods.goodsspecification_set.order_by('id') # 若当前sku的规格信息不完整,则不再继续 if len(sku_key) < len(specs): return for index, spec in enumerate(specs): # 复制当前sku的规格键 key = sku_key[:] # 该规格的选项 options = spec.specificationoption_set.all() for option in options: # 在规格参数sku字典中查找符合当前规格的sku key[index] = option.id option.sku_id = spec_sku_map.get(tuple(key)) spec.options = options # 渲染模板,生成静态html文件 context = { 'categories': categories, 'goods': goods, 'specs': specs, 'sku': sku } template = loader.get_template('detail.html') html_text = template.render(context) file_path = os.path.join(settings.GENERATED_STATIC_HTML_FILES_DIR, 'goods/'+str(sku_id)+'.html') with open(file_path, 'w') as f: f.write(html_text)
将形成商品类别部分的数据封装成一个公共函数,放在goods/utils.py中
from collections import OrderedDict
from .models import GoodsChannel
def get_categories(): """ 获取商城商品分类菜单 :return 菜单字典 """ # 商品频道及分类菜单 # 使用有序字典保存类别的顺序 # 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) return categories
异步任务的触发
运营人员在Admin站点保存商品信息时,应该触发生成商品静态页的异步任务。
我们需要调整Admin站点保存和删除商品信息时行为。
在Admin站点保存或删除数据时,Django是调用的Admin站点管理器类的save_model()方法和delete_model()方法,我们只需重新实现这两个方法,在这两个方法中调用异步任务即可。
编辑goods/admin.py
from django.contrib import admin
# Register your models here.
from . import models class SKUAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): obj.save() from celery_tasks.html.tasks import generate_static_sku_detail_html generate_static_sku_detail_html.delay(obj.id) class SKUSpecificationAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): obj.save() from celery_tasks.html.tasks import generate_static_sku_detail_html generate_static_sku_detail_html.delay(obj.sku.id) def delete_model(self, request, obj): sku_id = obj.sku.id obj.delete() from celery_tasks.html.tasks import generate_static_sku_detail_html generate_static_sku_detail_html.delay(sku_id) class SKUImageAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): obj.save() from celery_tasks.html.tasks import generate_static_sku_detail_html generate_static_sku_detail_html.delay(obj.sku.id) # 设置SKU默认图片 sku = obj.sku if not sku.default_image_url: sku.default_image_url = obj.image.url sku.save() def delete_model(self, request, obj): sku_id = obj.sku.id obj.delete() from celery_tasks.html.tasks import generate_static_sku_detail_html generate_static_sku_detail_html.delay(sku_id) admin.site.register(models.GoodsCategory) admin.site.register(models.GoodsChannel) admin.site.register(models.Goods) admin.site.register(models.Brand) admin.site.register(models.GoodsSpecification) admin.site.register(models.SpecificationOption) admin.site.register(models.SKU, SKUAdmin) admin.site.register(models.SKUSpecification, SKUSpecificationAdmin) admin.site.register(models.SKUImage, SKUImageAdmin)
脚本工具
为了开发方便,我们还可以编写手动生成所有商品静态页面的脚本regenerate_detail_html.py
#!/usr/bin/env python
"""
功能:手动生成所有SKU的静态detail html文件
使用方法:
./regenerate_detail_html.py
"""
import sys
sys.path.insert(0, '../') sys.path.insert(0, '../meiduo_mall/apps') import os if not os.getenv('DJANGO_SETTINGS_MODULE'): os.environ['DJANGO_SETTINGS_MODULE'] = 'meiduo_mall.settings.dev' import django django.setup() from django.template import loader from django.conf import settings from goods.utils import get_categories from goods.models import SKU def generate_static_sku_detail_html(sku_id): """ 生成静态商品详情页面 :param sku_id: 商品sku id """ # 商品分类菜单 categories = get_categories() # 获取当前sku的信息 sku = SKU.objects.get(id=sku_id) sku.images = sku.skuimage_set.all() # 面包屑导航信息中的频道 goods = sku.goods goods.channel = goods.category1.goodschannel_set.all()[0] # 构建当前商品的规格键 sku_specs = sku.skuspecification_set.order_by('spec_id') sku_key = [] for spec in sku_specs: sku_key.append(spec.option.id) # 获取当前商品的所有SKU skus = goods.sku_set.all() # 构建不同规格参数(选项)的sku字典 # spec_sku_map = { # (规格1参数id, 规格2参数id, 规格3参数id, ...): sku_id, # (规格1参数id, 规格2参数id, 规格3参数id, ...): sku_id, # ... # } spec_sku_map = {} for s in skus: # 获取sku的规格参数 s_specs = s.skuspecification_set.order_by('spec_id') # 用于形成规格参数-sku字典的键 key = [] for spec in s_specs: key.append(spec.option.id) # 向规格参数-sku字典添加记录 spec_sku_map[tuple(key)] = s.id # 获取当前商品的规格信息 specs = goods.goodsspecification_set.order_by('id') # 若当前sku的规格信息不完整,则不再继续 if len(sku_key) < len(specs): return for index, spec in enumerate(specs): # 复制当前sku的规格键 key = sku_key[:] # 该规格的选项 options = spec.specificationoption_set.all() for option in options: # 在规格参数sku字典中查找符合当前规格的sku key[index] = option.id option.sku_id = spec_sku_map.get(tuple(key)) spec.options = options # 渲染模板,生成静态html文件 context = { 'categories': categories, 'goods': goods, 'specs': specs, 'sku': sku } template = loader.get_template('detail.html') html_text = template.render(context) file_path = os.path.join(settings.GENERATED_STATIC_HTML_FILES_DIR, 'goods/'+str(sku_id)+'.html') with open(file_path, 'w') as f: f.write(html_text) if __name__ == '__main__': skus = SKU.objects.all() for sku in skus: print(sku.id) generate_static_sku_detail_html(sku.id)