前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python自动化开发学习19-Djan

Python自动化开发学习19-Djan

作者头像
py3study
发布2020-01-06 14:08:54
1.3K0
发布2020-01-06 14:08:54
举报
文章被收录于专栏:python3python3

接下来,我们把Django分为视图(View)、路由系统(URL)、ORM(Model)、模板(Templates )这4块进行学习。

视图

提交数据

上节课已经用过 request.POST.get() 获取提交的数据了,现在来看看有多选框的情况,多选的话应该要提交多个数据。先写一个有单选、多选、下拉列表的html:

代码语言:javascript
复制
<body>
<form action="/choice/" method="post">
    <p>
        性别:
        <input type="radio" name="gender" id="man" value="male" />
            <label for="man">男性</label>
        <input type="radio" name="gender" id="female" value="female" />
            <label for="female">女性</label>
    </p>
    <p>
        爱好:
        <input type="checkbox" id="football" name="favor" value="football" />
            <label for="football">足球</label>
        <input type="checkbox" id="basketball" name="favor" value="basketball" />
            <label for="basketball">篮球</label>
        <input type="checkbox" id="volleyball" name="favor" value="volleyball" />
            <label for="volleyball">排球</label>
        <input type="checkbox" id="baseball" name="favor" value="baseball" />
            <label for="baseball">棒球</label>
    </p>
    <p>
        <label for="city">城市:</label>
        <select name="city" id="city">
            <option value="BJ">北京</option>
            <option value="SH">上海</option>
            <option value="GJ">广州</option>
            <option value="SZ">深圳</option>
        </select>
    </p>
    <p>
        <label for="skill">技能:</label>
        <select name="skill" id="skill" multiple="multiple">
            <option value="python">Python</option>
            <option value="html">HTML</option>
            <option value="css">CSS</option>
            <option value="js">JavaScript</option>
        </select>
    </p>
    <p>
        上传:<input type="file" name="file" />
    </p>
    <input type="submit" value="提交" />
</form>
</body>

然后写一个处理函数,用get方法获取一下提交的值:

代码语言:javascript
复制
def choice(request):
    if request.method == 'GET':
        return render(request, 'choice.html')
    elif request.method == 'POST':
        gender = request.POST.get('gender')
        favor = request.POST.get('favor')
        city = request.POST.get('city')
        skill = request.POST.get('skill')
        file = request.POST.get('file')
        print(gender, favor, city, skill)
        print(file, type(file))
        return render(request, 'choice.html')
    # 处理POST和GET,还有其他的提交方法,比如:PUT、DELETE、HEAD、OPTION
    else:
        return redirect('/admin/')

所有的值都能获取到,但是对于多选的值也只能获取到一个,对于这种情况,我们需要用到另一个方法 request.POST.getlist() ,把上面的代码都替换成新的方法:

代码语言:javascript
复制
def choice(request):
    if request.method == 'GET':
        return render(request, 'choice.html')
    elif request.method == 'POST':
        gender = request.POST.getlist('gender')
        favor = request.POST.getlist('favor')
        city = request.POST.getlist('city')
        skill = request.POST.getlist('skill')
        file = request.POST.getlist('file')
        print(gender, favor, city, skill)
        print(file, type(file))
        return render(request, 'choice.html')
    # 除了POST和GET,客户端还可能有其他的提交方法,比如:PUT、DELETE、HEAD、OPTION
    else:
        return redirect('/admin/')

使用 getlist() 方法,返回的是一个列表,多个值的情况也能获取完整。当然,单选的话还是继续使用 get() 方法方便。 例子中还有个上传文件的input,这里只能获取到文件名,下面接着讲。

上传文件

普通的form接收不了文件,需要在form标签中要定义 enctype="multipart/form-data" 。然后把 type="file" 的input标签放在这个form里。所以得为上传文件单独写一个form。另外POST里并没有文件内容,文件内容再FILES里:

代码语言:javascript
复制
<form action="/upload/" method="post" enctype="multipart/form-data">
    <p>
        上传:<input type="file" name="file" />
    </p>
    <input type="submit" value="提交" />
</form>

然后再处理函数里先尝试获取一下接收到的内容:

代码语言:javascript
复制
def upload(request):
    if request.method == 'GET':
        return render(request, 'upload.html')
    elif request.method == 'POST':
        obj = request.FILES.get('file')
        print(obj, type(obj), obj.name)
        return render(request, 'upload.html')
    # 处理POST和GET,还有其他的提交方法,比如:PUT、DELETE、HEAD、OPTION
    else:
        return redirect('/admin/')

上面打印了3个变量,obj打印出来是个文件名,但是实际是个class。打印type(obj)可以看到它的类型。obj.name才是真正的文件名。现在要把文件保存到本地,先建一个专门的文件夹upload,准备存放接收到的文件。要接收文件的内容,我们需要读取 obj.chunks() ,所以要接收文件上传参考下面的方法:

代码语言:javascript
复制
def upload(request):
    if request.method == 'GET':
        return render(request, 'upload.html')
    elif request.method == 'POST':
        # 获取文件对象
        obj = request.FILES.get('file')
        # 本地创建一个文件用来接收上传的文件内容
        with open('%s/%s' % ('upload', obj.name), 'wb') as file:
            # 循环接收文件的内容,写入到本地的文件中去
            for data in obj.chunks():
                file.write(data)
        return render(request, 'upload.html')
    # 处理POST和GET,还有其他的提交方法,比如:PUT、DELETE、HEAD、OPTION
    else:
        return redirect('/admin/')

CBV 和 FBV

到目前为止,所有的处理都是写在一个函数里的。Django还提供另外一个方式,我们也可以通过类来处理。

  • FBV(function base views) 就是在视图里使用函数处理请求。
  • CBV(class base views) 就是在视图里使用类处理请求。

