基于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
原文地址:https://www.cnblogs.com/zhaopanpan/p/9295835.html