免費課程相關表設計
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>
View Code
router.js
{
path: '/login',
name: 'login',
component: () => import('./views/Login.vue')
},