Djongo表单

从请求对象中获取数据

属性 方法说明 示例
request.path 完整的路径,不含域名,但是含前导斜线 “/hello/”
request.get_host() 主机名(即通常所说的“域名”) “127.0.0.1:8000”或“www.example.com”
request.get_full_path() 包含查询字符串(如果有的话)的路径 “/hello/?print=true”
request.is_secure() 通过 HTTPS 访问时为True,否则为False True 或False
request.META[‘HTTP_USER_AGENT’] 入站前的 URL(可能没有)
request.META[‘HTTP_USER_AGENT’] 浏览器的用户代理(可能没有),请求头信息 “Mozilla/5.0 (X11; U; Linux i686; fr-FR; rv:1.8.1.17) Gecko/20080829 Firefox/2.0.0.17”
request.META[‘REMOTE_ADDR’] 客户端的 IP 地址 “12.345.67.89”。(如果请求经由代理,这个首部的值可能是一组 IP 地址,以逗号分隔)

简单的表单使用示例

这里可以打开页面及项目代码为大家演示

视图函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from django.shortcuts import render
from django.http import HttpResponse
from cerealsOils.models import Product

# Create your views here.

def product_search(request):
return render(request, 'product_search.html')

def search(request):
errors = []
if 'q' in request.GET : # 通过request对象直接获取传统get请求的参数: q=xxx
if ( request.GET['q']) : # 判断表单q参数是否为空
q = request.GET['q']
if len(q) > 5 :
errors.append("查询关键字不能超过五个字符")
else :
products = Product.objects.filter(title__contains=q) # 过滤查询
# name = products[0].vender[0]
return render(request, 'product_search.html', {'products': products, 'query': q})
else :
# products = Product.objects.all() # 查询所有
errors.append("查询关键字不能为空")
return render(request, 'product_search.html', {'errors':errors})

URL配置如下:

1
2
url(r'^product_search/$', views.product_search),  # 查询页面跳转
url(r'^search/$', views.search), # 查询控制层

product_search模板如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<html>
<head>
<title>产品查询</title>
<meta charset='UTF-8'>
</head>
<body>
{% if errors %}
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<!-- <form action="/search/" method="get"> -->
<form action="/search/" method="get">
<input type="text" name="q">
<input type="submit" value="Search">
</form>
<p>查询关键字: <strong>{{ query }}</strong></p>

{% if products %}
<p>产品总数 {{ products|length }} 产品{{ books|pluralize }}</p>
<ul>
{% for product in products %}
<li>产品名称:{{ product.title }} 生产日期:{{ product.product_date }}</li>
{% endfor %}
</ul>
{% else %}
<p>没有查询到商品</p>
{% endif %}
</body>
</html>

这样就完成了一个表单的使用示例

联系表单

Django 自带了一个表单库,django.forms,它能处理从显示 HTML 表单到验证。

这个表单框架的主要用法是为要处理的每个 HTML 表单定义一个Form 类,这个类可以放在任意位置,例如直接放在views.py 文件中,不过社区的约定是,把Form 类放在单独的forms.py 文件中。

下面对表单框架的各部分功能以及自定义校验规则等进行示例:

在views.py 文件所在的目录(mysite)中创建这个文件,然后输入下述内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 引入框架的表单库
from django import forms

class ContactForm(forms.Form):
subject = forms.CharField(max_length=20, min_length=2) # 校验长度
email = forms.EmailField(required=False) # 选项表示表单内容非必填
message = forms.CharField(widget=forms.Textarea) # 可以直接设置表单样式,注意表单样式设置也可以很灵活,使用时具体查看资料

# 自定义校验规则
# 注意校验方法名,需要以clean_开头,字段名称为结尾
# 再检查此规则前定义字段时设置的校验规则已经校验完毕
def clean_message(self):
message = self.cleaned_data['message']
if "香港" in message:
raise forms.ValidationError("消息内容不能包含敏感词")
return message # 一定要显式的返回清理后的值,cleaned_data是清理值

