day 113 添加购物车接口
购物车
1,用到了django_redis存储数据,存储数据快,好管理
cmd下载redies: pip install 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": "密码", } }, }
2,view文件夹里创建shopping_car.py写购物车的增删改查接口

from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin,ModelViewSet from rest_framework import generics from rest_framework.response import Response from course.models import Course,PricePolicy from django.core.exceptions import ObjectDoesNotExist from course.utils.response import BaseResponse from course.utils.exceptions import PricePolicyException from django_redis import get_redis_connection from course.utils.auth import LoginAuth import redis import json # # """ # 购物车 # redis 的数据结构设计 # redis = { # shopping_car_<user_id>_<course_id>: { # "id": course.id, 带用户id是为了用户查看购物车的时候需要 # "title": course.name, # "img": str(course.course_img), # "policy_dict": { #所有价格策略信息 # policy_id:{ #第一个价格策略信息 # "period": item.valid_period, 价格策略 # "period_display": item.get_valid_period_display(), 价格策略中文 # "price": item.price, 价格 # }, # policy_id2:{ # .... # } # }, # "default_policy": policy_id ,选择的价格策略id # } # # # } # """ # REDIS_CONN = redis.Redis(host="127.0.0.1",port=6379,decode_responses=True)#decode_responses=True把redis的byset类型取出来的时候变成字符串 SHOPPING_CAR_KEY = "shopping_car_%s_%s" class ShoppingCarView(APIView): # """ # 购物车接口 # 1010 代表成功 # 1011 课程不存在 # 1012 价格策略不存在 # 1013 获取购物车失败 # 1014 删除的购物车数据不存在 # """ # authentication_classes = [LoginAuth] #认证 # #展示购物车数据 def get(self, request, *args, **kwargs): res = BaseResponse() try: # 1 获取用户id user_id = request.user.id # 2 从redis获取购物车所有数据 shopping_car_key = SHOPPING_CAR_KEY % (user_id, "*") # 用redis中scan_iter方法过滤用户购物车的所有key all_names = REDIS_CONN.scan_iter(shopping_car_key) # print(all_names) #循环每个key 去 redis 中拿到这个key的value ret = [] for name in all_names: print(name) course_info=REDIS_CONN.hgetall(name)#获取name为键的所有 ret.append(course_info) # print(ret) res.data = ret except Exception as e: res.code = 1013 res.error = "获取购物车失败" return Response(res.dict) # 给购物车增加商品 # 传过来的数据 有 课程ID 以及价格策略ID def post(self, request, *args, **kwargs): res = BaseResponse() try: # 1 获取用户提交(前端传过来)的课程ID以及价格策略ID course_id = request.data.get("course_id", "") price_policy_id = request.data.get("price_policy_id", "") user_id = request.user.pk # 2 检查用户传过来的数据是否合法 # 2.1 查看课程ID是否存在 course_obj = Course.objects.filter(id=course_id).first() if not course_obj: res.code = 1011 res.error = "课程对象不存在" return Response(res.dict) # 2.2 查看价格策略price_policy_id是否合法 # 获取该课程的所有价格策略 判断price_policy_id是否在里面 price_policy_queryset = course_obj.price_policy.all() price_policy_dict = {} for price_policy in price_policy_queryset: price_policy_dict[price_policy.pk] = { "price": price_policy.price, "valid_period": price_policy.valid_period, #周期 "valid_period_text": price_policy.get_valid_period_display(), } if price_policy_id not in price_policy_dict: res.code = 1012 res.error = "课程的价格策略不存在" return Response(res.dict) # 3构建数据结构 构建出购物车的key 用户数据合法 得到用户的购物车key shopping_car_key = SHOPPING_CAR_KEY % (user_id, course_id) # 4 构建购物车数据 course_info = { "id": course_id, "title": course_obj.title, "img_src": str(course_obj.course_img), "default_policy": price_policy_id, "price_policy_dict": json.dumps(price_policy_dict,ensure_ascii=False) } # 5 写入redis hmset这个方法有值就更新 没值就新建 解决了用户重复点一个商品的问题 REDIS_CONN.hmset(shopping_car_key, course_info) res.data = "加入购物车成功" res.data = course_info # 把course_info放在了data里 except Exception as e: print(e) res.code = 1010 res.error = "加入购物车失败" print(res.dict) return Response(res.dict) # # # # 删除购物车数据 # # 传过来的数据 {course_ids: [1,2]} def delete(self, request, *args, **kwargs): res = BaseResponse() # 1 验证数据合法性 课程ID是否在购物车里 try: for course_id in request.data["course_ids"]: shopping_car_key = SHOPPING_CAR_KEY % (request.user.pk, course_id) if not REDIS_CONN.exists(shopping_car_key): res.code = 1014 res.error = "课程不存在购物车" # 2 数据合法 删除redis购物车里的数据 REDIS_CONN.delete(shopping_car_key) except Exception as e: res.code = 1015 res.error = "删除购物车失败" return Response(res.dict) # 更新购物车里的课程的价格策略 # 传过来的数据 有 course_id 以及 price_policy_id def patch(self, request, *args, **kwargs): res = BaseResponse() # 1 验证数据的合法性 course_id 以及 policy_id try: course_id = request.data["course_id"] policy_id = request.data["price_policy_id"] # 判断redis购物车里是否有course_id shopping_car_key = SHOPPING_CAR_KEY % (request.user.pk, course_id) if not REDIS_CONN.exists(shopping_car_key): res.code = 1014 res.error = "课程不存在购物车" # 判断policy_id 是否合法 redis中取 验证 price_policy_dict = json.loads(REDIS_CONN.hget(shopping_car_key, "price_policy_dict")) if str(policy_id) not in price_policy_dict: res.code = 1015 res.error = "价格策略不合法" # 2 更改购物车里的数据 REDIS_CONN.hset(shopping_car_key, "default_policy", policy_id) except Exception as e: print(e) res.code = 1016 res.error = "更新购物车失败" return Response(res.dict) # # # # #
3,在购物车接口中返回错误信息,状态码,实例化了BaseResponse(),在utils中写了response.py

