基于rest_framework和redis实现购物车的操作,结算,支付
前奏:
首先,要在主机中安装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')
models.py
基于rest_framework和redis实现购物车的操作,结算,支付的更多相关文章
- 基于 php-redis 的redis操作
基于 php-redis 的redis操作 林涛 发表于:2016-5-13 12:12 分类:PHP 标签:php,php-redis,redis 203次 redis的操作很多的,下面的例子都是基 ...
- $Django 路飞之显示视频,Redis存购物车数据,优惠卷生成表,优惠卷的一个领取表。(知识小回顾)
知识小回顾之json序列化问题 精髓:支持python的几种数据类型(注意不是对象,不能放对象),其次是tuple变list. ensure_ascii:默认值True,如果dict内含有non-AS ...
- day85:luffy:购物车根据有效期不同切换价格&购物车删除操作&价格结算&订单页面前戏
目录 1.购物车有效期切换 2.根据有效期不同切换价格 3.购物车删除操作 4.价格结算 5.订单页面-初始化 1.购物车有效期切换 1.关于有效期表结构的设计 1.course/models.py ...
- 基于nginx+lua+redis高性能api应用实践
基于nginx+lua+redis高性能api应用实践 前言 比较传统的服务端程序(PHP.FAST CGI等),大多都是通过每产生一个请求,都会有一个进程与之相对应,请求处理完毕后相关进程自动释放. ...
- Python 基于python操纵redis入门介绍
基于python操纵redis入门介绍 by:授客 QQ:1033553122 测试环境 redis-3.0.7 CentOS 6.5-x86_64 python 3.3.2 基于Python操作R ...
- Redis五大数据类型以及操作
目录: 一.redis的两种链接方式 二.redis的字符串操作(string) 三.redis的列表操作(list) 四.redis的散列表操作(类似于字典里面嵌套字典) 五.redis的集合操作( ...
- redis 五大数据类型以及操作
一.redis的两种链接方式 1.简单连接 import redis conn = redis.Redis(host='10.0.0.200',port=6379) conn.set('k1','年后 ...
- 第三百节,python操作redis缓存-其他常用操作,用于操作redis里的数据name,不论什么数据类型
python操作redis缓存-其他常用操作,用于操作redis里的数据name,不论什么数据类型 delete(*names)根据删除redis中的任意数据类型 #!/usr/bin/env pyt ...
- 基于Codis的Redis集群部署
Codis是基于代理的高性能Redis集群方案,使用Go语言进行开发,现在在在豌豆荚及其它公司内已经广泛使用,当然也包括我们公司. Codis与常见的Redis集群方案对比. 在搭建的时候,个人觉得R ...
随机推荐
- HTML5 canvas 创意:飞翔的凤凰
当我看到这件作品的时候,我表示非常喜欢.这个作品的产生不仅仅需要编程和算法,作者肯定是个充满了艺术细胞的人.倘若有什么canvas艺术作品比赛的话,我想它就是获奖的那个. 先观赏下演示吧.注意,要看到 ...
- linux 常见服务端口
Linux服务器在启动时需要启动很多系统服务,它们向本地和网络用户提供了Linux的系统功能接口,直接面向应用程序和用户.提供这些服务的程序是由运行在后台的守护进程(daemons) 来执行的.守护进 ...
- caffe环境的搭建(Ubuntu14.04 64bit,无CUDA,caffe在CPU下运行)
1. 安装BLAS : $ sudo apt-get install libatlas-base-dev 2. 安装依赖项: $ sudo apt-get install libprotobuf-de ...
- 题解【luoguP3644 [APIO2015]八邻旁之桥】
题目链接 题解 家和公司在同侧 简单,直接预处理掉 若 \(k=1\) 取所有的居民的\(\frac{家坐标+公司坐标}{2}\)的所有坐标的正中间建一座桥,使所有居民到的距离最小. 实现方法:线段树 ...
- vue-transition-move
<!Doctype> <html> <head> <meta charset="utf-8"> <meta name=&quo ...
- JavaMail实现邮件的发送
1,拷贝mail.jar 和activation.jar到项目中 2,开启邮箱的 POP3/SMTP服务,以QQ邮箱为例 进去QQ邮箱-->设置-->账号-->进行设置如下图 注意: ...
- jquery checkbox选中状态以及实现全选反选
jquery1.6以下版本获取checkbox的选中状态: $('.ck').attr('checked'); $('.ck').attr('checked',true);//全选 $('.ck'). ...
- rdlc 格式设置
在用vs2013开发rdlc报表时,发现好多报表样式问题: 1.导出的pdf偶数页总是空白页. 2.导出的Excel打印时,内容显示不全. 3.word内容显示不全. 查了好多资料终于找到解决方案了, ...
- 【Contest Hunter【弱省胡策】Round #0-Flower Dance】组合数学+DP
题目链接: http://ch.ezoj.tk/contest/%E3%80%90%E5%BC%B1%E7%9C%81%E8%83%A1%E7%AD%96%E3%80%91Round%20%230/F ...
- 【bzoj】1927 [Sdoi2010]星际竞速
[算法]最小费用最大流 [题解]跟滑雪略有类似,同样因为可以重复所以不是最小路径覆盖. 连向汇的边容量为1足矣,因为一个点只会出去一次(路径结束). bzoj 1927 [Sdoi2010]星际竞速 ...