创建处理请求的类 现在使用CBV把上面提交数据里的choice方法重新写一下:

代码语言:javascript
复制
from django.views import View
# CBV 的类需要继承上面的View
class Choice(View):

    def get(self, request):
        """GET请求提交到这里"""
        print('get')
        return render(request, 'choice.html')

    def post(self, request):
        """POST请求提交到这里"""
        gender = request.POST.getlist('gender')
        favor = request.POST.getlist('favor')
        city = request.POST.getlist('city')
        skill = request.POST.getlist('skill')
        file = request.POST.getlist('file')
        print(gender, favor, city, skill)
        print(file, type(file))
        return render(request, 'choice.html')

创建对应关系 urls.py 里的对应关系也要修改一下,中间写上类名,后面固定跟一个 .as_view() ,就是执行这个类的as_view()方法,具体如下:

代码语言:javascript
复制
from cmdb import views
urlpatterns = [
    # path('choice/', views.choice),
    path('choice/', views.Choice.as_view()),
]

各种类型的提交方法 先去看看继承的View类里有什么,在源码的base.py这个文件里。首先里面定义了一个公有属性:

代码语言:javascript
复制
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

所以处理post和get,还可以处理这么多的请求方法,用一起来也很简单,在类里照着别的一样定义一个同名方法就可以了。 处理执行前后自定义操作 继续看源码的View。这里可以跳过只看结论,调用了as_view()方法里面会再调用一个dispatch()方法。这个dispatch()方法里是通过映射获取我们的 request.method 即提交的方法来调用我们的处理方法的。dispatch()的源码如下:

代码语言:javascript
复制
    def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn't exist,
        # defer to the error handler. Also defer to the error handler if the
        # request method isn't on the approved list.
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)

结论就是,根据不同的请求类型提交到不同的处理方法,是用过dispatch()方法里通过映射来实现的。先执行dispatch()方法然后再调用对应的提交类型的处理方法。所以通过继承和重构dispatch()方法,可以在处理方法执行前和执行后自定义一些操作。如果需要的话就在我们的类里继承并重构,参考这里:

代码语言:javascript
复制
from django.views import View
# CBV 的类需要继承上面的View
class Choice(View):

    def dispatch(self, request, *args, **kwargs):
        print('before')  # 处理前执行的操作
        # 完全执行父类的这个方法
        obj = super(Choice, self).dispatch(request, *args, **kwargs)
        print('after')  # 处理后执行的操作
        # 这里一定要把处理结果返回
        return obj

所有提交类型都不匹配的情况 还是上面的dispatch()方法,在最后return之前,也就是提交的类型没有匹配的处理方法的时候,默认调用执行 http_method_not_allowed() 方法,返回一个405页面。如果需要按照之前FBV中的else那样匹配其余所有类型的提交方法的话,那就在我们的类里重构这个方法,把之前FBV中if里面最后的else的逻辑补上:

代码语言:javascript
复制
    def http_method_not_allowed(self, request, *args, **kwargs):
        return redirect('/admin/')

给views.py分类

默认所有的处理函数都是写在views.py这个文件里的。如果处理函数很多,全部写在一个文件里也会很乱。这是可以考虑创建一个views包来替代原来的views.py文件。然后在views包里创建多个py文件来写我们的处理函数。比如:

  • views/account.py 是用户相关的操作,登录认证之类的
  • views/test.py 是用来测试的处理函数
  • views/order.py 订单相关的操作

路由系统,URL

模板言语循环字典

模板语言不属于路由系统,由于后面的例子会用到,先讲一点。 先看一下模板语言如何处理字典的,在 views.py 里添加一个字典,然后在页面里返回:

代码语言:javascript
复制
DICT = {
    'k1': 'value1',
    'k2': 'value2',
    'k3': 'value3',
}

def dict(request):
    return render(request, 'dict.html', {'dict': DICT})

下面的html里演示了用法:

代码语言:javascript
复制
<body>
<p>返回整个字典</p>
{{ dict }}
<p>返回的是key</p>
<ul>
    {% for i in dict %}
        <li>{{ i }}</li>
    {% endfor %}
</ul>
<p>返回的是key</p>
<ul>
    {% for key in dict.keys %}
        <li>{{ key }}</li>
    {% endfor %}
</ul>
<p>返回的是value</p>
<ul>
    {% for value in dict.values %}
        <li>{{ value }}</li>
    {% endfor %}
</ul>
<p>返回key和value</p>
<ul>
    {% for k,v in dict.items %}
        <li>{{ k }}: {{ v }}</li>
    {% endfor %}
</ul>
</body>

循环字典,和python里是差不多的,就是后面没有括号():

  • 直接dict :循环的是key,不明确所以不推荐
  • dict.keys :循环key
  • dict.values :循环values
  • dict.items :循环key和values

一条对应关系对应多个页面

现在我们已经可以用模板语言处理字典了,先来一个有点数据的字典:

代码语言:javascript
复制
USER_DICT = {
    '1': {'name': 'Adam', 'age': 22, 'dept': 'IT'},
    '2': {'name': 'Bob', 'age': 32, 'dept': 'IT'},
    '3': {'name': 'Carmen', 'age': 30, 'dept': 'Sales'},
    '4': {'name': 'David', 'age': 40, 'dept': 'HR'},
    '5': {'name': 'Edda', 'age': 26, 'dept': 'HR'},
}

def users(request):
    return render(request, 'users.html', {'user_dict': USER_DICT})

上面的处理函数只是把内存的数据变的复杂了一点。另外这里的key用的是数字,我们可以把它当做是数据库获取到的数据的自增id。

基于get方法的实现

接下来重新写一个简单的html,页面里只显示字典的name的值,其他的值都不显示出来。换做提供一个a标签,可以通过点击a标签打开一个显示详细内容的页面:

