Django 03 Django ORM

作者 柚爸

在前后端的交互中,除了简单的业务逻辑以外,现代web应用的背后都是数据库在支撑.Django的数据库操作采用ORM模型,与之前学过的sqlalchemy异曲同工.

ORM的核心就是以类和对象来操作数据库,而不用管数据库底层是什么类型的数据库,只要选择好驱动就可以进行操作.

ORM模型的核心观点:

  • 类=数据库的表
  • 对象=数据库的一行数据
  • 对象的属性=字段内容

ORM可以对表和其中的数据进行修改,但无法操作数据库.

使用ORM的准备工作

由于之前了解了一些关于ORM的内容,所以直接来进行操作.在MySql里建立了一个名字叫做mydata的数据库,不用创建任何表格,之后打算把这个数据库交个Django处理,所以需要在settings.py里配置数据库相关内容.

DATABASES = {
    'default': {
        # 使用的数据库类型(引擎)
        'ENGINE': 'django.db.backends.mysql',
        # 连接数据库的地址
        "HOST": "127.0.0.1",
        # 端口
        "PORT": 3306,
        # 数据库名称
        "NAME": "mydata",
        # 用户
        "USER": 'root',
        # 密码
        "PASSWORD": "conyli.cc"
    }
}

将数据库的地址,端口,用户和密码以及数据库名称写在默认配置中.然后需要选择具体操作数据库的模块.在项目的__init__.py中,加上如下两句话:

import pymysql
pymysql.install_as_MySQLdb()

这样就会用pymysql代替默认的MySQLdb模块让Django去连接.
之后就是编写具体代码了,注意,ORM模块的代码,只能写在每个APP目录下的models.py内,写在其他地方不生效.

使用ORM操作数据库

来编写一些代码,models里已经默认引入了一个models模块,自己编写的表类,必须要继承models.Model类,就可以成为一个表类.

from django.db import models

class UserInfo(models.Model):
    # 创建一个自增的主键
    id = models.AutoField(primary_key=True)
    # 创建一个varchar类型的不能为空的字段
    name = models.CharField(null=False)

之后,需要在命令行执行两个命令来创建数据库:

python manage.py makemigrations
python manage.py migrate

之后一系列的过程结束之后,回到navicat里可以看到mydata数据库下边有一堆表,其中有一个名称叫做login_userinfo表格.这个名字就是APP的名称下划线加上类名的小写,也就是刚才写的类生成的表.到这里就建立好了Django项目所使用的数据库.

把建立数据库并且配置到Django的使用步骤总结一下:

  1. 在MySQL数据库里创建一个数据库,名字自定,不用添加任何表
  2. 在Django的settings.py内填入数据库的相关配置
  3. 修改项目的__init__.py, 引入pymysql并且让其按照MySQLdb的方式工作
  4. 在app下边的models.py文件中编写ORM代码
  5. 执行两个命令,让Django创建表格.

数据的增删改查

表和数据库就是一种数据结构,后边最重要的事情,就是按照设计好的结构,对里边的数据进行操作,比如前端发回来的数据需要更新进数据库,从数据库中取内容放到前端去等等.
Django对于数据的增删改查主要是通过对自己生成的表格类的一些特殊操作.

查询用户列表并在页面上显示出来

接触查询数据以及进一步的Django页面模板语言.
首先编写一个新的功能,去数据库中拿到数据.

def show_user(request):

    ret = models.UserInfo.objects.all()
    for row in ret:
        print(row.id,row.name)


    return HttpResponse('拿到数据了')

这里要记住的是固定用法,也就是类的object.all()方法,这个方法返回的是一个列表,列表的每一个元素是个UserInfo类的对象(也就是一行),对列表里的元素调用.id和.name,,也就是字段名称作为属性,即可拿到具体的值.这个例子里就会打印出数据库UserInfo表里每一行的id和名称.
那么剩下的问题就是,如何将结果在页面上展示出来.由于只能返回一个结果,显然不能够在函数内将数据解包出来.这个时候就要用到Django模板语言的循环功能,即让Django在替换页面字符串的时候,动态的解开数据然后生成和替换字符串.我们需要新建一个users.html用于展示用户列表.

<table class="table-bordered">
    <thead>
    <tr>
        <th>id</th>
        <th>用户名</th>
    </tr>
    </thead>
    <tbody>
    {% for row in user_list %}
        <tr>
            <td>{{ row.id }}</td>
            <td>{{ row.name }}</td>
        </tr>
    {% endfor %}
    </tbody>