class BaseResponse(): def __init__(self): self.code=1000 self.data = None self.msg = '' @property def dict(self): return self.__dict__ if __name__=="__main__": res = BaseResponse() res.code=1001 print(res.dict)
4,购物车中自定义报错信息在utils里写了excepyions.py

class PricePolicyException(Exception): def __init__(self,msg): self.msg=msg
5,urls.py

"""LuffyBoy URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.11/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import url from django.contrib import admin from rest_framework.routers import DefaultRouter from course.view.courseView import CourseView from course.view.categoryView import CategoryView from course.view.login import LoginView from course.view.shopping_car import ShoppingCarView router=DefaultRouter() #实例化 router.register("course",CourseView)#把路由注册进来course,CourseView对应的视图 router.register("category",CategoryView) urlpatterns = [ url(r'^admin/', admin.site.urls), url(r"^login/",LoginView.as_view()), url(r'^shoppingcar/',ShoppingCarView.as_view()), ] urlpatterns+=router.urls#把"course"添加到列表里 # http://127.0.0.1:8000/course/?format=json
================以上是购物车接口======
utils文件夹下
添加的认证类在utils文件中
auth.py:

from rest_framework.authentication import BaseAuthentication from course.models import UserToken from rest_framework.exceptions import AuthenticationFailed from django.core.cache import cache from django.utils.timezone import utc import datetime #第一版普通版本解决不了时效和缓存 # class LoginAuth(BaseAuthentication): # def authenticate(self, request): # print("request.META",request.META.get("HTTP_AUTHENTICATE")) # token=request.META.get("HTTP_AUTHENTICATE") # token = UserToken.objects.filter(token=token).first() # # if token: # return token.user,token.token # else: # raise AuthenticationFailed("认证失败") #第二版解决时效和缓存 class LoginAuth(BaseAuthentication): def authenticate(self,request): print("request.META",request.META.get("HTTP_AUTHENTICATE")) token=request.META.get("HTTP_AUTHENTICATE",'')#从响应头里获取 # 缓存查询token是否存在(第一次进来的时候token不存在,走数据库) user=cache.get("token_"+token) #价格字符串做标识 if user: return user, "token_"+token try: # 数据库查询是否存在token token=UserToken.objects.get(token=token) except Exception as e: raise AuthenticationFailed("认证失败!") # token是否过期 utcnow = datetime.datetime.utcnow().replace(tzinfo=utc) #当前时间 if utcnow - token.create_time > datetime.timedelta(days=14): #当前时间-token创建时间> 数据库设定存活时间 14天 raise AuthenticationFailed('认证信息过期') # 加入缓存 cache_token="token_"+token.token #"token_"做标识 # 14+create-now > 7 delta=utcnow-token.create_time#当前时间-创建时间 delta_7=datetime.timedelta(days=7) cache.set(cache_token,token.user,min(delta_7,delta).total_seconds())#total_seconds()转化为秒 # 返回元组 return token.user,cache_token
middlewares.py解决跨域:

#解决跨域 from django.middleware.security import SecurityMiddleware from django.utils.deprecation import MiddlewareMixin class MyCors(MiddlewareMixin): def process_response(self, request, response): # 简单请求 response["Access-Control-Allow-Origin"] = "*" # 复杂请求 会先发送预检请求 OPTIONS if request.method == "OPTIONS": response["Access-Control-Allow-Headers"] = "Content-Type" response["Access-Control-Allow-Methods"] = "POST, PUT, PATCH, DELETE" return response
serializer.py序列化

#序列化 from rest_framework import serializers from course.models import Course class CourseModelSerializer(serializers.ModelSerializer):#序列化器 class Meta: model=Course fields="__all__" price = serializers.SerializerMethodField() #价钱 level = serializers.SerializerMethodField() #等级 rel_teachers = serializers.SerializerMethodField() #讲师 rel_courses = serializers.SerializerMethodField() #推荐课程 price_policys = serializers.SerializerMethodField() #价格策略 def get_price(self,obj): #价钱 price_policy = obj.price_policy.all() if price_policy: return price_policy[0].price return "" def get_level(self,obj): #等级 return obj.get_level_display() #choice类型 def get_rel_teachers(self,obj): #讲师 print(obj) # l=[] #第一种方法 # for teacher in obj.coursedetail.teachers.all(): # l.append() # return l #第二种方法简化列表生成式 return [teacher.name for teacher in obj.coursedetail.teachers.all()] def get_rel_courses(self,obj): #推荐课程 return [(course.title,course.pk) for course in obj.coursedetail.recommend_courses.all()] def get_price_policys(self,obj): #价格策略 ret=[{"price":price_policy.price,"pk":price_policy.pk,"valid_period":price_policy.valid_period,"valid_period_text":price_policy.get_valid_period_display()} for price_policy in obj.price_policy.all()] return ret
view文件夹下
课程courseView.py

from rest_framework import serializers from rest_framework.views import APIView from rest_framework import generics from rest_framework.viewsets import ModelViewSet from rest_framework.response import Response from course.models import Course from course.utils.serializer import CourseModelSerializer from course.utils.auth import LoginAuth class CourseView(ModelViewSet): authentication_classes = [LoginAuth] #认证 queryset = Course.objects.all() serializer_class = CourseModelSerializer # def list(self, request, *args, **kwargs): # queryset = self.filter_queryset(self.get_queryset()) # page = self.paginate_queryset(queryset) # # response={"code":1000,"data":None,"err_msg":""} # # if page is not None: # serializer = self.get_serializer(page, many=True) # response["data"]=serializer.data # return self.get_paginated_response(response) # # serializer = self.get_serializer(queryset, many=True) # response["data"] = serializer.data # return Response(response)
分类序列化categoryView.py

from rest_framework import serializers from rest_framework.views import APIView from rest_framework import generics from rest_framework.viewsets import ModelViewSet from rest_framework.response import Response from course.models import Category class CategoryModelSerializer(serializers.ModelSerializer):#序列化器 class Meta: model=Category fields="__all__" class CategoryView(ModelViewSet): #序列化器 queryset = Category.objects.all() serializer_class = CategoryModelSerializer
登录login.py

from django.shortcuts import HttpResponse from rest_framework.views import APIView from course.models import Account,UserToken from rest_framework.response import Response class LoginView(APIView): def get_radon_str(self,user): import hashlib,time ctime = str(time.time()) md5 = hashlib.md5(bytes(user,encoding="utf-8")) #MD5加密根据用户名加盐 md5.update(bytes(ctime,encoding="utf-8")) #根据时间加盐 return md5.hexdigest() def post(self,request): #获取数据 user= request.data.get("user") pwd = request.data.get("pwd") #创建响应体 response = {"date":None,"code":1000} try: user = Account.objects.filter(username=user,password=pwd).first() if user: #校验成功 random_str = self.get_radon_str(user.username) #数据库更新 UserToken.objects.update_or_create(user=user,defaults={"token": random_str}) #创建token #创建响应体 response["data"] = {"name":user.username} response["token"] = random_str else:#检验失败 #构建响应体 response["code"] = 1001 #用户名或者密码错误 response["err_msg"] = "用户名或者密码错误" except Exception as e: #构建响应体 response["code"] = 2000 #用户名或者密码错误 request["err_msg"] = str(e) return Response(response)
表结构models.py

from django.db import models # Create your models here. from django.db import models from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType # Create your models here. __all__ = ["Category", "Course", "CourseDetail", "Teacher", "DegreeCourse", "CourseChapter", "CourseSection", "PricePolicy", "OftenAskedQuestion", "Comment", "Account", "CourseOutline"] class Account(models.Model): """ 用户表 """ username = models.CharField(max_length=32, verbose_name="用户姓名") password = models.CharField(max_length=128,verbose_name="密码",null=True) type_choices = [(1,"普通用户"),(2,"VIP"),(3,"SVIP")] user_type = models.IntegerField(choices=type_choices,default=1) # head_img = models.CharField(max_length=256, default='/static/frontend/head_portrait/logo@2x.png', # verbose_name="个人头像") def __str__(self): return self.username class Meta: verbose_name = "11-用户表" db_table = verbose_name verbose_name_plural = verbose_name class UserToken(models.Model): user = models.OneToOneField("Account") token = models.CharField(max_length=32) create_time = models.DateTimeField(auto_now_add=True)#auto_now_add自动添加当前时间 class Category(models.Model): """课程分类表""" title = models.CharField(max_length=32, unique=True, verbose_name="课程的分类") def __str__(self): return self.title class Meta: verbose_name = "01-课程分类表" db_table = verbose_name verbose_name_plural = verbose_name class Course(models.Model): """课程表""" title = models.CharField(max_length=128, unique=True, verbose_name="课程的名称") course_img = models.ImageField(upload_to="course/%Y-%m", verbose_name='课程的图片',null=True,blank=True) category = models.ForeignKey(to="Category", verbose_name="课程的分类") COURSE_TYPE_CHOICES = ((0, "付费"), (1, "vip专享"), (2, "学位课程")) course_type = models.SmallIntegerField(choices=COURSE_TYPE_CHOICES) degree_course = models.ForeignKey(to="DegreeCourse", blank=True, null=True, help_text="如果是学位课程,必须关联学位表") brief = models.CharField(verbose_name="课程简介", max_length=1024) level_choices = ((0, '初级'), (1, '中级'), (2, '高级')) level = models.SmallIntegerField(choices=level_choices, default=1) status_choices = ((0, '上线'), (1, '下线'), (2, '预上线')) status = models.SmallIntegerField(choices=status_choices, default=0) pub_date = models.DateField(verbose_name="发布日期", blank=True, null=True) order = models.IntegerField("课程顺序", help_text="从上一个课程数字往后排,尽量间隔几个数字") study_num = models.IntegerField(verbose_name="学习人数", help_text="只要有人买课程,订单表加入数据的同时给这个字段+1") # order_details = GenericRelation("OrderDetail", related_query_name="course") # coupon = GenericRelation("Coupon") # 只用于反向查询不生成字段 price_policy = GenericRelation("PricePolicy") often_ask_questions = GenericRelation("OftenAskedQuestion") course_comments = GenericRelation("Comment") def save(self, *args, **kwargs): if self.course_type == 2: if not self.degree_course: raise ValueError("学位课必须关联学位课程表") super(Course, self).save(*args, **kwargs) def __str__(self): return self.title class Meta: verbose_name = "02-课程表" db_table = verbose_name verbose_name_plural = verbose_name class CourseDetail(models.Model): """课程详细表""" course = models.OneToOneField(to="Course",related_name='coursedetail') hours = models.IntegerField(verbose_name="课程时长", default=7) # course_slogan = models.CharField(max_length=125, blank=True, null=True, verbose_name="课程口号") video_brief_link = models.CharField(max_length=255, blank=True, null=True) summary = models.TextField(max_length=2048, verbose_name="课程概述") why_study = models.TextField(verbose_name="为什么学习这门课程") what_to_study_brief = models.TextField(verbose_name="我将学到哪些内容") career_improvement = models.TextField(verbose_name="此项目如何有助于我的职业生涯") prerequisite = models.TextField(verbose_name="课程先修要求", max_length=1024) recommend_courses = models.ManyToManyField("Course", related_name="recommend_by", blank=True) teachers = models.ManyToManyField("Teacher", verbose_name="课程讲师") def __str__(self): return self.course.title class Meta: verbose_name = "03-课程详细表" db_table = verbose_name verbose_name_plural = verbose_name class Teacher(models.Model): """讲师表""" name = models.CharField(max_length=32, verbose_name="讲师名字") brief = models.TextField(max_length=1024, verbose_name="讲师介绍") def __str__(self): return self.name class Meta: verbose_name = "04-教师表" db_table = verbose_name verbose_name_plural = verbose_name class DegreeCourse(models.Model): """ 字段大体跟课程表相同,哪些不同根据业务逻辑去区分 """ title = models.CharField(max_length=32, verbose_name="学位课程名字") def __str__(self): return self.title class Meta: verbose_name = "05-学位课程表" db_table = verbose_name verbose_name_plural = verbose_name class CourseChapter(models.Model): """课程章节表""" course = models.ForeignKey(to="Course", related_name="course_chapters") chapter = models.SmallIntegerField(default=1, verbose_name="第几章") title = models.CharField(max_length=32, verbose_name="课程章节名称") def __str__(self): return self.title class Meta: verbose_name = "06-课程章节表" db_table = verbose_name verbose_name_plural = verbose_name unique_together = ("course", "chapter") class CourseSection(models.Model): """课时表""" chapter = models.ForeignKey(to="CourseChapter", related_name="course_sections") title = models.CharField(max_length=32, verbose_name="课时") section_order = models.SmallIntegerField(verbose_name="课时排序", help_text="建议每个课时之间空1至2个值,以备后续插入课时") section_type_choices = ((0, '文档'), (1, '练习'), (2, '视频')) free_trail = models.BooleanField("是否可试看", default=False) section_type = models.SmallIntegerField(default=2, choices=section_type_choices) section_link = models.CharField(max_length=255, blank=True, null=True, help_text="若是video,填vid,若是文档,填link") def course_chapter(self): return self.chapter.chapter def course_name(self): return self.chapter.course.title def __str__(self): return "%s-%s" % (self.chapter, self.title) class Meta: verbose_name = "07-课程课时表" db_table = verbose_name verbose_name_plural = verbose_name unique_together = ('chapter', 'section_link') class PricePolicy(models.Model): """价格策略表""" # conten_type 指定表id content_type = models.ForeignKey(ContentType) # 关联course or degree_course # 关联的表里的对象id object_id = models.PositiveIntegerField() # 关联的表里的那个对象 content_object = GenericForeignKey('content_type', 'object_id') valid_period_choices = ((1, '1天'), (3, '3天'), (7, '1周'), (14, '2周'), (30, '1个月'), (60, '2个月'), (90, '3个月'), (120, '4个月'), (180, '6个月'), (210, '12个月'), (540, '18个月'), (720, '24个月'), (722, '24个月'), (723, '24个月'), ) # 周期 valid_period = models.SmallIntegerField(choices=valid_period_choices) # 价格 price = models.FloatField() def __str__(self): return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price) class Meta: verbose_name = "08-价格策略表" db_table = verbose_name verbose_name_plural = verbose_name unique_together = ("content_type", 'object_id', "valid_period") class OftenAskedQuestion(models.Model): """常见问题""" content_type = models.ForeignKey(ContentType) # 关联course or degree_course object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') question = models.CharField(max_length=255) answer = models.TextField(max_length=1024) def __str__(self): return "%s-%s" % (self.content_object, self.question) class Meta: verbose_name = "09-常见问题表" db_table = verbose_name verbose_name_plural = verbose_name unique_together = ('content_type', 'object_id', 'question') class Comment(models.Model): """通用的评论表""" content_type = models.ForeignKey(ContentType, blank=True, null=True) object_id = models.PositiveIntegerField(blank=True, null=True) content_object = GenericForeignKey('content_type', 'object_id') content = models.TextField(max_length=1024, verbose_name="评论内容") account = models.ForeignKey("Account", verbose_name="会员名") date = models.DateTimeField(auto_now_add=True) def __str__(self): return self.content class Meta: verbose_name = "10-评价表" db_table = verbose_name verbose_name_plural = verbose_name class CourseOutline(models.Model): """课程大纲""" course_detail = models.ForeignKey(to="CourseDetail", related_name="course_outline") title = models.CharField(max_length=128) order = models.PositiveSmallIntegerField(default=1) # 前端显示顺序 content = models.TextField("内容", max_length=2048) def __str__(self): return "%s" % self.title class Meta: verbose_name = "12-课程大纲表" db_table = verbose_name verbose_name_plural = verbose_name unique_together = ('course_detail', 'title')
admin.py

from django.contrib import admin # Register your models here. from course import models for field in models.__all__: admin.site.register(getattr(models,field))
posted on 2018-11-15 19:32 liangliang123456 阅读(380) 评论(0) 收藏 举报