定义视图函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST) # Post请求体接受为form表单对象,这点类似于mvc
if form.is_valid(): # 校验表单输入是否符合定义的校验规则
cd = form.cleaned_data
send_mail(
cd['subject'],
cd['message'],
cd.get('email', 'noreply@example.com'),
['siteowner@example.com'],
)
return HttpResponseRedirect('/contact/thanks/')
else:
form = ContactForm(
initial={'subject': '默认值'} # 设置form表单默认值
)
return render(request, 'test/contact_form.html', {'form': form})

定义模板文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<html>
<head>
<title>联系表单</title>
<meta charset='UTF-8'>
</head>
<body>
<h1>表单测试</h1>
{% if form.errors %} <!-- 接受表单对象数据 -->
<p style="color: red;">
请处理表单错误内容{{ form.errors|pluralize }}
</p>
{% endif %}
<form action="" method="post">
<div class="field">
{{ form.subject.errors }}
<label for="id_subject">科目:</label>
{{ form.subject }}
</div>
<div class="field">
{{ form.email.errors }}
<label for="id_email">输入您的邮箱:</label>
{{ form.email }}
</div>
<div class="field">
{{ form.message.errors }}
<label for="id_message">消息内容:</label>
{{ form.message }}
</div>
{% csrf_token %} <!-- 跨域处理 -->
<input type="submit" value="提交">
</form>
</body>
</html>

至此一个联系表单就创建完成了

高级视图和URL

简化导入函数方式

简化导入函数方式就是说我们直接导入views 模块自身,如下示例:

1
2
3
4
5
6
# 直接导入view
from cerealsOils import views

