本篇文章首席CTO笔记来给大家介绍有关如何解决Django惰性查询以及django过滤查询的相关内容,希望对大家有所帮助,一起来看看吧。
本文目录一览:
1、求教Django中model类型为DateTimeField在查询时的问题2、如何有效的遍历django的QuerySet3、如何解决Django 1.8在migrate时失败4、如何优化 Django REST Framework 的性能5、Django中复杂的查询求教Django中model类型为DateTimeField在查询时的问题
Django还有一些warning打印出来:/Users/jay/workspace/te/env/lib/python2.7/site-packages/django/db/models/fields/__init__.py:903: RuntimeWarning: DateTimeField TestSuite.update_time received a naive datetime (2014-06-15 14:38:37.873873) while time zone support is active. RuntimeWarning)
这个warning的原因是,Django配置为使用timezone的datetime格式,而datetime.now是不包含timezone信息的。
如果不需要在程序中特别处理时区(timezone-aware),在Django项目的settings.py文件中,可以直接设置为“USE_TZ = False”就省心了。然后,在models.py中简单的设置为“ create_time = models.DateTimeField(auto_now_add=True)”和“update_time = models.DateTimeField(auto_now=True)”。
如果还要保持USE_TZ=True,则可设置为“default=datetime.now().replace(tzinfo=utc)” 。
如何有效的遍历django的QuerySet
开始的阶段没有遇到什么问题,我们举例,在models有一张员工表employee,对应的表结构中,postion列表示员工职位,前台post过来的参数赋给position,加上入职时间、离职时间,查询操作通过models.filter(position=params)完成,获取的员工信息内容由QuerySet和当前展示页与每页展示的记录数进行简单的计算,返回给前台页面进行渲染展示。编码如下:
def get_employees(position, start, end):
return employee.objects.filter(alert_time__lt=end,alert_time__gt=start).filter(position__in=position)
@login_required
def show(request):
if not validate(request):
return render_to_response('none.html',
context_instance=RequestContext(request, 'msg':'params error')
)
position = request.REQUEST.get('position')
time_range = request.REQUEST.get('time')
start, end = time_range[0], time_range[1]
num_per_page, page_num = get_num(request)
all_employees = get_employees(position, start, end)
# 根据当前页与每页展示的记录数,取到正确的记录
employees = employees_events[(page_num-1)*num_per_page:page_num*num_per_page]
return render_to_response('show_employees.html',
context_instance=RequestContext(
request,
'employees': employees,
'num_per_page': num_per_page,
'page_num':page_num,
'page_options' : [50, 100, 200]
)
)
运行之后可以正确的对所查询的员工信息进行展示,并且查询速度很快。employee表中存放着不同职位的员工信息,不同类型的详细内容也不相同,假设employees有一列名为infomation,存储的是员工的详细信息,infomation = {'age': 33, 'gender': 'male', 'nationality': 'German', 'degree': 'doctor', 'motto': 'just do it'},现在的需求是要展示出分类更细的员工信息,前台页面除了post职位、入职离职时间外,还会对infomation中的内容进行筛选,这里以查询中国籍的设计师为例,在之前的代码基础上,需要做一些修改。员工信息表employee存放于MySQL中,而MySQL为ORM数据库,它并未提供类似mongodb一样更为强大的聚合函数,所以这里不能通过objects提供的方法进行filter,一次性将所需的数据获取出来,那么需要对type进行过滤后的数据,进行二次遍历,通过information来确定当前记录是否需要返回展示,在展示过程中,需要根据num_per_page和page_num计算出需要展示数据起始以及终止位置。
def get_employees(position, start, end):
return employee.objects.filter(alert_time__lt=end,alert_time__gt=start).filter(position__in=position)
def filter_with_nation(all_employees, nationality, num_per_page, page_num):
result = []
pos = (page_num-1)*num_per_page
cnt = 0
start = False
for employee in all_employees:
info = json.loads(employee.information)
if info.nationality != nationality:
continue
# 获取的数据可能并不是首页,所以需要先跳过前n-1页
if cnt == pos:
if start:
break
cnt = 0
pos = num_per_page
start = True
if start:
result.append(employee)
return employee
@login_required
def show(request):
if not validate(request):
return render_to_response('none.html',
context_instance=RequestContext(request, 'msg':'params error')
)
position = request.REQUEST.get('position')
time_range = request.REQUEST.get('time')
start, end = time_range[0], time_range[1]
num_per_page, page_num = get_num(request)
all_employees = get_employees(position, start, end)
nationality = request.REQUEST.get('nationality')
employees = filter_with_nation(all_employees, num_per_page, page_num)
return render_to_response('show_employees.html',
context_instance=RequestContext(
request,
'employees': employees,
'num_per_page': num_per_page,
'page_num':page_num,
'page_options' : [50, 100, 200]
)
)
当编码完成之后,在数据employee表数据很小的情况下测试并未发现问题,而当数据量非常大,并且查询的数据很少时,代码运行非常耗时。我们设想,这是一家规模很大的跨国公司,同时人员的流动量也很大,所以employee表的数据量很庞大,而这里一些来自于小国家的员工并不多,比如需要查询国籍为梵蒂冈的员工时,前台页面进入了无尽的等待状态。同时,监控进程的内存信息,发现进程的内存一直在增长。毫无疑问,问题出现在filter_with_nation这个函数中,这里逐条遍历了employee中的数据,并且对每条数据进行了解析,这并不是高效的做法。
在网上查阅了相关资料,了解到:
Django的queryset是惰性的,使用filter语句进行查询,实际上并没有运行任何的要真正从数据库获得数据
只要你查询的时候才真正的操作数据库。会导致执行查询的操作有:对QuerySet进行遍历queryset,切片,序列化,对 QuerySet 应用 list()、len()方法,还有if语句
当第一次进入循环并且对QuerySet进行遍历时,Django从数据库中获取数据,在它返回任何可遍历的数据之前,会在内存中为每一条数据创建实例,而这有可能会导致内存溢出。
上面的原来很好的解释了代码所造成的现象。那么如何进行优化是个问题,网上有说到当QuerySet非常巨大时,为避免将它们一次装入内存,可以使用迭代器iterator()来处理,但对上面的代码进行修改,遍历时使用employee.iterator(),而结果和之前一样,内存持续增长,前台页面等待,对此的解释是:using iterator() will save you some memory by not storing the result of the cache internally (though not necessarily on PostgreSQL!); but will still retrieve the whole objects from the database。
这里我们知道不能一次性对QuerySet中所有的记录进行遍历,那么只能对QuerySet进行切片,每次取一个chunk_size的大小,遍历这部分数据,然后进行累加,当达到需要的数目时,返回满足的对象列表,这里修改下filter_with_nation函数:
def filter_with_nation(all_employees, nationality, num_per_page, page_num):
result = []
pos = (page_num-1)*num_per_page
cnt = 0
start_pos = 0
start = False
while True:
employees = all_employees[start_pos:start_pos+num_per_page]
start_pos += num_per_page
for employee in employees:
info = json.loads(employee.infomation)
if info.nationality != nationality:
continue
if cnt == pos:
if start:
break
cnt = 0
pos = num_per_page
start = True
if start:
result.append(opt)
cnt += 1
if cnt == num_per_page or not events:
break
return result
运行上述代码时,查询的速度更快,内存也没有明显的增长,得到效果不错的优化。这篇文章初衷在于记录自己对django中queryset的理解和使用,而对于文中的例子,其实正常业务中,如果需要记录员工详细的信息,最好对employee表进行扩充,或者建立一个字表,存放详细信息,而不是将所有信息存放入一个字段中,避免在查询时的二次解析。
如何解决Django 1.8在migrate时失败
1. 创建项目
运行下面命令就可以创建一个 django 项目,项目名称叫 mysite :
$ django-admin.py startproject mysite
创建后的项目目录如下:
mysite
├── manage.py
└── mysite
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
1 directory, 5 files
说明:
__init__.py :让 Python 把该目录当成一个开发包 (即一组模块)所需的文件。 这是一个空文件,一般你不需要修改它。
manage.py :一种命令行工具,允许你以多种方式与该 Django 项目进行交互。 键入python manage.py help,看一下它能做什么。 你应当不需要编辑这个文件;在这个目录下生成它纯是为了方便。
settings.py :该 Django 项目的设置或配置。
urls.py:Django项目的URL路由设置。目前,它是空的。
wsgi.py:WSGI web 应用服务器的配置文件。更多细节,查看 How to deploy with WSGI
接下来,你可以修改 settings.py 文件,例如:修改 LANGUAGE_CODE、设置时区 TIME_ZONE
SITE_ID = 1
LANGUAGE_CODE = 'zh_CN'
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = True
上面开启了 [Time zone]() 特性,需要安装 pytz:
$ sudo pip install pytz
2. 运行项目
在运行项目之前,我们需要创建数据库和表结构,这里我使用的默认数据库:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, contenttypes, auth, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying sessions.0001_initial... OK
然后启动服务:
$ python manage.py runserver
你会看到下面的输出:
Performing system checks...
System check identified no issues (0 silenced).
January 28, 2015 - 02:08:33
Django version 1.7.1, using settings 'mysite.settings'
Starting development server at
Quit the server with CONTROL-C.
这将会在端口8000启动一个本地服务器, 并且只能从你的这台电脑连接和访问。 既然服务器已经运行起来了,现在用网页浏览器访问 。你应该可以看到一个令人赏心悦目的淡蓝色 Django 欢迎页面它开始工作了。
你也可以指定启动端口:
$ python manage.py runserver 8080
以及指定 ip:
$ python manage.py runserver 0.0.0.0:8000
3. 创建 app
前面创建了一个项目并且成功运行,现在来创建一个 app,一个 app 相当于项目的一个子模块。
在项目目录下创建一个 app:
$ python manage.py startapp polls
如果操作成功,你会在 mysite 文件夹下看到已经多了一个叫 polls 的文件夹,目录结构如下:
polls
├── __init__.py
├── admin.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
1 directory, 6 files
4. 创建模型
每一个 Django Model 都继承自 django.db.models.Model
在 Model 当中每一个属性 attribute 都代表一个 database field
通过 Django Model API 可以执行数据库的增删改查, 而不需要写一些数据库的查询语句
打开 polls 文件夹下的 models.py 文件。创建两个模型:
import datetime
from django.db import models
from django.utils import timezone
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def was_published_recently(self):
return self.pub_date = timezone.now() - datetime.timedelta(days=1)
class Choice(models.Model):
question = models.ForeignKey(Question)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
然后在 mysite/settings.py 中修改 INSTALLED_APPS 添加 polls:
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'polls',
)
在添加了新的 app 之后,我们需要运行下面命令告诉 Django 你的模型做了改变,需要迁移数据库:
$ python manage.py makemigrations polls
你会看到下面的输出日志:
Migrations for 'polls':
0001_initial.py:
- Create model Choice
- Create model Question
- Add field question to choice
你可以从 polls/migrations/0001_initial.py 查看迁移语句。
运行下面语句,你可以查看迁移的 sql 语句:
$ python manage.py sqlmigrate polls 0001
输出结果:
BEGIN;
CREATE TABLE "polls_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL);
CREATE TABLE "polls_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_text" varchar(200) NOT NULL, "pub_date" datetime NOT NULL);
CREATE TABLE "polls_choice__new" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, "question_id" integer NOT NULL REFERENCES "polls_question" ("id"));
INSERT INTO "polls_choice__new" ("choice_text", "votes", "id") SELECT "choice_text", "votes", "id" FROM "polls_choice";
DROP TABLE "polls_choice";
ALTER TABLE "polls_choice__new" RENAME TO "polls_choice";
CREATE INDEX polls_choice_7aa0f6ee ON "polls_choice" ("question_id");
COMMIT;
你可以运行下面命令,来检查数据库是否有问题:
$ python manage.py check
再次运行下面的命令,来创建新添加的模型:
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, contenttypes, polls, auth, sessions
Running migrations:
Applying polls.0001_initial... OK
总结一下,当修改一个模型时,需要做以下几个步骤:
修改 models.py 文件
运行 python manage.py makemigrations 创建迁移语句
运行 python manage.py migrate,将模型的改变迁移到数据库中
你可以阅读 django-admin.py documentation,查看更多 manage.py 的用法。
创建了模型之后,我们可以通过 Django 提供的 API 来做测试。运行下面命令可以进入到 python shell 的交互模式:
$ python manage.py shell
下面是一些测试:
from polls.models import Question, Choice # Import the model classes we just wrote.
# No questions are in the system yet.
Question.objects.all()
[]
# Create a new Question.
# Support for time zones is enabled in the default settings file, so
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
# instead of datetime.datetime.now() and it will do the right thing.
from django.utils import timezone
q = Question(question_text="What's new?", pub_date=timezone.now())
# Save the object into the database. You have to call save() explicitly.
q.save()
# Now it has an ID. Note that this might say "1L" instead of "1", depending
# on which database you're using. That's no biggie; it just means your
# database backend prefers to return integers as Python long integer
# objects.
q.id
1
# Access model field values via Python attributes.
q.question_text
"What's new?"
q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=UTC)
# Change values by changing the attributes, then calling save().
q.question_text = "What's up?"
q.save()
# objects.all() displays all the questions in the database.
Question.objects.all()
[Question: Question object]
打印所有的 Question 时,输出的结果是 [Question: Question object],我们可以修改模型类,使其输出更为易懂的描述。修改模型类:
from django.db import models
class Question(models.Model):
# ...
def __str__(self): # __unicode__ on Python 2
return self.question_text
class Choice(models.Model):
# ...
def __str__(self): # __unicode__ on Python 2
return self.choice_text
接下来继续测试:
from polls.models import Question, Choice
# Make sure our __str__() addition worked.
Question.objects.all()
[Question: What's up?]
# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
Question.objects.filter(id=1)
[Question: What's up?]
Question.objects.filter(question_text__startswith='What')
[Question: What's up?]
# Get the question that was published this year.
from django.utils import timezone
current_year = timezone.now().year
Question.objects.get(pub_date__year=current_year)
Question: What's up?
# Request an ID that doesn't exist, this will raise an exception.
Question.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Question matching query does not exist.
# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Question.objects.get(id=1).
Question.objects.get(pk=1)
Question: What's up?
# Make sure our custom method worked.
q = Question.objects.get(pk=1)
# Give the Question a couple of Choices. The create call constructs a new
# Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation
# (e.g. a question's choice) which can be accessed via the API.
q = Question.objects.get(pk=1)
# Display any choices from the related object set -- none so far.
q.choice_set.all()
[]
# Create three choices.
q.choice_set.create(choice_text='Not much', votes=0)
Choice: Not much
q.choice_set.create(choice_text='The sky', votes=0)
Choice: The sky
c = q.choice_set.create(choice_text='Just hacking again', votes=0)
# Choice objects have API access to their related Question objects.
c.question
Question: What's up?
# And vice versa: Question objects get access to Choice objects.
q.choice_set.all()
[Choice: Not much, Choice: The sky, Choice: Just hacking again]
q.choice_set.count()
3
# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
Choice.objects.filter(question__pub_date__year=current_year)
[Choice: Not much, Choice: The sky, Choice: Just hacking again]
# Let's delete one of the choices. Use delete() for that.
c = q.choice_set.filter(choice_text__startswith='Just hacking')
c.delete()
上面这部分测试,涉及到 django orm 相关的知识,详细说明可以参考 Django中的ORM。
5. 管理 admin
Django有一个优秀的特性, 内置了Django admin后台管理界面, 方便管理者进行添加和删除网站的内容.
新建的项目系统已经为我们设置好了后台管理功能,见 mysite/settings.py:
INSTALLED_APPS = (
'django.contrib.admin', #默认添加后台管理功能
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'mysite',
)
同时也已经添加了进入后台管理的 url, 可以在 mysite/urls.py 中查看:
url(r'^admin/', include(admin.site.urls)), #可以使用设置好的url进入网站后台
接下来我们需要创建一个管理用户来登录 admin 后台管理界面:
$ python manage.py createsuperuser
Username (leave blank to use 'june'): admin
Email address:
Password:
Password (again):
Superuser created successfully.
总结
最后,来看项目目录结构:
mysite
├── db.sqlite3
├── manage.py
├── mysite
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── wsgi.py
├── polls
│ ├── __init__.py
│ ├── admin.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── __init__.py
│ ├── models.py
│ ├── templates
│ │ └── polls
│ │ ├── detail.html
│ │ ├── index.html
│ │ └── results.html
│ ├── tests.py
│ ├── urls.py
│ ├── views.py
└── templates
└── admin
└── base_site.htm
通过上面的介绍,对 django 的安装、运行以及如何创建视 图和模型有了一个清晰的认识,接下来就可以深入的学习 django 的自动化测试、持久化、中间件、国 际 化等知识。
如何优化 Django REST Framework 的性能
解决 Django 「懒惰」的基本方法
现在我们解决这个问题的方法就是「预加载」。从本质上讲,就是你提前警告 Django ORM 你要一遍又一遍的告诉它同样无聊的指令。在上面的例子中,在 DRF 开始获取前很简单地加上这句话就搞定了:
queryset = queryset.prefetch_related('orders')
当 DRF 调用上述相同序列化 customers 时,出现的是这种情况:
获取所有 customers (执行两个往返数据库操作,第一个是获取 customers,第二个获取相关 customers 的所有相关的 orders。)
对于第一个返回的 customers,获取其 order (不需要访问数据库,我们已经在上一步中获取了所需要的数据)
对于第二个返回的 customers,获取其 order (不需要访问数据库)
对于第三个返回的 customers,获取其 order (不需要访问数据库)
对于第四个返回的 customers,获取其 order (不需要访问数据库)
对于第五个返回的 customers,获取其 order (不需要访问数据库)
对于第六个返回的 customers,获取其 order (不需要访问数据库)
你又意识到,你可以有了 很多 customers ,已经不需要再继续等待去数据库。
其实 Django ORM 的「预备」是在第1步进行请求,它在本地高速缓存的数据能够提供步骤2+所要求的数据。与之前往返数据库相比从本地缓存数据中读取数据基本上是瞬时的,所以我们在有很多 customers 时就获得了巨大的性能加速。
解决 Django REST Framework 性能问题的标准化模式
我们已经确定了一个优化 Django REST Framework 性能问题的通用模式,那就是每当序列化查询嵌套字段时,我们就添加一个新的 @staticmethod 名叫 setup_eager_loading ,像这样:
class CustomerSerializer(serializers.ModelSerializer):
orders = OrderSerializer(many=True, read_only=True)
def setup_eager_loading(cls, queryset):
""" Perform necessary eager loading of data. """
queryset = queryset.prefetch_related('orders')
return queryset
这样,不管哪里要用到这个序列化,都只需在调用序列化前简单调用 setup_eager_loading ,就像这样:
customer_qs = Customers.objects.all()
customer_qs = CustomerSerializer.setup_eager_loading(customer_qs) # Set up eager loading to avoid N+1 selects
post_data = CustomerSerializer(customer_qs, many=True).data
或者,如果你有一个 APIView 或 ViewSet ,你可以在 get_queryset 方法里调用 setup_eager_loading :
def get_queryset(self):
queryset = Customers.objects.all()
# Set up eager loading to avoid N+1 selects
queryset = self.get_serializer_class().setup_eager_loading(queryset)
return queryset
Django中复杂的查询
在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?
Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。
filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象。
from django.db.models import Q
Q(title__startswith='Py')
Q 对象可以使用 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
查询名字叫水浒传或者价格大于100的书
你可以组合 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询:
查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如:
查询名字叫水浒传与价格大于100的书
结语:以上就是首席CTO笔记为大家整理的关于如何解决Django惰性查询的全部内容了,感谢您花时间阅读本站内容,希望对您有所帮助,更多关于django过滤查询、如何解决Django惰性查询的相关内容别忘了在本站进行查找喔。