工具类的代码应该包括三个部分
单独的工具类应用app
+ 应用中的utils模块
+ Django 自带的utils工具
单独的工具类应用app
为工具类代码创建一个 单独的应用, 用通常命名为 core
, common
, generic
, util 或 utils
common/
__init__.py
managers.py # contains the custom model manager(s)
models.py
views.py # Contains the custom view mixin(s)
应该使这个应用成为一个真正的 Django app
使用时,可以进行类似的 import :
from core.managers import PublishedManager
from core.views import IceCreamMixin
每个应用中 用utils模块
来简化代码
通常叫 utils.py
或 helpers.py
。
Storing Code Used in Many Places
把在多处会用到的代码保存到 Utility 模块中
Trimming Models
对数据模型model瘦身(奉行 fat model 后,数据模型中的代码会越来越多,可以将其中的通用代码整理到 Utility 模块中)
Easier Testing
更加易于测试(这些逻辑移到模块中的好处是更易于测试。在实现时,要注意这些工具类函数或类应该只关注一件事,并且将这件事做好。)
Django中自带的工具
django.utils
包中有丰富的工具,但是这些工具大部分是内部使用的,因此不推荐在项目中使用。但是以下这些工具可以放心拿来使用
Django Utils¶ 查看稳定的工具
1 django.contrib.humanize
A set of Django template filters
useful for adding a “human touch” to data.
To activate these filters, add 'django.contrib.humanize' to your INSTALLED_APPS setting
. Once you’ve done that, use { load humanize } in a template
, and you’ll have access to the following filters.
apnumber
1 becomes one.
2 becomes two.
10 becomes 10.
intcomma
4500 becomes 4,500.
4500.2 becomes 4,500.2.
45000 becomes 45,000.
450000 becomes 450,000.
4500000 becomes 4,500,000.
…
2 django.utils.decorators.method_decorator(decorator)
可以将我们的函数装饰器转变成类的方法装饰器
Django has some really great function decorators. Many of us have written decorators for Django
projects, especially when we’re working with Function-Based Views. However, there comes a time
when we discover that our favorite function decorator would also make sense as a method decorator.
Fortunately, Django provides the method_decorator
3 django.utils.decorator_from_middleware(middleware)
Django 中间件本质上是全局的,可能会产生额外或隐式的查询操作。我们可以通过该装饰器将中间件只应用于某个视图上
。
Middleware is a wonderful tool, but is global in nature. This can generate extra queries or other
complications. Fortunately, we can isolate the use of middleware on a per-view basis by using this
Also see the related decorator_from_middleware_with_args
decorator.
4 django.utils.encoding.force_text(value)
将任何输入都转化成 Python3 中的 str 或 Python2 中的 unicode。 除了 ango.utils.functional.__proxy__
object
This forces Django to take anything and turn it into a plain str representation on Python 3 and
unicode on Python 2.
It avoids the display of a django.utils.functional.__proxy__ object
5 django.utils.functional.cached_property
能将只有一个 self 参数的方法
的返回值 缓存到内存中
,有效期是对象的生命周期
。
对性能优化非常有帮助
,极力推荐使用
# the model
class Person(models.Model):
def friends(self):
# expensive computation
...
return friends
# in the view:
if person.friends():
...
# in the template
{ for friend in person.friends }
因为这时 view 和 template 中的person是一个对象, 这样 会调用两次 friends()
使用@cached_property
装饰器
from django.utils.functional import cached_property
class Person(models.Model):
@cached_property
def friends(self):
...
该函数如果用在 Django 之外
,在多线程环境下会有问题
。 因此可以看下第三方的 cached_property 库:
cached-property cached-property: Don’t copy/paste code
Django外的多线程下使用
cached_property
from cached_property import threaded_cached_property, cached_property
from time import sleep
from threading import Thread
class Monopoly(object):
def __init__(self):
self.price = 500
@threaded_cached_property
def boardwalk(self):
"""threaded_cached_property is really nice for when no one waits
for other people to finish their turn and rudely start rolling
dice and moving their pieces."""
sleep(1)
self.price += 50
return self.price
mon = Monopoly()
threads = []
for x in range(10):
thread = Thread(target=lambda :print(mon.boardwalk))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
550
550
550
550
550
550
550
550
550
550
from cached_property import threaded_cached_property, cached_property
from time import sleep
from threading import Thread
class Monopoly(object):
def __init__(self):
self.price = 500
@cached_property
def boardwalk(self):
"""threaded_cached_property is really nice for when no one waits
for other people to finish their turn and rudely start rolling
dice and moving their pieces."""
sleep(1)
self.price += 50
return self.price
mon = Monopoly()
threads = []
for x in range(10):
thread = Thread(target=lambda :print(mon.boardwalk))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
550
600
650
700
750
850
800
900
950
1000
6 django.utils.html.format_html(format_str, args, **kwargs)
和 Python 的 str.format() 方法类似,但是它是用于构造 HTML
段的。
所有的 args 和 kwargs 在传给 str.format() 前都已转义。
This is similar to Python’s str.format() method, except designed for building up HTML fragments. All args and kwargs are escaped before being passed to str.format() which then combines
the elements.
7 django.utils.html.strip_tags(value)
移除 HTML 标签并保留标签间的文本。
需要注意的是,使用 strip_tags
后得到的内容,如果之前没有转义过,不能被认为是安全的。
8 django.utils.six
py2, py3的兼容工具
Six is a Python 2 and 3 compatibility library
9 django.utils.text.slugify(value)
Converts to ASCII if allow_unicode is False (default).
默认转为ASCII码
Converts spaces to hyphens.
转换空格为连字符
Removes characters that aren’t alphanumerics, underscores, or hyphens.
去掉非字母,下划线,连字符
Converts to lowercase.
转为小写
Also strips leading and trailing whitespace.
去掉前后空格
Unicode characters
需要指定参数 allow_unicode
from django.utils.text import slugify
s = " 你好 $ apPle ß "
r = slugify(s, allow_unicode=True)
print(r)
# 你好-apple-ß
10 django.utils.timezone
If our users live in more than one time zone, it’s good practice for us to have time zone support enabled.
使用后,日期和时间在数据库中以 UTC 格式
保存,并在需要时转换为local time
。
11 django.utils.translation
Django’s i18n support
Django 中有用的异常
大多数异常都是内部使用的,但是有一些异常很实用。
1 django.core.exceptions.ImproperlyConfigured
可在 settings
模块中导入使用。
The ImproperlyConfigured exception is raised when Django is somehow improperly configured
– for example, if a value in settings.py is incorrect or unparseable.
2 django.core.exceptions.ObjectDoesNotExist
所有 DoesNotExist 异常的基类
。
它在获取泛数据模型实例时很有用:
# core/utils.py
from django.core.exceptions import ObjectDoesNotExist
class BorkedObject(object):
loaded = False
def generic_load_tool(model, pk):
try:
instance = model.objects.get(pk=pk)
except ObjectDoesNotExist:
return BorkedObject()
instance.loaded = True
return instance
创建类似于django.shortcuts.get_object_or_404
自己的异常处理函数 get_object_or_403
# core/utils.py
from django.core.exceptions import MultipleObjectsReturned
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import PermissionDenied
def get_object_or_403(model, **kwargs):
try:
return model.objects.get(**kwargs)
except ObjectDoesNotExist:
raise PermissionDenied
except MultipleObjectsReturned:
raise PermissionDenied
3 django.core.exceptions.PermissionDenied
在视图views中抛出 这个异常会返回 django.http.HttpResponseForbidden
, 强制 view to 显示 项目的 403 error page
在安全需要高的项目中,可以通过该异常来向用户显示 “Permission Denied” 页:
def finance_data_adjudication(store, sales, issues):
if store.something_not_right:
msg = "Something is not right. Please contact the support team."
raise PermissionDenied(msg)
# Continue on to perform other logic.
403 错误页的配置
In the root URLConf of a project
# urls.py
# This demonstrates the use of a custom permission denied view. The default
# view is django.views.defaults.permission_denied
handler403 = 'core.views.permission_denied_view'
序列化相关
Django 的序列化和反序列化工具支持对 JSON, YAML 和 XML
等格式的处理。
例子:
# 序列化
# serializer_example.py
from django.core.serializers import get_serializer
from favorites.models import Favorite
# Get and instantiate the serializer class
# The 'json' can be replaced with 'python' or 'xml'.
# If you have pyyaml installed, you can replace it with
# 'pyyaml'
JSONSerializer = get_serializer("json")
serializer = JSONSerializer()
favs = Favorite.objects.filter()[:5]
# Serialize model data
serialized_data = serializer.serialize(favs)
# save the serialized data for use in the next example
with open("data.json", "w") as f:
f.write(serialized_data)
# 反序列化
# deserializer_example.py
from django.core.serializers import get_serializer
from favorites.models import Favorite
favs = Favorite.objects.filter()[:5]
# Get and instantiate the serializer class
# The 'json' can be replaced with 'python' or 'xml'.
# If you have pyyaml installed, you can replace it with
# 'pyyaml'
JSONSerializer = get_serializer("json")
serializer = JSONSerializer()
# open the serialized data file
with open("data.txt") as f:
serialized_data = f.read()
# deserialize model data into a generator object
# we'll call 'python data'
python_data = serializer.deserialize(serialized_data)
# iterate through the python_data
for element in python_data:
# Prints 'django.core.serializers.base.DeserializedObject'
print(type(element))
# Elements have an 'object' that are literally instantiated
# model instances (in this case, favorites.models.Favorite)
print(
element.object.pk,
element.object.created
)
序列化注意点:
➤ Serialize data at the simplest level.
只对简单数据进行序列化
➤ Any database schema change may invalidate the serialized data.
数据库模式的修改会使序列化的数据失效
➤ Don’t just import serialized data. Consider using Django’s form libraries or Django Rest Framework serializers to validate incoming data before saving to the database.
不要只导入序列化数据。在存入数据库前使用 Django form 库对它们进行验证
1 django.core.serializers.json.DjangoJSONEncoder
Python 内置的 JSON 模块对日期/时间
或十进制数类型
处理不太好,而 DjangoJSONEncoder 对这些支持的不错
# json_encoding_example.py
import json
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import timezone
data = {"date": timezone.now()}
# If you don't add the DjangoJSONEncoder class then
# the json library will throw a TypeError.
json_data = json.dumps(data, cls=DjangoJSONEncoder)
print(json_data)
2 django.core.serializers.pyyaml
相比于第三方库,它能对时间进行转换。
While powered by the third-party library, pyyaml, Django’s YAML serializer tools handles the time
conversion from Python-to-YAML that pyyaml doesn’t
3 django.core.serializers.xml_serializer
它整合了 Python 内置的 XML 处理器和 defusexml 库
By default Django’s XML serializer uses Python’s built-in XML handlers.
It also incorporates elements of Christian Heimes’ defusedxml library, protecting usage of it from XML bomb attacks
4 rest_framework.serializers
There are times when Django’s built-in serializers just don’t do enough
. Here are common examples
of their limitations:
➤ They only serialize data stored in fields.
You can’t include data from methods or properties.
➤ You can’t constrain the fields serialized.
This can be a security or performance consideration.
When we run into these obstacles, it’s a good idea to consider switching to Django Rest Framework’s
Serializers toolset
. They allow for a lot more customization of both the serialization and deserialization process. While that power comes with complexity, we’ve found it’s worth it to use this tool rather
than constructing a manual process from scratch.