urlpatterns = [
url(r'^product_search/$', views.product_search), # 查询页面跳转
url(r'^search/$', views.search), # 查询控制层

具名分组(Python具名函数的使用)

正则表达式分组(通过括号实现)捕获 URL 中的片段,

1
2
3
4
5
6
urlpatterns = [
url(r'^reviews/2003/$', views.special_case_2003),
url(r'^reviews/([0-9]{4})/$', views.year_archive),
url(r'^reviews/([0-9]{4})/([0-9]{2})/$', views.month_archive),
url(r'^reviews/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.review_detail),
]

如上捕获参数时,我们可以通过具名分组,为参数赋予名称:

1
2
3
4
5
6
urlpatterns = [
url(r'^reviews/2003/$', views.special_case_2003),
url(r'^reviews/(?P<year>[0-9]{4})/$', views.year_archive),
url(r'^reviews/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
url(r'^reviews/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$',views.review_detail),
]

如上示例含义如下:

  • 对/reviews/2005/03/ 的请求调用views.month_archive(request, year=’2005’, month=’03’) 函数,而不是views.month_archive(request,’2005’,’03’)。

  • 对/reviews/2003/03/03/ 的请求调用views.review_detail(request, year=’2003’, month=’03’,day=’03’) 函数。

匹配/分组算法

URL 配置解析器解析正则表达式中具名分组和非具名分组所采用的算法如下:

  1. 如果有具名分组,使用具名分组,忽略非具名分组。

  2. 否则,以位置参数传递所有非具名分组。

不论如何,额外的关键字参数都会传给视图。

URL及视图一些特性

  • 注意url的匹配规则,它不区分请求的类型,例如GET POST等,只要url一样,都交给同一个视图函数处理。

  • 不管正则表达式匹配的是什么类型,捕获的每个参数都以普通的 Python 字符串传给视图。

例如:

1
url(r'^reviews/(?P<year>[0-9]{4})/$', views.year_archive)

虽然[0-9]{4} 只匹配字符串中的整数,但是传给views.year_archive() 视图函数的year 参数是字符串,而
不是整数。

  • 可以为视图函数的参数指定默认值(利用的也是Python函数的特性)

例如:

1
2
3
4
5
6
7
8
urlpatterns = [
url(r'^reviews/$', views.page), # 使用默认参数
url(r'^reviews/page(?P<num>[0-9]+)/$', views.page), # 这里有传值,不使用默认参数
]

# 视图(在 reviews/views.py 文件中)
def page(request, num="1"):
# 输出指定数量的书评
  • 性能问题:urlpatterns 中的每个正则表达式在首次访问时编译,因此系统的速度异常得快。

引入其他URl配置

urlpatterns 在任何位置都可以“引入”其他 URL 配置模块。通过这一行为可以把一些 URL 放在另一些名下。

例如:

1
2
3
4
5
6
urlpatterns = [
# ...
url(r'^community/', include('django_website.aggregator.urls')),
url(r'^contact/', include('django_website.contact.urls')),
# ...
]

注意,这里的正则表达式没有$(匹配字符串末尾的符号),但是末尾有斜线。Django 遇到include() 时,会把截至那一位置匹配的 URL 截断,把余下的字符串传给引入它的 URL 配置,做进一步处理。

可以利用这一点将公共的路径提取出来,示例如下:

1
2
3
4
5
6
7
8
9
10
11
注意,url的配置可以抽取共性配置
urlpatterns = [
url(r'^chehejia/',
include([ # 引入 ,引入可以引入其他URL配置模块,这里是写死的,还可以写成引入 xxx,其中xxx指的是 .py 文件,此文件中也有urlpatterns变量定义了url映射。 可以根据url的全路径匹配到引入模块的映射函数
url(r'^add/$', views.add),
url(r'^edit/$', views.edit),
url(r'^delete/$', views.delete),
url(r'^save/$', views.save),
])
),
]

注意:通过此方式引入的URl配置,我们在父URl中使用正则捕获到的参数,是可以传递给子URl视图函数的,所以可以放心使用

传递额外参数

URL 配置允许向视图函数传递额外的参数,这些参数放在一个 Python 字典中。django.conf.urls.url() 函数的第三个参数是可选的,如果指定,应该是一个字典,指定要传给视图函数的额外关键字参数及其值。

例如:

1
2
3
4
5
urlpatterns = [
url(r'^reviews/(?P<year>[0-9]{4})/$',
views.year_archive, {'foo': 'bar'} # 具名分组及设置默认参数
),
]

同样include()也同样适用此特性:

1
2
3
4
5
6
7
8
9
10
11
# main.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^reviews/', include('inner'), {'reviewid': 3}),
]

# inner.py
urlpatterns = [
url(r'^archive/$', views.archive),
url(r'^about/$', views.about),
]

反向解析URl

这个不太明白,我认为就是转发请求的时候使用,注意讨论一下

Django 提供了一种方案,只需在 URL 映射中设计 URL。我们为其提供 URL 配置,然后可以双向使用:

  • 从用户(浏览器)请求的 URL 开始,这个方案能调用正确的 Django 视图,并从 URL 中提取可能需要的参数及其值,传给视图。

  • 从 Django 视图对应的标识以及可能传入的参数值开始,获取相应的 URL。

第一点就是我们目前所讨论的处理方式。第二点称为反向解析 URL、反向匹配 URL、反向查找 URL 或 URL反转。

Django 在不同的层中提供了执行 URL 反转所需的工具:

  • 在模板中,使用url 模板标签。

  • 在 Python 代码中,使用django.core.urlresolvers.reverse() 函数。

  • 在处理 Django 模型实例 URL 相关的高层代码中,使用get_absolute_url() 方法。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# URl描述
urlpatterns = [
url(r'^reviews/([0-9]{4})/$', views.year_archive, name='reviews-year-archive'),
]

# 模板中如下描述
<ul>
{% for yearvar in year_list %}
<li><a href="{% url 'reviews-year-archive' yearvar %}">{{ yearvar }} Archive</a></li>
{% endfor %}
</ul>

# 视图函数如下
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect

def redirect_to_year(request):
# ...
year = 2012
# ...
return HttpResponseRedirect(reverse('reviews-year-archive', args=(year,)))

URl命名空间

URL 命名空间在反转具名 URL 模式时具有唯一确定性,即便不同的应用使用相同的名称也不怕。

正确使用 URL 命名空间的 Django 应用程序可以在同一个网站中多次部署。例如,django.contrib.admin 中有个AdminSite 类,可以轻易部署多个管理后台。URL 命名空间分为两部分,而且都是字符串:

  1. 应用命名空间。指明应用的名称。一个应用的每个实例都具有相同的应用命名空间。例如,你可能猜到了,Django 管理后台的应用命名空间是admin。

  2. 实例命名空间。标识具体的应用程序实例。实例命名空间在整个项目范围内应该是唯一的。不过,实例命名空间可以与应用命名空间相同,供应用的默认实例使用。例如,Django 管理后台实例的默认实例命名空间是admin

命名空间中的 URL 使用: 运算符指定。例如,管理后台的主页使用 admin:index 引用。其中,admin 是命名空间,index 是 URL 的名称。

命名空间还可以嵌套。members:reviews:index 在命名空间members 中查找命名空间reviews,再在里面查找index URL。

反转命名空间的URl的步骤

  1. 首先,Django 查找有没有匹配的应用命名空间(这里的reviews)。为此,会产出那个应用的实例列表。

  2. 如果有这么一个应用实例,Django 返回它的 URL 解析程序。当前应用可以通过请求的一个属性指定。预期有多个部署实例的应用应该在处理的请求上设定current_app 属性。

  3. 当前应用也可以手动指定,方法是作为参数传给reverse() 函数。

  4. 如果没有当前应用,Django 查找默认的应用实例。默认应用实例是指实例命名空间与应用命名空间匹配的实例(在这里是指名为reviews 的reviews 实例)。

  5. 如果没有默认的应用实例,Django 选中最后部署的应用实例,而不管实例的名称。

  6. 如果第 1 步找不到匹配的应用命名空间,Django 直接把它视作实例命名空间查找。

URL 命名空间和引入的 URL 配置示例

把引入的 URL 配置放入命名空间中有两种方式。

第一种,在 URL 模式中为include() 提供应用和实例命名空间:

1
url(r'^reviews/', include('reviews.urls', namespace='author-reviews', app_name='reviews'))

上述示例把reviews.urls 中定义的 URL 放在应用命名空间reviews 中,放在实例命名空间author-reviews中。

第二种,引入包含命名空间数据的对象。如果使用include() 引入一组url() 实例,那个对象中的 URL 都添加到全局命名空间中。然而,include() 的参数还可以是一个三元素元组:

1
2
3
4
5
6
reviews_patterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
]

url(r'^reviews/', include((reviews_patterns, 'reviews', 'author-reviews'))),

上述示例把指定的 URL 模式引入指定的应用和实例命名空间中。

记得要把一个元组传给include()。如果直接传入三个参数,例如include(reviews_patterns, ‘reviews’,’author-reviews’),Django 不会抛出错误,但是根据include() 的签名,’reviews’ 是实例命名空间,’author-reviews’ 是应用命名空间,而正确的顺序应该反过来。

高级模板技术

RequestContext和上下文处理器

模板要在上下文中渲染。上下文是django.template.Context 的实例,不过 Django 还提供了一个子类,django.template.RequestContext,其行为稍有不同。

RequestContext 默认为模板上下文添加很多变量,例如HttpRequest 对象或当前登录用户的信息,例如我们在视图函数中获取到的request对象,它就是Djongo为我们提供的上下文处理器,我们可以在此处理器中获取很多参数:

示例:

1
2
3
4
5
def view_1(request):
# ...
t = loader.get_template('template1.html')
c = RequestContext(request, {'message': 'I am view 1.'}, processors=[custom_proc])
return t.render(c)

上下文处理器使用示例,提供公共的上下文处理器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 定义一个上下文处理器,提供 'app'、'user' 和 'ip_address',可以在处理器中为多个请求提供共同的必要的上下文
# 注意:上下文处理器必须返回一个字典
# 自定义上下文处理器一般放在单独项目或者项目下的context_processors.py中
def custom_proc(request):
return {
'app': '我的上下文测试',
'user': request.user,
'ip_address': request.META['REMOTE_ADDR']
}

# 引用上下文处理器直接返回给模板进行渲染
# 注意processors函数的参数,第一个必须是request对象,第二个是可选的上下文处理器列表或元组
def view_1(request):
return render(request, 'template1.html',
{'message': '消息1'},
context_instance=RequestContext(
request, processors=[custom_proc]
)
)

# 与上述一致,公用上下文处理器
def view_2(request):
return render(request, 'template2.html',
{'message': '消息2'},
context_instance=RequestContext(
request, processors=[custom_proc]
)
)

Djongo提供的上下文处理器

在settings文件中,我们来逐一说明Djongo提供的上下文处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
'OPTIONS': {
# 默认的处理器上下文
'context_processors': [
# 启用这个处理器后,RequestContext 中将包含下面两个变量
# debug:True。可以在模板中测试是否在DEBUG 模式中。
# sql_queries:{'sql': …, 'time': …} 字典构成的列表,表示处理请求的过程中执行的 SQL 查询及其用时。列表中的值按查询的执行顺序排列,在访问时惰性生成
'django.template.context_processors.debug',

# 启用这个处理器后,RequestContext 中将包含request 变量,它的值是当前的HttpRequest 对象
'django.template.context_processors.request',

# 启用此处理器后将包含:
# user:auth.User 的实例,表示当前登录的用户(如未登录,是AnonymousUser 实例)。
# perms:django.contrib.auth.context_processors.PermWrapper 实例,表示当前登录用户拥有的权限。
'django.contrib.auth.context_processors.auth',

# 启用这个处理器后,RequestContext 中将包含下面两个变量:
# messages:消息框架设定的消息列表(里面的值是字符串)
# DEFAULT_MESSAGE_LEVELS:消息等级名称到数字值的映射
'django.contrib.messages.context_processors.messages',

# 非默认,启用后将包含:
# LANGUAGES:LANGUAGES 设置的值
# LANGUAGE_CODE:如果request.LANGUAGE_CODE 存在,返回它的值;否则返回LANGUAGE_CODE 设置的值
# django.template.context_processors.i18n

# 非默认,启用这个处理器后,RequestContext 中将包含MEDIA_URL 变量,提供MEDIA_URL 设置的值
# django.template.context_processors.media

# 非默认,启用这个处理程序后,RequestContext 中将包含STATIC_URL 变量,提供STATIC_URL 设置的值
# jango.template.context_processors.static

# 非默认,这个处理器添加一个令牌,供csrf_token 模板标签使用,用于防范跨站请求伪造,(暂时不清楚具体意思)
# django.template.context_processors.csrf
],
},

模板加载内部机制

DIRS 选项

告诉 Django 模板目录有哪些的方法是使用设置文件中TEMPLATES 设置的DIRS 选项,或者是Engine 的dirs 参数。这个选项的值是一个字符串列表,包含指向模板目录的完整路径:

1
2
3
4
5
6
7
8
9
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
'/home/html/templates/lawrence.com',
'/home/html/templates/default',
],
},
]

