python自动化发挥_Python自动化开发学习19-Django

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

视图

提交数据

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

性别:

男性

女性

爱好:

足球

篮球

排球

棒球

城市:

北京

上海

广州

深圳

技能:

Python

HTML

CSS

JavaScript

上传:

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

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() ,把上面的代码都替换成新的方法:

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里:

上传:

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

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() ,所以要接收文件上传参考下面的方法:

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方法重新写一下:

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()方法,具体如下:

from cmdb import views

urlpatterns = [

# path('choice/', views.choice),

path('choice/', views.Choice.as_view()),

]

各种类型的提交方法

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

http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

所以处理post和get,还可以处理这么多的请求方法,用一起来也很简单,在类里照着别的一样定义一个同名方法就可以了。

处理执行前后自定义操作

继续看源码的View。这里可以跳过只看结论,调用了as_view()方法里面会再调用一个dispatch()方法。这个dispatch()方法里是通过映射获取我们的 request.method 即提交的方法来调用我们的处理方法的。dispatch()的源码如下:

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()方法,可以在处理方法执行前和执行后自定义一些操作。如果需要的话就在我们的类里继承并重构,参考这里:

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的逻辑补上:

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 里添加一个字典,然后在页面里返回:

DICT = {

'k1': 'value1',

'k2': 'value2',

'k3': 'value3',

}

def dict(request):

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

下面的html里演示了用法:

返回整个字典

{{ dict }}

返回的是key

{% for i in dict %}

{{ i }}

{% endfor %}

返回的是key

{% for key in dict.keys %}

{{ key }}

{% endfor %}

返回的是value

{% for value in dict.values %}

{{ value }}

{% endfor %}

返回key和value

{% for k,v in dict.items %}

{{ k }}: {{ v }}

{% endfor %}

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

直接dict :循环的是key,不明确所以不推荐

dict.keys :循环key

dict.values :循环values

dict.items :循环key和values

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

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

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标签打开一个显示详细内容的页面:

{% for k,v in user_dict.items %}

{{ v.name }}

{% endfor %}

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

显示详细的处理函数:

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去获取到具体得详细数据并返回了,这里直接把数据显示出来:

详细信息

用户名:{{ detail_info.name }}

年龄:{{ detail_info.age }}

部门:{{ detail_info.dept }}

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

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

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

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本事是会变化的:

{% for k,v in user_dict.items %}

{{ v.name }}