代码语言:javascript
复制
<ul>
    {% for k,v in user_dict.items %}
        <li><a target='_blank' href='/detail/?nid={{ k }}'>{{ v.name }}</a></li>
    {% endfor %}
</ul>

去urls.py里添加完对应关系后,就可以打开这个页面。上面a标签里的连接指向的是一个detail的页面,并且提交的同时也提交一个nid值用于detail页面查找并显示出详细的内容。 显示详细的处理函数:

代码语言:javascript
复制
def detail(request):
    # 用get方法获取到nid
    nid = request.GET.get('nid')
    # 通过nid获取到详细数据,最后给return返回
    detail_info = USER_DICT[nid]
    return render(request, 'detail.html', {'detail_info': detail_info})

还要写一个detail.html 的页面。上面处理函数已经通过get请求的nid去获取到具体得详细数据并返回了,这里直接把数据显示出来:

代码语言:javascript
复制
<body>
<h1>详细信息</h1>
<h3>用户名:{{ detail_info.name }}</h3>
<h3>年龄:{{ detail_info.age }}</h3>
<h3>部门:{{ detail_info.dept }}</h3>
</body>

上面的方法是在users页面以get形式提交到detail页面,然后detail页面里分析get的请求内容,获取到对应的详细信息,在页面里显示出来。

基于正则表达式的url来实现

还有另外一种实现方式。下面说的效果一样,但是这种方式更好。不传入参数,而是不同的urel '/detail-1/' 这样,这个就需要用到正则表达式。先把urls.py里的对应关系改成正则的形式:

代码语言:javascript
复制
from django.urls import path, re_path
from cmdb import views
urlpatterns = [
    # path('detail/', views.detail),
    re_path('^detail-(\d+).html', views.detail),
]

因为这里要匹配正则了,之前的path不再适用,这里要导入re_path来匹配正则。url的正则表达式都以^开头,从头开始匹配 users.html显示不用改,但是要修改一个a标签里的内容,现在url后面不需要用get方式提交任何数据,但是请求的url本事是会变化的:

代码语言:javascript
复制
<ul>
    {% for k,v in user_dict.items %}
        <!--
        <li><a target='_blank' href='/detail/?nid={{ k }}'>{{ v.name }}</a></li>
        -->
        <li><a target='_blank' href='/detail-{{ k }}.html'>{{ v.name }}</a></li>
    {% endfor %}
</ul>

最后是显示详细信息的页面,detail.html不需要任何变动,只要views.py里处理函数的return不变就好,但是获取数据的方式变了:

代码语言:javascript
复制
def detail(request, nid):
    # print(nid)
    detail_info = USER_DICT[nid]
    return render(request, 'detail.html', {'detail_info': detail_info})

这里的处理函数多传入了一下参数nid。名字不重要,但是这个值是正确分组匹配的结果。正则是这个 'detail-(\d+).html' ,里面括号中的 \d+ 的内容就传给了后面的第一个参数。也可以传多个参数(用多个括号),但是数量要一致(处理函数开头的形式参数),否则打开的页面会报错。 为什么这种更好:路由关系是一个动态的关系,一对多,一类url对应一个函数或类。

捕获参数

