1.Django安装
框架很完美,组件非常多,但文件太多,过于臃肿
(1.
. Django是一个高级的Python Web框架,它鼓励快速开发和清洁,务实的设计。
· 由经验丰富的开发人员构建,它负责Web开发的许多麻烦,因此您可以专注于编写应用程序,而无需重新创建轮子。
· 它是免费的和开源的。
· 被官方称之为完美主义者的Web框架。
. Django可以更快地枚津更好的Web应用程序并减少代码。
(2. Django框架的特点:
. 快速开发:Django的宗旨在于帮助开发人员快速从概念到完成应用程序。
. 安全可靠:Django认真对待全面,帮助开发人员避免许多常见的安全错误。
. 超可伸缩性: Web上的一些最繁忙的网站利用了Django快速灵活扩展的能力。
1.安装模块
安装Django框架
pip3 install Django==3.2.7 安装Django
pip3 show django 显示Django
python -m django --version 查看Django版本
pip install --upgrade pip 升级pip
2.启动项目
-- python -m django startproject web 新建一个web项目
(或者 django-admin startproject web )
cd web 进入当前新建的项目
-- python manage.py startapp myhome 创建应用(注意需要进入项目下创建)
-- python manage.py runserver 8080 启动服务
tree ./web /F 查看文件树,基本目录结构
C:\USERS\ICY-YUN\PYCHARMPROJECTS\WORKSPACE_PY\PY-WEBBACKEND\WEB
│ db.sqlite3
│ manage.py
│ readme.md
│
└─web
│ asgi.py
│ settings.py
│ urls.py
│ wsgi.py
了解:
(1. wsgi.py文件 wsgi接口,是启动web服务网关接口
(2.settings.py文件 配置文件
DATABASES 是数据库的设置
TAMPLATES 是模板的配置
(3.urls.py 是路由文件
(4.init.py 是初始化文件
3.项目基本结构
\WEB -- 项目目录
│ db.sqlite3 -- django默认的数据库配置, 生成的数据库文件
│ manage.py -- 管理文件,当前项目唯一的入口文件
│ readme.md -- 自定义的提示文件
│
├─myhome -- 自定义创建的应用
│ │ admin.py
│ │ apps.py
│ │ models.py -- 当前应用中的模型文件
│ │ tests.py
│ │ urls.py -- 当前应用中的路由文件(子路由文件)
│ │ views.py -- 当前应用中的视力函数
│ │ __init__.py
│ │
│ ├─migrations
│ │ __init__.py
│ │
│ └─__pycache__
│ urls.cpython-38.pyc
│ views.cpython-38.pyc
│ __init__.cpython-38.pyc
│
├─static -- 静态文件夹
│ 1.css
│
├─templates -- 模板文件夹
│ └─a
│ ind.html
│
└─web -- 和项目同名的目录,当前项目的配置和管理
│ asgi.py
│ settings.py
│ urls.py
│ wsgi.py
│ __init__.py
│
└─__pycache__
settings.cpython-38.pyc
urls.cpython-38.pyc
wsgi.cpython-38.pyc
__init__.cpython-38.pyc
相关的一些概念
(1.路由:
路由就是去定义用户访问的url,并且把定义url路径和对应的视图函数产自映射
(2.视图:
就是一个函数或方法,也可以定义成类,
主要就是用于接收用户的请求,并且做出响应项目中的主要逻辑代码都在视图函数中
(3.模板:
在django框架中有一个模板引擎,可以做到把html和python逻辑代码分离
并且在视图函数中需要给用户响应模板时,返回,或传递数据
例:在 return render(request,'a/ind.html',{"abc","百度"}),
然后在ind.html中可以使用模板字符串{{abc}}
(4.静态文件
专门存放 在模板中需要使用的静态文件一目录,css,js,font,img,video
(5.模型:
专门处理数据层,
在django框架中,可以通过定义一个模型类,来实现对数据库中的数据进行管理(增、删,改,查)
在开发中,对类中的数据进行操作,会映射到时数据库,转化成对数据 的具体执行
框架的设计思想(设计模式)
核心思想:就是把代码,数据控制,和页面的展示完全分离,降低程序模块之间的耦合(低耦合,高内聚)
MVC设计模式
M Model 模型 ==》 数据层的管理
V View 视图 ==》 模块的管理,页面的展示
C Controller 控制器 ==》 逻辑代码的管理
是MVC模式的优化
MTV 设计模式Django ,Flask
M Model 模型 ==》 数据层的管理, 数据的增,删, 改,查
T Template 模板 ==》 模块的管理,页面的展示,html
V View 视图 ==》 逻辑层的管理, 逻辑代码, 流程控制
4.配置项目文件
1.配置路由结构
2.2.1 在创建好的应用中myhome,视图文件view.py,书写相应的信息
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def index(request):
return HttpResponse('Hello world')
2.2.2 给当前的视图函数 配置一个路由 myhome/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('',views.index),
2.2.3 在根路由中配置当前应用的路径 web/urls.py(初始化就有的文件)
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('myhome.urls')),
### include可以导入需要的文件
]
2.配置settings文件
1.template模板文件
TEMPLATES = [
{
......
# 'DIRS': [], 替换成下面的内容
'DIRS': [os.path.join (BASE_DIR, 'templates')],
....
},
]
2.static静态资源文件(可以被公开访问)
STATIC_URL = '/static/' 添加下面的内容
STATIC_ROOT = os.path.join(BASE_DIR, "static")
3.允许访问的域名 (Django框架独有的安全措施)
## 如果allow_host里面有内容,那一定要添加"localhost","127.0.0.1"
ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['localhost','127.0.0.1'] # 指定当前项目默认的ip
5.Git项目启动
(1.将本地计算机的项目下的模块打包
导出模块方法:python -m pip freeze > requirements.txt
导入模块方法:pip install -r requirements.txt
(2.获取项目依赖包
安装完以后,会出现 venv文件夹
1.获取环境中所有安装的包
(1.在当前工作空间的xxx\venv\Scripts位置打开cmd, 输入“activate”,进入到虚拟环境中。 "deactivate " 退出虚拟环境 。 (显示的结果是(venv)C:\xxxxx\venv\Scripts )
(2.输入pip freeze > pipinstall.txt 将模块名称导出到pipinstall.txt文件中 **(重要)**
2.获得了依赖包,我们就可以在新环境下安装依赖包的模块:
apt install python3-pip (服务器上)安装pip
pip install -r pipinstall.txt (进入到项目下)更改镜像后重新下载
(3.配置settings,设置静态文件的位置
DEBUG = True
ALLOWED_HOSTS = ['121.xxx'] ## 公网IP , 即使使用corsheaders 跨域,也不用替换成 “*”, 这是项目运行的主机。
STATIC_URL = '/static/'
# STATICFILES_DIRS = [ ## 不可以和STATIC_ROOT同时出现
# os.path.join(BASE_DIR, "static")
# ]
STATIC_ROOT = os.path.join(BASE_DIR, "static")
(4.数据库配置
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'ENGINE': 'django.db.backends.mysql',
'OPTIONS': {
'sql_mode': 'traditional',
'init_command': "SET sql_mode='traditional'", # STRICT_TRANS_TABLES
},
'NAME': dbName,
'USER': user,
'PASSWORD': passwd,
'HOST': host,
'PORT': port,
'charset': charset,
'TEST': {
'CHARSET': charset,
'COLLATION': 'utf8_general_ci',
},
'CONN_MAX_AGE':60
},
}
码不是root,而是它指向的真实森码
(5.iframe相关的配置
在settings.py添加以下内容
# 与iframe相关的配置
X_FRAME_OPTIONS = 'SAMEORIGIN'
6.包管理器的区别
环境 | 包管理器 | 手动安装 | 文件夹的包名 | 命令 | 模块依赖 |
---|---|---|---|---|---|
javascript | npm ,yarn | npm是nodejs自带的,yarn是手动安装的 | node_modules | npm install, yarn add | pakage.json |
python | pip | python自带的模块 | venv | pip install | requirements.txt, 并且需要手动打包依赖 |
php | composer | 手动安装 | vendor | composer require | package.json + .env |
2.Django框架学习
1.templates,static资源
1.配置环境
##在项目中使用模板
### 1.在manage.py 文件同级目录下创建templates文件夹,static文件夹
### 2.在模板文件夹中创建模板文件.html文件,static文件夹 中创建.css文件
### 3.在路由文件中使用视图函数 myhome/urls.py
### 4.修改settings.py模板引擎的配置目录settings.py / TEMPLATES / DIRS
‘DIRS' : [os.path.join(BASE_DIR, 'templates')],
### 5.修改settings.py模板引擎的配置目录settings.py 添加
# STATIC_ROOT = os.path.join(BASE_DIR, "static") # 设置根路径
STATICFILES_DIRS = [
os.path.join(BASE_DIR,"static")
]
2.使用静态资源
### 1.模板文件中使用静态资源
(1.方法一:使用load模板语法
{% load static %}
<img src="{% static 'img/bg-black.png' %}" alt="">
(2.方法二:在setting添加对应的配置文件
<img src="static/img/bg-main.png" alt=""> (如果路由不是/xxx, 则会出现问题)
<img src="/static/img/bg-main.png" alt=""> (添加上根路径下的文件就可以)
### 2.在视图函数中使用模板文件 myhome/views.py
def func(request):
return render(request,'a/ind.html')
### 3.直接访问静态资源
// static 类似于其它框架的public目录,但Django框架需要携带这个目录
http://localhost:8001/static/assets/upload/image/bgmain-1.png
2.urls路由文件
(1.404空页面
# 404页面(/属于正则表达式的内容,而*属于非法字符)
re_path ('/', IndexViews.empty, name="myhome_empty"),
(2.url路由传值
### myhome/urls.py 路由文件
from django.urls import path,re_path
from . import views // 从当前的脚本中引入views模块
urlpatterns = [
# path('',views.index),
path('love',views.func),
### 1.正则表达式模式
# re_path("^love$",'views.func'); 里面写的是正则表达式
### 2.获取当前url路径的一部分作为视图函数的参数
# path ('articles/2003/', views.special_case_2003),
# path ('articles/<int:year>/', views.year_archive),
# path ('articles/<int:year>/<int:month>/', views.month_archive),
# path ('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
# re_path('^abc/123/$',views.abc),
# re_path('^abc/([0-9]{3})/$',views.abcd),
# # re_path('^abc/?P<nums>([0-9]{3})/$',views.abcd),
//需要以特定的nums作为关键字参数
### 3.给路由定义名字url路由,还可以定义第三个关键字参数 name
re_path('^abc/123/$',views.abc,name="myhome_abc"),
re_path('^abc/([0-9]{3})/$',views.abcd,name="myhome_abcd"),
]
### myhome/views.py视图函数
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def index(request):
return HttpResponse('Hello world')
def func(request):
return render (request, 'a/ind.html',{'abc':"扯淡网",'n':'666'})
def special_case_2003(request):
return HttpResponse('special_case_2003')
def year_archive(request,year):
return HttpResponse(f'year_archive:{year}')
def month_archive(request,year,month):
return HttpResponse(f'month_archive:{year},{month}')
def article_detail(request,year,month,slug):
return HttpResponse(f'article_detail:{year},{month},{slug}')
def abc(request):
return HttpResponse('abc')
def abcd(request,num):
return HttpResponse(f'abcd:{num}')
### templates/a/ind.html
<body>
<center class="test">
欢迎来到{{abc}}
<a href="/abc/123">硬编码:跳转</a> <br>
<a href="{% url 'myhome_abc' %}">通过路由名:跳转</a> <br>
<a href="{% url 'myhome_abcd' 789 %}">带参数的路由名:跳转</a> <br>
<a href="{% url 'myhome_abcd' n %}">带参数的路由名:跳转</a> <br>
</center>
</body>
### 可以在花生壳软件买服务器
使用本地连接的宽带指定端口 python manage.py runserver 0.0.0.0:80 (没效果)
(3.引入封装的视图文件
----views文件夹/__init__.py (新建)
__all__ = [ # # 将views下的所有文件都暴露出来
'IndexViews',
'BooksViews',
'UsersViews',
'OrdersViews',
'TypesViews',
]
----- myhome/urls.py
from django.urls import path,re_path
from .views import *
urlpatterns = [ # 将views下的所有文件都引入
# 后台首页
path('',IndexViews.index,name="myadmin_index"),
# 后台订单管理
path('orders/index',OrdersViews.index,name="myadmin_orders_index"),
]
3.admin数据库后台
https://127.0.0.1:8080/admin/
(2.python manage.py createsuperuser
创建管理员用户 (当前建立的管理员是超级管理员)
注意:输入密码时,可能会不显示密码。
(3.修改settings.py文件的语言,和时区(可以不重启服务器)
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
(4.找配置后台myhome/admin.py, 数据库中是auth.user表
from django.contrib import admin
from . import models
# Register your models here.
class BooksAdmin(admin.ModelAdmin):
# 要展示的列表
list_display = ('id','name','author','price','publisher','pub_dte','abstract','img_url')
# ordering设置默认排序字段,负号表示降序排序
ordering = ('id',)
# list_editable 设置默认可编辑字段
list_editable = ["name","author",'price']
# list_per _page设置每页显示多少条记录,默认是100条
list_per_page = 2
# 搜索栏的搜索字段
search_fields =('name','author','publisher')
# select * from books where name like ‘%上海%’ or author like ‘%上海% or publisher like ‘%上海%
# 过滤器分组,可以快速访问
list_filter =( 'name' , 'author', 'price','pub_dte')
# 详细时间分层筛选
date_hierarchy = 'pub_dte'
admin.site.register(models.Book,BooksAdmin)
4.views视图文件
1.获取MySQL数据
注意: 获取的数据集是一个对象,可以使用for … in 进行遍历,但不能通过 字典的方法访问(item[‘xxxx’])和进行设置,也不能使用list的方法进行处理。只有使用 item.xxx的方法进行获取属性值。
for item in data_list2:
data_list.append({
'number':item.number,
'name':item.name,
'register_time':item.register_time,
'type':'教师',
})
(1.基本crud
# 1. .get(id=xxx) 通过单独属性值获取对象
(其实是对象,而不是字典),只能获取存在的单个对象数据,否则会出错
# 2. .all() 获取数据的对象
# 3. .filter(id=xxx) 通过过滤器获取数据
(可以获取多个符合条件的数据),获取的是对象的集合,需要遍历输出
# 3.1 .first() 过滤器的修饰方法,获取第一个对象(不可以用于update对象上)
# 3.2 .last() 过滤器的修饰方法,获取最后一个对象(不可以用于update对象上)
# 4.常用的函数
(1.[:5],获取前n条数据
new_data=Book_detail.objects.filter(book_id=book_id).order_by('-id')[:5]
(2count()查看总数
new_data=Book_detail.objects.all().count()
(3..distinct()去重
Purchase.objects.all().distinct('customer').order_by('customer', '-date')
(4.values()输出指定字段,默认全部输出为列表形式,(数据集对象不允许操作)
t_item = StudentGraduateArticle.objects.filter(teacher_id=id,student_id=item.id).values()
graduate_data.append(t_item[0])
(5.values_list("xxx",flat=True) 输出列表数据,(没有flat=True则为元组)
res=models.Publish.objects.filter(name='北京出版社').values_list('book__name')
## 1.查询数据
user_obj = User.objects.filter(userID=userID) ### 注意是objects
print(user_obj[0].xxx)
## 数据集对象需要使用 user_obj.xxx获取属性,使用user_obj['xxx']无效
## 这个和request.session.xxxx中都是保存对象的方式。
## 2.更新数据 (ORM关联的字段不需要更新。)
注意:更新前不能通过filter (nickname=nickname, sex=1).first (),这种先“过滤再获取对象”的方式获取对象, 经过两的过滤,已经没有原来的对象方法了。
user_obj = User.objects.filter(userID=userID)
usertoken_data = {
'token':token
}
usertoken_obj.update(**usertoken_data)
## 3.保存数据
## 3.1 存在就更新,不存在就创建, 对于更新一对一的子表很有效,
usertoken_obj = UserToken.objects.update_or_create (user=obj, defaults={'token': token})
## 3.2 保存新数据
studata = {
'name':'张三',
'age':20
}
stuobj = models.Stu(**studata)
stuobj.save()
## 4.删除数据
user_obj = User.objects.filter(userID=userID)
user_obj.delete()
(2.高级查询
1. filter + Q 多字段过滤查询
from django.db.models import Q
if selecttype == 'all':
user = user.filter ( Q (nickname__contains=keywords) | Q (phone__contains=keywords) | Q (sex__contains=keywords) | Q ( email__contains=keywords) | Q (userID__contains=keywords))
else:
serdata = {f'{selecttype}__contains': keywords}
user = user.filter (**serdata)
serdata = {f'{selecttype}__{mode}': f'{keywords}'} ## 可变形式, 常用替换if..else
user = user.filter (**serdata)
2.order_by 排序
user = User.objects.filter().order_by ('userID') # 升序
user = User.objects.filter().order_by ('-userID') # 降序(取反)
3.xxx_icontains 模糊查询
# user = user.filter (nickname__icontains = "爱你")
# 包含中文字符不能使用__contains系列,必需使用__icontains 不敏感匹配
4._in 联合查询( + values_list)
hashes = A.objects.values_list("device_hash",flat=True).filter(customer_id=1)
obj = B.objects.filter(device_in=hashes).values_list("device_hash")
.annotate(Sum("cmn_merge_count"))
5.filter过滤条件
__exact 精确等于 like '111'
__iexact 精确等于 忽略大小写 ilike '111'
__contains 包含 like '%111%'
__icontains 包含 忽略大小写 ilike '%111%',
__gt 大于
__gte 大于等于
__lt 小于
__lte 小于等于
__in 存在于一个list范围内
__startswith 以...开头
__istartswith 以...开头 忽略大小写
__endswith 以...结尾
__iendswith 以...结尾,忽略大小写
__range 在...范围内
__year 日期字段的年份
__month 日期字段的月份
__day 日期字段的日
__isnull=True/False
(3.日期模块
1. time模块获取当前日期
import time
>> time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())
>> 2022-07-05 14:01:25
注意:在模板输出日期数据时,需要使用到过滤器 {{item.register_time | date:'Y-m-d H:M:S'}}
常见日期格式 :[’%Y-%m-%d %H:%M:%S’, # ‘2006-10-25 14:30:59’]
2.datetime进行日期加减
import datetime
obj_pass_time = datetime.datetime.strptime(str_pass_time, '%Y-%m-%d %H:%M:%S')
obj_valid_time = obj_pass_time + datetime.timedelta(days= str_valid_days) # 字符串转换成日期格式
now_time = datetime.datetime.now()
now_time_str = now.strftime('%Y-%m-%d %H:%M:%S') # 日期格式转换成字符串格式
(4.ORM操作
1. 一对一 中 子类获取父类对象的数据集(ORM的操作)
# 虽然数据类型是不一样,但都可以通过下标访问。
# 数据集很类似数组,在一定程度上,可以使用数组代替数据集
arr = []
for i, item in enumerate (userinfo_obj):
print (userinfo_obj[i].uid, item)
arr.append (userinfo_obj[i].uid)
## 建议使用下面的方法, 先获取子类对象,再获取父类对象
userinfo_arr = [userinfo_obj[i].uid for i, item in enumerate (userinfo_obj)] if len ( userinfo_obj) > 0 else []
## 使用列表生成式 直接通过父子之前的联系,获取父类对象
userinfo_arr = [item for item in pre_obj if item.userinfo.phone == item or item.userinfo.user_lv == item or item.userinfo.user_type == item]
2.获取Request数据
## get方法, 和 FILE获取的方法
# nickname = request.GET.get("xxx")
# avatar_file = request.FILES.get("avatar") #注意是FILES
# file = request.FILES.get ('file', None)
# POST,PUT,DELETE 的方法
# data = request.POST.dict()
userID = data.get ('userID',None) # 整型在表单中传递到后端 ,结果是字符串
data.pop("userID",None)
## 获取header参数, 与请求方法无关
# 获取 token, META 获取数据 ”HTTP_ + header字段名称大写“
# 使用.get 与是否使用get请求无关
token = request.META.get('HTTP_TOKEN') # 获取 token
print("我携带的请求头token是",token)
## 获取当前页面的请求参数, get的所有参数也是一个字典
data = request.GET.dict()
# &selecttype=phone&keywords=3017
args = ''
for k,v in data.items(): ## 迭代获取字典中的参数
if k != 'page':
args += f"&{k}={v}"
### 获取 请求的方法
me = request.method
return JsonResponse ({"msg": "错误的请求"}, status=404)
JsonResponse ({'data':data,"code":200},safe=False)
--safe=True表示传入的参数是JSON数据
--safe=False表示传入的参数可以不是JSON数据
serializer = UserSerializer(user, many=True)
--many=True表示 传入的参数需要包含多个对象,默认many=False,( 用first()取出一个对象是使用many=True会报错,需要用all()。
--many=False 表示传入的参数需要只包含一个对象可以与fist()搭配使用
注意:需要把文件放在同一个域下运行,异步会存在跨域的问题。
所以需要把这个文件添加到 template
模板文件中
// 静态资源文件
STATICFILES_DIRS=[
(os.path.join(BASE_DIR,'static'))
]
// 运行上线的静态资源文件
# STATIC_ROOT = os.path.join(BASE_DIR,'static')
// 模板文件
TEMPLATE=[
...
'DIRS': [os.path.join(BASE_DIR, 'templates')],
]
// 数据库文件配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'uniapp',
'USER':'root',
"PASSWORD":"123456",
"PORT":'3306',
"HOST":"localhost"
}
}
3.响应Response数据
(1.响应模板文件
from django.shortcuts import render
from django.http import HttpResponse,JsonResponse
def index(request):
return HttpResponse ('Hello world') // 响应内容
return render(request,'index.html') // 响应模板文件
return render(request,'index.html',{'type':'admin'} ) // 响应模板文件,携带参数,
(参数必须是json或者字典格式,键值注意双引号,然后在模板中就可以使用{{type}}进行输出)
# JsonResponse => 响应json数据
# HttpResponse => 响应字符串
# render => 响应模板文件
(2.响应Json数据
1.方法一: httpResponse + json.dumps + (前端JSON.parse)
def index(request):
return HttpResponse(json.dumps({'code':200,'msg':'接收成功!'}))
注意:前端接收到的是string类型的字符串, javascript可以采用JSON.parse()解析json对象
res = JSON.parse(res)
2.方法二:JsonResponse (注意一定是json或者字典数据,键值必须有双引号)
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse
@csrf_exempt
def get_ts_list(request):
return JsonResponse ({'msg': '您当前没有权限访问数据', 'code': 403})
注意:JsonResponse的其它参数: status=404, , safe=False, json_dumps_params={'ensure_ascii': False}
'json_dumps_params因为json序列化中文用的是ascii编码, 所以传到前台的中文是ascii字符码,需要这一步转化为中文。
3.方法二:serializers序列化数据集
(数据集被json解析, 一般和JsonResponse混合使用)
from django.core import serializers
def doSearchTeacher(request):
data_list = Teacher.objects.filter(**searchdata)
return JsonResponse({'msg':"查询成功",'data':serializers.serialize("json",data_list),'code':200})
注意:因为序列化的结果是字符串,返回的结果在列表中的field字典里面,所以在前端需要使用JSON.parse(data) 进行解析。
[{"model": "myhome.teacher", "pk": 2, "fields": {"number": "n165151", "password": 123456, "name": "牛大大", "register_time": "2022-07-05T14:04:49Z"}}]
4.redirect重定向
from django.shortcuts import redirect
def Login(request):
return redirect('https://www.baidu.com') ## 使用路由
return redirect("myhome_login") ## 使用别名
5.装饰器拦截(?)
Django(6): 中间件,自定义中间件_python开发笔记的博客-CSDN博客_django自定义中间件
这种方法适合于程序中只有少数几个需要登录拦截的url。
@login_required(login_url='/user/login/')
def homepage(request):
pass
5.models模型类的定义
1.models模型定义
Model模型:
1.模型是你的数据的唯一的、权威的信息源。它包含你所储存数据的必要字段和行为。
2.通常,每个模型对应数据库中唯一的一张表。
3.每个模型都是django.db.models.Model的一个Python子类。
4.模型的每个属性都表示为数据库中的一个字段。
5.Django提供—套自动生成的用于数据库访问的API;
6.这极大的减轻了开发父员的工作量,不需要面对因数据库变更而导致的无效劳
模型与数据库的关系:
模型(Model)负责业务对象和数据库的关系映射(ORM)ORM是“对象-关系-映射"的简称,
主要任务是:
1.根据对象的类型生成表结构
2.将对象、列表的操作,转换为sql语句
3.将sql查询到的结果转换为对象、列表
2.Mysql数据库配置
注意:迁移文件是框架通过一定的语法,生成的数据库表、可以自动向数据库中添加数据。
如果不执行迁移文件,模型中也依然可以连接到数据库的表。(手动建表)
1 创建mydb数据库
root 123456 一般是使用gb_2312或utf8字符集编码
2 修改当前项目的数据库配置 settings.py/ DATABASE
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mydb',
'USER': 'root',
'PASSWORD': '123456',
'HOST': 'localhost',
'PORT': '3306',
}
}
3.创建模型之前,确保当前的应用已经在配置文件中定义 settings.py/INSTALLED_APPS
INSTALLED_APPS = [
...
'myhome' ## 新添加的
]
4.引入模型
from .models import *
5.出现错误
注意:如果当前环境中没有安装MySQLDB的替代包,就会报错(mysqlclient) ,
(1.安装mysql的模块
pip install PyMySQL
(2.在应用下的__init.py文件添加
import pymysql
pymysql.install_as_MySQLdb()
3.数据库表反映射(建议)
如果有已知的数据库及表格想要直接生成相应的模型类。
进入到项目根目录然后运行下面代码则可以自动生成models模型文件
python manage.py inspectdb > myhome/models.py
普通的User表创建的模型
注意:一定要设置id自增,否则会出现以下出现id字段, 会导致插入数据失败
from django.db import models
class User(models.Model):
id = models.IntegerField(primary_key=True) # 没有设置id自增的情况
name = models.CharField(max_length=255, blank=True, null=True)
age = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'user'
4.ORM模型迁移
1. 在应用中的models.py文件中定义模型
class Stu(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField(default=24)
sex = models.CharField(max_length=1,default='0')
address = models.CharField(max_length=50,null=True)
2. 生成迁移文件
python manage.py makemigrations myhome
3. 执行迁移文件
python manage.py migrate
5.ORM关系映射
1.一对一模型
---- myhome/views.py 视图函数
def demo1(request):
## 一对一模型关系
### 一、创建数据
studata = {
'name':'张三',
'age':20,
'sex':'0',
'address':'山东',
}
# 创建学员对象
stuobj = models.Stu(**studata)
stuobj.save()
# 学员详情数据
infodata = {
'sid':stuobj,
'xueli':'大学',
'phone':'123456'
}
infoobj = models.Stuinfo(**infodata)
infoobj.save()
### 二、查询数据
# 获取学员详情对象 (通过学员对象)
obj = models.Stu.objects.last()
print(obj.name)
print(obj.stuinfo) ## 小写stuinfo代表,即可访问Stuinfo类的数据
print(obj.stuinfo.xueli)
# 获取学员对象信息 (通过学员详情对象,反向获取)
obj = models.Stuinfo.objects.first()
print(obj.xueli)
print(obj.sid) ## 小写stuinfo代表,即可访问Stuinfo类的数据
print(obj.sid.name)
### 三、删除数据
obj = models.Stu.objects.last()
obj.delete()
return HttpResponse('测试')
---- myhome/models.py (设置外键,因此两张表就连接在一起,可以互相访问)
# 学生详情表
class Stuinfo(models.Model):
sid = models.OneToOneField(Stu,on_delete=models.CASCADE)
xueli = models.CharField(max_length=20)
phone = models.CharField(max_length=20)
2.一对多模型
### 从 父类到子类 Banji > Stu > Stuinfo
# obj.stu_set.Stu属性 obj.stuinfo.Stuinfo属性
### 从 子类 到父类 tuinfo > Stu > BanjiS
# obj.sid子属性.Stu父属性 obj.bid子属性.Banji属性
## 获取方法
# 1. .get(id=xxx) 通过单独属性值获取对象(其实是对象,而不是字典),只能获取存在的单个对象数据,否则会出错
# 2. .all() 获取数据的对象
# 3. .first() 获取第一个对象
# 4. .last() 获取最后 一个对象
# 5. .filter(id=xxx) 通过过滤器获取数据(可以获取多个符合条件的数据),获取的是对象的集合,需要遍历输出
---- myhome/views.py 视图函数
def demo1(request):
'''一对多模型'''
# ### 一、创建数据
# bobj= models.Banji(bname='2班')
# bobj.save()
#
# s1 = models.Stu(name='小明',bid=bobj)
# s2 = models.Stu(name='小红',bid=bobj)
# s3 = models.Stu(name='小黑',bid=bobj)
# s1.save()
# s2.save()
# s3.save()
# ### 二、查询数据
# 获取学员对象 (通过班级获取)
# bobj = models.Banji.objects.first()
# print(bobj.bname)
# print(bobj.stu_set.all()) # stu_set访问学员对象
# 获取班级对象 (通过学员获取)
# sobj = models.Stu.objects.last ()
# print (sobj.name)
# print (sobj.bid)
# print (sobj.bid.bname) # bid属性访问班级对象
#
# sobj = models.Stu.objects.get (id=1)
# print ("get方法",sobj.name)
# sobj1 = models.Stu.objects.filter (age=24)
# print ("过滤器",sobj1)
### 三、删除数据
# obj = models.Banji.objects.get(id=2) # 删除班级,会把关联到当前班级的所有学生都删除
# obj.delete()
# obj = models.Stu.objects.get(id=3) # 删除学生,不会把关联到当前学生的班级删除
# obj.delete()
return HttpResponse('测试')
---- myhome/models.py
# 学生模型
# 班级表
class Banji(models.Model):
bname = models.CharField(max_length=20)
class Stu(models.Model):
name = models.CharField(max_length=20) ## 代表一个字符串
age = models.IntegerField(default=24)
sex = models.CharField(max_length=1,default='0')
address = models.CharField(max_length=50,null=True)
# 一对多 , 由于已经创建表,所以不允许设置外键,需要注释重新生成迁移文件
bid = models.ForeignKey(to='Banji',on_delete=models.CASCADE)
def __repr__(self): ### 可以解析出对象中的数据
return f'[<Stu: Stu object (id:{self.id},name:{self.name})>'
# 学生详情表
class Stuinfo(models.Model):
sid = models.OneToOneField(Stu,on_delete=models.CASCADE)
xueli = models.CharField(max_length=20)
phone = models.CharField(max_length=20)
3.多对多模型
# 子类访问父类 Teacher > Banji
ts[0].bid.set(bs) ## 一个老师教多个班级
# 父类访问子类 Banji > Teacher
bs[1].teacher_set.set(ts)
---- myhome/views.py 视图函数
def demo1(request):
'''多对多模型'''
### 一、创建数据
# 添加老师和班级数据
# b1 = models.Banji(bname="1班")
# b2 = models.Banji(bname="2班")
# b3 = models.Banji(bname="3班")
# b1.save()
# b2.save()
# b3.save()
# t1 = models.Teacher(tname="王老师")
# t2 = models.Teacher(tname="张老师")
# t3 = models.Teacher(tname="李老师")
# t1.save()
# t2.save()
# t3.save()
# 添加关系
bs = models.Banji.objects.all()
ts = models.Teacher.objects.all()
# ts[0].bid.set(bs) ## 一个老师教多个班级
# bs[1].teacher_set.set(ts) ## 一个班级有多个老师
### 二、查询数据
# 查询班级信息 (通过老师)
r = ts[0].bid.all()
print(r)
# 查询老师信息 (通过班级)
r1 = bs[0].teacher_set.all()
print(r1)
### 三、删除数据
# 删除班级不会影响教师,删除教师不会影响班级,只会把相应的关系断开
return HttpResponse('测试')
---- myhome/models.py
# 教师表
class Teacher(models.Model):
## 先把已经存在的表给删除,再创建所有的表
# 先注释执行 迁移文件的两条命令, 再取消注释 重新执行迁移文件命令
tname = models.CharField(max_length = 20)
bid = models.ManyToManyField(to="Banji")
6.ORM实战事例
1.一对一模型
对应依赖,一般用于简介与详情, 删除父类,子类也会删除。 子类的对应属性是父类对象
父类访问子类
obj.stuinfo.stuinfo属性 ## 小写stuinfo代表,即可访问Stuinfo类的数据
子类访问父类
obj.sid.Stu父类属性 ## sid是子类连接父类的参数
class Stuinfo(models.Model):
sid = models.OneToOneField(Stu,on_delete=models.CASCADE) ## 删除不影响对方
xueli = models.CharField(max_length=20)
phone = models.CharField(max_length=20)
2.一对多模型
外键依赖 其它表而存在,如果其它表删除,则它所有关联到这个表的数据会被删除
### 父类访问子类
# obj.stu_set.Stu属性/all() # stu代表Stu, 而通过stu_set父类可以访问子类的属性
### 子类访问父类
# obj.bid.Stu父属性/all() # bid是Stu连接Banji的外键
class Banji(models.Model):
bname = models.CharField(max_length=20)
class Stu(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField(default=24)
sex = models.CharField(max_length=1,default='0')
address = models.CharField(max_length=50,null=True)
# 一对多 , 由于已经创建表,所以不允许设置外键,需要注释重新生成迁移文件
bid = models.ForeignKey(to='Banji',on_delete=models.CASCADE)
3.多对多模型
一般用于多个元素与多个元素交互,
删除不会影响到另外一个表
# 父类访问子类 (一个班级有多个老师)
bs[1].teacher_set.set(ts)/all() ## teacher表示Teacher,而teacher_set可以访问Teacher属性
# 子类访问父类 (一个老师教多个班级)
ts[0].bid.set(bs) /all() ## bid是Teacher访问Banji的中间字段,可以认为是外键(有中间表)
class Teacher(models.Model):
## 先把已经存在的表给删除,再创建所有的表
# 先注释执行 迁移文件的两条命令, 再取消注释 重新执行迁移文件命令
tname = models.CharField(max_length = 20)
bid = models.ManyToManyField(to="Banji")
6.models模型类的创建
1.模型常用字段
-
models.AutoField() —自增列 = int(11) 如果没有的话,默认会生成一个名称为 id 的列,如果要显示的自定义一个自增列,必须将给列设置为主键primary_key=True。
-
models.CharField() —字符串字段 单行输入,用于较短的字符串,如要保存大量文本, 使用 TextField。必须 max_length=‘最大长度’ 参数,django会根据这个参数在数据库层和校验层限制该字段所允许的最大字符数。
-
models.BooleanField() —布尔类型=tinyint(1) 不能为空,Blank=True
-
models.ComaSeparatedIntegerField() —用逗号分割的数字=varchar 继承CharField,所以必须 max_lenght 参数,
-
models.DateField() —日期类型 date 对于参数,auto_now = True 则每次更新都会更新这个时间;auto_now_add 则只是第一次创建添加,之后的更新不再改变。
-
models.DateTimeField() —日期类型 datetime 同DateField的参数,(常用)
-
models.Decimal() —十进制小数类型 = decimal 必须指定数据位数,不包括小数点 max_digits和小数位decimal_places
user_lv = models.DecimalField(verbose_name="用户等级",max_digits=4, decimal_places=2,default=0.01) 0 ~ 99.99
-
models.EmailField() —字符串类型(正则表达式邮箱) =varchar 对字符串进行正则表达式 一个带有检查 Email 合法性的 CharField,不接受 maxlength 参数。
-
models.FloatField() —浮点类型 = double 浮点型字段。 必须提供两个 参数, 参数描述:
(1)max_digits:总位数(不包括小数点和符号)
(2)decimal_places:小数位数。如:要保存最大值为 999 (小数点后保存2位),你要这样定义字段:models.FloatField(…,max_digits=5, decimal_places=2),要保存最大值一百万(小数点后保存10位)的话,你要这样定义:models.FloatField(…,max_digits=19, decimal_places=10) -
models.IntegerField() —整形 用于保存一个整数
from django.core.validators import MinValueValidator, MaxValueValidator size = models.IntegerField(u'尺寸大小', validators=[ MinValueValidator(1), MaxValueValidator(42) ]) 在Django中,传入的数据是字符串, 但数据类型是IntegerField, 它会自动转化为整型。 并且,即使在前端确认数据是Number类型,但经过了表单的传输,数据依旧会从整形转变为字符串。 我们可以理解为Django对整型数据做了优化与增强容错性的功能。 相当于解析表单字符串数据。
-
models.BigIntegerField() —长整形
integer_field_ranges = {undefined
‘SmallIntegerField’: (-32768, 32767),
‘IntegerField’: (-2147483648, 2147483647),
‘BigIntegerField’: (-9223372036854775808, 9223372036854775807),
‘PositiveSmallIntegerField’: (0, 32767),
‘PositiveIntegerField’: (0, 2147483647),
} -
models.IPAddressField() —字符串类型(ip4正则表达式) 一个字符串形式的 IP 地址, (如 “202.1241.30″)。
-
models.GenericIPAddressField() —字符串类型(ip4和ip6是可选的) 参数protocol可以是:both、ipv4、ipv6 验证时,会根据设置报错
-
models.NullBooleanField() —允许为空的布尔类型 类似 BooleanField, 不过允许 NULL 作为其中一个选项。 推荐使用这个字段而不要用 BooleanField 加 null=True 选项。 admin 用一个选择框 (三个可选择的值: “Unknown”, “Yes” 和 “No” ) 来表示这种字段数据。
-
models.PositiveIntegerField() —正Integer 类似 IntegerField, 但取值范围为非负整数(这个字段应该是允许0值的…可以理解为无符号整数)
-
models.PositiveSmallIntegerField() —正smallInteger 正小整型字段,类似 PositiveIntegerField, 取值范围较小(数据库相关)SlugField“Slug” 是一个报纸术语。 slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符。它们通常用于URLs。 若你使用 Django 开发版本,你可以指定 maxlength。 若 maxlength 未指定, Django 会使用默认长度: 50,它接受一个额外的参数:prepopulate_from: 来源于slug的自动预置列表
-
models.SlugField() —减号、下划线、字母、数字 它们通常用于URLs。
-
models.SmallIntegerField() —数字 数据库中的字段有:tinyint、smallint、int、bigint. 类似 IntegerField, 不过只允许某个取值范围内的整数。(依赖数据库)
-
models.TextField() —字符串=longtext ,一个容量很大的文本字段, admin 管理界面用 多行编辑框表示该字段数据。
-
models.TimeField() —时间 HH:MM[:ss[.uuuuuu]] 时间字段,类似于 DateField 和 DateTimeField。
-
models.URLField() —字符串,地址正则表达式 用于保存URL。若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在(即URL是否被有效装入且没有返回404响应).
-
models.BinaryField() —二进制
-
models.ImageField() —图片 类似 FileField, 不过要校验上传对象是否是一个合法图片。它有两个可选参数:height_field 和 width_field,如果提供这两个参数,则图片将按提供的高度和宽度规格保存。该字段要求 Python Imaging 库。
-
models.FilePathField() —选择指定目录按限制规则选择文件,有三个参数可选, 其中”path”必需的,这三个参数可以同时使用, 参数描述:
(1)path:必需参数,一个目录的绝对文件系统路径。 FilePathField 据此得到可选项目。 Example: “/home/images”;
(2)match:可选参数, 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名。 注意这个正则表达式只会应用到 base filename 而不是路径全名。 Example: “foo。*\。txt^”, 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif;
(3)recursive:可选参数, 是否包括 path 下全部子目录,True 或 False,默认值为 False。
match 仅应用于 base filename, 而不是路径全名。
如:FilePathField(path=”/home/images”, match=”foo.*”,recursive=True)…会匹配/home/images/foo.gif 而不匹配 /home/images/foo/bar.gif
-
models.FileField() —文件上传字段。 要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径。 这个路径必须包含 strftime formatting, 该格式将被上载文件的 date/time 替换(so that uploaded files don’t fill up the given directory)。在一个 model 中使用 FileField 或 ImageField 需要以下步骤:在你的 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件。 (出于性能考虑,这些文件并不保存到数据库。) 定义 MEDIA_URL 作为该目录的公共 URL。 要确保该目录对 WEB 服务器用户帐号是可写的。在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django 使用 MEDIA_ROOT 的哪个子目录保存上传文件。你的数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT)。 出于习惯你一定很想使用 Django 提供的 get__url 函数。举例来说,如果你的 ImageField 叫作 mug_shot, 你就可以在模板中以 {undefined{ object。get_mug_shot_url }} 这样的方式得到图像的绝对路径。
-
models.PhoneNumberField() —一个带有合法美国风格电话号码校验的 CharField(格式:XXX-XXX-XXXX)
-
models.USStateField() —美国州名缩写,由两个字母组成。
-
models.XMLField() —XML字符字段,校验值是否为合法XML的 TextField,必须提供参数:
schema_path:校验文本的 RelaxNG schema 的文件系统路径。
2.模型常用参数
null=True
数据库中字段是否可以为空
blank=True
django的 Admin 中添加数据时是否可允许空值
primary_key = False
主键,对AutoField设置主键后,就会代替原来的自增 id 列
auto_now=True 和 auto_now_add=True
auto_now 自动创建—无论添加或修改,都是当前操作的时间
auto_now_add 自动创建—永远是创建时的时间, (用于创建时间)
choices
数据库中保存的是数字,而表单输入的数据是整型(其实结果也是字符串),输出的数据是 中文
---- models.py 模型
class User(models.Model):
SEX_TYPES = (
(0,u'男'), # 默认utf8编码 ,不添加 u会报错
(1,u'女')
)
sex = models.IntegerField(verbose_name="性别",choices=SEX_TYPES,default=0,validators=[ MinValueValidator(0), MaxValueValidator(1)])
---- serializers.py 序列化模型数据,重写方法
class UserSerializer (serializers.ModelSerializer):
# 重写sex字段的方法
sex = serializers.SerializerMethodField() # 重写字段,并调用
class Meta:
model = User
fields = '__all__' # 指定要序列化的字段﹐如果是所有的话可以直接使用'’__all_'来代替
# fields = ('username', 'password', 'phone','mail', 'address', 'des', 'userImg', 'Relation')
def get_sex(self, obj): # 重写字段, 返回方法 get_xxx_display()
return obj.get_sex_display()
------- views.py 视图函数
user = User.objects.filter()
print ("数据是",user[0].get_sex_display ()) ## obj.get_xxx_display() 得到 男
-----
total_self_data["user_detail"]["user_type"] =item.userinfo.get_user_type_display()
### 如果是原生数据集,去显示,那结果是choice对对应的数据,因为经过序列化数据,就会被重写的数据覆盖。
max_length 最大长度
default 默认值
verbose_name Admin中字段的显示名称
name|db_column 数据库中的字段名称
unique=True 不允许重复
例如用户名注册时候是不允许重复的,在username字段里设置,不让重复
db_index = True 数据库索引
editable=True 在Admin里是否可编辑
error_messages=None 错误提示
#把错误提示修改成你想要的报错,这里加个字典来完成
gender = models.CharField(max_length=2,choices = GENDER_CHOICE,
error_messages={"错误类型":"错误原因"})
auto_created=False 自动创建
help_text 在Admin中提示帮助信息
validators=[] 提示区间,例如电话号码范围
upload-to 文件上传功能 在 FileField 里加入
#指明上传的文件防止根目录下的/upload/文件夹下
file = modles.FileField(upload-to = "./upload/")
3.创建表的问题
python manage.py makemigrations xxx
python manage.py migrate
如果项目下已经生成migrations文件,可能需要重新删除。
如果新生成的表与旧表相连接, 并且使用 OneToOneField 与旧表相连 则需要删除所有的旧表。
未来开发目标:
一、 等级设置,, (使用会话管理 session)
0-9.99
以首次登录时间,- 访问超过 30次, 并且 间隔时间少于2小时,每次只计算 最高3个小时,可以获取 (60x2)/ 0.05 超过时间 0.06
二、session设置 (cookie)
三、regis设置
4.测试开发接口
常用命令:创建项目: django-admin startproject TEST
创建应用: (进入TEST ) Django-admin startapp myApp / python manage.py startapp myApp
创建数据库: 在navicat中创建数据库,需要gb2312的编码(中文支持)(默认是SQLDB数据库)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mydb',
'USER': 'root',
'PASSWORD': '123456',
'HOST': 'localhost',
'PORT': '3306',
}
}
生成迁移文件: python manage.py makemigrations myApp;
执行迁移文件: python manage.py migrate
需要改变的位置: Test/settings.py中的应用名称
Test/urls.py 路由文件中引入应用的模块,
1.创建完应用后(myUser) BAPI项目,myUser应用
1.首先在BAPI项目下安装应用模块,( BAPI/setting.py)
INSTALLED_APPS = [
...
'rest_framework', ## api应用模块
'myUser' ## 应用
]
数据库不修改,使用默认的sqlite数据库
2.在项目下设置入口文件 (BAPI/urls.py)
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path("user/",include('myUser.urls')) ## 把新添加的应用加入到路由中
]
3.在应用下创建数据库模型(myUser/models.py) 然后可以生成mysql数据库表
from django.db import models
# Create your models here.
class User(models.Model):
GENDER_CHOICES={
(1,'male'),
(2,'female'),
}
name=models.CharField(max_length=32)
sex=models.IntegerField(choices=GENDER_CHOICES)
birth=models.DateField(blank=True,null=True)
desc=models.TextField(max_length=1024)
4.在应用下的视图函数中与模型连接(myUser/views.py)
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from . import models,serializer
# Create your views here.
class UserViewSet(ModelViewSet):
queryset = models.User.objects.all()
serializer_class = serializer.UserModelSerializer
5.在应用下创建视图函数依赖的串行化文件serializer.py (myUser/serializer.py)
from rest_framework import serializers
from . import models
class UserModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields="__all__"
6.将应用下的路由与视图函数连接(myUser/urls)
from rest_framework.routers import DefaultRouter
from . import views
urlpatterns=[]
router= DefaultRouter()
router.register('users',views.UserViewSet)
urlpatterns+=router.urls # 将添加的数据写入到sqlite中
7.Django模板语法
1.for…in遍历语句
{% for i in data %}
<tr>
<th scope="row">{{i.id}}</th>
<th><img src="{{i.img_url}}" alt="" style="max-width:100%;"></th>
<th>{{i.name}}</th>
<th>{{i.author}}</th>
<th>{{i.price}}</th>
<th>{{i.publisher}}</th>
<th>{{i.pub_dte}}</th>
<th>{{i.abstract}}</th>
<th>
<a href="">删除</a>
<a href="">修改</a>
</th>
</tr>
{% endfor %}
{% comment %} 注释内容
.....
{% endcomment %}
(1.enumerate枚举遍历
注意:在for …in模板中不能直接使用 enumerate, data.items(), data.values(), data.keys(), 以及{{ }}插值里面不允许直接进行计算。
---- 视图文件中
def title_list(request):
data_list = StudentGraduateArticle.objects.all()
return render(request,'admin/title_list.html',{'data_list':enumerate(data_list)})
---- 模板中使用
{% for key,item in data_list %}
<tr class="gradeX" data-id="{{item.id}}">
<td>{{key | add:1}}</td>
<td>{{item.student_name}}</td>
<td>{{item.teacher_name}}</td>
<td>
<div class="tpl-table-black-operation">
<a href="javascript:;" class="tpl-table-black-operation-del" onclick="onDeleteData('{{item.id}}')">
<i class="am-icon-trash"></i> 删除
</a>
</div>
</td>
</tr>
{% endfor %}
2.if…else选择语句
{% if xxxx=xxx %} aaa {% endif %} 条件语句(注意添加空格)
{% if xxxx=xxx %} aaa {% elif %} bbbb {% else %} ccccc {% endif %}
3.{{ }}插值语法
注意:{{… }} 里面不允许直接进行计算, 也不允许使用 ? : 三目运算符(本质上,python也不支持这个语法)
语法结构就使用 {% %} 进行包裹
数值就使用 {{ }}页面插值
Django 的是 {%csrf_token%} , . 或者 / 连接文件, {% %}进行的包裹
Laravel 的是 {{ csrf_field() }}, . 或者 / 或者 \ 连接文件, @ 和 {{ }} 进行包裹
结合过滤器使用:{{ A|date:“m-d” }} 12-02 (默认输出的日期格式不是字符串)
(1.配合过滤器的使用
Django中的过滤器
Django中过滤器用于在网页中对render传回的数据进行一系列处理,以下列举出常用的一些方法:
1、add :将value的值增加。使用形式为:{{ value | add: “2”}}。
2、addslashes:在value中的引号前增加反斜线。使用形式为:{{ value | addslashes }}。
3、capfirst:value的第一个字符转化成大写形式。使用形式为:{{ value | capfirst }}。
4、cut:从给定value中删除所有arg的值。使用形式为:{{ value | cut:arg}}。
5、date: 格式化时间格式。使用形式为:{{ value | date:“Y-m-d H:M:S” }}
6、default:如果value是False,那么输出使用缺省值。使用形式:{{ value | default: “nothing” }}。例如,如果value是“”,那么输出将是nothing
7、default_if_none:如果value是None,那么输出将使用缺省值。使用形式:{{ value | default_if_none:“nothing” }},例如,如果value是None,那么输出将是nothing
8、dictsort:如果value的值是一个字典,那么返回值是按照关键字排序的结果
使用形式:{{ value | dictsort:“name”}},例如,
如果value是:
[{‘name’: ‘python’},{‘name’: ‘java’},{‘name’: ‘c++’},]
那么,输出是:
[{‘name’: ‘c++’},{‘name’: ‘java’},{‘name’: ‘python’}, ]
9、dictsortreversed:如果value的值是一个字典,那么返回值是按照关键字排序的结果的反序。使用形式:与dictsort过滤器相同。
10、divisibleby:如果value能够被arg整除,那么返回值将是True。使用形式:{{ value | divisibleby:arg}},如果value是9,arg是3,那么输出将是True
11、escape:替换value中的某些字符,以适应HTML格式。使用形式:{{ value | escape}}。例如,< 转化为 <> 转化为 >’ 转化为 '" 转化为 "
13、filesizeformat:格式化value,使其成为易读的文件大小。使用形式:{{ value | filesizeformat }}。例如:13KB,4.1MB等。
14、first:返回列表/字符串中的第一个元素。使用形式:{{ value | first }}
16、iriencode:如果value中有非ASCII字符,那么将其进行转化成URL中适合的编码,如果value已经进行过URLENCODE,改操作就不会再起作用。使用形式:{{value | iriencode}}
17、join:使用指定的字符串连接一个list,作用如同python的str.join(list)。使用形式:{{ value | join:“arg”}},如果value是[‘a’,‘b’,‘c’],arg是’//'那么输出是a//b//c
18、last:返回列表/字符串中的最后一个元素。使用形式:{{ value | last }}
19、length:返回value的长度。使用形式:{{ value | length }}
20、length_is:如果value的长度等于arg的时候返回True。使用形式:{{ value | length_is:“arg”}}。例如:如果value是[‘a’,‘b’,‘c’],arg是3,那么返回True
21、linebreaks:value中的"\n"将被
替代,并且整个value使用
包围起来。使用形式:{{value|linebreaks}}
22、linebreaksbr:value中的"\n"将被
替代。使用形式:{{value |linebreaksbr}}
23、linenumbers:显示的文本,带有行数。使用形式:{{value | linenumbers}}
24、ljust:在一个给定宽度的字段中,左对齐显示value。使用形式:{{value | ljust}}
25、center:在一个给定宽度的字段中,中心对齐显示value。使用形式:{{value | center}}
26、rjust::在一个给定宽度的字段中,右对齐显示value。使用形式:{{value | rjust}}
27、lower:将一个字符串转换成小写形式。使用形式:{{value | lower}}
30、random:从给定的list中返回一个任意的Item。使用形式:{{value | random}}
31、removetags:删除value中tag1,tag2…的标签。使用形式:{{value | removetags:“tag1 tag2 tag3…”}}
32、safe:当系统设置autoescaping打开的时候,该过滤器使得输出不进行escape转换。使用形式:{{value | safe}}
33、safeseq:与safe基本相同,但有一点不同的就是:safe是针对字符串,而safeseq是针对多个字符串组成的sequence
34、slice:与python语法中的slice相同。使用形式:{{some_list | slice:“2”}}
37、striptags:删除value中的所有HTML标签.使用形式:{{value | striptags}}
38、time:格式化时间输出。使用形式:{{value | time:“H:i”}}或者{{value | time}}
39、title:转换一个字符串成为title格式。
40、truncatewords:将value切成truncatewords指定的单词数目。使用形式:{{value | truncatewords:2}}。例如,如果value是Joel is a slug 那么输出将是:Joel is …
42、upper:转换一个字符串为大写形式
43、urlencode:将一个字符串进行URLEncode
46、wordcount:返回字符串中单词的数目
4.{% url ’ xxxx’ %}路由加载
注意:index.html网页文件中不能有含参数的超链接注释,否则会编译不通过
<a href="{% url 'myhome_abcd' n %}">带参数的路由名:跳转</a>
5.request.xx页面请求
1.获取当前路由
request.path
<body data-type=" {% if request.path == '/myadmin/' %} index {% endif %}"></body>
2.获取当前会话
request.session
{{ request.session.userinfo.type }}
6.extends继承模板
1.被继承的模板文件中的“插槽”部分
{% block con %}
<div> ...</div> # 这是父模板默认值
{% endblock %}
2.继承模板文件,并在块状“插槽”中写入数据
{% extends 'myadmin/base.html' %}
{% block con %}
这是一个文件
{% endblock %}
3.使用实例
(1.myadmin/base.html (父模板文件)
......<!--header-->
{% block con %}
<div> ...</div> # 这是父模板默认值
{% endblock %}
......<!--footer-->
(2. myadmin/index.html(子继承父模板文件)
{% extends 'myadmin/base.html' %}
{% block con %}
<h2>这是一个文件</h2>
{% endblock %}
7.include模板引入
1.被引入的公共模板(如header)
<header>
这是头部内容
</header>
2.引入公共模板文件
{% include "header.html" %}
3.使用实例
----- header.html模板文件
<header> ... <header>
----- test.html引入模板文件
{% include "header.html" %}
8.Django功能模块
1.make_password密码加密
from django.contrib.auth.hashers import make_password,check_password
data["password"] = make_password(data["password"],None,'pbkdf2_sha256') # 密码加密
url = reverse('myadmin_users_index') ## 跳转路由
return HttpResponse(f"<script>alert('会员添加成功');location.href='{url}'</script>")
registerdate = models.DateTimeField(auto_now_add=True)
获取当前的注册时间,不会传入data表单中的数据,对象模型中会自动生成当前时间
{{i.registerdate|date:'Y-m-d H:i:s'}} 输出时间的格式
{{i.sex|default:'未知'}} 设置默认值
data["face"]
上传的文件不能通过字典的键来获取,会报错,并且表单中的文件也不会出现在dict字典里
Users(**data) 关键字参数,将数据以键值的方式写入,结果也依旧是是键值数据,并不是字典。
相反,如果函数中是以**data作为形参,传入a=10,b=10, 则会生成一个字典{a:'',b:''}。
with open (f'./static/uploads/{name} ', 'wb+') as fp:
<script src="/static/myadmin/assets/js/jquery.min.js"></script>
除了创建文件是需要指定当前项目,其它图片,css,js文件的路径起点都是根路径“/”
21.数据删除
$.get("{% url 'myadmin_users_delete' %}", {"id":id},function(data){
if(data["code"] == 0){
node.parents("tr").remove()
}
alert(data['msg'])
},'json') 使用ajax发起请求
os.remove('.'+data["face"]) 数据库中并没有保存图片,而是保存图片的路径。data["face"] =
JsonResponse({'code':1,'msg':'删除失败'}) 返回json响应数据
2.paginator分页模块
insert into myadmin_users select null,phone,password,face, nickname,homeaddress,sex,usertype,registerdate from myadmin_users
把查找的数据,添加到myadmin_users表中
---- myadmin/templatetags/pagetag.py
......
----- users/index.html
{% load pagetag %} 引入文件
{% 'iloveyou'|kong_upper %} 自定义过滤器
{% jia 10 10 %} 自定义标签
Django框架中的分页功能
----- users/UsersViews.py
from django.core.paginator import Paginator
# 后台用户列表
def index(request):
# print(settings.STATICFILES_DIRS)
# 查尚所有的会员数据
data = Users.objects.filter () ## 当前页面对当前数据进行过滤,不需要指定action的值
# 接收搜索条件
selecttype = request.GET.get ('selecttype', None)
keywords = request.GET.get ('keywords', None)
if selecttype:
# 有搜索条件
if selecttype == 'all':
# 按照多字段进行条件搜索
from django.db.models import Q
data = data.filter(Q(phone__contains = keywords)|Q(nickname__contains = keywords)|Q(sex__contains = keywords)|Q(usertype__contains = keywords))
else:
serdata = {f'{selecttype}__contains':keywords}
data = data.filter(**serdata)
# 查询所有的会员数据
# data = Users.objects.all() ### 对数据进行分页
# 实例化分页对象,参数1,数据集,参数2每页显示的条数
p = Paginator(data,10)
# 接收当前的页码数
inx_p = request.GET.get('page',1)
# 获取当前页的数据
# userlist = p.page (inx_p)
userlist = p.get_page(inx_p) ### 获取当前面的数据
# 分配数据
content = {'data': userlist,'contacts':userlist}
# 加载模板
return render (request, 'myadmin/users/index.html',content)
Django框架中的分页模板
----- myadmin/templatetags/pagetag.py
from django import template
from django.utils.html import format_html
register = template.Library()
#自定义过滤器
@register.filter
def kong__upper(val):
#print ( 'val from template: ' ,val)
return val.upper()
#自定义标签
@register.simple_tag
def jia(a,b):
res = int(a) + int(b)
return res
# 自定义分布优化 标签
@register.simple_tag
def showpage(num, request):
'''
num 总页数
p 当前页
'''
p = int(request.GET.get('page',1))
# h获取当前页面的请求参数
data = request.GET.dict() ## 分页处理数据前添加请求参数
# &selecttype=phone&keywords=3017
args = ''
for k,v in data.items():
if k != 'page':
args += f"&{k}={v}"
#print (args)
start = p - 5
end = p + 4
# 半断当前页如果小于5
if p <= 5:
start =1
end = 10
# 判断当前页如果大于总页数-5
if p > num-4:
start = num - 9
end = num
# 总页数小于等于10
if num <=10:
start = 1
end = num
s = ''
s += f'<li><a href="?page=1{args}">首页</a></li>'
if p-1 < 1:
s += f'<li class="am-disabled"><a href="?page=1{args}">«</a></li>'
else:
s += f'<li><a href="?page={p-1}{args}">«</a></li>'
for x in range (start, end + 1):
if x == p:
s += f'<li class="am-active"><a href="?page={x}{args}">{x}</a></li>'
else:
s += f'<li><a href="?page={x}{args}">{x}</a></li>'
if p+1 > num:
s += f'<li class="am-disabled"><a href="?page={num}{args}">»</a></li>'
else:
s += f'<li><a href="?page={p+1}{args}">»</a></li>'
s += f'<li><a href="?page={num}{args}">尾页</a></li>'
return format_html(s) ### 返回格式化成html节点
----- users/index.html
<li>总页数:{{ data.paginator.num_pages }}</li>
<li>总条数:{{ data.paginator.count }}</li>
{% load pagetag %}
{% showpage data.paginator.num_pages request %}
3.csrf 跨站表单提交*
1.方法一
不管是提交文件,还是get,post请求, form表单,还是ajax都需要以下这个csrfmiddlewaretoken字段,否则会出现403错误
{%csrf_token%} 提交的表单添加一个csrfmiddlewaretoken字段(laravel是_token)
2.方法二:(api普通接口专用)
也可以在视图添加注解
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def del_ts_list(request):
pass
注意:layui的自动上传也会被禁止,这个比Laravel更严格,(Laravel是允许的),但可以使用csrf注解, 允许跨域上传。
4.session身份认证
(1.配置文件
使用session前,在settings.py添加以下配置,把session保存在缓存中。
注意执行db迁移命令 python manage.py migrate
## session会话相关配置
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
## 添加SESSON_COOKIE_AGE 默认两周(单位s)
SESSON_COOKIE_AGE = 60*60*24*7*2
## 关闭浏览器失效(默认False)
SESSION_EXPIRE_AT_BROSER_CLOSE = False
其它配置
'''
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)
SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1800 # Session的cookie失效日期,单位秒(默认2周)
SESSION_EXPIRE_AT_BROWSER_CLOSE = True # 是否关闭浏览器使得Session过期(默认关闭)
SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认)
(2.普通使用
启用会话后,每个HttpRequest对象具有一个session属性,它是一个类字典对象,
可以保存一个字符串或者一个对象,比localStorage,sessionStorage能力更强。
# session设置
request.session[key] = value;
# session获取
request.session.get(key,default=Node)
# session删除
# (1.删除单个key,不存在时报错
del request.session['a']
# (2.清除所有会话,但不会删除数据
request.session.clear()
# (3.删除当前的连接会话数据(建议使用)
request.session.flush()
(3.封装成中间件
在myhome下创建middleware文件夹,添加_ init _.py文件, 然后创建LoginCheckMiddleware.py文件
添加以下内容
import re
from django.http import JsonResponse
from django.shortcuts import redirect
from django.utils.deprecation import MiddlewareMixin
# 对请求进行拦截
class LoginCheckMiddleware(MiddlewareMixin):
def process_request(self, request):
# 允许登录的路由
allow = [
'/login',
'/login/doLogin',
'/register',
'/register/doRegister',
'/doLogout',
'/',
]
# 检验路由,和session
route = request.path
userinfo = request.session.get('userinfo',None)
haveAuth = True if(userinfo) else False
for item in allow:
if route == item:
haveAuth = True
# 没有访问权限,则直接拦截,返回信息
print("结果是",haveAuth)
if not haveAuth:
# return JsonResponse({'msg':'没有访问权限!请登录','code':403})
return redirect('myhome_login')
然后在setting.py中配置中间件, 在MIDDLEWARE 下添加一个自己定义的中间件
MIDDLEWARE = [
.....
## 登录拦截器
'myhome.middleware.LoginCheckMiddleware.LoginCheckMiddleware'
]
在视图或者模板文件里使用
视图文件中是字典形式
def design_title(request):
id = request.session["userinfo"].get("id",None)
name = request.session["userinfo"].get("name",None)
模板中是对象形式
ID: {{request.session.userinfo.number}}
3.rest_framework接口框架
1.安装模块
pip install djangorestframework
向应用添加
INSTALLED_APPS = [
...
'rest_framework',
]
替换视图路径
urlpatterns = [
...
path('api-auth/', include('rest_framework.urls'))
]
-新添加的模块
import os,re,random, time
django-cors-headers, PyJWT ,restframework-jwt
pycryptodome
2.response响应数据
1.Response响应结果
模块的导入
from .. import models ### 将这整个文件都导入,并把models当作一个大的模块
from ..models import * ### 将这整个文件的内容都暴露,就像在使用当前文件生成的内容一样
1.状态模块
from django.http import Http404 ## 错误页
from rest_framework.views import APIView ### framework的api视图
from rest_framework.response import Response ### 响应
from rest_framework import status ### 状态码
from .models import Snippet ### 模型
from .serializers import SnippetSerializer ### 序列化器, 一般是无法看到查询集的数据。
2.返回结果
Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
- 将请求的数据(如JSON格式)转换为模型类对象 反序列化
- 操作数据库
- 将模型类对象转换为响应的数据(如JSON格式) 序列化serializers
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yqhyq4w4-1661622888517)(C:\Users\Icy-yun\AppData\Roaming\Typora\typora-user-images\image-20211129092046354.png)]
3.serializer序列化
1.序列化的作用
class userList(APIView):
def get(self, request):
user = User.objects.filter()
### 1.普通的Django
### 只有获取单个数据,无法遍历出结果,但在render 函数处理后就可以遍历, 数据集使用 i[0].id获取数据
# arr = []
# for i in data:
# arr.append(data[i]) ## 没有序列化,就是没有结果, 这是一个数据集
# print("结果是",arr)
### 2.使用序列化
serializer = UserSerializer(user, many=True)
print("序列化数据",serializer.data)
### 3.有序列化的结果, 只会在最后的JSONResponse的时候解析。所以中间的序列化数据也无法更改。但可以去修改序列化类。 print(**serializer.data) 无效,因为序列化的结果不是dict字典格式。
print(type(serializer.data)) ## 这是一个字典数据
data = {'data':serializer.data,'code':200}
# # return JsonResponse(serializer.data, safe=False) ### json格式数据
return JsonResponse (data)
2.序列化的使用方法
### 1.序列化一个类
class UserinfoSerializer (serializers.ModelSerializer):
class Meta:
model = Userinfo
fields = '__all__' # 指定要序列化的字段﹐如果是所有的话可以直接使用'’__all_'来代替
# fields = ('username', 'password', 'phone','mail', 'address', 'des', 'userImg', 'Relation')
### 2.使用序列化
userinfo_obj = Userinfo.objects.filter(uid_id=user_obj[0].id)
userinfo_serializer = UserinfoSerializer(userinfo_obj, many=True) ## 序列化
data = { ### 通过all, filter, get等获取的是数据集,无法直接使用 ** ,它们只是一个对象。必需使用序列化。
"data":user_serializer.data, ## 序列化的数据不能使用**
"detail":userinfo_serializer.data
}
4.middleware中间件
1.corsheaders跨域
# 安装模块 pip install django-cors-headers (3.10.0)
# 安装app
import corsheaders
INSTALLED_APPS = [
....
'corsheaders'
]
# 中间防火墙
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # 放在开头
...
]
#跨域增加忽略
CORS_ORIGIN_WHITELIST = (
'http://localhost:63343',
'http://runapi.showdoc.cc',
'http://www.showdoc.com.cn'
)
CORS_ALLOW_CREDENTIALS = True # 允许携带cookie
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
'VIEW',
)
CORS_ALLOW_HEADERS = (
'XMLHttpRequest',
'X_FILENAME',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
'Pragma',
)
2.jwt身份认证
开始写项目的时候,就应该设计包含token的字段的表
# 模块认证只保存在服务端,部署的时候可能会出问题
# 即使rest_framework JWT组件身份认证很便利,会把结果保存在服务端,用户很多的时候,会造成很严重的性能问题,不建议使用。
# 建议自己定义hashlib或者使用jwt模块认证,并且保存在数据库中。
# 创建模型,注意,应该在开始建立项目的时候就应设计
1.1 base64,jwt,hashlib的使用
import base64
import jwt
##### 1.使用base64
res_1 = base64.b64decode ('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9')
res_2 = base64.b64decode('eyJ1c2VybmFtZSI6Ilx1OGZkMFx1N2VmNFx1NTQ5N
lx1NTU2MVx1NTQyNyIsInNpdGUiOiJodHRwczovL29wcy1jb2ZmZWUuY24ifQ==')
print (res_1)
# b'{"typ":"JWT","alg":"HS256"}'
print(res_2)
# b'{"username":"\\u8fd0\\u7ef4\\u5496\\u5561\\u5427","site":"https://ops-coffee.cn"}'
# # 这里最后加=的原因是base64解码对传入的参数长度不是2的对象,需要再参数最后加上一个或两个等号=
# 可以对jwt编码后的结果,直接进行解码
##### 2.使用jwt
## 加密令牌, 解密令牌
encoded_jwt = jwt.encode({'username':'运维咖啡吧','site':'https://ops-coffee.cn'},'secret_key',algorithm='HS256')
newstr = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Ilx1OGZkMFx1N2VmNFx1NTQ5Nlx1NTU2MVx1NTQyNyIsInNpdGUiOiJodHRwczovL29wcy1jb2ZmZWUuY24ifQ.fIpSXy476r9F9i7GhdYFNkd-2Ndz8uKLgJPcd84BkJ4'
# newstr = bytes(newstr,'utf8') # 可以不用转, jwt会默认给我们转换
decoded_jwt = jwt.decode (newstr, 'secret_key', algorithms=['HS256'])
print(encoded_jwt)
print (decoded_jwt) # 生成的是原来的字典数据, 类似JSON.stringify() 和JSON.parse() 的用法
print (type(decoded_jwt))
# b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Ilx1OGZkMFx1N2VmNFx1NTQ5Nlx1NTU2MVx1NTQyNyIsInNpdGUiOiJodHRwczovL29wcy1jb2ZmZWUuY24ifQ.fIpSXy476r9F9i7GhdYFNkd-2Ndz8uKLgJPcd84BkJ4'
# {'username': '运维咖啡吧', 'site': 'https://ops-coffee.cn'}
'''
这里传了三部分内容给JWT:
第一部分是一个Json对象,称为有效载荷,主要用于存放有效的信息,例如用户名,过期时间等等所有你想要传递的信息
第二部分是一个秘钥字串,这个秘钥主要用在某些签名签名中,服务端被重置令牌合法性,这个秘钥只有服务端知道,不能替换
第三部分指定了Signature签名的算法
'''
##### 3.使用hashlib, 这个方法一定要选择使用不重复的id生成的hash值,并且生成的hash值无法反解出原来的结果
import hashlib
# md5加密
def make_md5(string): # md5加密
from time import time
new_str = string+str(time())
new_byte = new_str.encode('utf-8')
md5 = hashlib.md5(new_byte).hexdigest()
return md5
(1)字符串转datetime:
>>> string = '2014-01-08 11:59:58'
>>> time1 = datetime.datetime.strptime(string,'%Y-%m-%d %H:%M:%S')
>>> print time1
2014-01-08 11:59:58
(2)datetime转字符串:
>>> time1_str = datetime.datetime.strftime(time1,'%Y-%m-%d %H:%M:%S')
>>> time1_str
'2014-01-08 11:59:58'
实战使用: jwt生成token
(1.与md5的区别
- md5的方法,无法反解,只能通过存在性来判断
token = make_md5 (userID) ### 登录获取新的token
- .make_jwt 的方法,可以传入字典,反解出原来的结果
(2.处理流程
- 登录过程
通过phone 找到 userID , 再生成 删除替换原来的token ,
只有在登录的时候,才会刷新token的值,注册只会生成token值,其余的验证都不会刷新
-
注册过程
注册过程中,会生成唯一的userID, 然后将UserToken 的user属性连接 新创建的User对象
UserToken表和User表是一对一关系,最后将结果保存在UserToken表中。
-
退出登录过程
就是把token值从UserToken表中删除就可以(置为空),一般不要删除,否则会导致登录出现异常。
登录功能,就是刷新值。 因为登录采用的是
update_or_create
, 所以也可以直接删除,出于性能考虑,还是置为空更好 -
实现逻辑。
登录 和注册 生成新的token值,里面记录token的有效时间和userID
如果数据库中有信息,则验证token是否过期。
如果token过期,将无法通过认证,需要重新登录。
认证成功,则返回token值和基本的简要用户数据。
-
jwt 实现原理
因为在settings中设置了全局的认证,所以在每次请求前,都会对token值 进行认证。
token的值是保存在数据库中,所以每次认证都需要访问数据库,查找相关token信息。
特殊的路由,比如 **
login和Register
**就不需要token验证,因为它们都是生成token值的所以在views.py视图函数中如果需要禁用 则在post或者 get 等方法前,使用**
authentication_classes = []
** 禁用认证。其实jwt 在其中只相当于一个高级的加密,解密功能,作用就只是处理token值。最后 将token值返回给用户。
-
视图函数的处理
- 获取与UserToken关联的User对象
- 生成Token值
- 更新UserToken表中的数据
- 返回Token的值和其它数据
(3.实现代码
安装模块 pip install PyJWT
pip install djangorestframework-jwt==1.11.0
如果出现 AttributeError: module 'jwt' has no attribute 'encode'
则出现了 jwt和PyJWT同时存在的情况,所以 需要重新安装。
# jwt卸载命令 pip uninstall jwt
# 保险起见,将PyJWT一同卸载 pip uninstall PyJWT
# 重新安装PyJWT pip install PyJWT
------- uniapp/models.py模型
class UserToken (models.Model):
user = models.OneToOneField (User, on_delete=models.CASCADE) ## 与User用户表建立一对一关系
token = models.CharField (verbose_name="用户令牌",max_length=240)
-------- settings.py全局配置
## 不需要在setting中引入模块,因为模板的编译会从setting引入,
# 设置全局认证
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ['uniapp.utils.auth.FirstAuthentication', 'uniapp.utils.auth.MyAuthentication', ], ### 注意根据认证类的位置
}
--------uniapp/utils/auth.py 认证类
from rest_framework import exceptions
# from .. import models ### 将这整个文件都导入,并把models当作一个大的模块
from ..models import * ### 将这整个文件的内容都暴露,就像在使用当前文件生成的内容一样
from . import jwt
from rest_framework.authentication import BaseAuthentication
# 匿名用户 -- (非必要)
class FirstAuthentication(BaseAuthentication):
"""不返回值,则会执行 _not_authenticated() 方法,返回一个匿名用户"""
def authenticate(self, request):
pass
def authenticate_header(self, request):
pass
# 用户认证 -- (必要)
class MyAuthentication (BaseAuthentication):
"""认证类"""
def authenticate(self, request):
print("我已经启动认证啦!")
# 获取 token, META 获取数据 ”HTTP_ + header字段名称大写“
# 使用.get获取与是否在请求中使用get方法无关
token = request.META.get('HTTP_TOKEN') # 获取 token
# 先检查表中是否有相关token数据
token_obj = UserToken.objects.filter (token=token).first () ## 通过过滤的方法,只获取第一个数据,其实也是获取obj[0]
if not token_obj:
raise exceptions.AuthenticationFailed ({"result":'无权限请求服务器,用户认证失败',"code":403})
# 再验证token是否过期
result, return_user_data = jwt.parse_jwt(token)
if not result:
raise exceptions.AuthenticationFailed ({"result":',',**return_user_data})
def authenticate_header(self, val):
pass
--------uniapp/utils/jwt.py 实现jwt生成,解密 token
import jwt
# jwt生成token密钥
def make_jwt(**args):
# 传入的数据为 userID, 才能生成token, 最好不要添加其它参数,否则会超过最大值240, 目前是 176长度
# 可修改参数: valid_days 有效期天数可以修改,
# secret_key 服务器密钥也可以修改
# 返回token字符串值 和 user_data / json数据
try:
import datetime
now_time = datetime.datetime.now()
str_now_time = datetime.datetime.strftime(now_time,'%Y-%m-%d %H:%M:%S')
token_input_data = {
**args, # 用户ID,或者其它参数
'now_time':str_now_time, ## 当前的用户登录信息, 日期字符串
'valid_days':15 # 天数 days
}
## data为字典数据
# 1.有效载核,存放有效信息 2.密钥字串, 服务端被重置时的合法性 3.sinature签名算法
encoded_jwt = jwt.encode(token_input_data,'secret_key',algorithm='HS256')
if type(encoded_jwt) != str: ## 转换成普通的string字符串
encoded_jwt = str (encoded_jwt, encoding="utf-8")
return encoded_jwt,token_input_data
except Exception as e:
res_data = { 'msg':"生成token出错", 'code':500}
return None,res_data
# jwt解析token密钥
def parse_jwt(str_token):
## str_token 为token字符串
## 返回验证结果 True / False None , json数据 / user_data
try:
if type(str_token) != bytes:
str_token = bytes(str_token,encoding='utf8')
try:
token_output_data = jwt.decode (str_token, 'secret_key', algorithms=['HS256'])
except Exception as e:
res_data = { 'msg':"服务器已经更新生成密钥算法,请重新生成token", 'code':401}
return False,res_data
# 生成的必需使用
## token_input_data = {
# **args, # 用户的名称
# 'now_time':str_now_time, ## 当前的用户登录信息, 日期字符串
# 'valid_days':15 # 天数 days
# }
# print("data",token_output_data)
str_pass_time = token_output_data.get('now_time',None)
str_valid_days = token_output_data.get('valid_days',None)
if len(str_pass_time) == 0 or str(str_valid_days) == 0:
res_data = { 'msg':"密钥没有包含有效参数", 'code':401}
return False,res_data
import datetime
obj_pass_time = datetime.datetime.strptime(str_pass_time, '%Y-%m-%d %H:%M:%S')
obj_valid_time = obj_pass_time + datetime.timedelta(days= str_valid_days)
now_time = datetime.datetime.now()
if obj_valid_time < now_time:
res_data = { 'msg':"密钥已经过期,请重新登录", 'code':401}
return False,res_data
else:
return True,token_output_data
except Exception as e:
res_data = { 'msg':"解析token出错", 'code':500}
return None,res_data
# jwt直接获取 userID 参数值
def immediate_jwt(request):
'''直接 通过请求头中的 token 获取 userID'''
# 获取 token, META 获取数据 ”HTTP_ + header字段名称大写“
# 使用.get获取与是否在请求中使用get方法无关
str_token = request.META.get('HTTP_TOKEN') # 获取 token
if type(str_token) != bytes:
str_token = bytes(str_token,encoding='utf8')
try:
token_output_data = jwt.decode (str_token, 'secret_key', algorithms=['HS256'])
return token_output_data['userID']
except Exception as e:
res_data = { 'msg':"服务器已经更新生成密钥算法,请重新生成token", 'code':401}
return False,res_data
-------- views/UserViews.py视图函数
from ..utils import jwt # 自定义jwt生成token模块
class userLogin(APIView):
"""订单管理"""
# authentication_classes = [ FirstAuthentication,MyAuthentication,] # 添加认证
authentication_classes = [] # 当前路径不需要认证
def post(self, request, *args, **kwargs):
### ---- 1. 找到UserToken关联的User表
----------- 登录功能 (找到User表)
### 先获取userinfo对象,再获取user对象
userinfo_obj = Userinfo.objects.filter(phone=phone,password=password).first()
if not userinfo_obj:
return JsonResponse ({'msg': '当前的用户不存在!', 'code': 422})
### 通过userID获取user对像
userID = userinfo_obj.uid.userID # 子类获取父类的属性
user_obj = User.objects.filter (userID=userID).first ()
if not user_obj:
return JsonResponse ({'msg': '查询当前的用户失败!', 'code': 500})
serializer_user_data = UserSerializer (user_obj, many=False)
user_data = { # 序列化结果集,返回user用户简要数据
'data':serializer_user_data.data
}
----------- 注册功能 (找到User表)
userID = "YK"+ str(time()/100)[-8:] ### 获取时间戳,后8位
if len(User.objects.filter(userID=userID))>0:
print(f"发现重复的ID值{userID},正在重新生成")
userID = "YK"+ str(time()/100)[-8:] ### 重复的ID值会刷新
data.pop("confirm_password")
### 保存在User表
user_data = {'userID':userID }
user_obj = User(**user_data)
user_obj.save()
### 保存在Userinfo表
data["password"] = make_password(data["password"],None,'pbkdf2_sha256') ### 加密密码
userinfo_data = {
'uid':user_obj,
**data
}
userinfo_obj = Userinfo(**userinfo_data)
userinfo_obj.save()
....
### --- 2.生成token
# 1.md5的方法,无法反解,只能通过存在性来判断
# token = make_md5 (userID) ### 登录获取新的token
# 2.make_jwt 的方法,可以传入字典,反解出原来的结果
token,return_user_data = jwt.make_jwt(userID = userID)
if not token:
raise Exception("生成token出错")
print("新生成的token是",token)
...
### --- 3.更新UserToken表
--------- 登录更新UserToken表 (情况一)
# 更新UserToken表---存在就更新,不存在就创建
usertoken_obj = UserToken.objects.update_or_create (user=user_obj, defaults={'token': token})
print("usertoken_obj",usertoken_obj)
--------- 注册更新UserToken表 (情况二)
usertoken_data = {
'user':user_obj,
'token': token
}
usertoken_obj = UserToken (**usertoken_data)
usertoken_obj.save()
...
### ---- 4.返回token 和基本的用户数据
total_data = {
'user_data':return_user_data, # 用户ID和过期信息
'user_msg':user_data['data'], # 用户简要信息
'token':token
}
...
return JsonResponse ({'msg':'登录成功','data':total_data,"code":200}) ### json格式数据
# 退出登录 (get)
class userLogout(APIView):
def get(self, request):
try:
str_token = request.META.get('HTTP_TOKEN') # 从header中获取 token
usertoken_obj = UserToken.objects.filter(token=str_token)
if not usertoken_obj:
return JsonResponse ({'msg': '当前的用户已经不存在', 'code': 404}) ### json格式数据
# usertoken_obj.delete() ### 直接删除
usertoken_obj.update(**{'token':''}) ### 设置为空, 性能更好
return JsonResponse ({'msg': '退出登录成功','code': 204 }) ### json格式数据
except Exception as e:
print("服务器请求失败",e)
return JsonResponse ({'msg': '服务器内部错误','code': 500 })
3.make_password密码加密(略)
from django.contrib.auth.hashers import make_password,check_password
pas = '123456'
test = make_password(pas,None,'pbkdf2_sha256')# 13 + 75 = 88, 密码加密
print("加密的数据",test)
res = check_password(pas,test) # 验证密码 返回结果
print("匹配结果是",res)
4.paginator分页模块(略)
把一个数据集合,按照指定的分页标准进行分页,作用就是拆分一个数据集,把它分成小数据集。
-
分页器是传入数据集和单页数,返回一个实例化分页对象
-
这个实例化分页对象不可以直接序列化,只能进行迭代对其子项序列化。
-
实例化对象可以对数据进行处理,有上下页属性,页面总数,数据总数。
其实它也可以认为是一个窗口,对数据集进行过拆分处理而已
from django.core.paginator import Paginator # 分页器
from ..templatetags.pagetag import * # 排列分页
------ UserViews.py视图函数
# 用户查询 (get)
'''Authorization 认证 --JWT token'''
'''selecttype keywords page (1)'''
''' 可输入sex nickname / phone / email / userID '''
class userSearch(APIView):
def get(self, request):
user = User.objects.filter()
# 接收搜索条件
selecttype = request.GET.get ('selecttype', None) # all / phone / sex / nickname / phone / / email / userID
keywords = request.GET.get ('keywords', None)
if selecttype:
if selecttype == 'all':
# 按照多字段进行条件搜索, 有过滤效果
from django.db.models import Q
user = user.filter (
Q (nickname__contains=keywords) | Q (phone__contains=keywords) | Q (sex__contains=keywords) | Q (
email__contains=keywords ) | Q(userID__contains=keywords))
else:
serdata = {f'{selecttype}__contains': keywords}
user = user.filter (**serdata)
else:
return JsonResponse({'msg':'请填写必要的类型参数','code':422})
if not user:
return JsonResponse({'msg':'填写有效的表单数据','code':422})
#### ------------ 分页器---------------------
paginator = Paginator (user, 10) # 实例化分页对象: 参数1,数据集, 参数2 每页显示的条数
current_page_num = int (request.GET.get ('page', 1)) # 通过a标签的GET方式请求,默认显示第一页
user_page_obj = paginator.get_page (current_page_num) # 获取当前页面的数据对象,用于响应前端请求进行渲染显示
# 这是对象无法获取具体数据
# if user_page_obj.has_previous (): # 当前页面是否有前一页
# print ("有数据是",user_page_obj.previous_page_number ()) # 当前页面的前一页页码
# if user_page_obj.has_next (): # 当前页面是否有后一页
# print (user_page_obj.next_page_number ()) # 当前页面的后一页页码
### 分页的数据对象 <class 'django.core.paginator.Page'>
### 不可以使用序列化处理这个对象,但它的子项却是一个单独的对象,可以对它进行序列化。
arr_data = [] ## 页面子数据集
arr_range = [0,0] ## 页面范围
for item in user_page_obj: ## 对当前页面的子数据集进行处理
serializer = UserSerializer(item, many=False)
arr_data.append(serializer.data)
total_pages = user_page_obj.paginator.num_pages ## 总页数
total_count = user_page_obj.paginator.count ## 总条数
arr_range[0] = user_page_obj.paginator.page_range.start ## 页数范围
arr_range[1] = user_page_obj.paginator.page_range.stop - 1
page_data = showpage(total_pages, request) ## 引入自定义模块,对分页数据进行处理
detail= {'total_pages':total_pages,'total_count':total_count,'page_range':arr_range,'page_data':page_data}
return_data = {'detail':detail,'data':arr_data,'code':200}
return JsonResponse (return_data, safe=False) ### json格式数据
-------- templatetags/pagetag.py 分页排列文件
from django import template
from django.utils.html import format_html ## 可以格式化代码,返回一个解析html后的数据
register = template.Library()
#自定义过滤器
@register.filter
def kong__upper(val):
#print ( 'val from template: ' ,val)
return val.upper()
#自定义标签
@register.simple_tag
def jia(a,b):
res = int(a) + int(b)
return res
# 自定义分布优化 标签
@register.simple_tag
def showpage(num, request):
'''
num 总页数 p 当前页 默认显示 10页
'''
current = int(request.GET.get('page',1)) # 默认是首页
start = current - 5
end = current + 4
# 半断当前页如果小于5
if current <= 5:
start =1
end = 10
# 判断当前页如果大于总页数-5
if current > num-4:
start = num - 9
end = num
# 总页数小于等于10
if num <=10:
start = 1
end = num
page_data = {}
page_data['first_page'] = 1 ### 首页
if current-1 < 1: ### 上一页
page_data['last_page'] = 1
else:
page_data['last_page'] = current-1
pagination = {}
pagination['pagination_start_page'] = start
pagination['pagination_current_page'] = current
pagination['pagination_end_page'] = end
page_data['pagination'] = pagination ## 分页栏
if current+1 > num: ### 下一页
page_data['next_page'] = num
else:
page_data['next_page'] = current+1
page_data['final_page'] = num ### 尾页
return page_data
### 可变参数的分页器
## 可变参数的分页器
current = int (request.GET.get ('page', 1)) # 通过a标签的GET方式请求,默认显示第一页
each_page_num = int (request.GET.get ('each_page_num', 10)) # 通过a标签的GET方式请求,默认显示第一页
return_data = usePaginator( user, current, each_page_num)
----- UserViews.py 视图函数文件
# 使用paginator模块
def usePaginator( page_obj, current_page_num=1, each_page_num=10):
try:
my_paginator = Paginator(page_obj, each_page_num) # 实例化分页对象: 参数1,数据集, 参数2 每页显示的条数
user_page_obj = my_paginator.get_page (current_page_num) # 获取当前页面的数据对象,用于响应前端请求进行渲染显示
arr_data = [] ## 页面子数据集
arr_range = [0, 0] ## 页面范围
for item in user_page_obj: ## 对当前页面的子数据集进行处理
serializer = UserSerializer (item, many=False)
arr_data.append (serializer.data)
total_pages = user_page_obj.paginator.num_pages ## 总页数
total_count = user_page_obj.paginator.count ## 总条数
arr_range[0] = user_page_obj.paginator.page_range.start ## 页数范围
arr_range[1] = user_page_obj.paginator.page_range.stop - 1
### 分页数可以为 3 - 50 以内的任何数整数
page_data = showpage (current_page_num, total_pages, each_page_num) ## 引入自定义模块,对分页数据进行处理
detail = {'total_pages': total_pages, 'total_count': total_count, 'page_range': arr_range, 'page_data': page_data}
return_data = {'detail': detail, 'data': arr_data, 'code': 200}
return return_data
except Exception as e:
return {'msg':'获取分页数据失败','code':500}
----- pagetag.py文件
from django import template
from django.utils.html import format_html ## 可以格式化代码,返回一个解析html后的数据
register = template.Library()
#自定义过滤器
@register.filter
def kong__upper(val):
#print ( 'val from template: ' ,val)
return val.upper()
#自定义标签
@register.simple_tag
def jia(a,b):
res = int(a) + int(b)
return res
# 自定义分布优化 标签
@register.simple_tag
def showpage( current, total_num, each_pnum):
'''
## 可变的分页标签
current 当前页 total_num 总页数 each_pnum 分页数
'''
mid = int(each_pnum/2)
if each_pnum % 2 == 0: ## 偶数
add = 1 ## 没有中间位置,向右偏移
else:
add = 0 ## 刚好有中间位置,不偏移
start = current - mid ### 默认三都是固定的位置
end = current + (mid - add)
# 半断当前页如果小于 分页数的一半
if current <= mid:
start = 1
end = each_pnum
# 判断当前页如果大于总页数- 分页数的一半
if current > total_num- (mid - add):
start = total_num - (each_pnum - add )
end = total_num
# 总页数小于等于 分页数
if total_num <= each_pnum:
start = 1
end = total_num
page_data = {}
page_data['first_page'] = 1 ### 首页
if current-1 < 1: ### 上一页
page_data['last_page'] = 1
else:
page_data['last_page'] = current-1
pagination = {}
pagination['pagination_start_page'] = start
pagination['pagination_current_page'] = current
pagination['pagination_end_page'] = end
page_data['pagination'] = pagination ## 分页栏
if current+1 > total_num: ### 下一页
page_data['next_page'] = total_num
else:
page_data['next_page'] = current+1
page_data['final_page'] = total_num ### 尾页
return page_data
5.file.chunks文件分块写入
### 文件保存
with open (current_path+name, 'wb+') as fp:
## 分块写入文件
for chunk in file.chunks (): ## 分块写入
fp.write (chunk)
### 文件传递, 将图片数据读取,以二进制的形式传递,并使用
#(1.普通模式
image_data = open (pre_path+item+"/"+file_name, "rb").read () # 返回图片资源
return HttpResponse (image_data, content_type="image/png") # 注意旧版的资料使用mimetype,现在已经改为content_type
---- ts视频需要改为 “application/octet-stream”, 有时也可以写为 “video/mp4”格式,但建议区分开来。
#(2.Django模式
### 由于StreamingHttpResponse支持数据和文件输出,因此在使用时候需要设置响应输出类型和方式
StreamingHttpResponse的适用方式更为广泛,可支持大规模数据或文件输出,而FileResponse只支持文件输出。不推荐使用HttpResponse保存文件。
1.FileResponse
f=open(file_path, 'rb')
r = FileResponse(f,as_attachment=False,filename='dog.jpg')
return r
2.StreamingHttpResponse
file_con = open (pre_path+item+"/"+file_name, 'rb')
res_steam = StreamingHttpResponse (file_con) ### Django提供 的StreamingHttpResonse类响应文件数据
res_steam['content_Type'] = 'application/octet-stream'
res_steam['Content-Disposition'] = f'attachment;filename={file_name}'
return res_steam
注意: 这里传递ts文件,还需要使用cors跨域资源共享。
6.获取动态图片资源
把图片路径暴露在路由上,访问路由,也就找到图片对应的路径了
python可以读取图片资源,并响应图片资源。
---- uniapp/urls.py
from django.contrib import admin
from django.urls import path,re_path
# from . import views # 可以使用views
from .views import * # 从views文件(夹)中导入所有模块
from .utils import allow,router # 导入图片路由
urlpatterns = [
# 测试模块
...
#### 将静态资源文件当作路由暴露出去
# 可能会导致服务器的性能下降。如果需要,使用regis还是不错的选择。
path(f'uniapp/{router.get_pic_router()[0]}/<str:file_name>',allow.get_static),
path(f'uniapp/{router.get_pic_router()[1]}/<str:file_name>',allow.get_static),
...
]
------ utils/ router.py 定义图片路由
## 获取图片路由
def get_pic_router():
static_dir = [
'static/myadmin/assets/img', ## 头像路径
'static/myadmin/assets/pic' ## 背景路径
]
return static_dir
------ utils / allow.py 响应查询结果
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse,HttpResponse
from . import router
@csrf_exempt
#### 同域下的测试文件
def get_static(request,file_name):
print("访问成功",file_name)
try:
from os import getcwd,path
pre_path = getcwd() + '/uniapp/' ### 前缀
for item in router.get_pic_router():
if path.exists(pre_path+item+"/"+file_name):
image_data = open (pre_path+item+"/"+file_name, "rb").read () # 返回图片资源
return HttpResponse (image_data, content_type="image/png") # 注意旧版的资料使用mimetype,现在已经改为content_type
# return JsonResponse ({'msg':'您要找的资源存在','code':200} )
except Exception as e:
print("查找图片出现异常",e)
return JsonResponse ({'msg':'您要找的资源不存在','code':404} )
有可能还是访问不了,原因是nginx没有匹配到应用下的路由,被其它优先级更高的匹配了。
所以还要设置该项目的 nginx配置
# 将以下内容注释
# location /static/ { ## 静态资源 优先级 2
# alias /www/wwwroot/www.myDjangoAPI.com/static/; #静态资源路径
# }
# location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ ### 优先级 1
# {
# expires 30d;
# error_log /dev/null;
# access_log /dev/null;
# }
4.Django拓展知识
1.Django框架知识
1.Django中爬取数据
---- myhome/views.py 视图函数
## 测试爬虫
def testspider(request):
import requests
import re
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36'
}
res = requests.get ('http://www.tpszw.com/ps/3416/audio_1353584.html', headers=headers)
title = ''
content = ''
if res.status_code == 200:
contents1 = re.findall ('<h1>(.*?)</h1>', res.content.decode ('utf-8'), re.S) # 该小说页面的标题
contents3 = re.findall (' (.*?)<!--over-->', res.content.decode ('utf-8'),
re.S) # 该小说页面的正文最后一行
# 注意把信息改为正则表达式(.*?),需要正确找到该页面的信息,粘贴的信息不能多出,或者少空格.
# .*?为换行,空白(Y/N),可以多选数据以便于更好定位,没有换行不用re.S
# 边界匹配</div>.*?(.*?) </div>下一行就是数据,可先.*?(数据)数据后面部分
# 结尾边界匹配,通过 数据前面部分(数据)<!--over-->
title += contents1[0] if len (contents1) > 0 else '没有标题'
content += contents3[0] if len (contents3) > 0 else '没有内容'
print (title, content)
else:
print ("该页面爬取失败!")
title += "获取数据错误"
content += "获取数据错误"
context = {'title':title,'content':content,"tt":'abc'}
return render(request,'a/test.html',context)
### 1.字典的键,必需添加引号
### 2.index.html中只能使用键名来访问变量
### 3.数据只能是字典,不能是字符串
---- templates/a/test.html
<div class="container">
<div class="row">
<div class="col-md-6 col-md-off d-inline-block text-center">
<h1>{{tt}}</h1>
<h1>{{title}}</h1>
<!-- 单独的变量,不允许放在花括号和百分号,只能放在双括号里面。 但含有其它的关键字,即可使用前者-->
<div>{{content}}</div>
</div>
</div>
</div>
2.file接收文件上传
前端实现文件下载(a标签实现文件下载 避免直接打开问题)_良_123的博客-CSDN博客_前端a标签下载文件
(1. 服务器接收上传文件
def recognition_videoData(request):
# 接收上传的文件
file = request.FILES.get ('file', None)
print (f"音频是{file}")
if file:
# ·必理文件的上传操作
# zh = file.name.split ('.').pop ()
name = str (random.randint (10000, 99999) + time.time ()) + '.' + 'mp3'
print ("name是", name)
try: # 提前创建uploads文件夹
with open (f'./static/assets/file/{name} ', 'wb+') as fp:
## 分块写入文件
for chunk in file.chunks ():
fp.write (chunk)
filename = f'/static/assets/file/{name}'
print("链接是",filename)
return HttpResponse(json.dumps({'code':200,'msg':'接收成功!','data':filename}))
except Exception as e:
print ("error is ", e)
return HttpResponse(json.dumps({'code':403,'msg':'接收失败!'}))
(2.客户端上传文件
// 方法二:上传BlobUrl临时的二进制音频
function uploadBlobUrl(blobUrl ) {
// 音频地址
//let imgBase = baseStr;
// blob -> base64 ->新blob文件
blobURLtoBase64(blobUrl,function(baseUrl){
console.log("base64链接是",baseUrl)
let blob = base64URLtoBlob(baseUrl, 'audio/webm');
console.log("新blob链接是",blob)
var formData = new FormData();
let fileOfBlob = new File([blob], new Date() + '.mp3'); // 重命名了
formData.append("file", fileOfBlob);
formData.append("csrfmiddlewaretoken", $("input[type^='hidden'").val());
$.ajax({
url: "/recognition/videoData", //用于文件上传的服务器端请求地址
dataType: 'json',
type: 'POST',
async: false,
data: formData,
processData: false, // 使数据不做处理
contentType: false, // 不要设置Content-Type请求头
success:function(res){
console.log("提交成功!");
},
error:function(err){
console.log("提交失败!");
}
});
})
}
// 将base64转换为blob 二进制数据
function base64URLtoBlob(dataURI, type) {
let binary = atob(dataURI.split(',')[1]);
let array = [];
for (let i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: type});
}
3.a标签本地下载
// 方法一:客户端使用a标签下载数据
function aNodeDownload(blobUrl){
blobURLtoBase64(blobUrl,(blobAsDataUrl)=>{
let body = document.querySelector("body")
let newA = document.createElement('a');
newA.href = blobAsDataUrl;
newA.download = blobAsDataUrl+'.mp3';
newA.innerHTML = '下载文件';
newA.style.cssText = ' border:1px solid #1f1f1f;border-radius:5px;' +
'background:lightBlue;position:absolute;top:80px;left:40px;padding:10px;' +
'z-index:999;font:bold 16px "楷体";color:white;';
body.insertBefore(newA, body.firstChild);
newA.click()
})
}
// blob临时文件数据转换成base64数据
function blobURLtoBase64(blobUrl,callback){
// var blob = new Blob(["Hello, world!"], { type: 'text/plain' });
// var blobUrl = URL.createObjectURL(blob);
var xhr = new XMLHttpRequest;
xhr.responseType = 'blob';
xhr.onload = function() {
var recoveredBlob = xhr.response;
var reader = new FileReader;
reader.onload = function() {
var blobAsDataUrl = reader.result;
//window.location = blobAsDataUrl;
//console.log("结果是",blobAsDataUrl);
callback(blobAsDataUrl)
};
reader.readAsDataURL(recoveredBlob);
};
xhr.open('GET', blobUrl);
xhr.send();
}
4.blob临时文件与base64转换
js Blob对象_神奇大叔的博客-CSDN博客_blob撖寡情
MIME 类型大全,你值得收藏_后端老鸟的博客-CSDN博客_mime类型
**blob链接 <==> base64数据 **
注意:
1.生成的临时blob临时文件文件不能直接在浏览器打开,但可以经过转换(blob->base64->新blob含类型文件)再上传到服务里。
2.base64的数据可以在浏览器打开,客户端使用a标签+download属性下载。在服务端里面无法访问
base64格式的url, 可以在download属性中添加文件后缀。
// 将blob临时文件转换成base64数据
function blobURLtoBase64(blobUrl,callback){
// var blob = new Blob(["Hello, world!"], { type: 'text/plain' });
// var blobUrl = URL.createObjectURL(blob);
var xhr = new XMLHttpRequest;
xhr.responseType = 'blob';
xhr.onload = function() {
var recoveredBlob = xhr.response;
var reader = new FileReader;
reader.onload = function() {
var blobAsDataUrl = reader.result;
//window.location = blobAsDataUrl;
console.log("结果是",blobAsDataUrl);
callback(blobAsDataUrl)
};
reader.readAsDataURL(recoveredBlob);
};
xhr.open('GET', blobUrl);
xhr.send();
}
// 将base64转换为blob 二进制数据
function base64URLtoBlob(dataURI, type) {
let binary = atob(dataURI.split(',')[1]);
let array = [];
for (let i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: type});
}
5.URL生成链接(提高兼容性)
URL.createObjectURL(blob)
(1. 在canvas会生成base64的链接(浏览器中可以访问)
(2.在流文件中会生成blob临时文件临时url(audio的srcObject可以使用)
Chrome更新后不支持这种用法“ url = URL.createObjectURL(streamData); ” ,需要改为下面的方法
// 将流文件生成新的URl
function createURL(streamData){
let binaryData = [];
binaryData.push(streamData);
return URL.createObjectURL(new Blob(binaryData))
}
6.file二进制流下载
1.ts二进制流
from os import getcwd,path
from re import search
if not file_name or not len(file_name) > 0 or not search('([.])',file_name):
return JsonResponse({'msg':'当前没有传递有效video_prefix参数,请重试','code':422})
pre_path = getcwd() +"/" ### 前缀 a>b ? a : b
res = file_name.split('.')[-1]
tail_type = 'application/octet-stream' if res == 'ts' else 'image/'+res
for item in router.get_pic_router():
if path.exists(pre_path+item+"/"+file_name):
# if res == 'ts':
# file_con = open (pre_path+item+"/"+file_name, 'rb')
# res_steam = StreamingHttpResponse (file_con)
### Django提供 的StreamingHttpResonse类响应文件数据
# res_steam['content_Type'] = 'application/octet-stream'
# res_steam['Content-Disposition'] = f'attachment;filename={file_name}'
# return res_steam
# else:
# image_data = open (pre_path+item+"/"+file_name, "rb").read () # 返回图片资源
# return HttpResponse (image_data, content_type=f"{tail_type}")
# 注意旧版的资料使用mimetype,现在已经改为content_type
# # return JsonResponse ({'msg':'您要找的资源存在','code':200} )
image_data = open (pre_path + item + "/" + file_name, "rb").read () # 返回图片资源
return HttpResponse (image_data, content_type=f"{tail_type}")
# 注意旧版的资料使用mimetype,现在已经改为content_type
2.图片二进制流
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse,HttpResponse
from . import router
@csrf_exempt
#### 同域下的测试文件
def get_static(request,file_name):
print("访问成功",file_name)
try:
from os import getcwd,path
pre_path = getcwd() + '/uniapp/' ### 前缀
for item in router.get_pic_router():
if path.exists(pre_path+item+"/"+file_name):
image_data = open (pre_path+item+"/"+file_name, "rb").read () # 返回图片资源
return HttpResponse (image_data, content_type="image/png")
# 注意旧版的资料使用mimetype,现在已经改为content_type
# return JsonResponse ({'msg':'您要找的资源存在','code':200} )
except Exception as e:
print("查找图片出现异常",e)
return JsonResponse ({'msg':'您要找的资源不存在','code':404} )
7.服务器上传文件
# 1.静态
#### 静态上传文件 (post)
@csrf_exempt
def upload_static(request):
if request.method == 'POST':
try:
# 注意:会出现InsecureRequestWarning,这是一个不安全警告。这上传功能其实是不是很建议使用的。
filename = f'./{router.get_upload_router()[0]}/bgmain-1.png'
### 000webhostapp托管文件
post_url = 'https://miniwildcat.000webhostapp.com/up.php'
### 本地服务器(无效)
# post_url = 'http://localhost:80/php_demo/giteeWarehouse/study-php/PhpServerData/2.中转上传Github文件/www.hiphp.com/UploadGithub/setUploadStatic.php'
res = middle_upload_static (filename, post_url)
return JsonResponse (res)
except Exception as e:
print("错误是",e)
return JsonResponse ({'msg': '上传出现异常', 'code': 500})
else:
return JsonResponse ({"msg": "错误的请求"}, status=404)
#### 中间上传文件 (发起请求)
def middle_upload_static(filename=f'./xx.png', post_url="'https:xx.xx.xx'"):
print("文件正在上传...")
## 模仿浏览器的请求头
file_headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36' ,
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'token': 'token'}
## 读取二进制数据
file_stream = {
"file": open (filename, 'rb'),
"Content-Type": "application/octet-stream",
"Content-Disposition": "form-data",
"filename": filename}
print ("上传的文件:", filename)
print ("中转服务地址的文件:", post_url)
post_response = requests.post (url=post_url, headers=file_headers, files=file_stream, verify=False,timeout=10)
post_response.encoding = 'unicode_escape'
print ('静态提交的响应结果:{},{}'.format (post_response, post_response.text))
# json.loads会将null自动转换成None, 相反,它却无法识别None
# return json.loads (post_response.text)
return post_response.json ()
2.Django页面开发(实例)
1.添加表单页
(1.首先在models.py中创建模型
# 书籍模型
class Book(models.Model):
name = models.CharField(max_length=30) # 书名
price = models.FloatField() # 价格
author = models.CharField(max_length=100) # 作者
publisher = models.CharField(max_length=100) # 出版社
abstract = models.TextField() # 书面简介
img_url = models.CharField(max_length=150) # 封面
pub_dte = models.DateField() # 出版时间
(2.在视图函数中使用模板文件
---- myhome/views.py 视图函数
## 书箱添加
def addbook(request):
if request.method == "GET":
return render(request,'books/add.html')
else:
data = request.POST.dict() # 提交的表单是字典数据
data.pop('csrfmiddlewaretoken') # 删除字典中无用的数据
obj = models.Book(**data)
obj.save()
print(data)
return HttpResponse('接收表单提交的数据')
(3.给当前的视图函数添加路由
path('book/add/',views.addbook,name='addbook'),
(4.书写html模板文件
模板文件中引用static文件夹中的文件,需要使用 “/static/xxx”
---- templates/books/add.html 模板文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>书籍添加页面</title>
<!--移动设备优先-->
<!-- 注意:这里必需使用 / 代表根目录, 而vue是使用@ 和~ 代表根目录-->
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<link rel="stylesheet" href="/static/bootstrap-dist-4.21/css/bootstrap.css">
<script src="/static/bootstrap-dist-4.21/js/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin="anonymous"></script>
<script src="/static/bootstrap-dist-4.21/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-off">
<!--
enctype就是encodetype就是编码类型的意思。
multipart/form-data是指表单数据有多部分构成,既有文本数据,又有文件等二进制数据的意思。
需要注意的是:默认情况下,enctype的值是application/x-www-form-urlencoded,不能用于文件上传,
只有使用了multipart/form-data,才能完整的传递文件数据。
-->
<form action="{% url 'addbook' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<!-- CSRF验证失败,提交表单必需要使用CSRF cookie, 而在表单内添加上述代码,就会生成input的csrf_token类型的表单-->
<div class="form-group row">
<label for="inputEmail3" class="col-sm-3 col-form-label">书箱名称</label>
<div class="col-sm-9">
<input type="text" class="form-control inputEmail1" name="name">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-3 col-form-label">作者</label>
<div class="col-sm-9">
<input type="text" class="form-control inputPassword2" name="author">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-3 col-form-label">价格</label>
<div class="col-sm-9">
<input type="text" class="form-control inputPassword3" name="price">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-3 col-form-label">出版社</label>
<div class="col-sm-9">
<input type="text" class="form-control inputPassword3" name="publisher">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-3 col-form-label">出版时间</label>
<div class="col-sm-9">
<input type="date" class="form-control inputPassword3" name="pub_date">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-3 col-form-label">简介</label>
<div class="col-sm-9">
<textarea id="" rows="3" class="w-100" name="abstract"></textarea>
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-3 col-form-label">图书封面</label>
<div class="col-sm-9">
<input type="file" name="img_url">
<div id="result">
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-3 offset-6">
<button type="submit" class="btn btn-success">添加</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
<script>
$("input[name=img_url]").change(function(){
var file = this.files[0]
var reader = new FileReader()
// 创建一个文件读取的对象
reader.readAsDataURL(file);
reader.onload=function(e){
var result=document.getElementById( "result");
//显示文件
result.innerHTML='<img src="' + this.result +'" alt="" style="max-width:400px;"/>';
}
})
</script>
</html>
2.图片上传页
---- myhome/views.py 视图函数
## 书箱添加
def addbook(request):
if request.method == "GET":
return render(request,'books/add.html')
else:
data = request.POST.dict()
data.pop('csrfmiddlewaretoken') # 删除字典中无用的数据
filename = imgupload (request)
if filename:
data['img_url'] =filename
else:
#data.pop('img_url')
data['img_url']=''
print('error')
### 数据的添加
try:
'''这里可以嵌入爬虫程序'''
# import requests
# headers = {
# 'User-Agent ': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 '
# '(KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36'
# }
# res = requests.get ('https://www.baidu.com/', headers=headers)
# print("正在检测网络爬虫",res.text)
obj = models.Book(**data)
obj.save()
return HttpResponse("成功")
except Exception as e:
print('error',e)
return HttpResponse('接收表单提交的数据')
def imgupload(request):
# 接收上传的文件
file = request.FILES.get ('img_url', None)
print(f"图片是{file}")
import random, time
if file:
# ·必理文件的上传操作
zh = file.name.split ('.').pop ()
name = str (random.randint (10000, 99999) + time.time ()) + '.' + zh
print("name是",name)
try: # 提前创建uploads文件夹
with open (f'./static/uploads/{name} ', 'wb+') as fp:
## 分块写入文件
for chunk in file.chunks():
fp.write (chunk)
filename = f'/static/uploads/{name}'
return filename
except Exception as e:
print("error is ",e)
return False
3.列表展示页
---- myhome/views.py 视图函数
# 书箱列表
def listbook(request):
# 获取所有的书籍数据 (其实就是从数据库中获取数据)
data = models.Book.objects.all()
# 把数据分配到模板页面中
context = {'data' : data}
print(f'数据是{data}')
# 加载模板文件 (将数据传递给前端网页)
return render(request,'books/index.html',context)
---- templates/books/index.html
<div class="container">
<div class="row" style="margin-top:50px;">
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>书籍名称</th>
<th>作者</th>
<th>单价</th>
<th>出版社</th>
<th>出版时间</th>
<th>简介</th>
<th>操作</th>
</tr>
</thead>
<!-- ## vue在标签中的属性是不用双大括号 ,而Django需要 -->
<!-- ## vue在标签内使用v-for = " ",而Django是当作javascript的式子, -->
<!-- ## for i in data endfor-->
<tbody>
{% for i in data %}
<tr>
<th scope="row">{{i.id}}</th>
<th><img src="{{i.img_url}}" alt="" style="max-width:100%;"></th>
<th>{{i.name}}</th>
<th>{{i.author}}</th>
<th>{{i.price}}</th>
<th>{{i.publisher}}</th>
<th>{{i.pub_dte}}</th>
<th>{{i.abstract}}</th>
<th>
<a href="">删除</a>
<a href="">修改</a>
</th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
4.更新数据页
11.
---- myhome/views.py 视图函数
# 书箱修改
def editbook(request):
id = request.GET.get("id") # 从GET提交的数据中获取id值
obj = models.Book.objects.get(id=id)
print("数据",obj.id)
return render(request,'books/edit.html',{'obj':obj})
# 书籍数据的更新
def updatebook(request):
## request.POST.dict()
## request.GET.get("")
## request.FILES.get("")
data = request.POST.dict()
data.pop('csrfmiddlewaretoken') # 删除字典中无用的数据
## 判断当前是否更新图片
file = request.FILES.get("img_url",None) # 其实给input[type=file]的value值赋值是没有任何效果的
if file:
# 上传新的图片
filename = imgupload(request)
os.remove('.'+data['old_img_url']) # 这里的 . 代表本地中的文件路径
data['img_url'] = filename
else:
# 没有上传新图片
data['img_url'] = data['old_img_url']
data.pop("old_img_url")
# 更新操作
try:
models.Book.objects.filter(id=data['id']).update(**data)
url = reverse("listbook") ### 重定向到listbook路径
return HttpResponse("<script>alert('成功');location.href='"+url+"'</script>")
except Exception as e:
print('error',e)
url = reverse("editbook")
id = str(data['id'])
return HttpResponse(f"<script>alert('失败');location.href='{url}?id={id}'</script>")
return HttpResponse("更新")
---- myhome/urls.py 视图函数
from django.urls import path,re_path
from . import views
urlpatterns = [
path('',views.index,name="myhome_index"),
path('demo',views.demo,name='myhome_demo'),
path('testspider',views.testspider,name='myhome_test'),
path('book/add/',views.addbook,name='addbook'),
path('book/list/',views.listbook,name='listbook'),
path('book/del/<int:id>',views.delbook,name='delbook'),
path('book/edit/',views.editbook,name='editbook'),
path('book/update/',views.updatebook,name='updatebook'),
]
---- templates/books/index.html 模板文件
<div class="container">
<div class="row">
<div class="col-md-6 col-md-off">
<!--
enctype就是encodetype就是编码类型的意思。
multipart/form-data是指表单数据有多部分构成,既有文本数据,又有文件等二进制数据的意思。
需要注意的是:默认情况下,enctype的值是application/x-www-form-urlencoded,不能用于文件上传,
只有使用了multipart/form-data,才能完整的传递文件数据。
-->
<form action="{% url 'updatebook' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<!-- CSRF验证失败,提交表单必需要使用CSRF cookie, 而在表单内添加上述代码,就会生成input的csrf_token类型的表单-->
<input type="hidden" name="id" value="{{obj.id}}">
<div class="form-group row">
<label for="inputEmail3" class="col-sm-3 col-form-label">书箱名称</label>
<div class="col-sm-9">
<input type="text" class="form-control inputEmail1" name="name" value="{{obj.name}}">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-3 col-form-label">作者</label>
<div class="col-sm-9">
<input type="text" class="form-control inputPassword2" name="author" value="{{obj.author}}">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-3 col-form-label">价格</label>
<div class="col-sm-9">
<input type="text" class="form-control inputPassword3" name="price" value="{{obj.price}}">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-3 col-form-label">出版社</label>
<div class="col-sm-9">
<input type="text" class="form-control inputPassword3" name="publisher" value="{{obj.publisher}}">
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-3 col-form-label">出版时间</label>
<div class="col-sm-9">
<input type="date" class="form-control inputPassword3" name="pub_dte" value="{{obj.pub_dte|date:'Y-m-d'}}">
</div> <!--|date:'Y-m-d'设置格式-->
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-3 col-form-label">简介</label>
<div class="col-sm-9">
<textarea id="" rows="3" class="w-100" name="abstract">{{obj.abstract}}</textarea>
</div>
</div>
<div class="form-group row">
<label for="inputPassword3" class="col-sm-3 col-form-label">图书封面</label>
<div class="col-sm-9">
<input type="file" name="img_url" >
<input type="hidden" name="old_img_url" value="{{obj.img_url}}">
<img id="result" src="{{obj.img_url}}" alt="" style="max-width:400px;"/>
</div>
</div>
<div class="form-group row">
<div class="col-sm-3 offset-6">
<button type="submit" class="btn btn-success">更新</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
<script>
$("input[name=img_url]").change(function(){
var file = this.files[0]
var reader = new FileReader()
// 创建一个文件读取的对象
reader.readAsDataURL(file);
reader.onload=function(e){
var result=document.getElementById( "result");
//显示文件
result.src=this.result;
}
})
</script>
5.分类数据获取
def selectAllTypes():
## 按照一定的顺序排列
data = Booktype.objects.extra (select={ 'paths': 'concat(path,id)'}).order_by ('paths')
# select *,concat(path,id) as paths from myadmin_booktype order by paths;
for x in data:
num = x.path.count(',')-1
x.sb = '|---'*num ## 这时也会 给data添加数据项sb
if x.pid != 0: ## pid是整型,所以从表单中获取的下拉列表的数据也是整型
x.pname = Booktype.objects.get(id=x.pid).name
return data
# jqeuery在原位置修改数据
$('.tname').dblclick(function(){ // 编辑绑定双击事件
if ($(this)[0].dataset.click == 'false'){ // data- 中的数据是字符串,string类型
$(this)[0].dataset.click = 'true';
// $(this).unbind("dblclick"); // 给当前解除再次双击事件,会导致下一次无法点击
//先获取name
var tname = $(this).text();
var node = $(this);
//创建一个input
inp = $('<input type="text" value="'+tname+'" style="color:black;">')
$(this).html(inp) //把input加入到span中,相当于在原来的位置上添加覆盖一个输入框
inp.select() // 立即选中输入框
inp.blur(function(){ // 给input绑定丧失焦点事件,获取新的类名
var newname = $(this).val()
//判断当前newname是否更新了
if(newname == tname || newname.length == 0){
//不需要后台数据更新
node.text(tname)
}else{
//发送ajax请求后后台修改数据
id = node.parents('tr').find('td:first').text()
$.get('{% url 'myadmin_types_edit' %}', {'id':id ,'name':newname} ,function(data){
if (data['code'] ==0) {
//成功
node.text(newname);
alert(data['msg']);
}else{
node.text(tname);
alert(data['msg']);
}
},'json')
}
node[0].dataset.click = 'false'; // 再次设置标志位
})
}
})