模板可以放在任何位置,只要 Web 服务器有权限读取目录及里面的模板即可。模板的扩展名不限,可以是.html 或.txt,甚至可以没有。注意,这里的路径应该使用 Unix 风格的正斜线,即便在 Windows 中也是如此。

加载器类型说明

1
2
3
4
5
6
7
8
9
10
11
12
# 加载器
# 默认使用 ilesystem.Loader 文件系统加载器,如果不设定DIRS 选项,这个加载器找不到任何模板。
# DIRS 定义一个目录列表,模板引擎按顺序在里面查找模板源文件。
# 当前设置表示在项目根目录中放一些主模板,模板目录不一定非得叫'templates',可以自定义
'DIRS': [os.path.join(BASE_DIR, 'templates')],

# 还有应用目录加载器 pp_directories.Loader
# INSTALLED_APPS = ['myproject.reviews', 'myproject.music']
# 从文件系统中的 Django 应用里加载模板。这个加载器在INSTALLED_APPS 列出的各个应用中查找templates 子目录。如果找到,Django 在其中查找模板。
# 这意味着,应用可以自带模板。通过这一行为,便于分发带默认模板的 Django 应用。例此加载器会在设置的文件夹中顺序的加载模板,最先找到的被加载,所以设置顺序很重要

# 还有一些其他加载器,默认是禁用的,自行了解