捕获组就是把正则表达式中子表达式匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用。当然,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部。捕获组有两种形式:

  • 普通捕获组:(Expression)
  • 命名捕获组:(?P<name>Expression)这个是python中的语法,其他语言了有的有,但是可能有点小差别,比如没有这个P,比如不用尖括号换成引号 前面的就是普通捕获组的例子。如果你的正则有多个子表达式,比如:‘detail-(\d+)-(\d+).html’ 。那么定义函数的时候必须注意参数的位置(名字为所谓)。这里可以使用命名捕获组来写正则表达式,正则本身没有任何变化,只是在子表达式前面加上加上一个命名。views.py里的对应关系可以这么写 from django.urls import path, re_path from cmdb import views urlpatterns = [ # re_path('^detail2-(\d+)-(\d+).html', views.detail2), re_path('^detail2-(?P<nid>\d?)-(?P<uid>\d?).html', views.detail2), ] 上面被注释的是普通捕获组的写法,下面的是命名捕获组的写法。使用了命名捕获组后,我们的处理函数的参数名字就是正则中的命名,但是位置无所谓了。下面的处理函数直接在页面输出2个参数的值,就不写页面了: def detail2(request, uid, nid): return HttpResponse('%s-%s' % (nid, uid)) 可以用这样的url测试 http://127.0.0.1:8000/detail2-1-3.html 。 用 path() 方法实现捕获参数 课上讲的是旧版本,现在Django已经2.0了,url()方法被path()方法替代,用法也有区别。 re_path() 可以看做是2.0里向下兼容的一个方法,也就是旧的1.0的 url() 方法。在2.0里用 path() 方法也可以实现捕获组的对应关系。使用 path() 方法需要注意:
    1. 要捕获一段url中的值,需要使用尖括号,而不是之前的圆括号;
    2. 可以转换捕获到的值为指定类型,比如int。默认情况下,捕获到的结果保存为字符串类型,不包含 '/' 这个特殊字符;
    3. 匹配模式的最开头不需要添加 '/' ,因为默认情况下,每个url都带一个最前面的 '/' ,既然大家都有的部分,就不用浪费时间特别写一个了。

那么现在 urls.py 里的对应关系可以这么写:

代码语言:javascript
复制
    # re_path('detail2-(\d+)-(\d+).html', views.detail2),
    # re_path('^detail2-(?P<nid>\d+)-(?P<uid>\d+).html', views.detail2),
    path('detail2-<int:nid>-<int:uid>.html', views.detail2),

上面的例子,就是捕获一个0或正整数,并且返回一个int类型,再用冒号把命名也完成了。除了int,还有下面这些。 默认情况下,Django内置下面的路径转换器:

  • str:匹配任何非空字符串,但不含斜杠/,如果你没有专门指定转换器,那么这个是默认使用的;
  • int:匹配0和正整数,返回一个int类型;
  • slug:可理解为注释、后缀、附属等概念,是url拖在最后的一部分解释性字符。该转换器匹配任何ASCII字符以及连接符和下划线,比如’ building-your-1st-django-site‘ ;
  • uuid:匹配一个uuid格式的对象。为了防止冲突,规定必须使用破折号,所有字母必须小写,例如’075194d3-6885-417e-a8a8-6c931e272f00‘ 。返回一个UUID对象;
  • path:匹配任何非空字符串,重点是可以包含路径分隔符’/‘。这个转换器可以帮助你匹配整个url而不是一段一段的url字符串。

小结

上面各种实现的方法由浅入深,并且一个比一个好,推荐用最后面的实现方式:

  • 基于正则的url比使用get方式获取参数的好
  • 命名捕获组比普通捕获组好
  • 推荐还是用最后的 path() 方法来实现,如果是1.x的版本,那么就是推荐基于正则的命名捕获组的方法。

另外,在定义函数的时候也可以写成这种万能的模式: def detail2(request, *args, **kwargs): ,这样的话,要使用 args[0] (普通捕获组)或 kwargs['nid'] (命名捕获组)来取值。

路由对应的名称

还可以对url的关系进行命名,完成命名后,以后可以通过这个名字获取到对应的url。好处是html里使用url的名称而不是写死,那么urls.py里修改了url,不用到html里修改了。 命名是在写对应关系的时候,加上一个参数 name=[url的名称]

代码语言:javascript
复制
    path('myurl/', views.myurl, name='myurl'),

然后再网页中使用的时候用 {% url [url的名称] %} 替代原来写死的url。

代码语言:javascript
复制
<form action="{% url 'myurl' %}" method="get">
    <input type="text" name="name" placeholder="NAME" />
    <input type="submit" value="提交" />
</form>

打开页面后可以按F12进入开发这模式查看页面中的url已经是转化后的具体的url了,就是我们在 urls.py 的对应关系里写的内容。 带捕获参数的情况: url如果带有捕获参数,比如要捕获2个参数:

代码语言:javascript
复制
    path('jump-<int:nid>-<int:uid>/', views.jump, name='jump'),

那么首先处理函数你必须写上这2个参数(不写会报错),也可以用通用的 *args,**kwargs 的形式:

代码语言:javascript
复制
def jump(request, nid, uid):
    # return HttpResponse('%s-%s' % (nid, uid))
    return render(request, 'jump.html')

然后页面上做使用模板语言的时候,引用jump作为动态的url,但是后面也要跟上需要捕获的值:

代码语言:javascript
复制
<a href="{% url 'jump' 3 4 %}">跳转</a>
<a href="{% url 'jump' uid=3 nid=4 %}">跳转</a>

上面两种写法都可以,上面的是按照位置传参。下面故意先写了uid,会按照名字来传参。 访问测试页面随便输一个url,比如 “http://127.0.0.1:8000/jump-1-2/” 。然后页面里的两个a连接生成的是各自新的url。新url整体不变,但是捕获参数的值是在url名字后面的参数决定的。 引用当前页面的url:引申一下,{{ request.path_info }} 就是当前页面的url,如要要用的话,request默认就是处理函数里要返回的变量,所以页面里直接用就可以了。 在处理函数中根据名称获取url:from django.urls import reverse 使用这个reverse也能获取到url。直接看获取带捕获的url的方法:

代码语言:javascript
复制
def jump(request, nid, uid):
    from django.urls import reverse
    r1 = reverse('jump', args=(3, 4))
    r2 = reverse('jump', kwargs={'uid':3, 'nid':4})
    print(r1, r2, request.path_info)
    # return HttpResponse('%s-%s' % (nid, uid))
    return render(request, 'jump.html')

也是2种形式都可以。

路由分发

之前所有的对应关系都是写在app目录外的urls文件里的。当我们的项目有多个app的时候,所有的页面都写在一起也不好。应该是每个app各自管理自己的那部分url。这就需要路由分发。 首先我们app目录外的公共url文件中导入一个include方法,声明好app的url文件的位置:

代码语言:javascript
复制
from django.urls import include
urlpatterns = [
    path('admin/', admin.site.urls),
    path('cmdb/', include("cmdb.urls"))
]

上面的例子中保留了原有的admin的对应关系,如果有别的公共页面还是可以在这里写的。然后是声明了cmdb这个app的url文件的位置。现在去 cmdb 目录下创建一个 urls.py 的文件。在这里写这个app的对应关系:

代码语言:javascript
复制
from django.urls import path
from cmdb import views
urlpatterns = [
    path('login/', views.login),
]

上面例子中的url是在cmdb这个app中的,所以访问的时候要带先加上app的名字,应该是 http://127.0.0.1:8000/cmdb/login/ 。这样app里可以有一个login页面,外面也可以有一个login页面。如果还有别的app,那个app也可以有login页面。名字前面会加上自己的app的名字,命名空间不冲突。 梳理一下逻辑:当一个url请求过来之后,先到达项目目录下的urls文件进行匹配。这里如果匹配到了项目名,比如cmdb。那么会再把它分发给之后的(就是app目录里的)urls文件继续处理。所以配置过分发后,首先还是到项目目录下的urls文件里进行匹配,然后再用这里的规则分发出去。

ORM

连接sqlite数据库

默认使用的是sqlite3作为数据库,使用数据库需要一下步骤 一、创建你的数据库表结构 app目录下的models.py文件就是用来写你的表结构的:

代码语言:javascript
复制
from django.db import models

# Create your models here.

# 文件默认会有上面的2行,下面是我们添加的内容
class UserInfo(models.Model):
    # 默认会自动创建自增id并作为主键
    username = models.CharField(max_length=32)  # 字符串,长度32
    password = models.CharField(max_length=64)

上面的类等到去数据库创建表的时候,表名是 “cmdb_userinfo” ,也就是 [app名]_[类名] 。 二、设置settings.py文件 在 INSTALLED_APPS 注册你的app,把你的app追加到这个列表里:

代码语言:javascript
复制
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'cmdb',
]

