优惠券-积分
创建优惠券子应用
创建coupon子应用
git checkout master git merge feature/order git push git checkout -b feature/coupon cd luffycityapi/apps python ../../manage.py startapp coupon
注册子应用,settings/dev.py,代码:
INSTALLED_APPS = [ # 子应用 。。。 'coupon', ]
子路由,coupon/urls.py,代码:
from django.urls import path from . import views urlpatterns = [ ]
总路由,luffycityapi/urls.py,代码:
path("coupon/", include("coupon.urls")),
优惠券模型
模型分析:

coupon/models.py,模型创建,代码:
from models import BaseModel, models from courses.models import CourseDirection, CourseCategory, Course from users.models import User from orders.models import Order # Create your models here. class Coupon(BaseModel): discount_choices = ( (1, '减免'), (2, '折扣'), ) type_choices = ( (0, '通用类型'), (1, '指定方向'), (2, '指定分类'), (3, '指定课程'), ) get_choices = ( (0, "系统赠送"), (1, "自行领取"), ) discount = models.SmallIntegerField(choices=discount_choices, default=1, verbose_name="优惠方式") coupon_type = models.SmallIntegerField(choices=type_choices, default=0, verbose_name="优惠券类型") total = models.IntegerField(blank=True, default=100, verbose_name="发放数量") has_total = models.IntegerField(blank=True, default=100, verbose_name="剩余数量") start_time = models.DateTimeField(verbose_name="启用时间") end_time = models.DateTimeField(verbose_name="过期时间") get_type = models.SmallIntegerField(choices=get_choices, default=0, verbose_name="领取方式") condition = models.IntegerField(blank=True, default=0, verbose_name="满足使用优惠券的价格条件") per_limit = models.SmallIntegerField(default=1, verbose_name="每人限制领取数量") sale = models.TextField(verbose_name="优惠公式", help_text=""" *号开头表示折扣价,例如*0.82表示八二折;<br> -号开头表示减免价,例如-10表示在总价基础上减免10元<br> """) class Meta: db_table = "ly_coupon" verbose_name = "优惠券" verbose_name_plural = verbose_name class CouponDirection(models.Model): direction = models.ForeignKey(CourseDirection, on_delete=models.CASCADE, related_name="to_coupon", verbose_name="学习方向", db_constraint=False) coupon = models.ForeignKey(Coupon, on_delete=models.CASCADE, related_name="to_direction", verbose_name="优惠券", db_constraint=False) created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间") class Meta: db_table = "ly_coupon_course_direction" verbose_name = "优惠券与学习方向" verbose_name_plural = verbose_name class CouponCourseCat(models.Model): category = models.ForeignKey(CourseCategory, on_delete=models.CASCADE, related_name="to_coupon", verbose_name="课程分类", db_constraint=False) coupon = models.ForeignKey(Coupon, on_delete=models.CASCADE, related_name="to_category", verbose_name="优惠券", db_constraint=False) created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间") class Meta: db_table = "ly_coupon_course_category" verbose_name = "优惠券与课程分类" verbose_name_plural = verbose_name class CouponCourse(models.Model): course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name="to_coupon", verbose_name="课程", db_constraint=False) coupon = models.ForeignKey(Coupon, on_delete=models.CASCADE, related_name="to_course", verbose_name="优惠券", db_constraint=False) created_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间") class Meta: db_table = "ly_coupon_course" verbose_name = "优惠券与课程信息" verbose_name_plural = verbose_name class CouponLog(BaseModel): use_choices = ( (0, "未使用"), (1, "已使用"), (2, "已过期"), ) name = models.CharField(null=True, blank=True, max_length=100, verbose_name="名称/标题") user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="to_coupon", verbose_name="用户", db_constraint=False) coupon = models.ForeignKey(Coupon, on_delete=models.CASCADE, related_name="to_user", verbose_name="优惠券", db_constraint=False) order = models.ForeignKey(Order, null=True, blank=True, default=None, on_delete=models.CASCADE, related_name="to_coupon", verbose_name="订单", db_constraint=False) use_time = models.DateTimeField(null=True, blank=True, verbose_name="使用时间") use_status = models.SmallIntegerField(choices=use_choices, null=True, blank=True, default=0, verbose_name="使用状态") class Meta: db_table = "ly_coupon_log" verbose_name = "优惠券发放和使用日志" verbose_name_plural = verbose_name
数据迁移,终端下执行:
cd ../..
python manage.py makemigrations
python manage.py migrate
把当前子应用注册到Admin管理站点
coupon/apps.py,代码:
from django.apps import AppConfig class CouponConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'coupon' verbose_name = "优惠券管理" verbose_name_plural = verbose_name
coupon/admin.py,代码:
from django.contrib import admin from .models import Coupon, CouponDirection, CouponCourseCat, CouponCourse, CouponLog # Register your models here. class CouponDirectionInLine(admin.TabularInline): # admin.StackedInline """学习方向的内嵌类""" model = CouponDirection fields = ["id", "direction"] class CouponCourseCatInLine(admin.TabularInline): # admin.StackedInline """课程分类的内嵌类""" model = CouponCourseCat fields = ["id", "category"] class CouponCourseInLine(admin.TabularInline): # admin.StackedInline """课程信息的内嵌类""" model = CouponCourse fields = ["id", "course"] class CouponModelAdmin(admin.ModelAdmin): """优惠券的模型管理器""" list_display = ["id", "name", "start_time", "end_time", "total", "has_total", "coupon_type", "get_type", ] inlines = [CouponDirectionInLine, CouponCourseCatInLine, CouponCourseInLine] admin.site.register(Coupon, CouponModelAdmin) class CouponLogModelAdmin(admin.ModelAdmin): """优惠券发放和使用日志""" list_display = ["id", "user", "coupon", "order", "use_time", "use_status"] admin.site.register(CouponLog, CouponLogModelAdmin)
提交代码版本
cd /home/moluo/Desktop/luffycity git add . git commit -m "feature: 创建优惠券子应用并设计优惠券的存储数据模型" git push --set-upstream origin feature/coupon
实现后台管理员给用户分发优惠券时自动记录到redis中。
settings/dev.py,代码:
# 设置redis缓存 CACHES = { # 。。。 # 提供存储优惠券 "coupon": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://:@127.0.0.1:6379/5", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } }, }
coupon/admin.py,代码:
from django.contrib import admin from django_redis import get_redis_connection from .models import Coupon,CouponDirection,CouponCourseCat,CouponCourse,CouponLog from django.utils.timezone import datetime import json class CouponDirectionInLine(admin.TabularInline): # admin.StackedInline """学习方向的内嵌类""" model = CouponDirection fields = ["id","diretion"] class CouponCourseCatInLine(admin.TabularInline): # admin.StackedInline """课程分类的内嵌类""" model = CouponCourseCat fields = ["id","category"] class CouponCourseInLine(admin.TabularInline): # admin.StackedInline """课程信息的内嵌类""" model = CouponCourse fields = ["id","course"] class CouponModelAdmin(admin.ModelAdmin): """优惠券的模型管理器""" list_display = ["id","name","start_time","end_time","total","has_total","coupon_type","get_type",] inlines = [CouponDirectionInLine, CouponCourseCatInLine, CouponCourseInLine] admin.site.register(Coupon, CouponModelAdmin) class CouponLogModelAdmin(admin.ModelAdmin): """优惠券发放和使用记录""" list_display = ["id","user","coupon","order","use_time","use_status"] def save_model(self, request, obj, form, change): """ 保存或更新记录时自动执行的钩子 request: 本次客户端提交的请求对象 obj: 本次操作的模型实例对象 form: 本次客户端提交的表单数据 change: 值为True,表示更新数据,值为False,表示添加数据 """ obj.save() # 同步记录到redis中 redis = get_redis_connection("coupon") # print(obj.use_status , obj.use_time) if obj.use_status == 0 and obj.use_time == None: # 记录优惠券信息到redis中 pipe = redis.pipeline() pipe.multi() pipe.hset(f"{obj.user.id}:{obj.id}","coupon_id", obj.coupon.id) pipe.hset(f"{obj.user.id}:{obj.id}","name", obj.coupon.name) pipe.hset(f"{obj.user.id}:{obj.id}","discount", obj.coupon.discount) pipe.hset(f"{obj.user.id}:{obj.id}","get_discount_display", obj.coupon.get_discount_display()) pipe.hset(f"{obj.user.id}:{obj.id}","coupon_type", obj.coupon.coupon_type) pipe.hset(f"{obj.user.id}:{obj.id}","get_coupon_type_display", obj.coupon.get_coupon_type_display()) pipe.hset(f"{obj.user.id}:{obj.id}","start_time", obj.coupon.start_time.strftime("%Y-%m-%d %H:%M:%S")) pipe.hset(f"{obj.user.id}:{obj.id}","end_time", obj.coupon.end_time.strftime("%Y-%m-%d %H:%M:%S")) pipe.hset(f"{obj.user.id}:{obj.id}","get_type", obj.coupon.get_type) pipe.hset(f"{obj.user.id}:{obj.id}","get_get_type_display", obj.coupon.get_get_type_display()) pipe.hset(f"{obj.user.id}:{obj.id}","condition", obj.coupon.condition) pipe.hset(f"{obj.user.id}:{obj.id}","sale", obj.coupon.sale) pipe.hset(f"{obj.user.id}:{obj.id}","to_direction", json.dumps(list(obj.coupon.to_direction.values("direction__id","direction__name")))) pipe.hset(f"{obj.user.id}:{obj.id}","to_category", json.dumps(list(obj.coupon.to_category.values("category__id","category__name")))) pipe.hset(f"{obj.user.id}:{obj.id}","to_course", json.dumps(list(obj.coupon.to_course.values("course__id","course__name")))) # 设置当前优惠券的有效期 pipe.expire(f"{obj.user.id}:{obj.id}", int(obj.coupon.end_time.timestamp() - datetime.now().timestamp())) pipe.execute() else: redis.delete(f"{obj.user.id}:{obj.id}") def delete_model(self, request, obj): """删除记录时自动执行的钩子""" # 如果系统后台管理员删除当前优惠券记录,则redis中的对应记录也被删除 print(obj, "详情页中删除一个记录") redis = get_redis_connection("coupon") redis.delete(f"{obj.user.id}:{obj.id}") obj.delete() def delete_queryset(self, request, queryset): """在列表页中进行删除优惠券记录时,也要同步删除容redis中的记录""" print(queryset, "列表页中删除多个记录") redis = get_redis_connection("coupon") for obj in queryset: redis.delete(f"{obj.user.id}:{obj.id}") queryset.delete() admin.site.register(CouponLog, CouponLogModelAdmin)
添加测试数据,代码:
-- 优惠券测试数据 truncate table ly_coupon; INSERT INTO ly_coupon (id, name, is_deleted, orders, is_show, created_time, updated_time, discount, coupon_type, total, has_total, start_time, end_time, get_type, `condition`, per_limit, sale) VALUES (1, '30元通用优惠券', 0, 1, 1, '2022-05-04 10:35:40.569417', '2022-06-30 10:25:00.353212', 1, 0, 10000, 10000, '2022-05-04 10:35:00', '2023-01-02 10:35:00', 0, 100, 1, '-30'),(2, '前端学习通用优惠券', 0, 1, 1, '2022-05-04 10:36:58.401527', '2022-05-04 10:36:58.401556', 1, 1, 100, 100, '2022-05-04 10:36:00', '2022-08-04 10:36:00', 0, 0, 1, '-50'),(3, 'Typescript课程专用券', 0, 1, 1, '2022-05-04 10:38:36.134581', '2022-05-04 10:38:36.134624', 2, 3, 1000, 1000, '2022-05-04 10:38:00', '2022-08-04 10:38:00', 0, 0, 1, '*0.88'),(4, 'python七夕专用券', 0, 1, 1, '2022-05-04 10:40:08.022904', '2022-06-30 10:25:46.949197', 1, 2, 200, 200, '2022-05-04 10:39:00', '2022-11-15 10:39:00', 1, 0, 1, '-99'),(5, '算法学习优惠券', 0, 1, 1, '2021-08-05 10:05:07.837008', '2022-06-30 10:26:12.133812', 2, 2, 1000, 1000, '2022-08-05 10:04:00', '2022-12-25 10:04:00', 0, 200, 1, '*0.85'); -- 优惠券与学习方向的关系测试数据 truncate table ly_coupon_course_direction; INSERT INTO ly_coupon_course_direction (id, created_time, coupon_id, direction_id) VALUES (1, '2022-05-04 10:36:58.414461', 2, 1); -- 优惠券与课程分类的关系测试数据 truncate table ly_coupon_course_category; INSERT INTO .ly_coupon_course_category (id, created_time, category_id, coupon_id) VALUES (1, '2022-05-04 10:40:08.029505', 20, 4),(2, '2022-05-04 10:40:08.042891', 21, 4),(3, '2021-08-05 10:05:07.966221', 33, 5); -- 优惠券与课程信息的关系测试数据 truncate table ly_coupon_course; INSERT INTO ly_coupon_course (id, created_time, coupon_id, course_id) VALUES (1, '2022-05-04 10:38:36.140929', 3, 1),(2, '2022-05-04 10:38:36.143166', 3, 2); -- 优惠券的发放和使用日志的测试数据 truncate table ly_coupon_log; INSERT INTO luffycity.ly_coupon_log (id, is_deleted, orders, is_show, created_time, updated_time, name, use_time, use_status, coupon_id, order_id, user_id) VALUES (5, 0, 1, 1, '2022-05-04 12:00:25.051976', '2022-06-30 10:25:17.681298', '30元通用优惠券222', null, 0, 1, null, 1),(8, 0, 1, 1, '2022-05-04 12:03:24.331024', '2022-06-30 10:22:45.834401', '前端学习通用优惠券', null, 0, 2, null, 1),(9, 0, 1, 1, '2022-05-04 12:03:31.692397', '2022-06-30 10:23:41.492205', 'Typescript课程专用券', null, 0, 3, null, 1),(10, 0, 1, 1, '2022-05-04 12:03:38.225438', '2022-06-30 10:25:49.797318', 'python七夕专用券', null, 0, 4, null, 1),(11, 0, 1, 1, '2022-05-04 12:09:25.406437', '2022-06-30 10:23:55.832262', '前端学习通用优惠券', null, 0, 2, null, 1),(12, 0, 1, 1, '2021-08-05 10:06:06.036230', '2022-06-30 10:26:20.723668', '算法学习优惠券', null, 0, 5, null, 1);
注意:添加测试数据完成以后,因为是通过SQL语句来添加的。务必在Admin站点中对优惠券的发放和使用日志这功能中每一条数据进行一次的更新操作,打开数据详情页不需要修改任何数据,保存即可,这样才能让用户的优惠券信息同步到redis中!!!注意:如果是已经过期的优惠券,则不会被同步到redis中。
提交代码版本
cd /home/moluo/Desktop/luffycity git add . git commit -m "feature: 实现后台管理员给用户分发优惠券时自动记录到redis中" git push --set-upstream origin feature/coupon
获取用户本次下单的可用优惠券
封装工具函数,获取当前用户拥有的所有优惠券以及本次下单的可用优惠券列表,
coupon/services.py,代码:
import json from django_redis import get_redis_connection from courses.models import Course def get_user_coupon_list(user_id): """获取指定用户拥有的所有优惠券列表""" redis = get_redis_connection("coupon") coupon_list = redis.keys(f"{user_id}:*") try: coupon_id_list = [item.decode() for item in coupon_list] except: coupon_id_list = [] coupon_data = [] # 遍历redis中所有的优惠券数据并转换数据格式 for coupon_key in coupon_id_list: coupon_item = {"user_coupon_id": int(coupon_key.split(":")[-1])} coupon_hash = redis.hgetall(coupon_key) for key, value in coupon_hash.items(): key = key.decode() value = value.decode() if key in ["to_course", "to_category", "to_direction"]: value = json.loads(value) coupon_item[key] = value coupon_data.append(coupon_item) return coupon_data def get_user_enable_coupon_list(user_id): """ 获取指定用户本次下单的可用优惠券列表 # 根据当前本次客户端购买商品课程进行比较,获取用户的当前可用优惠券。 """ redis = get_redis_connection("cart") # 先获取所有的优惠券列表 coupon_data = get_user_coupon_list(user_id) # 获取指定用户的购物车中的勾选商品[与优惠券的适用范围进行比对,找出能用的优惠券] cart_hash = redis.hgetall(f"cart_{user_id}") # 获取被勾选的商品课程的ID列表 course_id_list = {int(key.decode()) for key, value in cart_hash.items() if value == b'1'} # 获取被勾选的商品课程的模型对象列表 course_list = Course.objects.filter(pk__in=course_id_list, is_deleted=False, is_show=True).all() category_id_list = set() direction_id_list = set() for course in course_list: # 获取被勾选的商品课程的父类课程分类id列表,并保证去重 category_id_list.add(int(course.category.id)) # # 获取被勾选的商品课程的父类学习方向id列表,并保证去重 direction_id_list.add(int(course.direction.id)) # 创建一个列表用于保存所有的可用优惠券 enable_coupon_list = [] for item in coupon_data: coupon_type = int(item.get("coupon_type")) if coupon_type == 0: # 通用类型优惠券 item["enable_course"] = "__all__" enable_coupon_list.append(item) elif coupon_type == 3: # 指定课程优惠券 coupon_course = {int(course_item["course__id"]) for course_item in item.get("to_course")} # 并集处理 ret = course_id_list & coupon_course if len(ret) > 0: item["enable_course"] = {int(course.id) for course in course_list if course.id in ret} enable_coupon_list.append(item) elif coupon_type == 2: # 指定课程分配优惠券 coupon_category = {int(category_item["category__id"]) for category_item in item.get("to_category")} # 并集处理 ret = category_id_list & coupon_category if len(ret) > 0: item["enable_course"] = {int(course.id) for course in course_list if course.category.id in ret} enable_coupon_list.append(item) elif coupon_type == 1: # 指定学习方向的优惠券 coupon_direction = {int(direction_item["direction__id"]) for direction_item in item.get("to_direction")} # 并集处理 ret = direction_id_list & coupon_direction if len(ret) > 0: item["enable_course"] = {int(course.id) for course in course_list if course.direction.id in ret} enable_coupon_list.append(item) return enable_coupon_list
