后端电商【免费下载链接】django-oscarDomain-driven e-commerce for Django项目地址https://gitcode.com/gh_mirrors/dj/django-oscar点击查看免费下载本篇技术指南面向在 django-oscar 电商框架中扩展促销系统的开发者完整讲解如何编写自定义 Range 类、通过create_range注册进数据库并使其出现在后台Dashboard的 Range 与 Offer 管理中。读完本文你将掌握 Range 的四种内置配置方式、自定义 Range 的接口约定name/contains_product/num_products/all_products、proxy_class动态加载的底层原理以及如何用 Django 数据迁移在多个环境间自动部署这些范围。Range 是什么促销系统里的商品集合django-oscar 的促销Offer系统高度依赖Range范围模型——它抽象表示一组来自商品目录catalogue的商品是优惠条件Condition与优惠权益Benefit作用的对象集合。其核心模型定义位于 src/oscar/apps/offer/abstract_models.pyAbstractRange可被自定义覆盖表结构包含name、slug、description基本标识信息includes_all_products是否包含整个商品目录included_products/excluded_products按具体商品显式收录 / 排除通过RangeProduct中间表支持display_order排序classes按商品类型ProductClass收录included_categories/excluded_categories按商品分类Category收录并支持排除分类在includes_all_products勾选时生效is_public是否生成面向顾客的公开页面get_absolute_url指向catalogue:rangeproxy_class指向自定义 Range 类的路径字符串本文核心。通过后台可配置的四种内置 Range原文档明确指出借助 Dashboard一个 Range 可以被配置为以下三种形态之一结合includes_all_products标志实为四种整个商品目录勾选includes_all_products配合excluded_products/excluded_categories做黑名单式筛选按 ID/SKU 挑选的商品子集在后台逐一手动添加或使用CSV 上传批量导入RangeProductFileUpload模型负责承载上传文件相关后台实现在 src/oscar/apps/dashboard/ranges/views.py一组商品分类通过included_categories多对多关联收录分类下的全部商品整个目录 分类排除includes_all_products为真时excluded_categories的语义变为从全量中剔除这些分类的商品。这些配置均由数据库字段驱动AbstractRange.product_queryset属性abstract_models.py负责把上述字段编译成一条缓存的实际商品QuerySet先以includesself起步再按子商品、商品类型、分类逐步扩展过滤条件最后剔除excludes中的商品并用distinct()去重。为什么需要自定义 Range内置配置覆盖了全目录 / 指定商品 / 指定分类三类常见需求但电商业务中常常出现商户专属的、无法用静态关联表达的商品集合原文档给出了几个典型例子所有适用低税率 VAT减免税率的商品某位威尔士作者的全部书籍标题里带感叹号的 DVD。这类规则的特点是判定逻辑存在于商品自身属性税率字段、作者字段、标题文本而非商品与分类/类型的静态关系里。若为每一种规则都手动维护一份商品清单会随目录增长迅速失控。自定义 Range 正是为这类可编程规则而生的扩展点——用一个 Python 类封装判定逻辑让促销系统在运行时动态计算集合。自定义 Range 的接口约定一个可用的自定义 Range 类必须实现以下四个成员原文档定义create_range会实际校验其中的name成员类型约定说明name字符串属性Range 在后台与购物车中的显示名称必须是普通文本不能是gettext_lazy代理对象contains_product(product)方法接收一个商品实例返回布尔值表示该商品是否属于此范围num_products()方法返回范围内商品数量若统计查询代价过高可返回Noneall_products()方法返回包含范围内全部商品的 DjangoQuerySet其中contains_product与all_products是促销引擎判断某商品是否命中优惠的两个入口num_products则用于后台列表等需要显示规模的场景。完整示例标题含感叹号的商品原文档给出的示例类可直接复制使用from oscar.apps.catalogue.models import Product class ExclamatoryProducts(object): name Products including a ! def contains_product(self, product): return ! in product.title def num_products(self): return self.all_products().count() def all_products(self): return Product.objects.filter(title__icontains!)注意两点实操细节all_products()返回的QuerySet会同时被contains_product之外的场景复用因此建议把真正的筛选逻辑集中在这里num_products直接count()它即可保持结果一致需要引用oscar.apps.catalogue.models.Product或按项目覆盖后的商品模型执行查询这正是范围 规则化查询思想的体现。注册自定义 Rangecreate_range定义好类之后调用 src/oscar/apps/offer/custom.py 中的工厂函数完成注册from oscar.apps.offer.custom import create_range create_range(ExclamatoryProducts)调用后该 Range 立即出现在后台的Ranges范围与Offers优惠管理列表中可被用于创建促销优惠也可在后台删除。create_range的源码校验逻辑create_range并非简单地写入一条记录它做了三层防护def create_range(range_class): if not hasattr(range_class, name): raise exceptions.ValidationError(A custom range must have a name attribute) # Ensure range name is text (not gettext wrapper) if range_class.name.__class__.__name__ __proxy__: raise exceptions.ValidationError( Custom ranges must have text names (not gettext proxies) ) try: return Range.objects.create( namerange_class.name, proxy_class_class_path(range_class) ) except IntegrityError: raise ValueError(The passed range already exists in the database.)对应关系如下必须提供name属性否则抛出ValidationError名称必须是纯文本若name是gettext_lazy产生的__proxy__包装对象同样抛出ValidationError——原因是 Django 懒翻译对象在序列化与跨请求比较时会带来不可预期行为接口强制要求已求值的文本数据库唯一性约束proxy_class字段在模型层被定义为uniqueTrue的NullCharField见 abstract_models.py因此对同一个类重复调用create_range会触发IntegrityError被转换为ValueError(The passed range already exists in the database.)。_class_path(klass)把类转换成模块名.类名的字符串%s.%s % (klass.__module__, klass.__name__)这个字符串就是存入proxy_class字段的值。同一模块中还提供了create_condition与create_benefit两个姊妹函数用于注册自定义 Condition / Benefit接口风格完全一致。底层原理proxy_class与动态类加载自定义 Range 之所以自定义关键在AbstractRange.proxy_class字段——它不存储数据而是存储类路径。运行时通过 src/oscar/apps/offer/utils.py 的load_proxy动态导入def load_proxy(proxy_class): module, classname proxy_class.rsplit(., 1) mod import_module(module) # 导入失败抛 ImproperlyConfigured return getattr(mod, classname) # 类不存在同样抛 ImproperlyConfigured加载后的实例由AbstractRange.proxy缓存属性提供cached_propertyabstract_models.py。随后Range 模型的三个核心方法会优先委托给自定义类def contains_product(self, product): if self.proxy: return self.proxy.contains_product(product) return self.product_queryset.filter(idproduct.id).exists() def num_products(self): if self.proxy: return self.proxy.num_products() if self.includes_all_products: return None return self.all_products().count() def all_products(self): if self.proxy: return self.proxy.all_products() return self.product_querysetabstract_models.py即只要proxy_class非空Range 的包含判断、数量统计、全部商品查询就完全由你的 Python 类接管内置的字段式组合逻辑product_queryset被整体替换。这也解释了原文档接口约定的由来——你的类必须完整实现这四个成员否则促销引擎在运行时就会因缺少方法而报错。此外is_editable属性return not self.proxy_class是后台不可编辑规则的来源is_reorderable则用于判断商品能否在范围内重排。后台行为可见、不可编辑、可删除原文档强调自定义 Range 在后台可见且可删除但不可编辑。这一点在源码中有精确对应不可编辑后台编辑视图 src/oscar/apps/dashboard/ranges/views.py 的RangeUpdateView.get_object会检查obj.is_editable对自定义 Range 直接抛出PermissionDenied(Not allowed)。原因很自然自定义 Range 的逻辑由 Python 代码定义后台表单无法表达感叹号标题这类规则可删除后台提供range-delete路由见 src/oscar/apps/dashboard/ranges/apps.pyRangeDeleteViewviews.py允许删除删除后该优惠即失效可用于优惠create_range之后在后台创建或编辑 Offer 时即可从范围下拉框中选择它。同时由于proxy_class存储的是类路径而非代码本身生产环境必须保证该类可被 import——否则load_proxy会抛出ImproperlyConfigured相关页面将直接报错。这也是下面部署章节要强调的原因。用数据迁移在环境间自动部署create_range是运行时操作若在每次部署后手工在测试 / 预发 / 生产各环境执行一遍既繁琐又容易遗漏。原文档推荐的标准做法是Django 数据迁移Data Migration让迁移在migrate时自动创建自定义 Range从而实现环境间的幂等同步。一个典型的迁移文件放在某个 app 的migrations/目录形如from django.db import migrations from oscar.apps.offer.custom import create_range def create_exclamatory_range(apps, schema_editor): # 注意迁移中应使用 apps.get_model 获取历史模型但自定义 Range # 类的查询基于当前模型因此直接调用 create_range 即可 from your_app.ranges import ExclamatoryProducts try: create_range(ExclamatoryProducts) except ValueError: # 已存在则跳过保证迁移可重复执行 pass def remove_exclamatory_range(apps, schema_editor): Range apps.get_model(offer, Range) Range.objects.filter( proxy_classyour_app.ranges.ExclamatoryProducts ).delete() class Migration(migrations.Migration): dependencies [ (offer, 0001_initial), # 视你实际迁移链而定 ] operations [ migrations.RunPython(create_exclamatory_range, remove_exclamatory_range), ]三点实践经验幂等处理create_range对重复创建会抛ValueError迁移内建议try/except捕获并跳过避免在已有数据的库上执行migrate失败反向迁移在RunPython中提供reverse_code如上例按proxy_class路径删除记录保证migrate可回退类必须可达迁移运行时会 import 你的 Range 类因为要取name与proxy_class路径请确保该类所在模块随代码一起发布。测试与验证仓库内的现成范式仓库自带的自定义 Range 测试 tests/integration/offer/test_custom_range.py 完整覆盖了本文涉及的行为可作为你编写自己测试的模板CustomRange类以product.title.startswith(A)作为包含规则test_creating_duplicate_range_fails同一类重复create_range断言抛ValueErrortest_must_have_a_text_name使用gettext_lazy作为name的CustomRangeLazy断言抛ValidationErrortest_correctly_includes_match/test_correctly_excludes_nonmatch分别用标题A tale与B tale的商品验证contains_product的包含 / 排除判定。该测试同时印证了create_range返回的 Range 实例本身就能直接调用contains_product内部经proxy委托到你的类因此在单元测试中无需走完整促销流程即可验证判定逻辑。最佳实践小结规则集中在all_products让contains_product与num_products尽量复用同一套查询避免逻辑分叉num_products谨慎返回None当规则无法用一条廉价 SQL 表达如跨表复杂计算时返回None后台会显示未知数量而非触发昂贵查询生产环境通过数据迁移部署所有环境保持一致的 Range 记录避免手工漂移注意名称文本限制不要用gettext_lazy包装name否则注册直接失败不要对自定义 Range 走后台编辑编辑接口会被is_editable拦截并抛PermissionDenied逻辑修改应改代码后重新部署必要时先删除旧记录再重建。至此你已经可以从零编写、注册、部署并测试一个 django-oscar 自定义 Range把它接入促销优惠体系覆盖任何字段规则型的商品集合需求。赞分享后端电商【免费下载链接】django-oscarDomain-driven e-commerce for Django项目地址https://gitcode.com/gh_mirrors/dj/django-oscar点击查看免费下载相关推荐django-oscar 自定义促销条件Custom Offer Condition开发指南代理模型、create_condition 注册与数据迁移部署django oscar 自定义促销条件Custom Offer Condition开发指南代理模型、create_condition 注册与数据迁移部署后端电商MiniMax-H3图像转视频教程I2V工作流核心技巧大公开MiniMax H3图像转视频教程I2V工作流核心技巧大公开 MiniMax H3是一款强大的AI模型专为ComfyUI设计提供了高效的图像转视频I2V媒体生成视频多模态人工智能如何在普通电脑上运行MiniCPM5-1B-GGUF打造你的专属本地AI助手如何在普通电脑上运行MiniCPM5 1B GGUF打造你的专属本地AI助手 你是否厌倦了依赖云端的AI服务担心隐私泄露想要一个完全掌控在自己手中的智能助人工智能大模型基础模型本地部署上一篇如何高效实现MATLAB到Python代码迁移智能转换工具实战指南下一篇如何在3分钟内解决iPhone USB网络共享在Windows上的驱动问题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?