配置你的数据库连接,默认已经配置好了一个sqlite3,所以不需要修改:

代码语言:javascript
复制
# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

三、去终端执行2条命令

代码语言:javascript
复制
python manage.py makemigrations
python manage.py migrate

第一条命令会在 app 目录下的 migrations 目录下创建一个文件(0001_initial.py),记录我们对 models.py 所做的变动。 第二条命令是真正去操作数据库了,除了创建我们自己写的表以外,还创建了很多 django 自己的表。 上面两条命令都是作用于全局的,如果要限定作用于只在某个app,可以在最后加上app的名称:

代码语言:javascript
复制
python manage.py makemigrations cmbd
python manage.py migrate cmdb

关于SQLite:

SQLite是一种嵌入式数据库,它的数据库就是一个文件。由于SQLite本身是C写的,而且体积很小,所以,经常被集成到各种应用程序中,甚至在iOS和Android的App中都可以集成。 Python就内置了SQLite3,所以,在Python中使用SQLite,不需要安装任何东西,直接使用。

使用SQLite

连接mysql数据库

步骤同上,理论上只要修改一下 settings.py 里的 DATABASES 的值就好了。但是还有一些别的坑。这里主要演示一下怎么连上mysql数据库,连上之后,后面的操作还是在SQLite下来做。 DATABASES 设置的上面就是官方的帮助文档的连接,或者直接参考下面的进行设置就好了:

代码语言:javascript
复制
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'cmdb',
        'USER': 'admin',
        'PASSWORD': 'admin123',
        'HOST': '192.168.246.12',
        'port': '3306',
    }
}

然后是坑,首先用户我们得自己去数据库上创建好,注意如果不是本地的数据库,需要能够远程访问。库也要自己创建好,创建库:

代码语言:javascript
复制
CREATE DATABASE cmdb CHARSET "utf8";

然后可以试着执行终端的2条命令,但是可能会报错:

import MySQLdb as Database ModuleNotFoundError: No module named 'MySQLdb'

意思是找不到这个库,在python3里mysql我们用 pymysql 这个库。不过装好了pymysql还是会提示找不到库,因为django就是耿直的要找MySQLdb。解决办法是编辑项目名同名目录下的 __init__.py 文件,在这里导入我们的pymysql并且会把它的名字就当做是 MySQLdb :

代码语言:javascript
复制
import pymysql
pymysql.install_as_MySQLdb()

ORM操作

添加

添加数据有2种方法,推荐用第一种。下面是写在app目录的views.py里的处理函数:

代码语言:javascript
复制
from cmdb import models
def add_user(request):
    models.UserInfo.objects.create(
        username='root',
        password='123456'
    )
    # 另外一个方法,先创建一个实例,然后调用它的save()方法
    obj = models.UserInfo(
        username='admin',
        password='admin123'
    )
    obj.save()
    # 方法一的变种,把字典直接作为参数传入
    dic = {'username': 'user', 'password': 'user123'}
    models.UserInfo.objects.create(**dic)
    return HttpResponse("add user")

首先我们要操作某个表,就要先把这个创建这个表的那个类导入进来,例子的第一行。上面一个创建了3条数据了。

查询

用all方法查询到的数据,首先是放在一个列表里,列表的元素是一个一个的对象,每一个对象就是一条记录。 筛选的方法有filter,这个返回的也是个列表,因为可能返回多条。

代码语言:javascript
复制
def show_user(request):
    res = models.UserInfo.objects.all()
    for row in res:
        print(row.id, row.username, row.password)
    users = models.UserInfo.objects.filter(username='root')
    for row in users:
        print(row.id, row.username, row.password)
    return HttpResponse("show user")

filter()里面还可以传入多个参数,就是多个条件,他们之间的关系是逻辑与(and)。 还有一个first()方法,取出第一个值,这样返回就不是列表而直接就是对象了。可以直接用,也可以用在filter()方法后面。all()方法后面也是可以用的,不过没意义

代码语言:javascript
复制
models.UserInfo.objects.first()
models.UserInfo.objects.filter(username='root', password='123456').first()
# 上面就是一个验证用户登录的逻辑了,返回结果是None或者是找到的第一个对象

另外还有一个get方法也可以获取到一条数据,但是如果数据不存在不是返回空而是会报错。如果要用那就得写个try:

代码语言:javascript
复制
models.UserInfo.objects.get(id=10)

QuerySet 对象,分别打印出查询结果和这个对象的query属性:

代码语言:javascript
复制
res = models.UserInfo.objects.all()
print(res)  # 结果在下面
# <QuerySet [<UserInfo: UserInfo object (1)>, <UserInfo: UserInfo object (2)>, <UserInfo: UserInfo object (3)>]>
print(res.query)  # 结果写下面
# SELECT "cmdb_userinfo"."id", "cmdb_userinfo"."username", "cmdb_userinfo"."password" FROM "cmdb_userinfo"

可以看到这是一个 QuerySet 对象,不是一个普通的列表。这里要引出它的一个属性 query 。 这个对象有一个query属性,该属性的内容是获取这个对象时对应的SQL语句。

删除

删除前首先要先做查找,调用查找结果的delete()方法,就完成了删除:

代码语言:javascript
复制
models.UserInfo.objects.all().delete
models.UserInfo.objects.filter(id=2).delete

修改

修改也是在查找的基础上,调用update()方法来完成的:

代码语言:javascript
复制
models.UserInfo.objects.filter(id=1).update(password='root123')

示例

