一起学习Django框架(二)request对象、ORM操作数据库

前言:

Django自带的功能有很多,基本能满足我们的网页开发,本章节将介绍:解析request用户请求、models文件的使用、Django ORM框架、数据库的CURD。那么话不多说,我们开始了解吧!



一、request解析

1.1 request简介

相信对Django了解过一些之后应该知道,每次在视图文件里面定义函数都会补上一个request形参:

def index(request):
	return HttpReponse('Hello World!')

作用:浏览器访问某个URL后,通过路由找到对应的视图里面某个函数,然后将浏览器的请求传递给这个函数,这也就是为何要定义一个形参了,而通常形参名都是:request,这是一种规范。

request内包含了浏览器向Django服务端发送的请求,常见发送请求的方法有两种:GET、POST

它们携带请求的形式是不同的,我们先尝试使用GET方法向Django发送请求


1.2 GET请求

index.html文件(前提是已经搭建好了Django的基本环境)

<!DOCTYPE html>
<html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>index</title>
      </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请求下面一起演示


1.3 POST请求

先将表单的提交请求方式修改为POST:

<!DOCTYPE html>
<html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>index</title>
      </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则是获取用户真正输入的数据。


1.4 get()与getlist()

Django内有一个很奇怪的现象,从上图中我们就可以发现,明明key对应的是一个列表,我们get()不是取出一个列表,而是将列表里面的值取出来。这是Django内自带的一个取值方式,而不是我们平常所见取字典值的get()。

其实这里的get()取出的是列表的内的最后一个值,我们修改一下HTML页面再提交就明白了

<!DOCTYPE html>
<html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>index</title>
      </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()就很明确了,直接获取整个列表。

在这里插入图片描述


1.5 获取文件上传数据

如果我们获取一个用户上传的文件数据呢,是否能够正常通过:GET、POST来获取呢。那么先来尝试一下:

index.html

<!DOCTYPE html>
<html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>index</title>
      </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>index</title>
      </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 属性规定在发送到服务器之前应该如何对表单数据进行编码。

  • 默认地,表单数据会编码为 “application/x-www-form-urlencoded”。就是说,在发送到服务器之前,所有字符都会进行编码(空格转换为 “+” 加号,特殊符号转换为 ASCII HEX 值)。
  • multipart/form-data:不对字符进行编码,在使用包含文件上传控件的表单时,必须使用该值。

提交再查看一下效果:
在这里插入图片描述
这倒好,直接文件名都没有了。但其实并不是这样,只是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连接数据库

pycharm提供了简单的数据库图形化管理界面,只能做增删改查的一些操作:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
像这种的了解一下即可,使用专业的数据库图形化管理软件会更好些。如:Navicat



三、Django连接数据库

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就好了



四、Django ORM框架

4.1 ORM简介

ORM:Object对象,Relations关系、Mapping映射。简称:对象关系映射

对象关系映射是通过面向对象的方式来操作数据库,这就需要对应的关系映射,数据中可以分为库,表,字段信息,一条条数据,而需要用面向对象的关系去对应。于是就有了下面对应关系。

  • 数据库 ======= 面向对象模型
  • 表 ======= 类
  • 字段 ======= 类属性
  • 记录 ======= 每个实例

ORM:目的就是为了能够让不熟悉SQL语句的人通过Python面向对象也能够轻松自如的操作数据库。



4.2 模型类的创建

首先确保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()

在创建完模型类以后,这个表并没有真正意义上保存到数据库内,而是需要进行数据库迁移操作。

迁移操作分为两步:

  1. 将数据库修改操作先记录下来(对应保存到项目下面的migrations文件夹)执行以下命令,确保执行命令的路径在manage.py文件所在的路径

    python3 manage.py makemigrations
    
  2. 将修改操作的记录同步到数据库内,将models.py里面相关的操作都迁移到数据库内

    python3 manage.py migrate
    

当执行完上序操作后,那么我们做的就真正意义保存到了数据库
在这里插入图片描述
此时我们查看一下连接数据库:
在这里插入图片描述

笔者这个数据库是新建出来的,而django第一次进行数据库迁移会额外创建出很多默认表,那些都是django需求的,我们不必理会,红框内的则是我们自己的表,那为什么不是我们定义的表名呢。Django它会默认在我们的表名前增加一个blog(app项目名)来作为标识。

往后我们要操作这个表,根据models.py里面的类名即可。如果我们动了models.py里面的数据库字段,新增、修改、或是删除都需要执行迁移操作,同步到数据库。



4.3 基于ORM进行CURD

本质上就是通过面向对象的方式,对数据库的数据进行增、删、改、查。

这里将会将我们之前所有内容结合到一起,首先确保基于上序操作已经建立好了User表,那么我们还需要建立几个HTML文件,只需要关注与提交数据有关的标签。

index.html:作为主页使用

<!DOCTYPE html>
<html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>index</title>
          <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>ID</th>
                            <th>Name</th>
                            <th>Age</th>
                            <th>Action</th>
                        </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。根据点击主页的按钮跳转的页面来配置路由。

在这里插入图片描述
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>add</title>
          <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>add</title>
          <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将数据库内的表名,生成上面这种形式:

    在这里插入图片描述
    我们直接复制根据数据库表结构生产的Python代码,放入模型内即可。

    在这里插入图片描述

为什么不执行迁移操作?因为我们并没有向模型类执行:新增表、或者新增、修改字段等操作。



六、基于ORM建立外键

通过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请求生命周期、路由层


技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请点赞 收藏+关注 子夜期待您的关注,谢谢支持!

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值