Django模型的高级用法

新增和修改对象

save和create方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
m01 = Manufacturers(name='金龙鱼',address='铁岭', city='大连', province='沈阳', website='www.xmy.com')
# save方法保存
m01.save()

# create方法保存
m02 = Manufacturers.objects.create(name='金龙鱼',address='铁岭', city='大连', province='沈阳', website='www.xmy.com')

# 当数据被保存后,及对象ID值是有的,直接再次调用save方法,就是修改对象,注意这里是全字段修改
m02.name = '鲁花'
m02.save()

# 一个语句更新一个对象
Manufacturers.objects.get(name='金龙鱼').save(city='黑龙江')

# 一个语句中更新多条数据
Manufacturers.objects.filter(name='金龙鱼').update(city='黑龙江')

查询数据

all、filter、get方法等,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 获取所有数据
Manufacturers.objects.all()

# 根据条件获取部分数据
Manufacturers.objects.filter(name='金龙鱼')

# 根据多个条件获取部分数据
Manufacturers.objects.filter(name='金龙鱼', address='铁岭')

# 类似SQL使用like语句过滤获取部分数据,字段加两个下划线,接contains
Manufacturers.objects.filter(name__contains='金')

# 获取单个数据,此方法返回的就不是列表了,而是单条数据,如果查询出多条数据或者没有查询出数据,会抛出异常
Manufacturers.objects.get(website='www.xmy.com')