先来写一个登录页面 index.html 用来提交用户名和密码进行验证:

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        label{
            width: 80px;
            text-align: right;
            display: inline-block;
        }
    </style>
</head>
<body>
<form action="/login/" method="post">
    <p>
        <label for="usernmae">用户名:</label>
        <input id="usernmae" name="user" type="text" />
    </p>
    <p>
        <label for="password">密码:</label>
        <input id="password" name="pwd" type="password" />
        <input type="submit" value="提交">
        <span style="color: red">{{ error_msg }}</span>
    </p>
</form>
</body>
</html>

然后是index的处理函数,用户验证失败报错误信息,验证成功跳转的下一个页面:

代码语言:javascript
复制
def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    elif request.method == 'POST':
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
        if obj:
            # 先跳转到admin,可以测一下,之后再写index页面
            return redirect('/admin/')
            # return redirect('/userlist/')
        else:
            return render(request,
                          'login.html',
                          {'error_msg': '用户名或密码错误'})
    else:
        return HttpResponse("不支持您的请求方式")

测试跳转没问题之后,就可以把上面的跳转从admin页面换到userlist页面,然后就来写这个userlist页面:

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <h2>添加用户</h2>
    <form method="post" action="/userlist/">
        <input type="text" name="user" placeholder="username">
        <input type="text" name="pwd" placeholder="password">
        <input type="submit" value="添加">
    </form>
</div>
<div>
    <h2>用户列表</h2>
    <table border="1">
        <thead>
        <tr>
            <th>id</th>
            <th>username</th>
            <th>password</th>
            <th>按钮</th>
        </tr>
        </thead>
        <tbody>
        {% for row in users %}
        <tr>
            <td>{{ row.id }}</td>
            <td>{{ row.username }}</td>
            <td>{{ row.password }}</td>
            <td>
                <a href="/userdel-{{ row.id }}/">删除</a>
                <span>|</span>
                <a href="/useredit-{{ row.id }}/">编辑</a>
            </td>
        </tr>
        {% endfor %}
        </tbody>
    </table>
</div>
</body>
</html>

首先页面的中要实现数据库查询的功能,就是显示用户列表,通过GET方法来实现。 另外还有一个增加数据的功能,页面上面的添加用户,请求是通过POST方法提交过来,完成的数据添加。POST方法可以有2中return的方式,直接的方式就是和GET方法一样。或者也可以用例子里使用的方法,就是再提交一次GET请求:

代码语言:javascript
复制
def user_list(request):
    if request.method == 'GET':
        users = models.UserInfo.objects.all()
        return render(request, 'userlist.html', {'users': users})
    elif request.method == 'POST':
        username = request.POST.get('user')
        password = request.POST.get('pwd')
        models.UserInfo.objects.create(username=username, password=password)
        # 这里可以和get返回的一样
        # return render(request, 'userlist.html', {'users': users})
        # 这里选择用redirect()方法返回,就是再调用一次get方法返回页面
        return redirect('/userlist/')

删除功能不需要写页面,只需要一个处理函数:

代码语言:javascript
复制
def user_del(request, nid):
    models.UserInfo.objects.filter(id=nid).delete()
    return redirect('/userlist/')

最后还有一个编辑功能,现在只能用写一个新的页面然后再那个页面里提交。这样实现起来比较简单,主要就是通过这个示例把数据库的最删改查都用一遍:

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <h2>编辑用户</h2>
    <form method="post" action="/useredit-{{ obj.id }}/">
        <input type="text" name="user" value="{{ obj.username }}">
        <input type="text" name="pwd" value="{{ obj.password }}">
        <input type="submit" value="提交">
    </form>
</div>
</body>
</html>

这个页面对应的处理函数是如下:

代码语言:javascript
复制
def user_edit(request, nid):
    if request.method == 'GET':
        obj = models.UserInfo.objects.filter(id=nid).first()
        return render(request, 'useredit.html', {'obj': obj})
    elif request.method == 'POST':
        username = request.POST.get('user')
        password = request.POST.get('pwd')
        models.UserInfo.objects.filter(id=nid).update(
            username=username, password=password)
        return redirect('/userlist/')

在userlist页面点击编辑按钮后,GET请求跳转到useredit页面。在编辑页面提交后向useredit发送一个POST请求修改数据,然后返回userlist页面,完成一次编辑。 上面一个有4个处理函数,其中3个有html页面,urls.py的对应关系如下:

代码语言:javascript
复制
    path('login/', views.login),
    path('userlist/', views.user_list),
    path('userdel-<int:nid>/', views.user_del),
    path('useredit-<int:nid>/', views.user_edit),

ORM表结构

修改表结构

修改过表结构之后,需要再执行一下下面的2行命令,把新的表结构应用到数据库。

代码语言:javascript
复制
python manage.py makemigrations
python manage.py migrate

修改数据长度、删除一列,这类情况没什么特别的问题。 增加一列,默认情况下字段值不允许为空,此时会有提示。要么全部都设为空,要么你给个默认值,全部都设为默认值。另外还可以直接定义到表结构中:

代码语言:javascript
复制
class UserInfo(models.Model):
    # 默认会自动创建自增id并作为主键
    username = models.CharField(max_length=32)  # 字符串,长度32
    password = models.CharField(max_length=64)
    email = models.CharField(max_length=64, null=True)  # 设置为允许空值

字段类型

基本的字段类型有:字符串、数字、时间、二进制。 Django的ORM提供了非常多的字段类型,比如:EmailField、URLField、GenericIPAddressField。这些其实都是字符串类型而已,并且确实对我们没任何用(并不能帮我们做数据验证)。这些字段类型的只有在用Django的后台管理页面 admin 的时候才能发挥数据验证的效果。只有通过admin提交数据的时候才会验证你的数据格式是否正确。接下来就先讲怎么登进去 自增id,之前定义表结构的时候,省略了主键,让Django帮我创建了自增id。也可以自己定义主键和自增id:

