前言:
Django自带的功能有很多,基本能满足我们的网页开发,本章节将介绍:解析request用户请求、models文件的使用、Django ORM框架、数据库的CURD。那么话不多说,我们开始了解吧!
相信对Django了解过一些之后应该知道,每次在视图文件里面定义函数都会补上一个request形参:
def index(request):
return HttpReponse('Hello World!')
作用:浏览器访问某个URL后,通过路由找到对应的视图里面某个函数,然后将浏览器的请求传递给这个函数,这也就是为何要定义一个形参了,而通常形参名都是:request,这是一种规范。
request内包含了浏览器向Django服务端发送的请求,常见发送请求的方法有两种:GET、POST
它们携带请求的形式是不同的,我们先尝试使用GET方法向Django发送请求
index.html文件(前提是已经搭建好了Django的基本环境)
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>indextitle>
head>
<body>
<form action="" method="get">
username:<input type="text" name="username"><br/>
password:<input type="password" name="password">
<input type="submit" value="提交">
form>
body>
html>
使用form表单以get方法提交请求,action不填写的话,默认向当前地址提交。
GET方法提交请求的方式:将请求数据携带在了URL内,我们在Django内通过debug查看一下。(需要先提交一遍,保留url的参数再开启debug,然后再刷新一下页面,因为debug一开启就进入阻塞了)
request内包含了浏览器提交请求的方法,在对应请求方法里面包含了携带的数据,我们可以提取这些数据进行处理。从上图可以看出,这些数据是一个字典形式的,而字典的key就是我们在HTML文件内输入框的name属性值
。
views.py
from django.shortcuts import render,HttpResponse,redirect
def index(request):
username = request.GET.get('username') # 获取get方法提交请求携带的数据,根据字典形式取值:key值就是name属性值
password = request.GET.get('password')
print(username,password)
return render(request,'index.html')
浏览器打开效果:
推荐字典key的取值方式使用.get(key)
,这样如果key不存在的话则返回一个None
,而使用[key]
取值的话,key不存在的话则报错。
但是通常使用GET方式提交数据是不安全的,相对这种账号、密码登录的功能,使用的都是POST请求。
注意:request.GET获取的是URL?
后面的内容
只要用户访问的URL前缀没问题,依然可以交给对应的视图函数处理:如
http://127.0.0.1:8080/index/?username=jack&password=000
这要用户输入这种URL,提交到后端只会识别ip+端口后面的/index/,而?
后面的内容则会被当做额外数据提交到后端。所以会将这个请求交给index视图函数。然后使用request.GET
就可以把那个额外数据取出来了。我们在post请求下面一起演示
先将表单的提交请求方式修改为POST:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>indextitle>
head>
<body>
<form action="" method="post">
username:<input type="text" name="username"><br/>
password:<input type="password" name="password">
<input type="submit" value="提交">
form>
body>
html>
可以发现,无法正常显示我们的HTML文件,查看报错信息发现是csrf原因,目前我们不需要了解为什么会报这个错误,解决方案就是,在settings.py文件内:
此时我们再重启一下Django,使用post方法提交就不会出现这个错误信息了。
我们在views.py内使用debug看一下POST请求的内容:
根据上图,我们可以发现request有两个属性都存放了浏览器提交请求的数据:POST、body。而使用GET方法提交时没有发现body属性存有值,那么我们来逐个打印一下,看看它们的不同之处。
POST属性可以理解为存储了字典形式的值,而body内存储的是二进制。所以方便取值我们还是使用POST来解决。
演示GET属性与POST属性一起使用:
修改URL,表单提交方式为post
可以发现,GET属性获取的只是URL里?
后面的内容,而POST则是获取用户真正输入的数据。
Django内有一个很奇怪的现象,从上图中我们就可以发现,明明key对应的是一个列表,我们get()不是取出一个列表,而是将列表里面的值取出来。这是Django内自带的一个取值方式,而不是我们平常所见取字典值的get()。
其实这里的get()取出的是列表的内的最后一个值,我们修改一下HTML页面再提交就明白了
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>indextitle>
head>
<body>
<form action="" method="post">
username:<input type="text" name="username"><br/>
username:<input type="text" name="username"><br/>
password:<input type="password" name="password">
<input type="submit" value="提交">
form>
body>
html>
可以发现,现在有两个name属性值相同的input,那么我们都输入完后再提交一次看看。
从上图可以发现,get()取出的是列表的最后一个值,那么getlist()就很明确了,直接获取整个列表。
如果我们获取一个用户上传的文件数据呢,是否能够正常通过:GET、POST来获取呢。那么先来尝试一下:
index.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>indextitle>
head>
<body>
<form action="" method="post">
username:<input type="text" name="username"><br/>
password:<input type="password" name="password"><br/>
上传图片:<input type="file" name="img_file"><br/>
<input type="submit" value="提交">
form>
body>
html>
我们发现,只是获取到了上传的文件名而已,而我们想要的是获取整个文件,那么需要在HTML表单提交时稍作手脚:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>indextitle>
head>
<body>
<form action="" method="post" enctype="multipart/form-data">
username:<input type="text" name="username"><br/>
password:<input type="password" name="password"><br/>
上传图片:<input type="file" name="img_file"><br/>
<input type="submit" value="提交">
form>
body>
html>
form表单的 enctype 属性规定在发送到服务器之前应该如何对表单数据进行编码。
提交再查看一下效果:
这倒好,直接文件名都没有了。但其实并不是这样,只是django识别到了这一操作,将包含文件的数据存放到了:request.FILES
属性内。
我们重启django,打印这个属性看看
此时我们可以将这个图片给下载到本地
from django.shortcuts import render,HttpResponse
def index(request):
img_obj = request.FILES.get('img_file')
print(img_obj.name) # 获取文件名
with open(img_obj.name,'wb') as f:
for i in img_obj.chunks(): # 写入到一个新的文件内
f.write(i)
return render(request,'index.html')
总结:
部分视图函数内,我们会写两种情况,第一种就是如果浏览使用POST传递了数据,我们执行的操作,另一种就是浏览器使用GET请求我们应该执行的操作。
通常访问页面发送的都是GET请求,如果是一个登录界面的话,视图函数会这样写:
def login(request):
if request.method == 'POST':
请求信息提取过程....
if 账号密码校验成功:
登录状态绑定...
return redirect(重定向到主页)
else:
return 返回账号或者密码错误结果
return render(request,'登录页面.html')
如果直接访问登录界面的URL,则返回一个登录页面.html。而如果在这个向这个URL提交post请求时,则执行处理post请求的代码块。
pycharm提供了简单的数据库图形化管理界面,只能做增删改查的一些操作:
像这种的了解一下即可,使用专业的数据库图形化管理软件会更好些。如:Navicat
Django默认使用的是sqlite数据库,我们可以修改成自身需要的数据库,如:MySQL、oracle等。配置一下settings.py文件即可。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': '数据库名称',
'HOST': '数据库主机名(127.0.0.1也可以)',
'PORT': 3306,
'USER': '登录数据库的用户名',
'PASSWORD': '用户密码'
}
}
在django项目下面的__init__.py
,里面增加以下固定代码:
import pymysql
pymysql.install_as_MySQLdb()
这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb 对于py3有很大问题,所以我们需要的驱动是PyMySQL 所以,我们只需要找到项目名文件下的init,在里面写入上序代码。
注意:
有部分的Python3.8解释器在修改了settings里的DATABASES配置,并在
__init__.py
里用pymysql替换了MySQLdb后,跑Django还是会报错(AttributeError: ‘str’ object has no attribute ‘decode’),可以根据最后的报错路径,把提示行的decode改成encode就好了
ORM:Object对象,Relations关系、Mapping映射。简称:对象关系映射
对象关系映射是通过面向对象的方式来操作数据库,这就需要对应的关系映射,数据中可以分为库,表,字段信息,一条条数据,而需要用面向对象的关系去对应。于是就有了下面对应关系。
ORM:目的就是为了能够让不熟悉SQL语句的人通过Python面向对象也能够轻松自如的操作数据库。
首先确保settings.py已经配置连接了数据库,才能执行以下操作。
使用面向对象的方式描述数据库的关系模型,Django采用了以下的方式。我们的模型类需要写在app项目下的models.py文件中
from django.db import models
class User(models.Model): # 表名
# 下面的类属性都是对应表内的字段:id、name、age
# SQL语句:id int primary key auto_increment
id = models.AutoField(primary_key=True)
# SQL语句:name varchar(32)
name = models.CharField(max_length=32) # CharField必须要加max_length参数,指定该字段长度
# SQL语句:age int
age = models.IntegerField()
在创建完模型类以后,这个表并没有真正意义上保存到数据库内,而是需要进行数据库迁移操作。
迁移操作分为两步:
将数据库修改操作先记录下来(对应保存到项目下面的migrations文件夹)执行以下命令,确保执行命令的路径在manage.py文件所在的路径
python3 manage.py makemigrations
将修改操作的记录同步到数据库内,将models.py里面相关的操作都迁移到数据库内
python3 manage.py migrate
当执行完上序操作后,那么我们做的就真正意义保存到了数据库
此时我们查看一下连接数据库:
笔者这个数据库是新建出来的,而django第一次进行数据库迁移会额外创建出很多默认表,那些都是django需求的,我们不必理会,红框内的则是我们自己的表,那为什么不是我们定义的表名呢。Django它会默认在我们的表名前增加一个blog(app项目名)来作为标识。
往后我们要操作这个表,根据models.py里面的类名即可。如果我们动了models.py里面的数据库字段,新增、修改、或是删除都需要执行迁移操作,同步到数据库。
本质上就是通过面向对象的方式,对数据库的数据进行增、删、改、查。
这里将会将我们之前所有内容结合到一起,首先确保基于上序操作已经建立好了User表,那么我们还需要建立几个HTML文件,只需要关注与提交数据有关的标签。
index.html:作为主页使用
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>indextitle>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js">script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js">script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-sm-offset-2">
<h1 class="text-center">用户信息表h1>
<a href="/add/" class="btn btn-info pull-right">新增数据a>
<table class="table table-hover">
<thead class="align-center">
<th>IDth>
<th>Nameth>
<th>Ageth>
<th>Actionth>
thead>
<tbody>
{% for obj in user_data %}
<tr>
<td>{{ obj.id }}td>
<td>{{ obj.name }}td>
<td>{{ obj.age }}td>
<td>
<a href="/edit/?ids={{ obj.id }}" class="btn btn-danger btn-xs">编辑a>
<a href="/delete/?ids={{ obj.id }}" class="btn btn-warning btn-xs">删除a>
td>
tr>
{% endfor %}
tbody>
table>
div>
div>
div>
body>
html>
主页已经建立好了,现在我们需要配置路由了urls.py。根据点击主页的按钮跳转的页面来配置路由。
from django.contrib import admin
from django.urls import path
from blog import views
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', views.index),
path('add/', views.add),
path('edit/', views.edit),
path('delete/', views.delete),
]
如果前后端不是同一人开发的话,这些url必须要提前规定好。
针对主页的视图函数
def index(request):
# 获取User表的所有内容,获取出来的都是一条条记录,然后发给页面,一条记录代表一行数据
obj = User.objects.all()
return render(request,'index.html',{'user_data':obj})
add.html:用于新增数据页
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>addtitle>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js">script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js">script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-sm-offset-2">
<h1 class="text-center">数据新增h1>
<form action="" method="post">
Name:<input type="text" name="name" class="form-control" />
Age:<input type="text" name="age" class="form-control" /><br/>
<input type="submit" value="提交新增" class="btn btn-info btn-block">
form>
div>
div>
div>
body>
html>
根据上面表单的action可以看出,数据是提交到原地提交post请求的。
add页面视图函数如下:
def add(request):
if request.method == 'POST': # 接收用户增加数据发送的post请求
name = request.POST.get('name')
age = request.POST.get('age')
age = int(age) # 因为接受的是字符串类型,而我们age字段定义的是整型,所以转换一下
User.objects.create(name=name,age=age) # id字段不需要传值,因为设置在自动增长
return redirect('/index/') # 重定向到主页
return render(request,'add.html')
当原地提交post请求的数据会被我们当前视图函数接受到,然后再写入数据库内。
此时我们就可以从主页点击新增数据,然后输入完毕后,来检验效果:
此时已经达到了数据同步到web页面的效果了,那么我们再来尝试编辑。
edit.html:修改数据的页面
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>addtitle>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js">script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js">script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-sm-offset-2">
<h1 class="text-center">编辑数据h1>
<form action="" method="post">
Name:<input type="text" name="name" class="form-control" />
Age:<input type="text" name="age" class="form-control" /><br/>
<input type="submit" value="提交修改" class="btn btn-warning btn-block">
form>
div>
div>
div>
body>
html>
编辑页面的套路基本和新增数据的页面差不多,观察表单的提交地址是原地,再根据input元素的name属性值在后端接收数据。
因为我们在index页面内定义了,点击编辑按钮,URL地址中还会携带一个用户的编号,那么我们需要接收这个编号,再根据它来修改用户信息。
def edit(request):
if request.method == 'POST':
ids = request.GET.get('ids') # GET可以获取URL内 问号 后面的数据
name = request.POST.get('name')
age = request.POST.get('age')
age = int(age) # 因为接受的是字符串类型,而我们age字段定义的是整型,所以转换一下
obj = User.objects.get(id=ids) # 获取这个用户对象
obj.name = name # 修改这个用户的姓名
obj.age = age # 修改用户的年龄
obj.save() # 将修改后的数据保存到数据库
# ob.update(name=name,age=age)执行这个方法不需要save保存
return redirect('/index/') # 重定向到主页
return render(request,'edit.html')
删除自然就不需要什么页面了,在主页点击后就会跳转到一个URL执行一个视图函数,并且这个URL内携带用户的编号,那么基本操作套路就是一样了。
def delete(request):
ids = request.GET.get('ids')
User.objects.get(id=ids).delete() # 删除数据库内,和页面传递过来相同编号的用户。
return redirect('/index/') # 重定向到主页,达到一个刷新的效果
那么来总结一下上序所操作所用到的内容。
obj = User.object.get(id=ids) # 获取一个用户对象
print(obj.name) # 查询这个用户的name值
obj_all = User.object.all() # 获取所有用户对象
User.object.create(name='jack',age=18) # 向数据库写入一条记录,name字段值为jack,age字段值为18
obj.name = 'tom' # 修改这个用户的name属性值
obj.age = 18 # 修改这个用户的age属性值
obj.save() # 将修改后的属性值,同步到数据库
obj.delete() # 在数据库内删除这个用户
至此已经完成了基本操作,可以通过面向对象的形式来操作数据库里面的数据,但前提是模型类是已经存在的数据库表,如果不存在则当我们执行迁移时,Django帮助我们自动创建。那么如果要导入已经存在的表到我们的模型里面呢。那么我们来了解一下吧!
在Django内操作数据库是通过模型models.py里面的类,而我们目前只了解怎么通过它创建数据库表,而没有了解过如何使用它导入已经存在数据库内的表。
其方式有两种:
在模型内,按照表的完整数据结构创建类名、类属性,整体代码如下:
class Book(models.Model):
name = models.CharField(max_length=30, blank=True, null=True)
price = models.FloatField(blank=True, null=True)
author = models.CharField(max_length=20, blank=True, null=True)
class Meta:
db_table = 'book'
会发现多出来一个Meta内部类,其作用我们目前不深究,知道此时它的作用即可:通过db_table属性,指定模型类对应的数据库表名。
偷懒方式:通过Django自带的命令inspectdb将数据库内的表名,生成上面这种形式:
为什么不执行迁移操作?因为我们并没有向模型类执行:新增表、或者新增、修改字段等操作。
通过ORM可以完整将表与表之间的外键关系全部建立出来,甚至创建出中间表。
表之间的关系有三种:
如果有四张逻辑表:书籍表、出版社表、作者表、作者详细表
书籍表:对应出版社表、对应作者表;(一对多)
作者表:对应作者详细表(一对一)
在实验开始前,为了避免不必要错误,可以新建一个Django项目;
那么我们现在使用ORM将它们创建出来,建议实验的话,创建一个新的数据库保存这些表,别忘了settings.py连接数据库:
models.py
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8,decimal_places=2) # 总共8位 小数占2位
# 出版社外键
publish = models.ForeignKey(to='Publish') # 默认就是主键
"""自动在外键字段后面加id后缀"""
# 作者外键
authors = models.ManyToManyField(to='Author') # 自动帮你创建书籍和作者的第三张表
"""虚拟字段不会在表中实例化出来 而是告诉ORM创建第三张关系表"""
class Publish(models.Model):
title = models.CharField(max_length=32)
email = models.EmailField()
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
author_detail = models.OneToOneField(to='AuthorDetail')
"""自动在外键字段后面加id后缀"""
class AuthorDetail(models.Model):
phone = models.BigIntegerField()
addr = models.CharField(max_length=128)
由于我们在models新增了表,那么需要进行迁移才能保存到数据库内;进入项目根目录下,运行以下两条命令:
python manage.py makemigrations;
python manage.py migrate
此时我们就会发现,Django的ORM帮助我们创建了全部有关联性的表,其中:blog_book_authors第三张表,每个书籍对应参与的各个作者。
相信这部门内容,对于有数据库相关经验的小伙伴来说,应该没有那么复杂,只是借助于Django的ORM创建的。
如果本文对您有帮助,别忘一键3连,您的支持就是笔者最大的鼓励,感谢阅读!
下一章节传送门:Django请求生命周期、路由层
技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请
点赞 收藏+关注
子夜期待您的关注,谢谢支持!