ContentType是Django的內置的一個應用,能夠追蹤項目中全部的APP和model的對應關係,並記錄在ContentType表中。
當項目作數據遷移後,會有不少django自帶的表,其中就有django_content_type表前端
ContentType組件應用mysql
from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation # Create your models here. class Food(models.Model): """ id title 1 麪包 2 牛奶 """ title = models.CharField(max_length=32) # 不會生成字段 只用於反向查詢 coupons = GenericRelation(to="Coupon") class Fruit(models.Model): """ id title 1 蘋果 2 香蕉 """ title = models.CharField(max_length=32) # 若是有40張表 # class Coupon(models.Model): # """ # id title food_id fruit_id # 1 麪包九五折 1 null # 2 香蕉滿10元減5元 null 2 # """ # title = models.CharField(max_length=32) # food = models.ForeignKey(to="Food") # fruit = models.ForeignKey(to="Fruit") # class Coupon(models.Model): # """ # id title table_id object_id # 1 麪包九五折 1 1 # 2 香蕉滿10元減5元 2 2 # """ # title = models.CharField(max_length=32) # table = models.ForeignKey(to="Table") # object_id = models.IntegerField() # # # class Table(models.Model): # """ # id app_name table_name # 1 demo food # 2 demo fruit # """ # app_name = models.CharField(max_length=32) # table_name = models.CharField(max_length=32) class Coupon(models.Model): title = models.CharField(max_length=32) # 第一步:注意沒有引號由於是導入的 content_type = models.ForeignKey(to=ContentType, on_delete=None) # 第二步 object_id = models.IntegerField() # 第三步 不會生成字段,用來操做增刪改查 content_object = GenericForeignKey("content_type", "object_id")
from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response from .models import Food, Coupon from django.contrib.contenttypes.models import ContentType # Create your views here. class DemoView(APIView): def get(self, request): # 給麪包建立一個優惠券 food_obj = Food.objects.filter(id=1).first() # Coupon.objects.create(title="麪包九五折", content_type_id=8, object_id=1) # Coupon.objects.create(title="雙十一面包九折促銷", content_object=food_obj) #查詢食物都有哪些優惠券 #定義了反向查詢 coupons = food_obj.coupons.all() print(coupons) # 若是沒定義反向查詢 content = ContentType.objects.filter(app_label="app01", model="food").first() coupons = Coupon.objects.filter(content_type=content, object_id=1).all() print(coupons) # 優惠券查對象 # 查詢優惠券id=1綁定了哪一個商品 coupon_obj = Coupon.objects.filter(id=1).first() content_obj = coupon_obj.content_object print(coupon_obj.title,content_obj.title) # 經過ContentType表找表模型 content = ContentType.objects.filter(app_label="app01", model="food").first() # content=food 獲取food表的表模型用model_class() model_class = content.model_class() ret = model_class.objects.all() print(ret) return Response("ContentType測試")
#靜態文件 STATIC_URL = '/static/' STATICFILES_DIRS=( os.path.join(BASE_DIR,'static'), ) # Media配置 MEDIA_URL = "media/" MEDIA_ROOT = os.path.join(BASE_DIR, "media")
from django.conf.urls import url, include from django.contrib import admin from django.views.static import serve from new_luffy import settings urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^api/course/', include("course.urls")), # media路徑配置 url(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}) ]
from django.contrib import admin from django.urls import path, include, re_path from django.views.static import serve from LuffyCity import settings from Login.views import GeetestView urlpatterns = [ path('admin/', admin.site.urls), path('api/course/', include("Course.urls")), path('api/shop/', include("shopping.urls")), path('api/', include("Login.urls")), path('pc-geetest/register', GeetestView.as_view()), path('pc-geetest/ajax_validate', GeetestView.as_view()), # media路徑配置 # path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}) re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}) ]
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 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='課程的圖片') category = models.ForeignKey(to="Category", verbose_name="課程的分類", on_delete=None) 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="若是是學位課程,必須關聯學位表", on_delete=None) 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", on_delete=None) 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", on_delete=None) 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", on_delete=None) 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): """價格策略表""" content_type = models.ForeignKey(ContentType, on_delete=None) # 關聯course or degree_course 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, on_delete=None) # 關聯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, on_delete=None) 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="會員名", on_delete=None) 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 Account(models.Model): username = models.CharField(max_length=32, verbose_name="用戶姓名") pwd = models.CharField(max_length=32, verbose_name="密文密碼") # head_img = models.CharField(max_length=256, default='/static/frontend/head_portrait/logo@2x.png', # verbose_name="我的頭像") balance = models.IntegerField(verbose_name="貝里餘額", default=0) def __str__(self): return self.username class Meta: verbose_name = "11-用戶表" db_table = verbose_name verbose_name_plural = verbose_name class CourseOutline(models.Model): """課程大綱""" course_detail = models.ForeignKey(to="CourseDetail", related_name="course_outline", on_delete=None) 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')
from django.urls import path from .views import CategoryView, CourseView, CourseDetailView, CourseChapterView, CourseCommentView, QuestionView from .video_view import PolyvView urlpatterns = [ path('category', CategoryView.as_view()), path('list', CourseView.as_view()), path('detail/<int:pk>', CourseDetailView.as_view()), path('chapter/<int:pk>', CourseChapterView.as_view()), path('comment/<int:pk>', CourseCommentView.as_view()), path('question/<int:pk>', QuestionView.as_view()), path('polyv', PolyvView.as_view()), ]
from rest_framework import serializers from . import models class CategorySerializer(serializers.ModelSerializer): class Meta: model = models.Category fields = "__all__" class CourseSerializer(serializers.ModelSerializer): level = serializers.CharField(source="get_level_display") price = serializers.SerializerMethodField() def get_price(self, obj): print(obj.price_policy.all()) return obj.price_policy.all().order_by("price").first().price class Meta: model = models.Course fields = ["id", "title", "course_img", "brief", "level", "study_num", "price"] class CourseDetailSerializer(serializers.ModelSerializer): level = serializers.CharField(source="course.get_level_display") study_num = serializers.IntegerField(source="course.study_num") recommend_courses = serializers.SerializerMethodField() teachers = serializers.SerializerMethodField() price_policy = serializers.SerializerMethodField() course_outline = serializers.SerializerMethodField() def get_course_outline(self, obj): return [{"id": outline.id, "title": outline.title, "content": outline.content} for outline in obj.course_outline.all().order_by("order")] def get_price_policy(self, obj): return [{"id": price.id, "valid_price_display": price.get_valid_period_display(), "price": price.price} for price in obj.course.price_policy.all()] def get_teachers(self, obj): return [{"id": teacher.id, "name": teacher.name} for teacher in obj.teachers.all()] def get_recommend_courses(self, obj): return [{"id": course.id, "title": course.title} for course in obj.recommend_courses.all()] class Meta: model = models.CourseDetail fields = ["id", "hours", "summary", "level", "study_num", "recommend_courses", "teachers", "price_policy", "course_outline"] class CourseChapterSerializer(serializers.ModelSerializer): sections = serializers.SerializerMethodField() def get_sections(self, obj): return [{"id": section.id, "title": section.title, "free_trail": section.free_trail} for section in obj.course_sections.all().order_by("section_order")] class Meta: model = models.CourseChapter fields = ["id", "title", "sections"] class CourseCommentSerializer(serializers.ModelSerializer): account = serializers.CharField(source="account.username") class Meta: model = models.Comment fields = ["id", "account", "content", "date"] class QuestionSerializer(serializers.ModelSerializer): class Meta: model = models.OftenAskedQuestion fields = ["id", "question", "answer"]
from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response from . import models from .serializers import CategorySerializer, CourseSerializer, CourseDetailSerializer, CourseChapterSerializer from .serializers import CourseCommentSerializer, QuestionSerializer # Create your views here. class CategoryView(APIView): """課程分類接口""" def get(self, request): # 經過ORM操做獲取全部分類數據 queryset = models.Category.objects.all() # 利用序列化器去序列化咱們的數據 ser_obj = CategorySerializer(queryset, many=True) # 返回 return Response(ser_obj.data) class CourseView(APIView): """查看全部免費課程的接口""" def get(self, request): # 獲取過濾條件中的分類ID category_id = request.query_params.get("category", 0) # 根據分類獲取課程 if category_id == 0: # 證實沒有分類,能夠拿全部的課程數據 queryset = models.Course.objects.all().order_by("order") else: queryset = models.Course.objects.filter(category_id=category_id).all().order_by("order") # 序列化課程數據 ser_obj = CourseSerializer(queryset, many=True) # 返回 return Response(ser_obj.data) class CourseDetailView(APIView): """課程詳情頁面""" def get(self, request, pk): # 根據pk獲取到課程詳情對象 course_detail_obj = models.CourseDetail.objects.filter(course__id=pk).first() if not course_detail_obj: return Response({"code": 1001, "error": "查詢的課程詳情不存在"}) # 序列化課程詳情 ser_obj = CourseDetailSerializer(course_detail_obj) # 返回 return Response(ser_obj.data) class CourseChapterView(APIView): """課程章節接口""" def get(self, request, pk): # 數據結構["第一章": {課時一, 課時二}] queryset = models.CourseChapter.objects.filter(course_id=pk).all().order_by("chapter") # 序列化章節對象 ser_obj = CourseChapterSerializer(queryset, many=True) # 返回 return Response(ser_obj.data) class CourseCommentView(APIView): def get(self, request, pk): # 經過課程id找到課程全部的評論 queryset = models.Course.objects.filter(id=pk).first().course_comments.all() # 序列化 ser_obj = CourseCommentSerializer(queryset, many=True) # 返回 return Response(ser_obj.data) class QuestionView(APIView): def get(self, request, pk): queryset = models.Course.objects.filter(id=pk).first().often_ask_questions.all() ser_obj = QuestionSerializer(queryset, many=True) return Response(ser_obj.data)
from django.contrib import admin # Register your models here. from . import models for table in models.__all__: admin.site.register(getattr(models, table))
import redis POOL = redis.ConnectionPool(host="127.0.0.1", port=6379, decode_responses=True, max_connections=10)
class BaseResponse(object): def __init__(self): self.code = 1000 self.data = None self.error = None @property def dict(self): return self.__dict__
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from .redis_pool import POOL from Course.models import Account import redis CONN = redis.Redis(connection_pool=POOL) class LoginAuth(BaseAuthentication): def authenticate(self, request): # 從請求頭中獲取前端帶過來的token token = request.META.get("HTTP_AUTHENTICATION", "") if not token: raise AuthenticationFailed("沒有攜帶token") # 去redis比對 user_id = CONN.get(str(token)) if user_id == None: raise AuthenticationFailed("token過時") user_obj = Account.objects.filter(id=user_id).first() return user_obj, token
urlpatterns = [ path('register', RegisterView.as_view()), path('login', LoginView.as_view()), path('test_auth', TestView.as_view()), ]
from rest_framework import serializers from Course.models import Account import hashlib class RegisterSerializer(serializers.ModelSerializer): class Meta: model = Account fields = "__all__" def create(self, validated_data): pwd = validated_data["pwd"] pwd_salt = "luffy_password" + pwd md5_str = hashlib.md5(pwd_salt.encode()).hexdigest() user_obj = Account.objects.create(username=validated_data["username"], pwd=md5_str) return user_obj
from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response from .serializers import RegisterSerializer from utils.base_response import BaseResponse from Course.models import Account from utils.redis_pool import POOL import redis import uuid from utils.my_auth import LoginAuth from utils.geetest import GeetestLib from django.http import HttpResponse import json # Create your views here. class RegisterView(APIView): def post(self, request): res = BaseResponse() # 用序列化器作校驗 ser_obj = RegisterSerializer(data=request.data) if ser_obj.is_valid(): ser_obj.save() res.data = ser_obj.data else: res.code = 1020 res.error = ser_obj.errors return Response(res.dict) class LoginView(APIView): def post(self, request): res = BaseResponse() username = request.data.get("username", "") pwd = request.data.get("pwd", "") user_obj = Account.objects.filter(username=username, pwd=pwd).first() if not user_obj: res.code = 1030 res.error = "用戶名或密碼錯誤" return Response(res.dict) # 用戶登陸成功生成一個token寫入redis # 寫入redis token : user_id conn = redis.Redis(connection_pool=POOL) try: token = uuid.uuid4() # conn.set(str(token), user_obj.id, ex=10) conn.set(str(token), user_obj.id) res.data = token except Exception as e: print(e) res.code = 1031 res.error = "建立令牌失敗" return Response(res.dict) class TestView(APIView): authentication_classes = [LoginAuth, ] def get(self, request): return Response("認證測試") pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c" pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4" REDIS_CONN = redis.Redis(connection_pool=POOL) class GeetestView(APIView): def get(self, request): user_id = 'test' gt = GeetestLib(pc_geetest_id, pc_geetest_key) status = gt.pre_process(user_id) # request.session[gt.GT_STATUS_SESSION_KEY] = status REDIS_CONN.set(gt.GT_STATUS_SESSION_KEY, status) # request.session["user_id"] = user_id REDIS_CONN.set("gt_user_id", user_id) response_str = gt.get_response_str() return HttpResponse(response_str) def post(self, request): # print(request.session.get("user_id")) print(request.META.get("HTTP_AUTHENTICATION")) print(request.data) gt = GeetestLib(pc_geetest_id, pc_geetest_key) challenge = request.data.get(gt.FN_CHALLENGE, '') validate = request.data.get(gt.FN_VALIDATE, '') seccode = request.data.get(gt.FN_SECCODE, '') # username # pwd # status = request.session.get(gt.GT_STATUS_SESSION_KEY) # print(status) # user_id = request.session.get("user_id") # print(user_id) status = REDIS_CONN.get(gt.GT_STATUS_SESSION_KEY) user_id = REDIS_CONN.get("gt_user_id") if status: result = gt.success_validate(challenge, validate, seccode, user_id) else: result = gt.failback_validate(challenge, validate, seccode) result = {"status": "success"} if result else {"status": "fail"} # if result: # # 證實驗證碼經過 # # 判斷用戶名和密碼 # else: # # 返回驗證碼錯誤 return HttpResponse(json.dumps(result))
# 拓展以前課程模塊下的用戶表 class Account(models.Model): username = models.CharField(max_length=32, verbose_name="用戶姓名", unique=True) password = models.CharField(max_length=32, verbose_name="用戶密碼") # head_img = models.CharField(max_length=256, default='/static/frontend/head_portrait/logo@2x.png', # verbose_name="我的頭像") token = models.UUIDField(null=True, blank=True) def __str__(self): return self.username class Meta: verbose_name = "11-用戶表" db_table = verbose_name verbose_name_plural = verbose_name
class BaseResponse(object): def __init__(self): self.code = 1000 self.data = None self.error = None @property def dict(self): return self.__dict__
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from Course.models import Account # django 提供的拿時間的接口 提供的是根據django配置的時區拿到的當前時間 from django.utils.timezone import now class LoginAuth(BaseAuthentication): def authenticate(self, request): # 從請求頭中獲取前端帶過來的token token = request.META.get("HTTP_AUTHENTICATION", "") if not token: raise AuthenticationFailed("沒有攜帶token") # 去redis比對 user_obj = Account.objects.filter(token=token).first() if not user_obj: raise AuthenticationFailed("token過時") else: old_time = user_obj.create_token_time if (now() - old_time).days > 7: raise AuthenticationFailed({"code": 1020, "error": "無效的token"}) return user_obj, token
urlpatterns = [ path('register', RegisterView.as_view()), path('login', LoginView.as_view()), path('test_auth', TestView.as_view()), ]
from rest_framework import serializers from Course.models import Account import hashlib class RegisterSerializer(serializers.ModelSerializer): class Meta: model = Account fields = "__all__" def create(self, validated_data): pwd = validated_data["pwd"] pwd_salt = "luffy_password" + pwd md5_str = hashlib.md5(pwd_salt.encode()).hexdigest() user_obj = Account.objects.create(username=validated_data["username"], pwd=md5_str) return user_obj
from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response from .serializers import RegisterSerializer from utils.base_response import BaseResponse from Course.models import Account import uuid # Create your views here. class RegisterView(APIView): def post(self, request): res = BaseResponse() # 用序列化器作校驗 ser_obj = RegisterSerializer(data=request.data) if ser_obj.is_valid(): ser_obj.save() res.data = ser_obj.data else: res.code = 1020 res.error = ser_obj.errors return Response(res.dict) class LoginView(APIView): def post(self, request): res = BaseResponse() username = request.data.get("username", "") pwd = request.data.get("pwd", "") user_obj = Account.objects.filter(username=username, pwd=pwd).first() if not user_obj: res.code = 1030 res.error = "用戶名或密碼錯誤" return Response(res.dict) try: token = uuid.uuid4() ### user_obj.update(token=token) res.data = token except Exception as e: print(e) res.code = 1031 res.error = "建立令牌失敗" return Response(res.dict) # 全部這是一個須要認證的接口 class TestView(APIView): authentication_classes = [LoginAuth, ] def get(self, request): return Response("認證測試")
from django.urls import path from .views import ShoppingCarView from .settlement_view import SettlementView from .payment_view import PaymentView urlpatterns = [ path('shopping_car', ShoppingCarView.as_view()), path('settlement', SettlementView.as_view()), path('payment', PaymentView.as_view()), ]
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from .redis_pool import POOL from Course.models import Account import redis CONN = redis.Redis(connection_pool=POOL) class LoginAuth(BaseAuthentication): def authenticate(self, request): # 從請求頭中獲取前端帶過來的token token = request.META.get("HTTP_AUTHENTICATION", "") if not token: raise AuthenticationFailed("沒有攜帶token") # 去redis比對 user_id = CONN.get(str(token)) if user_id == None: raise AuthenticationFailed("token過時") user_obj = Account.objects.filter(id=user_id).first() return user_obj, token
from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response from utils.base_response import BaseResponse from utils.my_auth import LoginAuth from utils.redis_pool import POOL from Course.models import Course import json import redis # Create your views here. # 前端傳過來 course_id price_policy_id # 把購物車數據放入redis """ { SHOPPINGCAR_USERID_COURSE_ID: { "id", 課程id "title", 課程標題 "course_img", "price_policy_dict": { price_policy_id: "{valid_period, price, valid_period_display}" price_policy_id2: "{valid_period, price, valid_period_display}" price_policy_id3: "{valid_period, price, valid_period_display}" }, "default_price_policy_id": 1 默認選中的價格id } } """ SHOPPINGCAR_KEY = "SHOPPINGCAR_%s_%s" CONN = redis.Redis(connection_pool=POOL) class ShoppingCarView(APIView): authentication_classes = [LoginAuth, ] # 給購物車增長商品 def post(self, request): res = BaseResponse() try: # 1, 獲取前端傳過來的數據以及user_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 = 1040 res.error = "課程id不合法" return Response(res.dict) # 2.2 校驗價格策略id是否合法 price_policy_queryset = course_obj.price_policy.all() price_policy_dict = {} for price_policy in price_policy_queryset: price_policy_dict[price_policy.id] = { "price": price_policy.price, "valid_period": price_policy.valid_period, "valid_period_display": price_policy.get_valid_period_display() } if price_policy_id not in price_policy_dict: res.code = 1041 res.error = "價格策略id不合法" return Response(res.dict) # 3,構建redisKEY key = SHOPPINGCAR_KEY % (user_id, course_id) # 4,構建數據結構 course_info = { "id": course_obj.id, "title": course_obj.title, "course_img": str(course_obj.course_img), "price_policy_dict": json.dumps(price_policy_dict, ensure_ascii=False), "default_price_policy_id": price_policy_id } # 5 寫入redis CONN.hmset(key, course_info) res.data = "加入購物車成功" except Exception as e: res.code = 1012 res.error = "加入購物車失敗" return Response(res.dict) def get(self, request): res = BaseResponse() try: # 1, 拼接redis key user_id = request.user.pk shopping_car_key = SHOPPINGCAR_KEY % (user_id, "*") # 2, 去redis中讀取數據 # 2.1 模糊匹配全部的keys # 3,構建數據結構展現 all_keys = CONN.scan_iter(shopping_car_key) ret = [] for key in all_keys: ret.append(CONN.hgetall(key)) res.data = ret except Exception as e: res.code = 1013 res.error = "獲取購物車失敗" return Response(res.dict) def put(self, request): # 前端 course_id price_policy_id res = BaseResponse() try: # 1, 獲取前端傳過來的數據以及user_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 course_id是否合法 key = SHOPPINGCAR_KEY % (user_id, course_id) if not CONN.exists(key): res.code = 1043 res.error = "課程id不合法" return Response(res.dict) # 2,2 price_policy_id是否合法 price_policy_dict = json.loads(CONN.hget(key, "price_policy_dict")) if str(price_policy_id) not in price_policy_dict: res.code = 1044 res.error = "價格策略不合法" return Response(res.dict) # 3, 更新redis default_price_policy_id CONN.hset(key, "default_price_policy_id", price_policy_id) res.data = "更新成功" except Exception as e: res.code = 1014 res.error = "更新購物車失敗" return Response(res.dict) def delete(self, request): # course_list = [course_id, ] res = BaseResponse() try: # 1 獲取前端傳來的數據以及user_id course_list = request.data.get("course_list", "") user_id = request.user.pk # 2 校驗course_id是否合法 for course_id in course_list: key = SHOPPINGCAR_KEY % (user_id, course_id) if not CONN.exists(key): res.code = 1045 res.error = "課程ID不合法" return Response(res.dict) # 3, 刪除redis數據 CONN.delete(key) res.data = "刪除成功" except Exception as e: res.code = 1014 res.error = "刪除購物車失敗" return Response(res.dict)
結算中心要開始選擇優惠券了,有單獨的課程優惠券還有全局優惠券。ajax
from django.db import models # Create your models here. from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey from Course.models import Account # Create your models here. __all__ = ["Coupon", "CouponRecord", "Order", "OrderDetail", "TransactionRecord"] 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="等值貨幣", null=True, blank=True, default=0) off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只針對折扣券,例7.9折,寫79", blank=True, null=True, default=100) minimum_consume = models.PositiveIntegerField("最低消費", default=0, help_text="僅在滿減券時填寫此字段", null=True, blank=True) content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=None) object_id = models.PositiveIntegerField("綁定課程", blank=True, null=True, help_text="能夠把優惠券跟課程綁定") # 不綁定表明全局優惠券 content_object = GenericForeignKey('content_type', 'object_id') 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 = "13. 優惠券生成規則記錄" db_table = verbose_name_plural verbose_name = verbose_name_plural 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", on_delete=None) number = models.CharField(max_length=64, unique=True, verbose_name="用戶優惠券記錄的流水號") account = models.ForeignKey(to=Account, verbose_name="擁有者", on_delete=None) 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="關聯訂單", on_delete=None) # 一個訂單能夠有多個優惠券 class Meta: verbose_name_plural = "14. 用戶優惠券領取使用記錄表" db_table = verbose_name_plural verbose_name = verbose_name_plural def __str__(self): return '%s-%s-%s' % (self.account, 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) # 考慮到訂單合併支付的問題 account = models.ForeignKey(to=Account, on_delete=None) 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 = "15. 訂單表" db_table = verbose_name_plural verbose_name = verbose_name_plural def __str__(self): return "%s" % self.order_number class OrderDetail(models.Model): """訂單詳情""" order = models.ForeignKey("Order", on_delete=None) content_type = models.ForeignKey(ContentType, on_delete=None) # 可關聯普通課程或學位 object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') original_price = models.FloatField("課程原價") price = models.FloatField("折後價格") valid_period_display = models.CharField("有效期顯示", max_length=32) # 在訂單頁顯示 valid_period = models.PositiveIntegerField("有效期(days)") # 課程有效期 memo = models.CharField(max_length=255, blank=True, null=True, verbose_name="備忘錄") def __str__(self): return "%s - %s - %s" % (self.order, self.content_type, self.price) class Meta: verbose_name_plural = "16. 訂單詳細" db_table = verbose_name_plural verbose_name = verbose_name_plural class TransactionRecord(models.Model): """貝里交易紀錄""" account = models.ForeignKey(to=Account, on_delete=None) amount = models.IntegerField("金額") balance = models.IntegerField("帳戶餘額") transaction_type_choices = ((0, '收入'), (1, '支出'), (2, '退款'), (3, "提現")) # 2 爲了處理 訂單過時未支付時,鎖按期貝里的回退 transaction_type = models.SmallIntegerField(choices=transaction_type_choices) transaction_number = models.CharField(unique=True, verbose_name="流水號", max_length=128) date = models.DateTimeField(auto_now_add=True) memo = models.CharField(max_length=128, blank=True, null=True, verbose_name="備忘錄") class Meta: verbose_name_plural = "17. 貝里交易記錄" db_table = verbose_name_plural verbose_name = verbose_name_plural def __str__(self): return "%s" % self.transaction_number
from rest_framework.views import APIView from rest_framework.response import Response from utils.base_response import BaseResponse from utils.redis_pool import POOL from django.utils.timezone import now from utils.my_auth import LoginAuth import redis from .views import SHOPPINGCAR_KEY from .models import CouponRecord import json CONN = redis.Redis(connection_pool=POOL) SETTLEMENT_KEY = "SETTLEMENT_%s_%s" GLOBAL_COUPON_KEY = "GLOBAL_COUPON_%s" """ 結算中心 在購物車裏選擇了商品以及價格策略點擊結算 才進入結算中心 在結算中心用戶能夠選擇優惠券 前端傳過來數據 course_list redis = { settlement_userid_courseid: { id, 課程id, title, course_img, valid_period_display, price, course_coupon_dict: { coupon_id: {優惠券信息} coupon_id2: {優惠券信息} coupon_id3: {優惠券信息} } # 默認不給你選 這個字段只有更新的時候才添加 default_coupon_id: 1 } global_coupon_userid: { coupon_id: {優惠券信息} coupon_id2: {優惠券信息} coupon_id3: {優惠券信息}, # 這個字段只有更新的時候才添加 # 在用戶進入結算中心選擇優惠券的時候 也就是更新請求的時候更改 default_global_coupon_id: 1 } } """ class SettlementView(APIView): authentication_classes = [LoginAuth, ] def post(self, request): res = BaseResponse() try: # 1 獲取前端的數據以及user_id course_list = request.data.get("course_list", "") user_id = request.user.pk # 2 校驗數據的合法性 for course_id in course_list: # 2.1 判斷course_id 是否在購物車中 shopping_car_key = SHOPPINGCAR_KEY % (user_id, course_id) if not CONN.exists(shopping_car_key): res.code = 1050 res.error = "課程ID不合法" return Response(res.dict) # 3 構建數據結構 # 3.1 獲取用戶的全部合法優惠券 user_all_coupons = CouponRecord.objects.filter( account_id=user_id, status=0, coupon__valid_begin_date__lte=now(), coupon__valid_end_date__gte=now(), ).all() print(user_all_coupons) # 3.2 構建優惠券dict course_coupon_dict = {} global_coupon_dict = {} for coupon_record in user_all_coupons: coupon = coupon_record.coupon if coupon.object_id == course_id: course_coupon_dict[coupon.id] = { "id": coupon.id, "name": coupon.name, "coupon_type": coupon.get_coupon_type_display(), "object_id": coupon.object_id, "money_equivalent_value": coupon.money_equivalent_value, "off_percent": coupon.off_percent, "minimum_consume": coupon.minimum_consume } elif coupon.object_id == "": global_coupon_dict[coupon.id] = { "id": coupon.id, "name": coupon.name, "coupon_type": coupon.get_coupon_type_display(), "money_equivalent_value": coupon.money_equivalent_value, "off_percent": coupon.off_percent, "minimum_consume": coupon.minimum_consume } # 3.3 構建寫入redis的數據結構 course_info = CONN.hgetall(shopping_car_key) price_policy_dict = json.loads(course_info["price_policy_dict"]) default_policy_id = course_info["default_price_policy_id"] valid_period = price_policy_dict[default_policy_id]["valid_period_display"] price = price_policy_dict[default_policy_id]["price"] settlement_info = { "id": course_info["id"], "title": course_info["title"], "course_img": course_info["course_img"], "valid_period": valid_period, "price": price, "course_coupon_dict": json.dumps(course_coupon_dict, ensure_ascii=False) } # 4 寫入redis settlement_key = SETTLEMENT_KEY % (user_id, course_id) global_coupon_key = GLOBAL_COUPON_KEY % user_id CONN.hmset(settlement_key, settlement_info) if global_coupon_dict: CONN.hmset(global_coupon_key, global_coupon_dict) # 5 刪除購物車中的數據 CONN.delete(shopping_car_key) res.data = "加入結算中心成功" except Exception as e: res.code = 1020 res.error = "結算失敗" return Response(res.dict) def get(self, request): res = BaseResponse() try: # 1, 獲取user_id user_id = request.user.pk # 2, 拼接全部key # 3, 去redis取數據 settlement_key = SETTLEMENT_KEY % (user_id, "*") global_coupon_key = GLOBAL_COUPON_KEY % user_id all_keys = CONN.scan_iter(settlement_key) ret = [] for key in all_keys: ret.append(CONN.hgetall(key)) global_coupon_info = CONN.hgetall(global_coupon_key) res.data = { "settlement_info": ret, "global_coupon_dict": global_coupon_info } except Exception as e: res.code = 1024 res.error = "獲取結算中心失敗" return Response(res.dict) def put(self, request): # course_id course_coupon_id global_coupon_id res = BaseResponse() try: # 1, 獲取前端傳過來數據 course_id = request.data.get("course_id", "") course_coupon_id = request.data.get("course_coupon_id", "") global_coupon_id = request.data.get("global_coupon_id", "") user_id = request.user.pk # 2, 校驗數據合法性 # 2.1 校驗course_id key = SETTLEMENT_KEY % (user_id, course_id) if course_id: if not CONN.exists(key): res.code = 1060 res.error = "課程ID不合法" return Response(res.dict) # 2.2 校驗 course_coupon_id if course_coupon_id: course_coupon_dict = json.loads(CONN.hget(key, "course_coupon_dict")) if str(course_coupon_id) not in course_coupon_dict: res.code = 1061 res.error = "課程優惠券ID不合法" return Response(res.dict) # 2.3 校驗global_coupon_id if global_coupon_id: global_coupon_key = GLOBAL_COUPON_KEY % user_id if not CONN.exists(global_coupon_key): res.code = 1062 res.error = "全局優惠券ID不合法" return Response(res.dict) CONN.hset(global_coupon_key, "default_global_coupon_id", global_coupon_id) # 3,修改redis中數據 CONN.hset(key, "default_coupon_id", course_coupon_id) res.data = "更新優惠券成功" except Exception as e: res.code = 1026 res.error = "更改優惠券失敗" return Response(res.dict)
from rest_framework.views import APIView from rest_framework.response import Response from utils.my_auth import LoginAuth from utils.base_response import BaseResponse from .settlement_view import SETTLEMENT_KEY, GLOBAL_COUPON_KEY from utils.redis_pool import POOL import redis from Course.models import Course from .models import Coupon from django.utils.timezone import now COON = redis.Redis(connection_pool=POOL) # price balance class PaymentView(APIView): authentication_classes = [LoginAuth, ] def post(self, request): res = BaseResponse() # 1 獲取數據 balance = request.data.get("balance", 0) price = request.data.get("price", "") user_id = request.user.pk # 2 校驗數據的合法性 # 2.1 校驗貝里數是否合法 if int(balance) > request.user.balance: res.code = 1070 res.error = "抵扣的貝里錯誤" return Response(res.dict) # 2.2 從用戶的結算中心拿數據 跟數據庫比對是否合法 settlement_key = SETTLEMENT_KEY % (user_id, "*") all_keys = COON.scan_iter(settlement_key) # 課程id是否合法 course_rebate_total_price = 0 for key in all_keys: settlement_info = COON.hgetall(key) course_id = settlement_info["id"] course_obj = Course.objects.filter(id=course_id).first() if not course_obj or course_obj.status == 1: res.code = 1071 res.error = "課程id不合法" return Response(res.dict) # 課程優惠券是否過時 course_coupon_id = settlement_info.get("default_coupon_id", 0) if course_coupon_id: coupon_dict = Coupon.objects.filter( id=course_coupon_id, couponrecord__status=0, couponrecord__account_id=user_id, object_id=course_id, valid_begin_date__lte=now(), valid_end_date__gte=now(), ).values("coupon_type", "money_equivalent_value", "off_percent", "minimum_consume") if not coupon_dict: res.code = 1072 res.error = "優惠券不合法" return Response(res.dict) # 2.3 校驗price # 獲得全部的課程的折後價格和 course_pirce = settlement_info["price"] course_rebate_price = self.account_price(coupon_dict, course_pirce) if course_rebate_price == -1: res.code = 1074 res.error = "課程優惠券不符合要求" return Response(res.dict) course_rebate_total_price += course_rebate_price # 跟全局優惠券作折扣 # 校驗全局優惠券是否合法 global_coupon_key = GLOBAL_COUPON_KEY % user_id global_coupon_id = int(COON.hget(global_coupon_key, "default_global_coupon_id")) if global_coupon_id: global_coupon_dict = Coupon.objects.filter( id=global_coupon_id, couponrecord__status=0, couponrecord__account_id=user_id, valid_begin_date__lte=now(), valid_end_date__gte=now(), ).values("coupon_type", "money_equivalent_value", "off_percent", "minimum_consume") if not global_coupon_dict: res.code = 1073 res.error = "全局優惠券id不合法" return Response(res.dict) global_rebate_price = self.account_price(global_coupon_dict, course_rebate_total_price) if global_rebate_price == -1: res.code = 1076 res.error = "全局優惠券不符合要求" return Response(res.dict) # 抵扣貝里 balance_money = balance / 100 balance_rebate_price = global_rebate_price - balance if balance_rebate_price < 0: balance_rebate_price = 0 # 終極校驗price if balance_rebate_price != price: res.code = 1078 res.error = "價格不合法" return Response(res.dict) # 先去建立訂單 訂單狀態未支付狀態 # 3 調用支付寶接口支付 # 若是成功支付支付寶會給咱們發回調 # 改變訂單的狀態 # 注意訂單詳情表有多個記錄 # 更改優惠券的使用狀態 # 更改用戶表裏的貝里 貝里要添加交易記錄 def account_price(self, coupon_dict, price): coupon_type = coupon_dict["coupon_type"] if coupon_type == 0: # 通用優惠券 money_equivalent_value = coupon_dict["money_equivalent_value"] if price - money_equivalent_value >=0: rebate_price = price - money_equivalent_value else: rebate_price = 0 elif coupon_type == 1: # 滿減 money_equivalent_value = coupon_dict["money_equivalent_value"] minimum_consume = coupon_dict["minimum_consume"] if price >= minimum_consume: rebate_price = price - money_equivalent_value else: return -1 elif coupon_type == 2: # 折扣 minimum_consume = coupon_dict["minimum_consume"] off_percent = coupon_dict["off_percent"] if price >= minimum_consume: rebate_price = price * (off_percent / 100) else: return -1 return rebate_price