免費課程相關表設計
models的設計
from django.contrib.contenttypes.fields import GenericRelation class Course(models.Model): name = models.CharField(verbose_name="課程名", max_length=32) title = models.CharField(verbose_name="課程簡介", max_length=128, null=True, blank=True) # 存放media文件夾下圖片的相對路徑:eg: media/python入門.jpeg image = models.CharField(verbose_name="海報", max_length=64, null=True, blank=True) # 用戶連表查詢,不會在數據庫中產生字段 price_policy = GenericRelation(to='PricePolicy') def __str__(self): return self.name class Meta: verbose_name_plural = "免費課程" class CourseDetail(models.Model): level = models.IntegerField(default=1, choices=((0, '初級'), (1, "中級"), (2, "高級"))) movie_url = models.CharField(max_length=64) info = models.TextField() course = models.OneToOneField(to='Course', null=True, on_delete=models.SET_NULL, db_constraint=False) teacher = models.OneToOneField(to='Teacher', null=True, on_delete=models.SET_NULL, db_constraint=False) def __str__(self): return self.course.name + '的詳情' class Meta: verbose_name_plural = "免費課程詳情" class Teacher(models.Model): name = models.CharField(max_length=32) info = models.TextField() image = models.CharField(max_length=64) level = models.IntegerField(default=0, choices=((0, '初級講師'), (1, "金牌講師"), (2, "特約講師"))) def __str__(self): return self.name class Meta: verbose_name_plural = "講師" class DegreeCourse(models.Model): name = models.CharField(max_length=32) price_policy = GenericRelation(to='PricePolicy') def __str__(self): return self.name class Meta: verbose_name_plural = "學位課程" from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey class PricePolicy(models.Model): day = models.IntegerField() price = models.CharField(max_length=8) object_id = models.IntegerField() content_type = models.ForeignKey(to=ContentType, null=True) # 改操作只用於ContentType連表查詢,不會產生表字段, 就是關聯課程表的對象 model_obj = GenericForeignKey() def __str__(self): return "%s:(%s天 ¥:%s)" % (self.model_obj.name, self.day, self.price) class Meta: verbose_name_plural = "價格策略"
前台路由與頁面
APP.vue組件
<div id="nav"> <router-link to="/">主頁</router-link> | <router-link to="/login">登錄</router-link> | <router-link to="/course">免費課程</router-link> | <!-- 在頁面中轉跳路由傳遞參數, 傳遞了params參數,this.$router.params可以拿到這個參數字典 --> <!--<router-link :to="{'name': 'degree-course', 'params': {'id': 1, 'name': 'owen'}}">學位課程</router-link>--> <router-link :to="{'name': 'degree-course'}">學位課程</router-link> </div>
router.js路由配置
# 在views文件夾中建立對應的組件 { path: '/login', name: 'login', component: () => import('./views/Login.vue') }, { path: '/course', name: 'course', component: () => import('./views/Course.vue') }, { path: '/degree-course', name: 'degree-course', component: () => import('./views/DegreeCourse.vue') }, { path: '/course-detail', name: 'course-detail', component: () => import('./views/CourseDetail.vue') }
項目開發視圖與響應的二次封裝
# api.utils.py class ApiResponse: def __init__(self, status=0, message='ok'): self.status = status self.message = message # 要返回給前台的api字典 @property def api_dic(self): return self.__dict__
# 在api應用文件夾下創建包views # 1.分文件管理不同的視圖類,視圖類繼承ModelViewSet管理一系列視圖函數 # 2.在__ini__文件中統一管理到 視圖類 # __init__.py from .Course import CourseView # views文件夾/Course文件/CourseView from rest_framework.viewsets import ModelViewSet from rest_framework.response import Response from api.utils import ApiResponse from api import models, objectjson class CourseView(ModelViewSet): def get(self, request, *args, **kwargs): api_response = ApiResponse() course_list = models.Course.objects.all() course_data = objectjson.CourseJson(course_list, many=True).data api_response.results = course_data return Response(api_response.api_dic)
免費課程首頁展示
main.js配置后台請求根路徑
// 后台根接口 Vue.prototype.$base_api = 'http://127.0.0.1:8000/';
views/Course.vue
<template> <div class="course"> <h2>免費課程</h2> <!--course_list是從后台獲取,有多少條數據,就會渲染多少個子組件CourseView--> <div v-for="(course,i) in course_list" :key="course.name+i"> <!--通過綁定屬性的方式,將course傳給屬性course,在組件內部提高props=['course']拿到父組件的數據--> <CourseView :course="course"></CourseView> </div> </div> </template> <script> import CourseView from "../components/CourseView"; export default { name: "Course", data: function () { return { course_list: [] } }, components: { CourseView: CourseView, }, mounted: function () { let _this = this; this.$ajax({ method: 'get', url: this.$base_api + 'courses/', }).then(function (response) { _this.course_list = response.data.results }) } } </script> <style scoped> </style>
components/CourseView.vue
<template> <div class="course-view"> <div class="box"> <h3 @click="goDetail(course.id)">{{course.name }}</h3> <p>{{ course.title }}</p> </div> <img @click="goDetail(course.id)" :src="$base_api + course.image" alt=""> </div> </template> <script> export default { name: "CourseView", props: ['course'], methods: { goDetail: function (pk) { //前往詳情頁,在方法中,router如何完成路由的跳轉 this.$router.push('/course-detail'); // 業務邏輯下,路由轉跳攜帶參數 this.$router.course_id = pk; window.console.log(pk) } } } </script> <style scoped> .course-view { padding: 0 200px; margin: 25px 0; } .course-view .box { float: left; width: 380px; } .course-view img { width: 400px; float: left; } .course-view:after { display: block; clear: both; content: ''; } </style>
views/Course.vue/CourseDetail.vue
<template> <div class="course-detail"> <h2>免費課程詳情</h2> <p>{{ detail }}</p> </div> </template> <script> export default { name: "CourseDetail", data: function () { return { detail: {}, } }, mounted: function () { let _this = this; this.$ajax({ method: 'get', url: this.$base_api + 'course/' + this.$router.course_id + '/', }).then(function (response) { window.console.log(response); _this.detail = response.data.results; }) } } </script> <style scoped> </style>
前后台登錄認證
時區國際化settings.py
TIME_ZONE = 'Asia/Shanghai' USE_TZ = False
models.py
class User(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=32) class UserToken(models.Model): token = models.CharField(max_length=64) user = models.OneToOneField(to='User', null=True, on_delete=models.SET_NULL, db_constraint=False) update_time = models.DateTimeField(auto_now=True) failed_time = models.FloatField(default=10)
objectson.py
class UserJson(serializers.ModelSerializer): class Meta: model = models.User fields = '__all__' class UserTokenJson(serializers.ModelSerializer): class Meta: model = models.UserToken fields = '__all__'
auth.py
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from api import models class LoginAuthenticate(BaseAuthentication): def authenticate(self, request): token = request.META.get('HTTP_TOKEN') result = models.UserToken.objects.filter(token=token).first() # type:models.UserToken if result: # 最近一次登錄的時間 before_time = result.update_time # 過期時長秒數 failed_time = result.failed_time # 當你時間往前推演到過期的時長前的最后時刻 import datetime time_difference = datetime.datetime.now() - datetime.timedelta(seconds=failed_time) import time # 存在時區問題,通過國際化解決 before_time_stamp = time.mktime(before_time.timetuple()) # 拿到時間戳 time_difference_stamp = time.mktime(time_difference.timetuple()) if before_time_stamp >= time_difference_stamp: return result.user, token raise AuthenticationFailed('登錄過期') else: raise AuthenticationFailed('認證失敗')
前台安裝操作cookie的模塊

<template> <div class="login"> <h2>登錄頁面</h2> <div class="form"> <el-form :model="form" label-width="100px"> <el-form-item label="用戶名"> <el-input v-model="form.username"></el-input> </el-form-item> <el-form-item label="密碼"> <el-input type="password" v-model="form.password" autocomplete="off"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="onSubmit">登錄</el-button> </el-form-item> </el-form> </div> </div> </template> <script> export default { name: "Login", data: function () { return { form: { username: '', password: '', } } }, methods: { onSubmit() { // window.console.log('aaaa'); let _this = this; this.$ajax({ method: 'post', url: this.$base_api + 'login/', data: { username: this.form.username, password: this.form.password } }).then(function (response) { window.console.log(response); let token = response.data.token; _this.$cookie.set('token', token); _this.$router.push('/') }) } } } </script> <style scoped> </style>
{ path: '/login', name: 'login', component: () => import('./views/Login.vue') },