{% endfor %}

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

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\d?)-(?P\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() 方法需要注意:

要捕获一段url中的值,需要使用尖括号,而不是之前的圆括号;

可以转换捕获到的值为指定类型,比如int。默认情况下,捕获到的结果保存为字符串类型,不包含 '/' 这个特殊字符;

匹配模式的最开头不需要添加 '/' ,因为默认情况下,每个url都带一个最前面的 '/' ,既然大家都有的部分,就不用浪费时间特别写一个了。

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

# re_path('detail2-(\d+)-(\d+).html', views.detail2),

# re_path('^detail2-(?P\d+)-(?P\d+).html', views.detail2),

path('detail2--.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的名称]

path('myurl/', views.myurl, name='myurl'),

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

打开页面后可以按F12进入开发这模式查看页面中的url已经是转化后的具体的url了,就是我们在 urls.py 的对应关系里写的内容。

带捕获参数的情况:

url如果带有捕获参数,比如要捕获2个参数:

path('jump--/', views.jump, name='jump'),

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

def jump(request, nid, uid):

# return HttpResponse('%s-%s' % (nid, uid))

return render(request, 'jump.html')

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

跳转

跳转

上面两种写法都可以,上面的是按照位置传参。下面故意先写了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的方法:

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文件的位置:

from django.urls import include

urlpatterns = [

path('admin/', admin.site.urls),

path('cmdb/', include("cmdb.urls"))

]

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

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文件就是用来写你的表结构的:

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追加到这个列表里:

INSTALLED_APPS = [

'django.contrib.admin',

'django.contrib.auth',

'django.contrib.contenttypes',

'django.contrib.sessions',

'django.contrib.messages',

'django.contrib.staticfiles',

'cmdb',

]

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

# 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条命令

python manage.py makemigrations

python manage.py migrate

第一条命令会在 app 目录下的 migrations 目录下创建一个文件(0001_initial.py),记录我们对 models.py 所做的变动。

第二条命令是真正去操作数据库了,除了创建我们自己写的表以外,还创建了很多 django 自己的表。

上面两条命令都是作用于全局的,如果要限定作用于只在某个app,可以在最后加上app的名称:

python manage.py makemigrations cmbd

python manage.py migrate cmdb

关于SQLite:

SQLite是一种嵌入式数据库,它的数据库就是一个文件。由于SQLite本身是C写的,而且体积很小,所以,经常被集成到各种应用程序中,甚至在iOS和Android的App中都可以集成。

Python就内置了SQLite3,所以,在Python中使用SQLite,不需要安装任何东西,直接使用。

连接mysql数据库

步骤同上,理论上只要修改一下 settings.py 里的 DATABASES 的值就好了。但是还有一些别的坑。这里主要演示一下怎么连上mysql数据库,连上之后,后面的操作还是在SQLite下来做。

DATABASES 设置的上面就是官方的帮助文档的连接,或者直接参考下面的进行设置就好了:

DATABASES = {

'default': {

'ENGINE': 'django.db.backends.mysql',

'NAME': 'cmdb',

'USER': 'admin',

'PASSWORD': 'admin123',

'HOST': '192.168.246.12',

'port': '3306',

}

}

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

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 :

import pymysql

pymysql.install_as_MySQLdb()

ORM操作

添加

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

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,这个返回的也是个列表,因为可能返回多条。

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()方法后面也是可以用的,不过没意义:

models.UserInfo.objects.first()

models.UserInfo.objects.filter(username='root', password='123456').first()

# 上面就是一个验证用户登录的逻辑了,返回结果是None或者是找到的第一个对象

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

models.UserInfo.objects.get(id=10)

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

res = models.UserInfo.objects.all()

print(res) # 结果在下面

# , , ]>

print(res.query) # 结果写下面

# SELECT "cmdb_userinfo"."id", "cmdb_userinfo"."username", "cmdb_userinfo"."password" FROM "cmdb_userinfo"

可以看到这是一个 QuerySet 对象,不是一个普通的列表。这里要引出它的一个属性 query 。

这个对象有一个query属性,该属性的内容是获取这个对象时对应的SQL语句。

删除

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

models.UserInfo.objects.all().delete

models.UserInfo.objects.filter(id=2).delete

修改

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

models.UserInfo.objects.filter(id=1).update(password='root123')

示例

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

Title

label{

width: 80px;

text-align: right;

display: inline-block;

}

用户名:

密码:

{{ error_msg }}

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

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页面:

Title

添加用户

用户列表

idusernamepassword按钮

{% for row in users %}

{{ row.id }}{{ row.username }}{{ row.password }}

删除

|

编辑

{% endfor %}

首先页面的中要实现数据库查询的功能,就是显示用户列表,通过GET方法来实现。

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

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/')

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

def user_del(request, nid):

models.UserInfo.objects.filter(id=nid).delete()

return redirect('/userlist/')

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

Title

编辑用户

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

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的对应关系如下:

path('login/', views.login),

path('userlist/', views.user_list),

path('userdel-/', views.user_del),

path('useredit-/', views.user_edit),

ORM表结构

修改表结构

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

python manage.py makemigrations

python manage.py migrate

修改数据长度、删除一列,这类情况没什么特别的问题。

增加一列,默认情况下字段值不允许为空,此时会有提示。要么全部都设为空,要么你给个默认值,全部都设为默认值。另外还可以直接定义到表结构中:

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:

class UserGroup(models.Model):

uid = models.AutoField(primary_key=True) # 数据类型是自增,并且设为主键

group = models.CharField(max_length=32)

登录Admin

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

创建超级管理员,输入命令后会提示你输入用户名、邮箱(可以直接回车)、密码(似乎有长度和复杂度的要求):

python manage.py createsuperuser

配置后台管理url,就是admin页面的对应关系,默认urls.py里面已经配好了:

path('admin/', admin.site.urls),

注册和配置 admin 后台管理页面,把你的表注册号之后,就可以通过admin进行管理了:

from django.contrib import admin

# Register your models here.

# 上面是文件默认就有的内容

from cmdb import models

admin.site.register(models.UserInfo)

参数

null :数据库中字段是否可以为空

default :数据库中字段的默认值

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

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 :自动生成一个当前时间,数据创建时

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中显示选择框的内容。(用不变动的数据放在内存中从而避免跨表操作,跨表操作会涉及到性能问题)

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

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:

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条命令使新的表结构生效:

python manage.py makemigrations

python manage.py migrate

添加部门数据

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

Title

添加部门

部门列表

iddept

{% for row in dept %}

{{ row.group_id }}{{ row.dept }}

{% endfor %}

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

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关系:

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表格的部分内容:

用户列表

idusernamedeptpassword按钮

{% for row in users %}

{{ row.id }}{{ row.username }}{{ row.user_group.dept }}{{ row.password }}

删除

|

编辑

{% endfor %}

添加用户时选择部门

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

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/')

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

添加用户

{% for item in depts %}

{{ item.dept }}

{% endfor %}

数据添加的另外一种方法

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

group = models.UserGroup.objects.filter(id=1).first()

models.UserInfo.objects.create(username=username, password=password, user_group=group)

两种方法根据实际情况选择,不过传值的方法更好,少一次数据库的操作。

还有更多内容要下节讲了

模板

这天没讲到

课后练习

用户管理:

一张用户表、一张用户组表。做好外键关联。分别实现对两张表的增删改查

添加,做成模态对话框的形式

修改,目前可以用页面跳转的形式,但是要显示默认值

做一个比较好看的页面,推荐套用模板

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值