Django中的工具


工具类的代码应该包括三个部分

单独的工具类应用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.pyhelpers.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.

django.contrib.humanize¶

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 参数的方法的返回值 缓存到内存中有效期是对象的生命周期

对性能优化非常有帮助,极力推荐使用

cached_property


# 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.

format_html

7 django.utils.html.strip_tags(value)

移除 HTML 标签并保留标签间的文本。

需要注意的是,使用 strip_tags 后得到的内容,如果之前没有转义过,不能被认为是安全的。

8 django.utils.six

py2, py3的兼容工具

github.com/benjaminp/six

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.utils.translation¶

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.

Buy me a 肥仔水!