代码语言:javascript
复制
class UserGroup(models.Model):
    uid = models.AutoField(primary_key=True)  # 数据类型是自增,并且设为主键
    group = models.CharField(max_length=32)

登录Admin

admin具体要到后面讲,这里先让我们登录进去

  1. 创建超级管理员,输入命令后会提示你输入用户名、邮箱(可以直接回车)、密码(似乎有长度和复杂度的要求): python manage.py createsuperuser
  2. 配置后台管理url,就是admin页面的对应关系,默认urls.py里面已经配好了: path('admin/', admin.site.urls),
  3. 注册和配置 admin 后台管理页面,把你的表注册号之后,就可以通过admin进行管理了: from django.contrib import admin # Register your models here. # 上面是文件默认就有的内容 from cmdb import models admin.site.register(models.UserInfo)

参数

null :数据库中字段是否可以为空 default :数据库中字段的默认值 db_column :数据库中字段的列名。默认列明就是我们的变量名,可以通过这个参数设置成不一样的

代码语言:javascript
复制
class UserInfo(models.Model):
    username = models.CharField(max_length=32)  # 字段名就是变量名 username
    password = models.CharField(max_length=64, db_column='pwd')  # 数据库中的字段名会是 pwd

db_index :是否建立索引 unique :是否建立唯一索引 unique_for_date :只对字段中【日期】部分建立唯一索引 unique_for_month :只对字段中【月】部分建立唯一索引 unique_for_year :只对字段中【年】部分建立唯一索引 auto_now :自动生成一个当前时间,数据更新时(包括创建) auto_now_add :自动生成一个当前时间,数据创建时

代码语言:javascript
复制
class UserInfo(models.Model):
    # 比如用户注册时会生成用户名和密码
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    # 创建记录时会生成当前时间存放在ctime里,这个就是用户的注册时间
    ctime = models.DateTimeField(auto_now_add=True)
    # 用户修改密码会更新uptime的时间,这个就是上次修改密码的时间
    uptime = models.DateTimeField(auto_now=True)

# models.UserInfo.objects.filter(id=nid).update(password='123456') 这种方法更新是不会刷新 auto_now 的时间的
# 用save()方法更新可以刷新 auto_now 的时间
# obj =  models.UserInfo.objects.filter(id=nid).first()
# obj.password = '654321'
# obj.save()

Admin中有效果的参数 choices :Admin中显示选择框的内容。(用不变动的数据放在内存中从而避免跨表操作,跨表操作会涉及到性能问题)

代码语言:javascript
复制
class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    # 用户有各种类型
    user_type_choices = (
        (1, '管理员')
        (2, '普通用户')
        (3, '访客')
    )
    # 定义一个用户类型的字段
    user_type_id = models.IntegerField(choices=user_type_choices, default=2)
# 这样数据库里是一个整数类型,值是1、2、3。使用字段名取值 obj.user_type_id 获取到的是数值
# 如果要获取后面的内容,使用 get_FOO_display() 方法, 即 obj.get_user_type_id_display()
# 但是我们在admin里看选择框的时候看到的是“管理员”、“普通用户”、“访客”,这就是因为把选项所对应的内容放到了内存中了
# 有了Django这个功能就不用再搞一张表,存放各个数值对应的内容了,还要做外键关联,用的时候还要连表查询
# 即使不用admin,我们也可以在自己的代码里读取这个属性获取到内容,避免连表查询

blank :Admin中是否允许用户输入为空 verbose_name :Admin中显示的字段名称,默认显示为变量名 editable :Admin中是否可以编辑。默认是True,设为False后就是在admin中不可编辑了,也不会显示出来了。 error_messages :自定义错误信息(字典类型)。字典key:null、blank、invalid、invalid_choice、unique、unique_for_date

代码语言:javascript
复制
class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64, error_messages={'null': "不能为空", 'invalid': '格式错误'})

help_text :Admin中该字段的提示信息。默认没有提示信息,设置后会显示在input框的下方 validators :自定义错误验证(列表类型),具体要等到后面讲

外键操作-一对多

上面讲的choices参数,提供了一种将数据存在内存中来提高效率的方法。好处是避免了跨表操作提高了效率。坏处也有,就是数据不方便修改。如果要修改,那就要修改好之后重启一下服务使你的修改生效。而重启操作是有风险的应该避免,那么对于这种经常要修改的内容就不适合放在内存中了,而是要放到另外一张表里。

创建外键关联-修改表结构

在models.py里修改我们的表结构,新增一张用户部门表,原来的用户信息表中新增一列部门id:

代码语言:javascript
复制
from django.db import models

# Create your models here.

# 新增一张表
class UserGroup(models.Model):
    group_id = models.AutoField(primary_key=True)  # 这次自己写自增id
    dept = models.CharField(max_length=32, unique=True)

class UserInfo(models.Model):
    # 默认会自动创建自增id并作为主键
    username = models.CharField(max_length=32)  # 字符串,长度32
    password = models.CharField(max_length=64)
    # 新增一列存放部门
    # to_field参数可以缺省,默认就是主键
    # on_delete=models.CASCADE,这个在1里应该是默认值,现在不能缺省了
    user_group = models.ForeignKey('UserGroup', on_delete=models.CASCADE, to_field='group_id')

然后去终端执行那2条命令使新的表结构生效:

代码语言:javascript
复制
python manage.py makemigrations
python manage.py migrate

添加部门数据

这里可以的话最好直接是直接去操作数据库,否则简单搞个网页来添加数据。参看之前的userlist,简单搞个只做显示和添加的页面:

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <h2>添加部门</h2>
    <form method="post" action="/grouplist/">
        <input type="text" name="dept" placeholder="部门名称">
        <input type="submit" value="添加">
    </form>