排序数据

order_by方法,具体如下:

1
2
3
4
5
6
7
8
# 根据name排序,正向
Manufacturers.objects.order_by('name')

# 根据多个字段排序
Manufacturers.objects.order_by('name', 'province')

# 反向排序,方法是在字段名称前面加上“-”(减号)
Manufacturers.objects.order_by('-name')

链式查找

既过滤加排序,如下:

1
Manufacturers.objects.filter(name='金龙鱼').order_by('-name')

切片数据

及传统上理解的分页查找,如下:

1
2
3
4
5
# 返回查询出的第一条数据
Manufacturers.objects.filter(name='金龙鱼')[0]

# 分页查询,底层使用的是Mysql的Limit函数
Manufacturers.objects.filter(name='金龙鱼')[0, 10]

删除数据

delete方法,如下:

1
2
3
4
5
6
7
8
# 删除一条数据
Manufacturers.objects.get(name='金龙鱼').delete()

# 删除多条数据
Manufacturers.objects.filter(name='金龙鱼').delete()

# 删除全部数据
Manufacturers.objects.all().delete()

访问外键数据

根据外键查询出关联的对象,如下:

1
2
3
# 一对一关联
manufacturers = Product.objects.get(title='小米').manufacturers
manufacturersName = Product.objects.get(title='小米').manufacturers.name

访问多对多数据

多对多关联数据获取如下:

1
2
3
4
5
6
7
8
9
10
# 获取所有关联
vender = Product.objects.get(title='小米').vender.all()

# 过滤多对多数据
vender = Product.objects.get(title='小米').vender.filter(name='供应商')

# 反过来,查看经销商经销的所有产品,字段加 _set
Vender.objects.get(name='供应商').product_set.all()

count = Product.vender.count()

管理器

在Book.objects.all() 语句中,objects 是个特殊的属性,我们通过它查询数据库,这是模型的管理器(manager)。现在,我们要深入说明管理器的作用和用法。

添加额外的自定义管理器,修改模型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 自定义模型管理器
class MyProductManager(models.Manager):
# 覆盖的get_queryset() 返回的是一个QuerySet 对象,它对应着我们管理器的all()方法
def get_queryset(self):
return super(MyProductManager, self).get_queryset().filter(name='大米')

# 产品
class Product(models.Model):
title = models.CharField(max_length=100, verbose_name='产品名称')
vender = models.ManyToManyField(Vender, verbose_name='经销商')
manufacturers = models.ForeignKey(Manufacturers, verbose_name='厂家')
product_date = models.DateField(verbose_name='生产日期')
# fields = ('title', 'product_date', 'vender', 'manufacturers')

# 我们明确地把objects 设为一个普通的Manager 示例,如若不然,唯一可用的管理器将是dahl_objects
objects = models.Manager() # 默认的管理器
my_objects = MyProductManager() # 专门查询 产品名称为大米 的管理器

def __str__(self):
return u'%s %s' % (self.title, self.product_date)

# 任何模型都可以使用Meta 类指定多个针对所在模型的选项。
class Meta:
ordering = ['product_date']

下面是使用上述自定义管理器示例:

1
2
3
4
# get_queryset() 返回的是一个QuerySet 对象,因此可以在其上调用filter()、exclude() 和其他所有QuerySet 支持的方法
Product.my_objects.all()
Product.my_objects.filter(title='Matilda')
Product.my_objects.count()

