# s25
**Repository Path**: energylu/s25
## Basic Information
- **Project Name**: s25
- **Description**: 老男孩django项目实战:轻量级bug管理平台
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-04-18
- **Last Updated**: 2022-09-07
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
autoauto- [项目概述](#项目概述)auto - [1. 同步运行环境](#1-同步运行环境)auto - [1.1 建立虚拟环境,同步python版本](#11-建立虚拟环境同步python版本)auto - [1.2 同步项目依赖包](#12-同步项目依赖包)auto - [2. 独立配置文件](#2-独立配置文件)auto - [3. 腾讯云平台](#3-腾讯云平台)auto - [4. redis](#4-redis)auto - [5. 项目开发分期](#5-项目开发分期)auto- [项目开发](#项目开发)auto - [1. 前戏](#1-前戏)auto - [1.1 项目虚拟环境](#11-项目虚拟环境)auto - [1.2 项目框架](#12-项目框架)auto - [1.3 git实战应用](#13-git实战应用)auto - [1.3.1 创建远程代码仓库](#131-创建远程代码仓库)auto - [1.3.2 本都代码推送到远程仓库](#132-本都代码推送到远程仓库)auto - [1.4 通过python结合腾讯sms发送短信](#14-通过python结合腾讯sms发送短信)auto - [1.5 Django的ModleForm组件](#15-django的modleform组件)auto - [1.5.1 ModleForm应用场景](#151-modleform应用场景)auto - [1.5.2 实现过程](#152-实现过程)auto - [1.5.3 页面美化](#153-页面美化)auto - [1.5.4 注册实现](#154-注册实现)auto - [1.6 redis](#16-redis)auto - [1.6.1 安装redis软件](#161-安装redis软件)auto - [1.6.2 安装redis模块](#162-安装redis模块)auto - [1.6.3 Django-redis模块](#163-django-redis模块)auto - [2. 实现注册](#2-实现注册)auto - [2.1 展示注册页面](#21-展示注册页面)auto - [2.1.1 创建web应用](#211-创建web应用)auto - [2.1.2 创建模板文件目录](#212-创建模板文件目录)auto - [2.1.3 母版准备](#213-母版准备)auto - [2.1.4 url准备](#214-url准备)auto - [2.1.5 form表单](#215-form表单)auto - [2.2 点击获取验证码](#22-点击获取验证码)auto - [2.2.1 绑定点击事件](#221-绑定点击事件)auto - [2.2.2 获取手机号](#222-获取手机号)auto - [2.2.3 发送ajax请求](#223-发送ajax请求)auto - [2.2.4 手机号格式校验](#224-手机号格式校验)auto - [2.2.5 验证通过](#225-验证通过)auto - [2.2.6 发送成功或失败](#226-发送成功或失败)auto - [2.3 点击注册](#23-点击注册)auto - [2.3.1 注册按钮绑定事件](#231-注册按钮绑定事件)auto - [2.3.2 数据校验](#232-数据校验)auto - [2.3.3 写入数据库](#233-写入数据库)auto - [3. 短信登录](#3-短信登录)auto - [3.1 展示页面](#31-展示页面)auto - [3.2 点击发送短信](#32-点击发送短信)auto - [3.3 点击登录](#33-点击登录)auto - [3.4 补充](#34-补充)auto - [4. 用户名密码登陆](#4-用户名密码登陆)auto - [4.1 python生成验证码图片](#41-python生成验证码图片)auto - [4.2 Session & Cookie](#42-session--cookie)auto - [4.3 页面显示](#43-页面显示)auto - [4.4 登陆](#44-登陆)auto - [4.5 登陆成功中间件](#45-登陆成功中间件)auto- [二期](#二期)auto - [1. 管理中心概述](#1-管理中心概述)auto - [1.1 django离线脚本](#11-django离线脚本)auto - [1.2 探讨业务](#12-探讨业务)auto - [1.2.1 价格策略,建表](#121-价格策略建表)auto - [1.2.2 用户](#122-用户)auto - [1.2.3 交易表](#123-交易表)auto - [1.2.4 创建存储](#124-创建存储)auto - [1.2.5 项目](#125-项目)auto - [1.2.6 项目参与者](#126-项目参与者)auto - [1.3 设计表结构](#13-设计表结构)auto - [1.4 添加项目](#14-添加项目)auto - [1.4.1 项目列表母版+样式](#141-项目列表母版样式)auto - [1.4.2 添加项目](#142-添加项目)auto - [1.4.3 查看项目列表](#143-查看项目列表)auto - [1.4.4 星标](#144-星标)auto - [1.5 功能实现](#15-功能实现)auto - [1.6 wiki,项目文档库](#16-wiki项目文档库)auto - [1.6.1 表结构设计](#161-表结构设计)auto - [1.6.2 快速开发](#162-快速开发)auto - [1.6.3 应用markdown组件](#163-应用markdown组件)auto - [1.6.4 腾讯COS做上传](#164-腾讯cos做上传)auto - [2. 文件管理](#2-文件管理)auto - [2.1 知识点](#21-知识点)auto - [2.2 设计](#22-设计)auto - [2.3 表结构设计](#23-表结构设计)auto - [2.4 其他知识点](#24-其他知识点)auto - [3.5 文件上传](#35-文件上传)auto - [3.6 创建并设置CORS](#36-创建并设置cors)auto - [3. 其他](#3-其他)autoauto
# 项目概述
## 1. 同步运行环境
### 1.1 建立虚拟环境,同步python版本
```
conda create -n [环境名称] python==[版本号]
```
### 1.2 同步项目依赖包
+ 导出依赖包
```
pip freeze > requirement.txt
```
+ 安装依赖包
```
pip install -r requirement.txt
```
## 2. 独立配置文件
+ 在settings.py同级目录下建立local_settings.py文件,并将本地配置信息写入local_settings.py文件
+ 在settings.py文件末尾添加如下代码:
```python
try:
from .local_settings import *
except ImportError:
pass
```
> 即表示用local_settings.py文件中配置信息覆盖settings.py,若为空则pass
## 3. 腾讯云平台
+ sms短信,申请服务
+ cos对象存储,腾讯给你云硬盘,用于项目文件上传/下载/查看
## 4. redis
+ 远程字典服务
+ 数据存在内存里,要考虑内存大小
+ 键值对型数据
+ 通过 redis==3.4.1 模块操作
+ 内存操作比硬盘(MySQL)快
+ 可以设置数据超时时间,超时自动删除
## 5. 项目开发分期
+ 一期:
用户认证 (短信验证,图片验证码,django ModelForm组件)
+ 二期:
wiki,文件,问题管理
+ 三期:
支付,部署
# 项目开发
## 1. 前戏
### 1.1 项目虚拟环境
+ 使用 virtualenv 包管理虚拟环境
+ 安装 virtualenv 模块:
```
pip install virtualenv -ihttps://pypi.douban.com/simple
```
+ 创建虚拟环境:
```
virtualenv [环境名称]
```
> 会在当前目录创建[环境名称]文件夹,建议单独创建一个目录
+ 激活虚拟环境:
```
source s25/bin/activate
```
> 和conda activate可互相覆盖
+ 退出虚拟环境:
```
deactivate
```
+ 搭建django环境
+ 安装django包:
```
pip install django==1.11.28 -ihttps://pypi.douban.com/simple
```
+ 创建django项目:
```
django_admin startproject s25
```
+ 创建app:
```
cd s25
python manage.py startapp app01
```
> 注意: 一定要创建虚拟开发环境,避免环境冲突
### 1.2 项目框架
+ 重写local_settings,设置本地配置文件:
```python
DEBUG = True
ALLOWED_HOSTS = ['*',]
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01',
]
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False
SMS = 666
```
> 给别人代码时不要把local_settings.py文件给他
### 1.3 git实战应用
#### 1.3.1 创建远程代码仓库
+ 使用gitee创建仓库
#### 1.3.2 本都代码推送到远程仓库
+ 在项目根目录下创建文件 .gitignore 用于git上传时忽略部分文件
```
# pycharm
.idea/
.DS_Store
__pycache__/
*.py[cod]
*$py.class
# Django stuff:
local_settings.py
*.sqlite3
# database migrations
*/migrations/*.py
!*/migrations/__init__.py
```
+ 创建本地git:
```
git init
git add .
git commit -m "new project store"
```
+ git 本地项目推送到远程仓库
```
(s28) [root@VM-0-4-centos s25]# git push https://gitee.com/energylu/s25.git master
Counting objects: 17, done.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (17/17), 5.27 KiB | 0 bytes/s, done.
Total 17 (delta 0), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-5.0]
To https://gitee.com/energylu/s25.git
* [new branch] master -> master
```
+ 测试获取代码:
```
git clone [git地址]
```
+ 导出项目依赖包:
导出包:
```
pip freeze > requirement.txt
```
导入包
```
pip install -r requirement.txt
```
### 1.4 通过python结合腾讯sms发送短信
+ 项目中应用:
+ 注册
+ 登录
+ 腾讯云平台>开通云短信
https://pythonav.com/wiki/detail/10/81/
+ 安装SDK
```
pip install qcloudsms_py
```
+ 代码拿来项目中用
新建一个 utils/tencent 目录,在目录下新建 sms.py 文件,把代码粘进去
+ 把appid,key等信息改到settings或local_settings里
> from django.conf import settings
+ 编写路由和视图函数,实现发送短信
### 1.5 Django的ModleForm组件
#### 1.5.1 ModleForm应用场景
+ 自动生成标签
+ 实现表单认证
#### 1.5.2 实现过程
+ 配置路由,编写视图函数
+ 在app01目录下新建templates目录,编写register.html文件
+ 在视图函数中加入ModelForm配置,并调整视图视图函数
```python
from django import forms
from app01 import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
class RegisterModelForm(forms.ModelForm):
# 手机号正则验证
mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
# 将密码改为密文显示
pass_word = forms.CharField(label='密码',widget=forms.PasswordInput())
# 确认密码
confirm_password = forms.CharField(label='重复密码',widget=forms.PasswordInput())
# 增加验证码字段
code = forms.CharField(label='验证码',widget=forms.TextInput())
class Meta():
model = models.UserInfo
fields = "__all__"
def register(request):
form = RegisterModelForm()
return render(request, 'register.html', {'form': form})
```
> ModelForm可以修改model中定义的字段,也可以新增字段(确认密码,验证码)
#### 1.5.3 页面美化
+ 到 https://www.bootcdn.cn/twitter-bootstrap/ 页面去找个css样式
+ 使用3.4.1 https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap-theme.min.css
+ 在register.html里加入对应设置
+ 重写 RegisterModelForm 的 __init__ 方法:
```python
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
field.widget.attrs['placeholder'] = '请输入%s' % (field.label,)
```
+ html中,验证码按钮实现
```html
{% if field.name == 'code' %}
{% else %}
{{ field }}
{% endif %}
```
+ 修改meta中的fields属性,重排各字段显示顺序,把验证码和手机号放一起
```python
class Meta():
model = models.UserInfo
fields = ['username', 'email', 'pass_word', 'confirm_password', 'mobile_phone', 'code']
```
#### 1.5.4 注册实现
+ 点击获取验证码
+ 获取手机号
+ 向后台发送ajax
+ 手机
+ tpl=register
+ 向手机发送验证码
+ 验证码时效60s(redis)
### 1.6 redis
#### 1.6.1 安装redis软件
> https://pythonav.com/wiki/detail/10/82/
> https://www.cnblogs.com/happywish/p/10944253.html
> /usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf
#### 1.6.2 安装redis模块
+ 安装模块:
```
pip install redis
```
+ 代码连接:
```python
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis
# 直接连接redis
conn = redis.Redis(host='10.211.55.28', port=6379, password='foobared', encoding='utf-8')
# 设置键值:15131255089="9999" 且超时时间为10秒(值写入到redis时会自动转字符串)
conn.set('15131255089', 9999, ex=10)
# 根据键获取值:如果存在获取值(获取到的是字节类型);不存在则返回None
value = conn.get('15131255089')
print(value)
```
+ 连接池连接(推荐):
```python
import redis
# 创建redis连接池(默认连接池最大连接数 2**31=2147483648)
pool = redis.ConnectionPool(host='10.211.55.28', port=6379, password='foobared', encoding='utf-8', max_connections=1000)
# 去连接池中获取一个连接
conn = redis.Redis(connection_pool=pool)
# 设置键值:15131255089="9999" 且超时时间为10秒(值写入到redis时会自动转字符串)
conn.set('name', "武沛齐", ex=10)
# 根据键获取值:如果存在获取值(获取到的是字节类型);不存在则返回None
value = conn.get('name')
print(value)
```
#### 1.6.3 Django-redis模块
在django中方便使用redis
```
pip install django-redis==4.11.0 -ihttps://pypi.douban.com/simple
```
使用下列代码连接redis:
```python
# local_settings 里加入配置
# 上面是django项目settings中的其他配置....
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://10.211.55.28:6379", # 安装redis的主机的 IP 和 端口
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"CONNECTION_POOL_KWARGS": {
"max_connections": 1000,
"encoding": 'utf-8'
},
"PASSWORD": "foobared" # redis密码
}
}
}
# 在django的视图中操作redis
from django.shortcuts import HttpResponse
from django_redis import get_redis_connection
def index(request):
# 去连接池中获取一个连接
conn = get_redis_connection("default")
conn.set('nickname', "武沛齐", ex=10)
value = conn.get('nickname')
print(value)
return HttpResponse("OK")
```
## 2. 实现注册
### 2.1 展示注册页面
#### 2.1.1 创建web应用
+ 把views.py文件改为views目录
#### 2.1.2 创建模板文件目录
+ 模板文件路径处理
#### 2.1.3 母版准备
+ 创建静态文件
+ 本地静态文件
+ bootstrap -- 3.3.7 (css,js线上框架)
+ FontAwesome -- 4.7.0 (图标)
+ 模板->头部菜单,可以直接到bootstrap里找
> basic.html 头部要加上 {% load static %}, 否则静态文件无法加载
+ 导航条处理
+ 注册页面处理
#### 2.1.4 url准备
```python
# 根路由
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^app01/', include('app01.urls',namespace='app01')),
url(r'^/', include('web.urls')),
]
# app01.urls
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^send/sms/', views.send_sms),
url(r'^register/', views.register),
url(r'^login/', views.login),
]
# web.urls
from django.conf.urls import url
from web.views import account
urlpatterns = [
url(r'^register/', account.register, name='account'),
]
```
> namespace用于反向解析区分同名路由,django2.2以后在urls.py文件中加上app_name='app01'
#### 2.1.5 form表单
+ 在web目录下新建form目录,并写入代码:
```python
from django import forms
# from app01 import models
from web import models
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
class RegisterModelForm(forms.ModelForm):
mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
pass_word = forms.CharField(label='密码',widget=forms.PasswordInput())
confirm_password = forms.CharField(label='重复密码',widget=forms.PasswordInput())
code = forms.CharField(label='验证码',widget=forms.TextInput())
class Meta():
model = models.UserInfo
fields = ['username', 'email', 'pass_word', 'confirm_password', 'mobile_phone', 'code']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'
field.widget.attrs['placeholder'] = '请输入%s' % (field.label,)
```
+ 在views/account.py写入代码:
```python
from web.forms.account import RegisterModelForm
def register(request):
form = RegisterModelForm()
return render(request, 'register.html', {'form': form})
```
+ 把app01/models.py中UserInfo迁移过来,并修改forms/account.py中引用
### 2.2 点击获取验证码
#### 2.2.1 绑定点击事件
```js
// 页面框架加载完成之后自动执行函数
$(function () {
bindClickBtnSms();/*发送验证码*/
bindClickSubmit();/*注册*/
});
```
#### 2.2.2 获取手机号
```js
// 获取用户输入的手机号
// 找到输入框的ID,根据ID获取值,如何找到手机号的那个ID?
var mobilePhone = $('#id_mobile_phone').val();
```
#### 2.2.3 发送ajax请求
```js
// 发送ajax请求,把手机号发送过去
$.ajax({
url: "{% url 'send_sms' %}", // 等价于 /send/sms/
type: "GET",
data: {mobile_phone: mobilePhone, tpl: "register"},
success: function (res) {
console.log(res)
}
})
```
#### 2.2.4 手机号格式校验
+ 不能为空,格式正确
在forms/account.py中添加SendSmsForm类校验:
```python
class SendSmsForm(forms.Form):
mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
```
填充send_sms视图函数
```python
from web.forms.account import SendSmsForm
def send_sms(request):
"""发送短信"""
print(request.GET)
mobile_phone = request.GET.get('mobile_phone')
tpl = request.GET.get('tpl')
form = SendSmsForm(data=request.GET)
# 只是校验手机号不为空或格式校验
if form.is_valid():
pass
return HttpResponse('成功')
```
+ 未注册
在SendSmsForm类中添加自定义校验:
```python
class SendSmsForm(forms.Form):
mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
def clean_mobile_phone(self):
mobile_phone = self.cleaned_data['mobile_phone']
# 校验手机号在数据库中是否存在
exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()
if exists:
raise ValidationError('手机号已存在')
return mobile_phone
```
+ tpl校验(校验尽量都放到高等函数里)
```python
# views/account.py
def send_sms(request):
"""发送短信"""
print(request.GET)
mobile_phone = request.GET.get('mobile_phone')
tpl = request.GET.get('tpl')
form = SendSmsForm(request, data=request.GET) # 把request传给SendSmsForm
# 只是校验手机号不为空或格式校验
if form.is_valid():
pass
return HttpResponse('成功')
# forms/account.py
class SendSmsForm(forms.Form):
mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
def __init__(self,request,*args,**kwargs):
super().__init__(*args,**kwargs)
self.request = request
def clean_mobile_phone(self):
"""手机号校验的钩子"""
mobile_phone = self.cleaned_data['mobile_phone']
# 判断短信模板是否有问题
tpl = self.request.GET.get('tpl')
template_id = settings.TENCENT_SMS_TEMPLATE.get(tpl)
if not template_id:
raise ValidationError('模板不存在')
# 校验手机号在数据库中是否存在
exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()
if exists:
raise ValidationError('手机号已存在')
return mobile_phone
```
> 重写SendSmsForm的__init__方法,可以实现把request传到SendSmsForm类中使用高等函数校验
#### 2.2.5 验证通过
将校验方法写在钩子函数中,forms/account.py -> class SendSmsForm -> def clean_mobile_phone:
```python
# 导入模块
import random
from utils.tencent.sms import send_sms_single
from django_redis import get_redis_connection
# 钩子函数内容
# 发短信和写redis
code = random.randrange(1000, 9999)
# 发送短信
sms = send_sms_single(mobile_phone, template_id, [code, ])
if sms['result'] != 0:
raise ValidationError("短信发送失败,{}".format(sms['errmsg']))
# 验证码写入redis(django-redis)
conn = get_redis_connection()
conn.set(mobile_phone, code, ex=60)
```
完善视图函数views/account.py -> send_sms:
```python
def send_sms(request):
"""发送短信"""
print(request.GET)
mobile_phone = request.GET.get('mobile_phone')
tpl = request.GET.get('tpl')
form = SendSmsForm(request, data=request.GET) # 把request传给SendSmsForm
# 只是校验手机号不为空或格式校验
if form.is_valid():
# 发短信
# 写redis
return JsonResponse({'status': True})
return JsonResponse({'status': False,'error': form.errors})
```
+ 发送短息
+ 将短信保存到redis中(60s)
#### 2.2.6 发送成功或失败
+ 发送成功 -> 倒计时
+ 发送失败 ->
> dataType: "JSON";//将服务端返回的数据反序列化为字典
完善前端js,若发送成功,开始倒计时,发送失败,返回错误信息:
```js
if (res.status) {
sendSmsRemind();
} else {
// 错误信息
// console.log(res); // {status:False, error:{ mobile_phone: ["错误信息",],code: ["错误信息",] } }
$.each(res.error, function (key, value) {
$("#id_" + key).next().text(value[0]);
})
}
}
```
sendSmsRemind(),写倒计时函数:
```js
function sendSmsRemind() {
var $smsBtn = $('#btnSms');
$smsBtn.prop('disabled', true); // 禁用
var time = 60;
var remind = setInterval(function () {
$smsBtn.val(time + '秒重新发送');
time = time - 1;
if (time < 1) {
clearInterval(remind);
$smsBtn.val('点击获取验证码').prop('disabled', false);
}
}, 1000)
}
```
### 2.3 点击注册
发送ajax:
```js
$.ajax({
url: '...',
type: 'POST',
data: {},
dataType: "JSON",
success: function(res){
}
})
```
#### 2.3.1 注册按钮绑定事件
```js
/*
点击提交(注册)
*/
function bindClickSubmit() {
$('#btnSubmit').click(function () {
$('.error-msg').empty();
// 收集表单中的数据(找到每一个字段)$('#regForm').serialize()
// 数据ajax发送到后台
$.ajax({
url: "{% url 'register' %}",
type: "POST",
data: $('#regForm').serialize(), // 所有字段数据 + csrf token
dataType: "JSON",
success: function (res) {
if(res.status){
location.href = res.data;
}else{
$.each(res.error, function (key, value) {
$("#id_" + key).next().text(value[0]);
})
}
}
})
})
}
```
> data: $('#regForm').serialize(), // 可收集字段数据 + 同时把csrf也收集了
补充register函数:
```python
def register(request):
if request.method == 'GET':
form = RegisterModelForm()
return render(request, 'register.html', {'form': form})
print(request.POST)
return JsonResponse({})
```
#### 2.3.2 数据校验
> 模型字段定义时,db_index=True,创建索引,查询加速
> 每个字段都要写校验钩子函数
#### 2.3.3 写入数据库
+ 补全视图函数:
```python
def register(request):
if request.method == 'GET':
form = RegisterModelForm()
return render(request, 'register.html', {'form': form})
form = RegisterModelForm(data=request.POST)
if form.is_valid():
# 验证通过,写入数据库(密码要是密文)
form.save()
return JsonResponse({'status': True, 'data': '/login/'})
return JsonResponse({'status': False, 'error': form.errors})
```
+ 编写注册js:
```js
/*
点击提交(注册)
*/
function bindClickSubmit() {
$('#btnSubmit').click(function () {
$('.error-msg').empty();
// 收集表单中的数据(找到每一个字段)$('#regForm').serialize()
// 数据ajax发送到后台
$.ajax({
url: "{% url 'register' %}",
type: "POST",
data: $('#regForm').serialize(), // 所有字段数据 + csrf token
dataType: "JSON",
success: function (res) {
if(res.status){
location.href = res.data; //页面刷新跳转
}else{
$.each(res.error, function (key, value) {
$("#id_" + key).next().text(value[0]);
})
}
}
})
})
}
```
## 3. 短信登录
### 3.1 展示页面
+ 配置路由和视图函数:
```python
# web/urls.py
urlpatterns = [
url(r'^login/sms/$', account.login_sms, name='login_sms'),
]
# web/views/account.py
def login_sms(request):
if request.method == 'GET':
form = LoginSMSForm()
return render(request, 'login_sms.html', {'form': form})
```
+ 写login_sms.html文件:
> 把register.html复制过来,把注册改为登录,把对应路由改为login_sms
+ 在web/forms/account.py中新建LoginSMSForm类:
```python
# 把init方法封装到BootStrapForm类中,实现输入框中的样式
class LoginSMSForm(BootStrapForm,forms.Form):
mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
code = forms.CharField(label='验证码',widget=forms.TextInput())
```
### 3.2 点击发送短信
+ 绑定点击事件: 复制register.html,tpl改为的值改为login,倒计时沿用不改
> 事件仍然使用send_sms路由,需要把SendSmsForm表单验证中识别tpl,并区分登录或注册
```python
# 根据tpl区分登录和注册
exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()
if tpl == 'login':
if not exists:
raise ValidationError('手机号不存在')
else:
# 校验手机号在数据库中是否存在
if exists:
raise ValidationError('手机号已存在')
```
### 3.3 点击登录
+ 绑定点击事件: 复制register.html,修改提交路由为login_sms
+ 完善login_sms视图函数,实现点击跳转
```python
def login_sms(request):
if request.method == 'GET':
form = LoginSMSForm()
return render(request, 'login_sms.html', {'form': form})
form = LoginSMSForm(request.POST)
if form.is_valid():
# 用户输入正确,登录成功
return JsonResponse({'status': True,'data': "/index/"})
return JsonResponse({'status': False, 'error': form.errors})
```
+ 提交校验
```python
mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^(1[3|4|5|6|7|8|9])\d{9}$', '手机号格式错误'), ])
code = forms.CharField(label='验证码',widget=forms.TextInput())
def clean_mobile_phone(self):
mobile_phone = self.cleaned_data['mobile_phone']
exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()
if not exists:
raise ValidationError('手机号不存在')
return mobile_phone
def clean_code(self):
"""校验验证码同时校验手机号"""
code = self.cleaned_data['code']
# mobile_phone = self.cleaned_data['mobile_phone']
mobile_phone = self.cleaned_data.get('mobile_phone')
# 手机号不存在,验证码不需要判断
if not mobile_phone:
return code
conn = get_redis_connection()
redis_code = conn.get(mobile_phone)
if not redis_code:
raise ValidationError('验证码失效或未发送,请重新发送')
redis_str_code = redis_code.decode('utf-8')
if code.strip() != redis_str_code:
raise ValidationError('验证码错误,请重新输入')
return code
```
### 3.4 补充
+ 校验mobile_phone时,把mobile_phone改为user_object,方便后续使用
```python
def clean_mobile_phone(self):
mobile_phone = self.cleaned_data['mobile_phone']
user_object = models.UserInfo.objects.filter(mobile_phone=mobile_phone).first()
if not user_object:
raise ValidationError('手机号不存在')
return user_object
```
> lean_code函数也要更改,get('mobile_phone')获取的是用户对象,不是电话号码了
```python
def clean_code(self):
"""校验验证码同时校验手机号"""
code = self.cleaned_data['code']
# mobile_phone = self.cleaned_data['mobile_phone']
user_object = self.cleaned_data.get('mobile_phone') # 此次获取的是用户对象
# 手机号不存在,验证码不需要判断
if not user_object:
return code
conn = get_redis_connection()
redis_code = conn.get(user_object.mobile_phone)
if not redis_code:
raise ValidationError('验证码失效或未发送,请重新发送')
redis_str_code = redis_code.decode('utf-8')
if code.strip() != redis_str_code:
raise ValidationError('验证码错误,请重新输入')
return code
```
+ 视图函数中处理user_object,放到session中:
```python
def login_sms(request):
if request.method == 'GET':
form = LoginSMSForm()
return render(request, 'login_sms.html', {'form': form})
form = LoginSMSForm(request.POST)
if form.is_valid():
# 用户输入正确,登录成功
user_object = form.cleaned_data['mobile_phone']
# 用户信息放入session,以后再实现
print(user_object)
return JsonResponse({'status': True,'data': "/index/"})
return JsonResponse({'status': False, 'error': form.errors})
```
## 4. 用户名密码登陆
### 4.1 python生成验证码图片
> https://www.cnblogs.com/wupeiqi/articles/5812291.html
导入pillow模块
```
pip install pillow
```
### 4.2 Session & Cookie
### 4.3 页面显示
+ 配置路由login
+ 配置视图函数views/account.py 中 login
+ 写前端页面login.html
> 改为 img 标签
> 此页面使用表单验证,加上{{ field.error.0 }}
+ 配置表单 forms/account.py 中 定义 LoginForm
+ 生成图片操作
> 字体文件放到根目录
+ 验证码数据写入session
> 数据存在django_session表中
+ 点击验证码刷新
> 写js
```js
```
### 4.4 登陆
+ 表单认证钩子函数
```python
def clean_pass_word(self):
pwd = self.cleaned_data['pass_word']
# 加密 & 返回
return encrypt.md5(pwd)
def clean_code(self):
""" 钩子 图片验证码是否正确? """
# 读取用户输入的yanzhengm
code = self.cleaned_data['code']
# 去session获取自己的验证码
session_code = self.request.session.get('image_code')
if not session_code:
raise ValidationError('验证码已过期,请重新获取')
if code.strip().upper() != session_code.strip().upper():
raise ValidationError('验证码输入错误')
return code
```
+ 视图函数实现提交登陆
```python
def login(request):
""" 用户名和密码登录 """
if request.method == 'GET':
form = LoginForm(request)
return render(request, 'login.html', {'form': form})
form = LoginForm(request, data=request.POST)
if form.is_valid():
username = form.cleaned_data['username']
pass_word = form.cleaned_data['pass_word']
# user_object = models.UserInfo.objects.filter(username=username, password=password).first()
# (手机=username and pwd=pwd) or (邮箱=username and pwd=pwd)
user_object = models.UserInfo.objects.filter(Q(email=username) | Q(mobile_phone=username)).filter(
pass_word=pass_word).first()
if user_object:
# 登录成功为止1
request.session['user_id'] = user_object.id
request.session.set_expiry(60 * 60 * 24 * 14)
return redirect('index')
form.add_error('username', '用户名或密码错误')
return render(request, 'login.html', {'form': form})
```
> Q 模块实现邮箱或手机号登陆
+ 首页显示(html页面)
> 用图片填充
> 导航栏,首页,登陆,注册设置
> 短信登陆,用户名密码登陆跳转
### 4.5 登陆成功中间件
+ web/middleware/auth.py写入中间件:
```python
from django.utils.deprecation import MiddlewareMixin
from web import models
class AuthMiddleware(MiddlewareMixin):
def process_request(self,request):
""" 如果用户已登陆,则在request中赋值 """
user_id = request.session.get('user_id', 0)
user_object = models.UserInfo.objects.filter(id=user_id).first()
request.tracer = user_object
```
+ 写退出登陆视图函数,并配置url和前端页面:
```python
def logout(request):
request.session.flush()
return redirect('index')
```
+ 改为连接mysql
> mysql设置允许远程连接
```
mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
mysql> FLUSH PRIVILEGES;
```
> local_settings.py文件中数据库配置
# 二期
## 1. 管理中心概述
### 1.1 django离线脚本
+ django: web框架
+ 离线: 非web运行时
+ 脚本: 一个或一组py文件
作用: 在某个py文件中对django项目做一些操作
> web未启动时,django中的模块无法直接使用,如需使用,需要做一些处理
示例1:使用离线脚本在用户表插入数据
```python
# 导入
# 模拟django环境
import os
import sys
import django
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 设置根路径
sys.path.append(base_dir)
os.environ.setdefault('DJANGO_SETTINGS_MODULE','s25.settings') # 把项目添加到环境变量
django.setup() # 模拟启动django
```
示例2:数据库录入全国省市县(批量数据)
示例3:朋友圈项目,敏感字识别
示例4:免费版设置:1G文件,5个项目,10人参与
### 1.2 探讨业务
#### 1.2.1 价格策略,建表
新用户注册拥有免费额度。
#### 1.2.2 用户
如何与价格策略关联
#### 1.2.3 交易表
id,状态,用户,价格....
#### 1.2.4 创建存储
基于腾讯对象存储COS存储数据
#### 1.2.5 项目
id,项目名称,描述,颜色,是否星标,参与人数,创建者,已使用空间
#### 1.2.6 项目参与者
id,项目id,用户id,是否星标
### 1.3 设计表结构
+ models.py文件中添加,然后数据库迁移
+ 创建离线脚本,在价格策略表中添加个人免费版
+ 把导入django模拟环境代码写到base.py文件中,其他脚本直接import base
+ 判断一下表中免费用户是否存在,不存在则添加
+ 修改用户注册表,创建用户同时创建交易记录,默认购买免费版
```python
def register(request):
if request.method == 'GET':
form = RegisterModelForm()
return render(request, 'register.html', {'form': form})
form = RegisterModelForm(data=request.POST)
if form.is_valid():
# 验证通过,写入数据库(密码要是密文)
instance = form.save()
# 创建交易记录
policy_object = models.PricePolicy.objects.filter(category=1, title='个人免费版').first()
models.Transaction.objects.create(
status=2,
order=str(uuid.uuid4()),
user=instance,
price=0,
price_policy=policy_object,
count=0,
start_datetime=datetime.datetime.now()
)
return JsonResponse({'status': True, 'data': '/login/sms/'})
return JsonResponse({'status': False, 'error': form.errors})
```
### 1.4 添加项目
#### 1.4.1 项目列表母版+样式
> https://v3.bootcss.com/components/ bootcss文档
+ 配置urls > project/list
+ 编写视图函数 views/project.py project_list
+ 编写项目管理母版 layout/manage.html
+ 编写项目列表页面 project_list.html
+ 中间件实现判断是否登录,未登录自动跳转至登录页面(添加白名单)
+ 获取当前登录用户的额度
> 可以在用户表中加个付费策略字段,查询加速
> 把user,object,price_policy封装到Tracer,记得把前端页面tracer.usernam改为tracer.user.username
#### 1.4.2 添加项目
+ 添加按钮绑定事件
+ 创建对话框实现
+ 创建项目form表单 forms/project.py ProjectModelForm类, fields显示字段
+ 创建项目提交js
+ 视图函数完成创建项目存入数据库,creator字段要赋值
+ 表单验证钩子函数
+ 前端页面和js,钩子函数,对照
+ 添加项目测试
#### 1.4.3 查看项目列表
+ 从数据库中获取数据:
+ 我创建的项目
+ 我参与的项目
+ 提取星标项目
+ 修改视图函数get请求相关代码:
```python
if request.method == 'GET':
# GET请求查看项目列表
"""
1. 从数据库中获取两部分数据
我创建的所有项目:已星标、未星标
我参与的所有项目:已星标、未星标
2. 提取已星标
列表 = 循环 [我创建的所有项目] + [我参与的所有项目] 把已星标的数据提取
得到三个列表:星标、创建、参与
"""
project_dict = {'star': [], 'my': [], 'join': []} # 创建字典
my_project_list = models.Project.objects.filter(creator=request.tracer.user)
for row in my_project_list:
# 遍历后分别添加到star或my
if row.star:
project_dict['star'].append(row)
else:
project_dict['my'].append(row)
join_project_list = models.ProjectUser.objects.filter(user=request.tracer.user)
for item in join_project_list:
# 遍历后分别添加到star或join
if item.star:
project_dict['star'].append(item.project)
else:
project_dict['join'].append(item.project)
form = ProjectModelForm(request)
return render(request, 'project_list.html', locals())
```
+ 在前端页面中添加项目展示框
```html
星标
{% for item in project_dict.star %}
{{ item.name }}
{% endfor %}
```
> 踩坑: html多行注释中,如果有无法解析的路由,也会导致页面解析错误,不知道啥原因
+ 样式设置,调整前端页面代码:
```html
{% for item in project_dict.star %}
{% endfor %}
```
+ 编写css样式代码
> 踩坑: html解析报错,改为wxml正常
#### 1.4.4 星标
+ 配置添加/移除星标路由,
```python
urlpatterns = [
# /project/star/my/1
# /project/star/join/1
# 需要传入参数,project_type: my/join/star,project_id:1/2/3
url(r'^project/star/(?P\w+)/(?P\d+)/$', project.project_star, name='project_star'),
url(r'^project/unstar/(?P\w+)/(?P\d+)/$', project.project_unstar, name='project_unstar'),
]
```
+ 编写对应视图函数
```python
def project_star(request, project_type, project_id):
""" 星标项目 """
if project_type == 'my':
models.Project.objects.filter(id=project_id, creator=request.tracer.user).update(star=True)
return redirect('project_list')
if project_type == 'join':
models.ProjectUser.objects.filter(project_id=project_id, user=request.tracer.user).update(star=True)
return redirect('project_list')
return HttpResponse('请求错误')
def project_unstar(request, project_type, project_id):
""" 取消星标 """
if project_type == 'my':
models.Project.objects.filter(id=project_id, creator=request.tracer.user).update(star=False)
return redirect('project_list')
if project_type == 'join':
models.ProjectUser.objects.filter(project_id=project_id, user=request.tracer.user).update(star=False)
return redirect('project_list')
return HttpResponse('请求错误')
```
+ 前端页面中绑定点击事件
+ 为移除星标,需要在后台中,星标字典中,加入type以区分join和my,同时前端页面引用都需要修改
> 踩坑: 前端页面,只有星标模块需要加value,my和join模块不需要加
### 1.5 功能实现
+ 项目颜色选择
+ 修改Bootstrap类,设置部分应用(面向对象),web/forms/bootstrap.py
```python
class BootStrapForm(object):
BOOTSTRAP_CLASS_EXCLUDE = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, field in self.fields.items():
if name in self.BOOTSTRAP_CLASS_EXCLUDE:
continue
old_class = field.widget.attrs.get('class', "")
field.widget.attrs['class'] = '{} form-control'.format(old_class)
field.widget.attrs['placeholder'] = '请输入%s' % (field.label,)
```
+ 自定义ModelForm插件,web/forms/widgets.py
```python
class ColorRadioSelect(RadioSelect):
# template_name = 'django/forms/widgets/radio.html'
# option_template_name = 'django/forms/widgets/radio_option.html'
template_name = 'widgets/color_radio/radio.html'
option_template_name = 'widgets/color_radio/radio_option.html'
```
+ 重写对应母版,添加标签,web/templates/widgets
+ 前端页面写css样式代码
```css
.color-radio label {
margin-left: 0;
padding-left: 0;
}
.color-radio input[type="radio"] {
display: none;
}
.color-radio input[type="radio"] + .cycle {
display: inline-block;
height: 25px;
width: 25px;
border-radius: 50%;
border: 2px solid #dddddd;
}
.color-radio input[type="radio"]:checked + .cycle {
border: 2px solid black;
}
```
+ 自定义插件中设置样式,web/forms/project.py
```python
class ProjectModelForm(BootStrapForm,forms.ModelForm):
BOOTSTRAP_CLASS_EXCLUDE = ['color',] # 继承时改写此参数
class Meta:
model = models.Project
fields = ['name', 'color', 'desc']
widgets = {
'desc': forms.Textarea(attrs={'xx':123}), # 'xx':123表示设置属性
'color': ColorRadioSelect(attrs={'class': 'color-radio'})
}
```
+ 下拉项目列表
+ inclusion_tag: web/templatetags 目录下py文件用于导入templates/inclusion 目录下前端模板导入到前端页面作为一个代码块
+ web/templatetags 目录下,新建project.py文件:
```python
from django.template import Library
register = Library()
@register.inclusion_tag('inclusion/all_project_list.html') # templates里找html文件,然后完成渲染
def all_project_list():
return {'name': 'jack'}
```
+ templates/inclusion 目录下,新建all_project_list.html文件:
```html
safsaf: {{ name }}
```
+ 母版 manage.html 中写入:
```html
{% load project %}
...
{% all_project_list %}
```
+ 把project.py和all_project_list.html中函数完善:
```python
def all_project_list(request):
# 1. 获我创建的所有项目
my_project_list = models.Project.objects.filter(creator=request.tracer.user)
# 2. 获我参与的所有项目
join_project_list = models.ProjectUser.objects.filter(user=request.tracer.user)
return {'my': my_project_list, 'join': join_project_list,'request':request}
```
```html
```
+ 进入项目显示项目管理
+ 路由分发
```python
urlpatterns = [
# 单个项目管理,路由分发
url(r'^manage/(?P
\d+)/', include([
url(r'^dashboard/$', manage.dashboard, name='dashboard'),
url(r'^issues/$', manage.issues, name='issues'),
url(r'^statistics/$', manage.statistics, name='statistics'),
url(r'^file/$', manage.file, name='file'),
url(r'^wiki/$', manage.wiki, name='wiki'),
url(r'^setting/$', manage.setting, name='setting'),
], None, None)),
]
```
+ 视图函数
+ 前端页面对应位置绑定路由点击
+ 进入项目展示菜单
+ 使用中间件,判断url是否以url开头,project_id是否是我参与的或创建的
```python
def process_view(self, request, view, args, kwargs):
# 路由匹配后执行
# 1.判断url是否以manage开头
if not request.path_info.startswith('/manage/'):
return
# 2.判断project_id是否是用户创建或参与的
project_id = kwargs.get('project_id')
request.tracer.user
# 是否是我创建的
project_object = models.Project.objects.filter(creator=request.tracer.user, id=project_id).first()
if project_object:
# 是我创建的
request.tracer.project = project_object
return
project_user_object = models.ProjectUser.objects.filter(user=request.tracer.user, project_id=project_id).first()
if project_user_object:
# 是我参与的
request.tracer.project = project_user_object
return
# 都不是,最后返回到project_list
return redirect('project_list')
```
> 踩坑: process_view函数名不要加s
+ 菜单显示bug修复,把 {% all_project_list request %} 和 {% if request.tracer.project %} 放到一个ul标签里
+ 绑定路由完成点击跳转
+ 点击默认active
+ 编写中间件manage_menu_list,将标签写入中间件
+ 通过中间件模板实现标签循环
+ 判定点击并添加active
+ 总结
+ 项目实现思路
+ 星标/取消星标
+ inclusion_tag实现项目切换
+ 项目菜单(中间件:process_request,process_view,默认选中:inclusion_tag)
+ 路由分发
+ 颜色选择: 源码+扩展(了解就行,不要深究)
### 1.6 wiki,项目文档库
#### 1.6.1 表结构设计
```python
class Wiki(models.Model):
project = models.ForeignKey(verbose_name='项目', to='Project')
title = models.CharField(verbose_name='标题', max_length=32)
content = models.TextField(verbose_name='内容')
# 自关联
parent = models.ForeignKey(verbose_name='父标题', to='self', null=True, blank=True, related_name='children')
```
#### 1.6.2 快速开发
+ 首页展示
+ 为方便维护,wiki路由做分发,视图函数新建views/wiki.py
+ 编写前端页面
```html
{% extends 'layout/manage.html' %}
{% block css %}
{% endblock %}
{% block content %}
wiki文档
目录
《{{ request.tracer.project.name }}》文档库
新建文章
{% endblock %}
```
+ 把all_project_list下拉框设置成显示项目名
+ 多级目录
+ 得到的数据构造层次,要用到递归,代码复杂,效率低,不采用
+ 前端ajax+ID选择器结合后端数据实现,ajax的回调函数获取后循环处理
> 踩坑: parent_id必须比id小,否则遍历时会找不到parent_id相关记录,无法添加li标签
+ 加个深度字段,就可以搞定上面那个坑
+ models.Wiki中加个depth字段
+ 数据库查询时根据depth和id排序
+ 添加文章时根据是否有parent_id写入depth
+ ModelForm里要把depth设置为不可见(exclude)
+ 简单设置点击文章查看详细
+ 添加文章
+ 定义models时,可以改写str方法,使调用类对象时显示标题
```python
def __str__(self):
return self.title
```
+ 视图函数:
```python
def wiki_add(request,project_id):
""" wiki添加 """
if request.method == 'GET':
form = WikiModelForm()
return render(request, 'wiki_add.html',locals())
form = WikiModelForm(request.POST)
if form.is_valid():
form.instance.project = request.tracer.project
form.save()
url = reverse('wiki',kwargs={'project_id': project_id})
return redirect(url)
return render(request, 'wiki_add.html',locals())
```
+ 父文章区分项目
```python
# 重写ModelForm的init方法
def __init__(self,request,*args,**kwargs):
super().__init__(*args,**kwargs)
# 把想要展示的字段把绑定显示数据重置
# 去数据库中获取当前项目的所有
total_data_list = [('','请选择'),]
data_list = models.Wiki.objects.filter(project=request.tracer.project).values_list('id','title')
total_data_list.extend(data_list)
self.fields['parent'].choices = total_data_list
```
+ 注意有关多级目录相关修改
+ 预览文章
+ 直接在wiki函数中实现
```python
def wiki(request,project_id):
""" wiki的首页 """
wiki_id = request.GET.get('wiki_id')
if not wiki_id or not wiki_id.isdecimal():
return render(request, 'wiki.html')
wiki_object = models.Wiki.objects.filter(id=wiki_id, project_id=project_id).first()
return render(request, 'wiki.html')
```
+ wiki.html页面中做判断
```html
{% if wiki_object %}
{{ wiki_object.content }}
{% else %}
《{{ request.tracer.project.name }}》文档库
新建文章
{% endif %}
```
+ 修改文章
+ 同删除
+ 视图函数
+ 与添加使用同一模板
+ 视图函数post请求处理
+ 编辑成功跳转到预览
+ 添加判断,不能选择自己为父标题
+ 删除文章
+ 前端按钮实现
+ 路由和视图函数
+ 按钮绑定路由
+ 把wiki中标题栏复制到wiki_form相关位置,使编辑时也显示目录
#### 1.6.3 应用markdown组件
示例: https://pandao.github.io/editor.md/examples/index.html
源码: https://github.com/pandao/editor.md
+ 源码下载下来,保存到 web/static/plugin/editor-md
+ 找到添加和编辑页面(wiki_form.html)中 textarea 输入框 -> 转换为 markdown 编辑器:
```html
```
+ 判断是否为content,特殊处理
+ 应用js
+ 应用css
1. textarea 框通过div包裹并添加id,以便以后查找并转化编辑器
```html
...
```
2. 应用css/js
3. 进行初始化
```js
$(function(){
initEditorMd();
});
//初始化markdown编辑器
function initEditorMd() {
editormd('editor', {
placeholder: "请输入内容",
height: 500,
path: "{% static 'plugin/editor-md/lib/' %}",//设置依赖路径
//imageUpload:true,
//imageFormats:["jpg",'jpeg','png','gif'],
//imageUploadURL:WIKI_UPLOAD_URL
})
}
```
4. 全屏问题,设置css样式,使全屏覆盖标题栏
```css
.editormd-fullscreen{
z-index: 1001;
}
```
5. 预览页面,把markdown各种转化,wiki.html文件,相同操作
> 防xss攻击
6. 更多相关见文档或示例
#### 1.6.4 腾讯COS做上传
安装SDK
> pip install -U cos-python-sdk-v5
+ 腾讯COS入门,/home/Django/s25/scrips/cos_upload_demo.py
+ 创建桶
+ 上传文件
+ markdown上传本地文件: 修改wiki_form.html中js
> 需要设置CSRF豁免
+ 编写wiki_upload路由和视图函数完成文件上传操作
+ 创建桶和上传文件函数封装到 utils/tencent/cos.py 中
## 2. 文件管理
### 2.1 知识点
1. 模态对话框 & ajax & 后台ModelForm校验
2. 目录切换: 展开当前文件夹&文件
3. 删除文件夹: 嵌套的文件一同删除
4. js上传文件到cos
5. 删除文件时,连带cos一同删除
6. 上传进度条
7. 下载文件
### 2.2 设计
1. 功能设计
2. 表结构设计
3. 单独知识点
### 2.3 表结构设计
字段: ID,项目ID,文件名,类型,大小,父目录,key(用于区分同名文件)
```python
class FileRepository(models.Model):
""" 文件库 """
project = models.ForeignKey(verbose_name='项目', to='Project')
file_type_choices = (
(1, '文件'),
(2, '文件夹')
)
file_type = models.SmallIntegerField(verbose_name='类型', choices=file_type_choices)
name = models.CharField(verbose_name='文件夹名称', max_length=32, help_text="文件/文件夹名")
key = models.CharField(verbose_name='文件储存在COS中的KEY', max_length=128, null=True, blank=True)
# int类型最大表示的数据
file_size = models.BigIntegerField(verbose_name='文件大小', null=True, blank=True, help_text='字节')
file_path = models.CharField(verbose_name='文件路径', max_length=255, null=True,
blank=True) # https://桶.cos.ap-chengdu/....
parent = models.ForeignKey(verbose_name='父级目录', to='self', related_name='child', null=True, blank=True)
update_user = models.ForeignKey(verbose_name='最近更新者', to='UserInfo')
update_datetime = models.DateTimeField(verbose_name='更新时间', auto_now=True)
```
### 2.4 其他知识点
1. url传参/不传参
```python
url(r'^file/$', manage.file, name='file'),
```
```python
# /file/
# /file/?folder_id=50
def file(request, folder_id):
folder_id = request.GET.get('folder_id')
```
2. 模态对话框+警告框
3. 获取导航条
```python
# /file/
# /file/?folder_id=50
def file(request, folder_id):
folder_id = request.GET.get('folder_id')
url_list = [
{'id': 2, 'name': '周杰伦'}
{'id': 5, 'name': '侯佩岑'}
{'id': 7, 'name': '王洋'}
]
if not folder_id:
pass
else:
file_project = models.FileRepository.objects.filter(id=folder_id,file_type=2).first()
row_object = file_project
while row_object:
# 循环去父目录
url_list.insert(0,{'id': row_object.id, 'name': row_object.name})
row_object = row_object.parent
```
### 3.5 文件上传
1. python上传
> 密钥是安全的
2. 通过js直接上传 [建议看看官方文档]
+ 下载js,即前端SDK
> 地址: https://github.com/tencentyun/cos-js-sdk-v5/tree/master/dist
```html
```
+ 前端代码:
```html
{% load static %}
Document
实例1: 通过密钥进行删除文件
```
+ 跨域问题:在腾讯云后台设置允许跨域,跨域访问CORS设置,添加规则
3. 临时密钥上传文件(推荐)
+ 路由
```python
url(r'^demo2/$', manage.demo2, name='demo2'),
url(r'^cos/credential/$', manage.cos_credential, name='cos_credential'),
```
+ 视图
```python
def demo2(request):
return render(request, 'demo2.html')
def cos_credential(request):
# 1. 生成一个临时凭证并给前端返回, pip install -U qcloud-python-sts
# 2. 写代码
from sts.sts import Sts
config = {
# 临时密钥有效时长,单秒,30分钟=1800秒
'duration_seconds': 1800,
# 固定密钥 id
'secret_id': 'AKIDEF1a0MVi7KMDzdiljWI6lEVFsNjv3mV2',
# 固定密钥 key
'secret_key': 'vT0RhohLhqdDXbPEzUjjlhOo4LM6rM4W',
# 换成你的bucket
'bucket': 'test-1305783752',
'region': 'ap-chengdu',
# 这里改成允许路径的前缀
'allow_actions': '*',
# 密钥权限列表,详见 https://cloud.tencent.com/document/product/436/31923
'allow_actions': [
'name/cos:PostObject',
# 'name/cos:DeleteObject',
# 'name/cos:UploadPart',
# 'name/cos:UploadPartCopy',
# 'name/cos:CompleteMultipartUpload',
# 'name/cos:AbortMultipartUpload',
'*'
]
}
```
+ 前端页面,scripts/demo2.html
### 3.6 创建并设置CORS
> 在 utils/cos.py 文件中 create_bucket 加上:
```python
# 创建跨域规则
cors_config = {
'CORSRule': [
{
'AllowedOrigin': '*',
'AllowedMethod': ['GET', 'PUT', 'HEAD', 'POST', 'DELETE'],
'AllowedHeader': "*",
'ExposeHeader': "*",
'MaxAgeSeconds': 500
}
]
}
client.put_bucket_cors(
Bucket=bucket,
CORSConfiguration=cors_config
)
```
### 3.7 markdown上传图片
> 无改动
### 3.8 js上传文件
- 临时凭证: 当前项目的桶&区域(request.tracer.project...)
- js上传文件:设置当前的 桶&区域
### 3.9 this
js中,每个函数都是一个作用域,在他内部都会存在this,谁调用函数,this就指谁,
```js
// 1.
var name = '全栈28期'
function func(){
var name = '全栈25期'
console.log(name)
}
func();
//输出: 全栈25期
// 2.
var name = '全栈28期'
function func(){
var name = '全栈25期'
console.log(this.name)
}
func();
//输出: 全栈28期
//3.
var name = '全栈28期'
info = {
name: '全栈25期',
func: function(){
console.log(this.name)
}
info.func()
//输出: 全栈25期
```
### 3.10 闭包
- js中,循环发送三次ajax请求,由于ajax是异步请求,发送请求时不等待,直接往下执行
```js
data_list = [11,22,33]
for (var i = 0; i++; i < data_list.length){
function xx(data){
$.ajax({
url: "...",
data: {value: data_list[data]},
success: function (res) {
//若此处阻塞1分钟
console.log(data); // 输出: 0/1/2
}
})
}
xx(i) // 此处若不使用函数闭包,则内部 console.log 输出3次2
}
console.log(i) //输出: 2
```
> 上传文件可能需要使用异步请求,需要使用闭包
### 3.11
## 3. 其他