</div>
<div>
    <h2>部门列表</h2>
    <table border="1">
        <thead>
        <tr>
            <th>id</th>
            <th>dept</th>
        </tr>
        </thead>
        <tbody>
        {% for row in dept %}
        <tr>
            <td>{{ row.group_id }}</td>
            <td>{{ row.dept }}</td>
        </tr>
        {% endfor %}
        </tbody>
    </table>
</div>
</body>
</html>

然后是views.py里的处理函数:

代码语言:javascript
复制
def group_list(request):
    if request.method == 'GET':
        dept = models.UserGroup.objects.all()
        return render(request, 'grouplist.html', {'dept': dept})
    elif request.method == 'POST':
        dept = request.POST.get('dept')
        models.UserGroup.objects.create(dept=dept)
        return redirect('/grouplist/')

urls.py里的对应g关系:

代码语言:javascript
复制
    path('grouplist/', views.group_list),

查看被关联的属性

对于UserInfo中新增的一列,在类中我们的属性名称是 "user_group" ,而实在在数据库中创建的自动名是 "user_group_id"。 我们再操作的时候就有2个属性可以操作:

  • .user_group_id :就是这个字段里的值,也就是数据库里实际存放的内容
  • .user_group :这是一个对象,通过这个对象取到UserGroup里的内容,比如:
    • .user_group.group_id :就是UserGroup表里的自增id,结果和 .user_group_id 应该是一样的
    • .user_group.dept :就是这个username锁关联的部门名称了

修改之前的userlist页面,现在把部门名称也显示出来。这里只需要改html,处理函数时不用修改的。实际也只需要在表格中加上一列直接可以去到关联的表里的属性值。下面是userlist表格的部分内容:

代码语言:javascript
复制
<div>
    <h2>用户列表</h2>
    <table border="1">
        <thead>
        <tr>
            <th>id</th>
            <!-- 这里加一列 -->
            <th>username</th>
            <th>dept</th>
            <th>password</th>
            <th>按钮</th>
        </tr>
        </thead>
        <tbody>
        {% for row in users %}
        <tr>
            <td>{{ row.id }}</td>
            <td>{{ row.username }}</td>
            <!-- 这里加一列,直接就能取到部门名称 -->
            <td>{{ row.user_group.dept }}</td>
            <td>{{ row.password }}</td>
            <td>
                <a href="/userdel-{{ row.id }}/">删除</a>
                <span>|</span>
                <a href="/useredit-{{ row.id }}/">编辑</a>
            </td>
        </tr>
        {% endfor %}
        </tbody>
    </table>
</div>

添加用户时选择部门

显示没问题了,页面的上部还有添加用户,现在再要添加用户就需要把用户部门也加上了。部门搞成一个下拉框,不过下拉框的内容还需要修改处理函数传值过来。处理函数还要处理页面提交的内容:

代码语言:javascript
复制
def user_list(request):
    if request.method == 'GET':
        users = models.UserInfo.objects.all()
        # 这里多获取一个部门的列表,传给页面,页面的下来列表会用到。直接找UserGroup获取数据
        # 把对象传给页面的下拉列表,列表的value就是对象的id,列表的内容就是对象的dept
        depts = models.UserGroup.objects.all()
        return render(request, 'userlist.html', {'users': users, 'depts': depts})
    elif request.method == 'POST':
        username = request.POST.get('user')
        password = request.POST.get('pwd')
        # 这里通过select获取到的直接就是id的值,所以提交的时候也简单的提交值就可以了
        group_id = request.POST.get('group_id')
        models.UserInfo.objects.create(username=username, password=password, user_group_id=group_id)
        return redirect('/userlist/')

页面里加上下拉列表,下面是添加用户的部分:

代码语言:javascript
复制
<div>
    <h2>添加用户</h2>
    <form method="post" action="/userlist/">
        <input type="text" name="user" placeholder="username">
        <select name="group_id">
            {% for item in depts %}
            <option value="{{ item.group_id }}">{{ item.dept }}</option>
            {% endfor %}
        </select>
        <input type="text" name="pwd" placeholder="password">
        <input type="submit" value="添加">
    </form>
</div>

数据添加的另外一种方法 上面通过下拉列表方便的获取到了部门id的值,所以直接通过传值给user_group_id完成了数据的添加。也可以通过传对象给user_group完成数据的添加,大概是这样的:

代码语言:javascript
复制
        group = models.UserGroup.objects.filter(id=1).first()
        models.UserInfo.objects.create(username=username, password=password, user_group=group)

两种方法根据实际情况选择,不过传值的方法更好,少一次数据库的操作。 还有更多内容要下节讲了

模板

这天没讲到

课后练习

用户管理:

  • 一张用户表、一张用户组表。做好外键关联。分别实现对两张表的增删改查
  • 添加,做成模态对话框的形式
  • 修改,目前可以用页面跳转的形式,但是要显示默认值
  • 做一个比较好看的页面,推荐套用模板
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-09-16 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 视图
    • 提交数据
      • 上传文件
        • CBV 和 FBV
          • 给views.py分类
          • 路由系统,URL
            • 模板言语循环字典
              • 一条对应关系对应多个页面
                • 基于get方法的实现
                • 基于正则表达式的url来实现
                • 捕获参数
                • 小结
              • 路由对应的名称
                • 路由分发
                • ORM
                  • 连接sqlite数据库
                    • 连接mysql数据库
                      • ORM操作
                        • 添加
                        • 查询
                        • 删除
                        • 修改
                      • 示例
                        • ORM表结构
                          • 修改表结构
                          • 字段类型
                          • 登录Admin
                          • 参数
                        • 外键操作-一对多
                          • 创建外键关联-修改表结构
                          • 添加部门数据
                          • 查看被关联的属性
                          • 添加用户时选择部门
                      • 模板
                      • 课后练习
                      相关产品与服务
                      数据库
                      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档