如果需要,我们可以在同一个模型上使用多个管理器。

模型方法

模型方法就是为Model提供一些方法,我们调用这些方法的时候能处理一些我们想要的逻辑。

模型为我们自动提供的常用方法有如下:

  • str()。这是 Python 的一个“魔法方法”,返回对象的 Unicode 表示形式。需要以普通的字符串显示模型实例时,Python 和 Django 会调用这个方法。尤其要注意,在交互式控制台或管理后台中显示对象调用的都是这个方法。这个方法一定要自定义,因为默认的实现没什么用。

  • get_absolute_url()。这个方法告诉 Django 如何计算一个对象的 URL。Django 在管理后台和需要生成对象的 URL 时调用这个方法。具有唯一标识的 URL 的对象都要定义这个方法。

模型方法大多可以被直接覆盖,最常见的就是覆盖str()方法

一下演示覆盖预定义的模型方法,是针对数据库执行行为来覆盖的,例如:

1
2
3
4
5
6
7
8
9
10
11
# 定义Blog模型,覆盖它的save方法,如下定义某些情况下不允许保存
class Blog(models.Model):

name = models.CharField(max_length=100)
tagline = models.TextField()

def save(self, *args, **kwargs):
if self.name == "Yoko Ono's blog":
return # Yoko 肯定不会开博客的!
else:
super(Blog, self).save(*args, **kwargs) # 调用“真正的”save () 方法

执行原始SQL

模型的查询 API 不够用时,可以编写原始 SQL。Django 为执行原始 SQL 查询提供了两种方式:使用Manager.raw() 执行,返回模型实例集合;或者完全不用模型层,直接执行自定义SQL

第一种方式执行示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 基本查询分页
Product.objects.raw('SELECT * FROM CEREALSOILS_PRODUCT LIMIT 0, 5')

# 当我们定义的模型字段名称与数据库字段名称不一致时,可以通过AS将字段对应起来
Product.objects.raw('SELECT product_name as name FROM CEREALSOILS_PRODUCT')

# 延期模型字段
# 注意:指定查询字段的时候,必须要包含主键字段
p = Product.objects.raw('SELECT id, title FROM CEREALSOILS_PRODUCT')
title = p.title # 上述执行取出的数
product_date = p.product_date # 又执行了一次SQL来取出的此字段的值

# 为raw传递参数,注意,这里是防注入的用法,参数写在raw方法内才有防注入的作用
# 注意:Djongo中的占位符是 %s ,而不是 ?
# 查询中有 % ,则需要写两个 %
title = '大米'
pa = Product.objects.raw('SELECT * FROM CEREALSOILS_PRODUCT where title = %s', title)
p9 = Product.objects.raw('SELECT * FROM cerealsOils_product where title like %s LIMIT 0, 5', ['%米%'])

第二种方式执行示例:

django.db.connection 对象表示默认的数据库连接。若想使用这个数据库连接,调用connection.cursor(),然后,调用cursor.execute(sql, [params]) 执行 SQL,再调用cursor.fetchone() 或cursor.fetchall() 返回所得的行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from django.db import connection

def my_custom_sql(self):
cursor = connection.cursor()
cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])
cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
row = cursor.fetchone()
return row

# 当需要连接多个数据库时,可以获取指定的数据库连接
cursor = connections['my_db_alias'].cursor()

# 连接和游标(类似于Java中 Try-with-resouce)
with connection.cursor() as c:
c.execute(...)
等效于:
c = connection.cursor()
try:
c.execute(...)
finally:
c.close()

# 查询数据,只返回结果没有字段名称映射
cursor = connection.cursor()
cursor.execute("SELECT id, title FROM cerealsOils_product")
row01 = cursor.fetchone()
row02 = cursor.fetchone()
print(row01)
print(row02)

# 查询数据,有字段名称映射
cursor01 = connection.cursor()
cursor01.execute("SELECT id, title FROM cerealsOils_product")
desc = cursor01.description
dict(zip([col[0] for col in desc], row))
for row in cursor01.fetchall():
print(row)

最后更新: 2019年08月11日 13:40

原始链接: https://jjw-story.github.io/2019/08/04/Django-02/

× 请我吃糖~
打赏二维码