</table>

这里关键一步,是要了解Django模板语言的循环功能,{% for row in user_list %}和{% endfor %}之间就是要被循环的功能,通过看代码就知道,这里的代码解析很类似python语言,就是从user_list变量里读出每一行,然后一行两列显示id和name.由于是返回一个页面,因此还需要对刚才的show_user函数进行改写.

def show_user(request):
    ret = models.UserInfo.objects.all()
    return render(request, 'users.html', {'user_list': ret})

这里也是关键的一步,就是返回users.html页面,同时用和页面里的同名变量作为键,值是对应的表格查询结果.这样就可以在users.html内看到所有的用户了.
总结一下,有三个关键点:

  1. models.UserInfo.objects.all()查询出全部的行,放在一个列表内返回,通过行的字段名可以拿到值
  2. Django的页面模板语言除了替换特定变量,还可以用循环指令做类似python的工作
  3. 页面返回的时候,将查询结果直接返回到页面,由模板语言完成动态添加内容.

新增用户

通过学到的查询知识,可以很容易的想到,ORM模型应该还支持各种方法操作数据,对于目前每一个工作都要实现前后端交互的我们,只需要再编写一个页面,让用户提交表单,然后将该用户增加到数据库中,并且可以在刚才的页面内显示增加后的用户列表.有了前边接受并处理表单数据的经验,可以很快编写出函数:

def add_user(request):
    error_hint = ''
    username = request.POST.get('username', None)
    if request.method == 'POST':
        if len(username) > 32 or len(username) == 0:
            error_hint='输入的用户名太长或太短'
            return render(request, 'add_user.html', {"error": error_hint})
        else:
            models.UserInfo.objects.create(name=username)
            return redirect("/users/")
    if request.method == "GET":
        return render(request, 'add_user.html')

这个add_user函数当请求是GET的时候,返回输入名字的页面,当请求是POST的时候,拿到username,符合标准就添加进数据库并且返回当前的用户列表,不符合标准就返回带有错误提示的页面.
这里的关键是ORM的固定用法.create(name=username),直接将字段名当做关键字参数,就可以新建一行数据了.
至于web页面可以简单的写一个表单:

<form action="/add_user/" method="post" class="form-horizontal">
    <label for="username" class="col-sm-2 control-label">请输入新增的用户名</label>
    <div class="col-sm-4">
    <input type="text" id="username" name="username" >
    </div>
    <div class="col-sm-12">
    <button type="submit" class="btn btn-primary col-xs-offset-4">Submit</button>
        </div>
    <p>{{ error }}</p>
</form>

删除用户

在删除用户的过程中,将要用到ORM操作数据库的新功能,也就是相当于SQL语句的条件语句使用,以及通过GET方式像后端传递数据的方法来实现删除.
添加用户的过程是肯定要让用户输入的,但是删除用户如果让用户输入id或者名称来删除,则有点过于麻烦,而且用户名很可能会有重名,因为名称没有设置不能重复.最好的办法就是将这个操作视觉化,也就是在每行后边直接添加删除按钮,让用户所见即所得.但是有个问题,添加的按钮如何能够像表单一样来返回数据给后端呢,要为每个按钮都用一个表单来包裹吗.这个地方就可以用到通过GET请求发送数据的方法,这样就无需使用表单,只要使用A标签即可,为了美观,将A标签通过Bootstrap制作成按钮一样的外观就可以了.
还有一个小知识点,即模板语言for内部可以通过forloop.counter来获得从1开始的for循环每次的次数,一般可以显示在表格前边当做序号.根据这两个新知识修改HTML文件如下:

<tbody>
    {% for row in user_list %}

        <tr>
            <td>{{ forloop.counter }}</td>
            <td>{{ row.id }}</td>
            <td>{{ row.name }}</td>
            <td class="text-center"><a href ='/delete_user/?id={{ row.id }}' class="btn btn-default" type="button">删除</a></td>
        </tr>

    {% endfor %}
{#用a标签做get请求,和post不同,#}
    </tbody>

这里的关键是a标签的href属性,由于在生成的时候每行可以拿到id,就可以把id加在GET请求内一同返回(多个属性就用&连在一起),这样后端就可以拿到id对应的数值来操作了.再编写后端delete_user函数:

def delete_user(request):
    # 取到删除指定数据的id,由于id唯一,通过id给
    user_id = request.GET.get("id", None)
    if user_id:
        try:
            del_obj = models.UserInfo.objects.get(id=user_id)
            del_obj.delete()
        except:
            return render(request, 'users.html',
                          {'user_list': models.UserInfo.objects.all(), "error_on_delete": "用户不存在"})
        return redirect("/users/")
    else:
        return render(request, 'users.html', {'user_list': models.UserInfo.objects.all(), "error_on_delete": "用户不存在"})

这里用到的是ORM的新操作方法,就是通过objects.get(id=user_id)来取出所在的那一行(后边会学到get方法只能取到一行),然后对这个对象执行.delete()方法删除对象.当找不到id的时候,就返回用户不存在的错误.
这样就完成了一个删除行的前后端交互方法.删除按钮放在表格内非常直观,删除后的结果也很方便的可以看到.由于目前只是在写Django内的代码,还没有在前端写JS,实际上通过JS可以将添加和删除都做在同一个用户界面里.而且也可以在页面里不显示id,因为普通用户对于id可能不清楚是做什么的,只保留序号即可.

GET和POST方法

在Django 2部分的第二篇文章里提到了GET和POST方法,那时候才刚开始使用GET和POST,现在自己编写的代码中已经使用过了GET和POST方法,可以来回头看一看GET和POST方法了.

GET方法的主要用途:

  1. 直接在浏览器地址栏输入URL访问网站
  2. a标签发出的是GET请求
  3. 用?key=value&key2=value2传递数据,但是GET请求携带的数据不能够超过40K

POST的主要用途:

  1. 向后端提交大段数据或者结构化的数据
  2. 向后端提交包含隐私的数据,比如登录和注册,支付,各种验证等
  3. 上传文件

可见,像刚才的删除请求,如果不点删除按钮,而且用户已经知道了我们的方法,那么可以直接在浏览器中输入对应的链接,也可以成功删除指定的数据.相比接收POST请求然后进行操作,GET请求的私密性要差很多.所以一般还是需要用户认证登录等额外手段来保证操作有权限.

修改用户名

修改用户的思路和之前的查找思路非常类似,先找到对应id的行,然后修改name为新的值.然后前端需要创建元素用于修改即可.
用刚才相同的思路,即增加一个按钮用于修改,然后增加一个页面用于提交,最后用ORM操作数据库.
这里需要注意的是,增加的页面里设置input按钮的内容为需要修改的内容的默认值,此外在post的时候,还必须要传递id,这个时候可以采取在HTML页面里埋一个隐藏的input框,其值设置为生成网页时候的id值,就可以继续传递了.

HTML页面的代码如下:

<div class="container">
    <h1>输入新的用户名</h1>
    <form action="/edit_user/" method="post" class="form-inline">
        <div class="form-group">
            <label for="username" class="sr-only control-label "></label>
            <input type="text" class="form-control" id="username" placeholder="Username" name="username" value="{{ edit_obj.name }}">
        </div>
        <div class="form-group">
            <button type="submit" class="btn btn-primary">Submit</button>
        </div>
        <input type="hidden" value ="{{ user_id }}" name="id">
    </form>
    <div class="row">
        <p class="text-danger text-center col-sm-2">{{ error }}</p>
    </div>

此外,还需要在原来的用户列表增加一个按钮,也是带着id一起返回给edit_user函数.

修改用户的函数如下:

def edit_user(request):
    if request.method == "POST":
        new_name = request.POST.get("username", None)
        edit_id = request.POST.get("id", None)
        if len(new_name) > 32 or len(new_name) == 0:
            return render(
                request, 'edit_user.html', {
                    "edit_obj": models.UserInfo.objects.get(
                        id=edit_id), "user_id": edit_id, "error": "用户名长度为1-32个字符"})

        row = models.UserInfo.objects.get(id=edit_id)
        row.name = new_name
        row.save()
        return redirect("/users/")

    else:
        edit_id = request.GET.get('id', None)
        if edit_id:
            edit_obj = models.UserInfo.objects.get(id=edit_id)
            return render(
                request, 'edit_user.html', {
                    "edit_obj": edit_obj, "user_id": edit_id})

这里引入修改的方法是先设置好值,然后用.save()方法submit到数据库中.