前奏:
首先,要在主机中安装redis,windows中安装,下载一个镜像,直接进行下一步的安装,安装成功后,在cmd中输入redis-cli
安装python的依赖库: redis 和 django-redis
redis是一个python的库,用来操作redis。
django默认支持的缓存是memcache,使用redis作为django的缓存,就需要django-redis了,django-redis是一个开源的库。
只需要在全局settings中配置即可。
django-redis的配置使用
在settings.py中的配置:
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {"max_connections": 100}
# "PASSWORD": "密码",
}
}
"可自定义配置":{...}
}
from django_redis import get_redis_connection
conn = get_redis_connection("default")
安装配置后就可以使用了,但是据说性能不够高,官方文档见https://niwinz.github.io/django-redis/latest/
异常错误信息的自定义
# 自定义一个类名,这个类继承Exception即可 class PriceNoExistsError(Exception): def __init__(self,msg): self.msg = msg
模型表数据存储的校验
models.py中模型表可以在数据保存前做数据校验,重写save方法即可
比如:校验优惠券存储的开始和结束时间
class Coupon(models.Model): """优惠券生成规则""" name = models.CharField(max_length=64, verbose_name="活动名称") brief = models.TextField(blank=True, null=True, verbose_name="优惠券介绍") coupon_type_choices = ((0, '通用券'), (1, '满减券'), (2, '折扣券')) coupon_type = models.SmallIntegerField(choices=coupon_type_choices, default=0, verbose_name="券类型") money_equivalent_value = models.IntegerField(verbose_name="等值货币",blank=True,null=True) off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只针对折扣券,例7.9折,写79", blank=True, null=True) minimum_consume = models.PositiveIntegerField("最低消费", default=0, help_text="仅在满减券时填写此字段",blank=True,null=True) content_type = models.ForeignKey(ContentType, blank=True, null=True) object_id = models.PositiveIntegerField("绑定课程", blank=True, null=True, help_text="可以把优惠券跟课程绑定") content_object = GenericForeignKey('content_type', 'object_id') quantity = models.PositiveIntegerField("数量(张)", default=1) open_date = models.DateField("优惠券领取开始时间") close_date = models.DateField("优惠券领取结束时间") valid_begin_date = models.DateField(verbose_name="有效期开始时间", blank=True, null=True) valid_end_date = models.DateField(verbose_name="有效结束时间", blank=True, null=True) coupon_valid_days = models.PositiveIntegerField(verbose_name="优惠券有效期(天)", blank=True, null=True, help_text="自券被领时开始算起") date = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "31. 优惠券生成记录" def __str__(self): return "%s(%s)" % (self.get_coupon_type_display(), self.name) def save(self, *args, **kwargs): if not self.coupon_valid_days or (self.valid_begin_date and self.valid_end_date): if self.valid_begin_date and self.valid_end_date: if self.valid_end_date <= self.valid_begin_date: raise ValueError("valid_end_date 有效期结束日期必须晚于 valid_begin_date ") if self.coupon_valid_days == 0: raise ValueError("coupon_valid_days 有效期不能为0") if self.close_date < self.open_date: raise ValueError("close_date 优惠券领取结束时间必须晚于 open_date优惠券领取开始时间 ") super(Coupon, self).save(*args, **kwargs)
对于购物车的操作,结算,支付等部分视图函数做登录认证的处理
借助rest_framework的局部认证功能
from api import models from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed class LoginAuth(BaseAuthentication): def authenticate(self, request): token = request.query_params.get('token') token_obj = models.Token.objects.filter(name=token).first() if token_obj: return token_obj.user,token_obj else: raise AuthenticationFailed('认证失败')
封装响应数据的结构
可以将所有视图函数返回给前端的响应信息封装到一个类中,什么时候需要直接实例化对象使用。
class StatusRet(object): '''构建返回响应的结构'' def __init__(self): self.code = 1000 self.msg = '' self.data = None @property def dict(self): return self.__dict__
借助admin快速添加数据
from django.contrib import admin # Register your models here. from django.apps import apps all_model_gene =apps.get_app_config('api').get_models() # 将所有的模型类放在生成器中 for model_cls in all_model_gene: admin.site.register(model_cls)
from django.contrib import admin
from django.apps import apps app_models =apps.get_app_config("api").get_models() from django.contrib.admin.sites import AlreadyRegistered for i in app_models: try: admin.site.register(i) except AlreadyRegistered: pass
购物车的增删改查:
1.url的设计
url(r'^shopping_car/',views.ShoppingCar.as_view()),2.视图类的处理
redis中数据的存储格式: 通过这种name的形式来存储 哪个用户的哪门课程信息
SHOP_CAR_COURSE_KEY = 'shop_car_%s_%s' # 第一个%s 代表当前的用户的主键id 第二个%s 代表某个课程的主键id
具体的视图函数逻辑:
class ShoppingCar(APIView): '''购物车的增删改查''' # 购物车的登录认证 authentication_classes = [LoginAuth]
conn = get_redis_connection('default') def get(self,request): '''获取当前用户在购物车中的所有记录''' # 获取返回值对象 ret = StatusRet() # 从redis中获取当前用户所有的key shop_car_key = settings.SHOP_CAR_COURSE_KEY % (request.user.pk, '*') # 利用scan_iter 将当前用户所有的keys生成一个生成器 price_policy_gene = self.conn.scan_iter(shop_car_key) # 构建接口数据 ''' [ { key1:{ title:...,price:... }}, { key2:{... }},... ] ''' price_policy_list = [] for keys in price_policy_gene: data_dict = { 'title': self.conn.hget(keys,'title').decode(), 'price': self.conn.hget(keys,'price').decode(), 'course_img': self.conn.hget(keys,'course_img').decode(), 'price_policy': json.loads(self.conn.hget(keys,'price_policy').decode()), # 直接json反序列化 'checked_price_policy_id': self.conn.hget(keys,'checked_price_policy_id').decode() } price_policy_list.append(data_dict) # 将查询的所有数据返回 ret.data = price_policy_list return Response(ret.dict) def post(self,request): '''添加购物车记录''' ret = StatusRet() # 获取从前端发来的数据 course_id : 课程的pk值 price_policy_id : 价格策略的pk值 course_id = request.data.get('course_id') price_policy_id = request.data.get('price_policy_id') # 校验前端传来数据的合法性 try: course_obj = models.Courses.objects.get(pk=course_id) price_policy_list = course_obj.price_policy.all() price_policy_dict = {} for item in price_policy_list: price_policy_dict[item.pk] = {"name":item.name,"price":float(item.price)} if int(price_policy_id) not in price_policy_dict: raise PriceNoExistsError('价格策略不存在') shop_car_key = settings.SHOP_CAR_COURSE_KEY%(request.user.pk,course_id) # 构建数据 val_dict = { "title":course_obj.title, "price":course_obj.price, "course_img":course_obj.course_img, "price_policy":json.dumps(price_policy_dict,ensure_ascii=False), #提前将数据json序列化,便于后期的取值 "checked_price_policy_id":price_policy_id } # 将数据添加到redis中 self.conn.hmset(shop_car_key,val_dict) ret.data = val_dict except PriceNoExistsError as e: ret.code = 2000 ret.msg = str(e) except ObjectDoesNotExist as e: ret.code = 2000 ret.msg = '课程不存在' except Exception as e: ret.code = 2000 ret.msg = '出错了' return Response(ret.dict) def put(self,request): '''修改购物车记录''' ret = StatusRet() # 获取前端传来的数据 course_id = request.data.get('course_id') price_policy_id = request.data.get('price_policy_id') # 校验数据的合法性 try: shop_car_key = settings.SHOP_CAR_COURSE_KEY % (request.user.pk, course_id) if not self.conn.exists(shop_car_key): raise CourseNoExistsErrot('修改课程不存在') course_data = self.conn.hgetall(shop_car_key) course_policy = json.loads(self.conn.hget(shop_car_key,"price_policy").decode()) if str(price_policy_id) not in course_policy: raise PriceNoExistsError('价格策略不存在') # 将redis中的值进行修改 self.conn.hset(shop_car_key,'checked_price_policy_id',price_policy_id) # 构建返回给前端的数据 return_data = {} for k,v in course_data.items(): if k == b"price_policy": val = json.loads(v.decode()) else: val = v.decode() return_data[k.decode()] = val ret.data = return_data except CourseNoExistsErrot as e: ret.code = 2000 ret.msg = str(e) except PriceNoExistsError as e: ret.code = 2000 ret.msg = str(e) except Exception as e: ret.code = 2000 ret.msg = '更新失败' return Response(ret.dict) def delete(self,request): '''删除购物车记录''' ret = StatusRet() # 校验前端传来数据的合法性 try: course_id_list = request.data.get('course_id') shop_key_list =[] for course_id in course_id_list: shop_car_key = settings.SHOP_CAR_COURSE_KEY % (request.user.pk, course_id) if not self.conn.exists(shop_car_key): raise CourseNoExistsErrot('删除课程不存在') else: # 将所有校验合法的key存在列表中 shop_key_list.append(shop_car_key) # 从redis中删除记录 self.conn.delete(*shop_key_list) ret.data = '' except CourseNoExistsErrot as e: ret.code = 2000 ret.msg = str(e) return Response(ret.dict)
备注:使用json进行序列化时,如果字段类型为decimal时,要先将数据转为float类型,否则json无法序列化,json不支持decimal的数据类型。
结算页面
购物车中增删改查实现后,就是结算页面了,结算包含两种情况,一种是购物车中选中商品后的直接结算,是post请求,将该商品的所有信息和所有可用的优惠券(包含专用的和通用的),以及其他,比如散币(腾讯的Q币,淘宝的积分,等等)查询出来后存入redis中,并将数据构建后返回。另一种是用户选择结算后的延迟,在此使用时,可以通过未支付页面的get请求,直接从redis中取数据,并返回给前端的展示。
1.url的构建
url(r'^settle_center/',views.SettlementView.as_view()),
2.redis中的存储方式:
PAYMENT_COURSE_KEY = 'payment_%s_%s' #课程专用优惠券
PAYMENT_COMMON_KEY = 'pay_common_%s' #通用优惠券
USER_BALANCE_COUNT = 'balance_count_%s' #用户贝里数
3.视图逻辑
class SettlementView(APIView): '''结算的逻辑操作''' authentication_classes = [LoginAuth] #登录认证 conn = get_redis_connection('default') #redis的连接 def get_return_data(self,request): '''构建返回给前端的数据''' #获取用户pk值 user_id = request.user.pk #获取redis中该用户的贝里数 user_balance_key = settings.USER_BALANCE_COUNT user_balance_count = user_balance_key % user_id balance_data = self.conn.hgetall(user_balance_count) # 获取redis中该用户的所有课程专用优惠券 payment_key = settings.PAYMENT_COURSE_KEY % (user_id, '*') keys = self.conn.scan_iter(payment_key) #获取redis中该用户的通用优惠券 common_key = settings.PAYMENT_COMMON_KEY % user_id common_data = self.conn.hgetall(common_key) return_data = [] # 构建贝里数数据 balance_dict = {} for k,v in balance_data.items(): balance_dict[k.decode()] = v.decode() # 构建通用优惠券的信息 common_show_dict = {} for com_k, com_v in common_data.items(): common_show_dict[com_k.decode()] = json.loads(com_v.decode()) # 处理普通优惠券的数据 coupon_dict = {} for key in keys: course_id = key.decode().split('_')[-1] coupon_dict[course_id] = {} course_coupon_data = self.conn.hgetall(key) for cou_k, cou_v in course_coupon_data.items(): if cou_k == b'coupons': coupons_data = {} for coup_k, coup_v in json.loads(cou_v.decode()).items(): coupons_data[coup_k] = json.loads(coup_v) coupon_dict[course_id][cou_k.decode()] = coupons_data else: coupon_dict[course_id][cou_k.decode()] = json.loads(cou_v.decode()) return_data.append(balance_dict) return_data.append(common_show_dict) return_data.append(coupon_dict) return return_data def get(self,request): '''获取redis中当前登录用户的所有的待支付信息''' ret = StatusRet() return_data = self.get_return_data(request) ret.data = return_data return Response(ret.dict) def post(self,request): '''将用户选择的商品信息存储到redis中''' ret = StatusRet() # 获取前端发送的所有选中的数据 course_id_list = request.data.get('course_id') for course_id in course_id_list: shop_car_key = settings.SHOP_CAR_COURSE_KEY%(request.user.pk,course_id) # 校验数据是不是在购物车中 if not self.conn.exists(shop_car_key): ret.code = 2000 ret.msg = '课程不存在' return Response(ret.dict) else: # 校验通过后 获取当前的时间 now = datetime.datetime.now() # 获取选中课程在购物车中的详细信息 course_price_data = self.conn.hgetall(shop_car_key) # 将取出的数据进行重新构建 course_detail = {} for key,val in course_price_data.items(): if key == b'price_policy': course_detail[key.decode()] = json.loads(val.decode()) else: course_detail[key.decode()]=val.decode() # 构建存进redis中的数据 coupon_dict = {} common_dict = {} # 设置全局的存储name的格式 pay_course_settings = settings.PAYMENT_COURSE_KEY payment_key =pay_course_settings%(request.user.pk,course_id) # 获取当前用户在优惠券领取记录表中所有的未使用,并且未过期的优惠券 couponrecord_list = models.CouponRecord.objects.filter(user=request.user,status=0,coupon__valid_begin_date__lte=now,coupon__valid_end_date__gte=now,number__gte=1,coupon__object_id=course_id) for coupon_record_obj in couponrecord_list: coupon_obj = coupon_record_obj.coupon temp = { 'name':coupon_obj.name, 'coupon_type':coupon_obj.coupon_type, 'money_equivalent_value':coupon_obj.money_equivalent_value, 'off_percent':coupon_obj.off_percent, 'minimum_consume':coupon_obj.minimum_consume, 'object_id':coupon_obj.object_id } # 判断是通用优惠券还是某个课程的专用优惠券 if not coupon_obj.object_id: common_dict[coupon_record_obj.pk] = json.dumps(temp,ensure_ascii=False) else: coupon_dict[coupon_record_obj.pk] = json.dumps(temp,ensure_ascii=False) val_dict = {} val_dict['course_detail'] = json.dumps(course_detail,ensure_ascii=False) val_dict['coupons'] = json.dumps(coupon_dict,ensure_ascii=False) self.conn.hmset(payment_key,val_dict) common_settint_key = settings.PAYMENT_COMMON_KEY common_key = common_settint_key%(request.user.pk) # 如果没有通用优惠券,在存储redis时会报错,因为存储的value不能为一个空字典 if common_dict: self.conn.hmset(common_key,common_dict) # # 获取当前用户的贝里数 balance = request.user.beli_count balance_dict = {'balance': balance} user_balance_key = settings.USER_BALANCE_COUNT user_balance_count = user_balance_key%request.user.pk self.conn.hmset(user_balance_count,balance_dict) # 获取返回前端的数据 return_data = self.get_return_data(request) ret.data = return_data return Response(ret.dict)
支付页面
结算完毕就该支付页面了,用户点击支付后,会将所有要支付的数据发回来,进行数据的校验,校验通过后,生成order订单表(或订单详情表),然后就可以调用支付的接口
1.url的构建
url(r'^payment/',views.PaymentViewSet.as_view()),
2.逻辑的实现
数据取出可以是从redis中取,也可以从数据库中取,进行校验,具体的实现粒度视情况
class PaymentViewSet(APIView): authentication_classes = [LoginAuth] conn = get_redis_connection('default') now = datetime.datetime.now() def post(self,request): ''' 前端发送的数据结构: { "money":87.12, # 总价格 "balance":0, # 贝里数 "course_detail": #课程详细 包含该课程选中的优惠券pk值,价格id,价格 { "1":{"coupon_id":1,"policy_id":1,"price":99}, "2":{"coupon_id":5,"policy_id":4,"price":0} }, "common_coupon_id":4 # 通用优惠券的pk值 } ''' #获取数据 ret = StatusRet() user_id = request.user.pk _balance = models.UserInfo.objects.filter(pk=user_id).first().beli_count course_detail = request.data.get("course_detail") money = request.data.get('money') currency_coupon_id = request.data.get('common_coupon_id') balance = request.data.get('balance') # 数据处理 try: if balance>_balance: raise OrderError('贝利数异常,支付失败') course_price_list = [] for course_id,course_info in course_detail.items(): # 从redis中购物车中取数据校验 # pay_key = settings.PAYMENT_COURSE_KEY%(user_id,course_id) # pay_ret = self.conn.hgetall(pay_key) # print(pay_ret,'pay_key') # for key,value in pay_ret.items(): # # key = key.decode() # value = json.loads(value.decode()) # print(key,value) # if key == b'coupons': # if course_info['coupon_id'] not in value: # raise OrderError('优惠券不存在') # elif key == b'course_detail': # for key_course,value_course in value.items(): # if key_course == 'title': # pass # elif key_course == 'checked_price_policy_id': # if value_course != str(course_info['policy_id']): # raise OrderError('选中的价格策略异常') # elif key_course == 'price_policy': # # print(value_course,type(value_course)) # course_price = value_course[str(course_info['policy_id'])]['price'] # if course_price != course_info['price']: # raise OrderError('课程价格异常') # course_price_list.append(course_price) # 数据库中取数据校验 course_obj = models.Courses.objects.filter(pk=course_id).first() if not course_obj: raise OrderError('课程不存在') if course_obj.status !=0: raise OrderError('课程已下架') policy_list = list(course_obj.price_policy.all().values('pk','price')) # print(type(course_info['policy_id'])) if course_info['policy_id'] not in [i['pk'] for i in policy_list]: raise OrderError('价格策略不存在') print(policy_list, '价格策略列表') print([float(i['price']) for i in policy_list if i['pk'] ==course_info['policy_id'] ]) policy_price = [float(i['price']) for i in policy_list if i['pk'] ==course_info['policy_id']][0] if policy_price != course_info['price']: raise OrderError('价格异常') # 更细粒度的校验实现 # xxx = models.CouponRecord.objects.filter(pk=course_info['coupon_id'],user_id=user_id).first() # if not xxx: # raise OrderError('优惠券不存在') # if xxx.status !=0: # raise OrderError('优惠券未上架') # if int(xxx.number) <1: # raise OrderError('优惠券数量不够') # ... if course_info['coupon_id']: coupon_obj = models.CouponRecord.objects.filter(pk=course_info['coupon_id'],user_id=user_id,status=0,number__gte =1,coupon__valid_begin_date__lte=self.now,coupon__valid_end_date__gte=self.now,coupon__object_id=course_id).first() if not coupon_obj: raise OrderError('优惠券异常,支付失败') # 获取每个课程的价格 price_course = self.account_price(coupon_obj,policy_price) else: price_course = policy_price course_price_list.append(price_course) # 获取价格总和 course_price_total = sum(course_price_list) # 通用优惠券的校验和使用 if currency_coupon_id: currency_coupon_obj = models.CouponRecord.objects.filter(pk=currency_coupon_id,user_id=user_id,status=0,number__gte =1,coupon__valid_begin_date__lte=self.now,coupon__valid_end_date__gte=self.now).first() if not currency_coupon_obj: raise OrderError('通用优惠卷异常') course_total = self.account_price(currency_coupon_obj,course_price_total) else: course_total = course_price_total # 贝里数的扣减 if balance>0: _finally_price = course_total- balance/100 else: _finally_price = course_total finally_price = round(_finally_price,2) # 总价格的校验 if finally_price != money: raise OrderError('总价格异常') # 生成订单信息 order_obj = models.Order.objects.create(payment_type=1,order_number=self.get_random_order(),actual_amount=finally_price,user_id=user_id,status=1) # models.OrderDetail.objects.create(order=order_obj,original_price=course_price_total,price=finally_price,valid_period_display='课程有效期',valid_period=100,content_type=) # 调用支付接口 # xxxxxxxxxxxxxxxxxxxxx except OrderError as e: ret.code = 2000 ret.msg = str(e) return Response(ret.dict) def account_price(self, coupon_record_obj, price): """ 根据优惠券记录对象,以及优惠前的价格 计算出使用优惠券的价格 """ coupon_type = coupon_record_obj.coupon.coupon_type if coupon_type == 0: money_equivalent_value = coupon_record_obj.coupon.money_equivalent_value # --立减 # 0 = 获取原价 - 优惠券金额 或 # 折后价格 = 获取原价 - 优惠券金额 if price - money_equivalent_value >= 0: rebate_price = price - money_equivalent_value else: rebate_price = 0 elif coupon_type == 1: # -- 满减 是否满足限制 minimum_consume = coupon_record_obj.coupon.minimum_consume if price >= minimum_consume: # 折后价格 = 获取原价 - 优惠券金额 money_equivalent_value = coupon_record_obj.coupon.money_equivalent_value rebate_price = price - money_equivalent_value else: return price elif coupon_type == 2: # -- 折扣 off_percent = coupon_record_obj.coupon.off_percent # 折后价格 = 获取原价* 80/100 rebate_price = price * (off_percent / 100) else: rebate_price = price return rebate_price def get_random_order(self): '''生成随机订单号''' import random time_current = self.now.strftime('%Y%m%d%H%M%S') random_str = ''.join([str(random.randint(0,9)) for i in range(4)]) return f'{time_current}{random_str}'
附表信息:
from datetime import datetime from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericRelation,GenericForeignKey # Create your models here. class Courses(models.Model): title = models.CharField(max_length=64) price = models.DecimalField(max_digits=8,decimal_places=2) classes = ((1,'初级'),(2,'中级'),(3,'高级')) course_class = models.SmallIntegerField(choices=classes) course_img = models.CharField(max_length=128) status_choices = ((0, '上线'), (1, '下线'), (2, '预上线')) status = models.SmallIntegerField(choices=status_choices, default=0,help_text='课程的状态') price_policy = GenericRelation(to='PricePolicy') questions = GenericRelation(to='OftenQuestion') coupons = GenericRelation(to='Coupon') def __str__(self): return self.title class CourseDetail(models.Model): course = models.OneToOneField(to='Courses') slogen = models.CharField(max_length=128) desc = models.CharField(max_length=512) hours = models.IntegerField('课时',default=10) recommend_courses = models.ManyToManyField(to='Courses',related_name="recourse") def __str__(self): return self.course.title class ChapterList(models.Model): '''课程章节''' num = models.SmallIntegerField() name = models.CharField(max_length=64) course = models.ForeignKey(to='Courses',on_delete=models.CASCADE) def __str__(self): return "%s:(第%s章)%s"%(self.course,self.num,self.name) class Meta: unique_together = ('num','course') class CourseSection(models.Model): '''课时目录''' name = models.CharField(max_length=64) charpter = models.ForeignKey(to='ChapterList') def __str__(self): return self.name class UserInfo(models.Model): name = models.CharField(max_length=16) password = models.CharField(max_length=32) beli_count = models.IntegerField(default=0) def __str__(self): return self.name class Token(models.Model): name = models.CharField(max_length=128) user = models.OneToOneField(to='UserInfo') def __str__(self): return self.name class PricePolicy(models.Model): '''价格策略表''' name = models.CharField(max_length=128) price = models.DecimalField(max_digits=6,decimal_places=2,default=199) content_type = models.ForeignKey(to=ContentType) object_id = models.PositiveIntegerField() content_obj = GenericForeignKey('content_type','object_id') def __str__(self): return self.name class Meta: unique_together = ('content_type','object_id','name') class OftenQuestion(models.Model): question = models.CharField(max_length=128) answer = models.TextField() content_type = models.ForeignKey(to=ContentType) object_id = models.PositiveIntegerField() content_obj = GenericForeignKey('content_type','object_id') def __str__(self): return self.question class Meta: unique_together = ('content_type','object_id','question') class Coupon(models.Model): """优惠券生成规则""" name = models.CharField(max_length=64, verbose_name="活动名称") brief = models.TextField(blank=True, null=True, verbose_name="优惠券介绍") coupon_type_choices = ((0, '通用券'), (1, '满减券'), (2, '折扣券')) coupon_type = models.SmallIntegerField(choices=coupon_type_choices, default=0, verbose_name="券类型") money_equivalent_value = models.IntegerField(verbose_name="等值货币",blank=True,null=True) off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只针对折扣券,例7.9折,写79", blank=True, null=True) minimum_consume = models.PositiveIntegerField("最低消费", default=0, help_text="仅在满减券时填写此字段",blank=True,null=True) content_type = models.ForeignKey(ContentType, blank=True, null=True) object_id = models.PositiveIntegerField("绑定课程", blank=True, null=True, help_text="可以把优惠券跟课程绑定") content_object = GenericForeignKey('content_type', 'object_id') quantity = models.PositiveIntegerField("数量(张)", default=1) open_date = models.DateField("优惠券领取开始时间") close_date = models.DateField("优惠券领取结束时间") valid_begin_date = models.DateField(verbose_name="有效期开始时间", blank=True, null=True) valid_end_date = models.DateField(verbose_name="有效结束时间", blank=True, null=True) coupon_valid_days = models.PositiveIntegerField(verbose_name="优惠券有效期(天)", blank=True, null=True, help_text="自券被领时开始算起") date = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "31. 优惠券生成记录" def __str__(self): return "%s(%s)" % (self.get_coupon_type_display(), self.name) def save(self, *args, **kwargs): if not self.coupon_valid_days or (self.valid_begin_date and self.valid_end_date): if self.valid_begin_date and self.valid_end_date: if self.valid_end_date <= self.valid_begin_date: raise ValueError("valid_end_date 有效期结束日期必须晚于 valid_begin_date ") if self.coupon_valid_days == 0: raise ValueError("coupon_valid_days 有效期不能为0") if self.close_date < self.open_date: raise ValueError("close_date 优惠券领取结束时间必须晚于 open_date优惠券领取开始时间 ") super(Coupon, self).save(*args, **kwargs) class CouponRecord(models.Model): """优惠券发放、消费纪录""" coupon = models.ForeignKey("Coupon") number = models.CharField(max_length=64, unique=True) user = models.ForeignKey("UserInfo", verbose_name="拥有者") status_choices = ((0, '未使用'), (1, '已使用'), (2, '已过期')) status = models.SmallIntegerField(choices=status_choices, default=0) get_time = models.DateTimeField(verbose_name="领取时间", help_text="用户领取时间") used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用时间") order = models.ForeignKey("Order", blank=True, null=True, verbose_name="关联订单") # 一个订单可以有多个优惠券 class Meta: verbose_name_plural = "32. 用户优惠券" def __str__(self): return '%s-%s-%s' % (self.user, self.number, self.status) class Order(models.Model): """订单""" payment_type_choices = ((0, '微信'), (1, '支付宝'), (2, '优惠码'), (3, '贝里')) payment_type = models.SmallIntegerField(choices=payment_type_choices) payment_number = models.CharField(max_length=128, verbose_name="支付第3方订单号", null=True, blank=True) order_number = models.CharField(max_length=128, verbose_name="订单号", unique=True) # 考虑到订单合并支付的问题 user = models.ForeignKey("UserInfo") actual_amount = models.FloatField(verbose_name="实付金额") status_choices = ((0, '交易成功'), (1, '待支付'), (2, '退费申请中'), (3, '已退费'), (4, '主动取消'), (5, '超时取消')) status = models.SmallIntegerField(choices=status_choices, verbose_name="状态") date = models.DateTimeField(auto_now_add=True, verbose_name="订单生成时间") pay_time = models.DateTimeField(blank=True, null=True, verbose_name="付款时间") cancel_time = models.DateTimeField(blank=True, null=True, verbose_name="订单取消时间") class Meta: verbose_name_plural = "37. 订单表" def __str__(self): return "%s" % self.order_number class OrderDetail(models.Model): """订单详情""" order = models.ForeignKey("Order") content_type = models.ForeignKey(ContentType) # 可关联普通课程或学位 object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') original_price = models.FloatField("课程原价") price = models.FloatField("折后价格") content = models.CharField(max_length=255, blank=True, null=True) # ? valid_period_display = models.CharField("有效期显示", max_length=32) # 在订单页显示 valid_period = models.PositiveIntegerField("有效期(days)") # 课程有效期 memo = models.CharField(max_length=255, blank=True, null=True) def __str__(self): return "%s - %s - %s" % (self.order, self.content_type, self.price) class Meta: verbose_name_plural = "38. 订单详细" unique_together = ("order", 'content_type', 'object_id')