完整目录请见:Django 3网页开发指南 – 第4版
本章中包含如下小节:
- 使用Django shell
- 使用数据库查询表达式
- slugify()的猴子补子改进国际化支持
- 切换Debug工具栏
- 使用ThreadLocalMiddleware
- 使用信号来通知管理员新条目
- 检测缺失的设置
引言
本章中我们会学习一些重要的点点滴滴来更好地理解和使用Django。这里会总览如何使用Django shell来在文件中编写前进行代码的测试。还会介绍猴子补丁,也称“杂牌军补丁”,这是像Python和Ruby这样的动态语言所具备的强大特性。同时会讨论全文本搜索的功能,并会学习到如何调试代码并检测其性能。然后,我们会学习如何获取任意模块中登录的用户(和其它请求参数)。还会学习到如何处理信号及创建系统检测。准备开启有趣的编程体验之行吧!
技术要求
运行本章的代码要求安装最新稳定版的Python 3、MySQL或PostgreSQL数据库以及通过虚拟环境创建的Django项目。
可在GitHub仓库的Chapter10目录中查看本章的代码。
使用Django shell
启用虚拟环境并进入当前项目目录,在命令行工具中输入如下命令:
1 |
(env)$ python manage.py shell |
通过执行以上命令,会进入一个交互的Python shell,配置为你的Django项目,在其中可以实时操作代码、检测类、测试方法或执行脚本。本节中,我们将学习操作Django shell所需了解的一些最重要的功能。
准备工作
可以安装IPython或bpython来对Python shell提供更多的界面选项,也可以同时安装这两者进行选择。它们会在Django shell的输出中高亮语法显示,并会添加一些帮助类。使用如下命令在虚拟环境中进行安装:
1 2 |
(env)$ pip install ipython (env)$ pip install bpython |
如何实现…
按照如下内容学习使用Django shell的基础知识:
- 输入如下命令运行Django shell:
1(env)$ python manage.py shell
如已安装了IPython或bpython,那么不管安装的是哪个,都会在进入shell时自动变成默认界面。可以通过对上述命令添加-i <interface>选项指定具体的界面。根据所使用的界面提示界面会不同。下图显示IPython的样子,命令行前面是[1]:
TODO
如果使用bpython,shell中会显示>>>弹出框,在代码会进行高亮并且在输入时会有文本自动实例,如下:
TODO
默认的Python界面shell如下,同样在命令行中使用>>>,但前面会有一段有关系统的信息:
TODO
现在就可以导入类、函数或变量,并进行各类操作。例如,查看一个已安装模块的版本,可以导入该模块,然后尝试读取其__version__、VERSION或version属性(下面使用bpython展示,同时演示了其高亮和自动补全功能),如下:
TODO - 要获取模块、类、函数、方法、关键字或文档主题的全面描述,可使用help()函数。可以传递一个到具体实体的路径或实体本身字符串,如下:
1>>> help("django.forms")
这会打开django.forms模块的帮助页面,使用方向键上下翻去页面。按下Q回到shell。如果运行help()并不带参数,会打开交互式帮助页面。在其中可输入请注意模块路径、类、函数等,获取有关其作用及如何使用的信息。退出交互帮助界面,按Ctrl + D。 - 以下是如何在IPython中向help()函数传递实体的示例:
TODO
这时会打开ModelForm类的帮助页面,如下:
TODO
快速查看在模型实例中有哪些字段和值可以使用,使用 __dict__属性。可以使用pprint()函数来以可读性更强的形式打印字典(不是简单的一长行),如下图所示。注意在使用__dict__时,不获取多对多关联,但足够快速查看字段和值了:
TODO - 获取一个对象的所有可用属性和方法,可使用dir()函数,如下:
TODO - 每行打印一个属性,可以使用下图中的代码:
TODO - Django shell对于在将QuerySet或正则表达式放入模型方法、视图或管理命令前进行研究很有帮助。例如,要检测email验证的正则表达式,可以在Django shell中输入如下代码:
1234>>> import re>>> email_pattern = re.compile(r"[^@]+@[^@]+\.[^@]+")>>> email_pattern.match("aidas@bendoraitis.lt")<_sre.SRE_Match object at 0x1075681d0> - 如果希望测试不同的查询集,使用如下代码:
123>>> from django.contrib.auth.models import User>>> User.objects.filter(groups__name="Editors")[<User: admin>] - 退出Django shell,按下Ctrl + D或输入如下命令:
1>>> exit()
实现原理…
普通Python shell 和 Django shell之间的区别在于在运行Django shell时,manage.py设置的DJANGO_SETTINGS_MODULE让其指向项目的settings.py路径,然后Django shell中的所有代码都在项目的上下文中进行处理。通过使用第三方的IPython或bpython界面,可以进一步的增强默认的Python shell,比如语法高亮、自动补全等等。
相关内容
- 使用数据库查询表达式一节
- slugify()的猴子补子改进国际化支持一节
使用数据库查询表达式
Django对象关系映射(ORM)具有特殊的抽象构造,可用于构建复杂的数据库查询。称为查询表达式,让我们可以过滤数据、排序、注解新字段及聚合关联等。本节中,我们会学习在实际场景中如何使用。我们创建一个显示爆款视频的应用并计算每个视频被匿名或登录用户观看的次数。
准备工作
首先,创建一个带有ViralVideo模型的viral_videos应用并配置系统来让其默认自动记录入日志文件:
- 创建viral_videos应用并将其添加到配置文件的INSTALLED_APPS中:
123456# myproject/settings/_base.pyINSTALLED_APPS = [#..."myproject.apps.core","myproject.apps.viral_videos",] - 为爆款视频创建一个模型,主键为一个通用唯一识别码(UUID),并包含创建和修改时间戳、标题、嵌入代码、匿名用户观看数和已登录用记观看数,如下:
123456789101112131415161718192021222324252627282930313233343536# myproject/apps/viral_videos/models.pyimport uuidfrom django.db import modelsfrom django.utils.translation import ugettext_lazy as _from myproject.apps.core.models import (CreationModificationDateBase,UrlBase,)class ViralVideo(CreationModificationDateBase, UrlBase):uuid = models.UUIDField(primary_key=True, default=None, editable=False)title = models.CharField(_("Title"), max_length=200, blank=True)embed_code = models.TextField(_("YouTube embed code"), blank=True)anonymous_views = models.PositiveIntegerField(_("Anonymous impressions"), default=0)authenticated_views = models.PositiveIntegerField(_("Authenticated impressions"), default=0)class Meta:verbose_name = _("Viral video")verbose_name_plural = _("Viral videos")def __str__(self):return self.titledef get_url_path(self):from django.urls import reversereturn reverse("viral_videos:viral_video_detail", kwargs={"pk": self.pk})def save(self, *args, **kwargs):if self.pk is None:self.pk = uuid.uuid4()super().save(*args, **kwargs) - 为新应用生成并运行迁移,这样数据库就准备就绪了:
12(env)$ python manage.py makemigrations(env)$ python manage.py migrate - 将日志配置加入到配置文件中:
12345678910111213LOGGING = {"version": 1,"disable_existing_loggers": False,"handlers": {"file": {"level": "DEBUG","class": "logging.FileHandler","filename": os.path.join(BASE_DIR, "tmp", "debug.log"),}},"loggers": {"django": {"handlers": ["file"], "level": "DEBUG","propagate": True}},}
这会将调试信息记录至临时文件tmp/debug.log中。
如何实现…
为讲解查询表达式,我们来创建一个爆款视频详情页并将其加入到URL配置中,如下:
- 在views.py中创建爆款视频列表和详情视图如下:
1234567891011121314151617181920212223242526272829303132333435363738394041424344# myproject/apps/viral_videos/views.pyimport loggingfrom django.conf import settingsfrom django.db import modelsfrom django.utils.timezone import now, timedeltafrom django.shortcuts import render, get_object_or_404from django.views.generic import ListViewfrom .models import ViralVideoPOPULAR_FROM = getattr(settings, "VIRAL_VIDEOS_POPULAR_FROM", 500)logger = logging.getLogger(__name__)class ViralVideoList(ListView):template_name = "viral_videos/viral_video_list.html"model = ViralVideodef viral_video_detail(request, pk):yesterday = now() - timedelta(days=1)qs = ViralVideo.objects.annotate(total_views=models.F("authenticated_views") +models.F("anonymous_views"),label=models.Case(models.When(total_views__gt=POPULAR_FROM, then=models.Value("popular")),models.When(created__gt=yesterday, then=models.Value("new")),default=models.Value("cool"),output_field=models.CharField(),),)# DEBUG: check the SQL query that Django ORM generateslogger.debug(f"Query: {qs.query}")qs = qs.filter(pk=pk)if request.user.is_authenticated:qs.update(authenticated_views=models.F("authenticated_views") + 1)else:qs.update(anonymous_views=models.F("anonymous_views") + 1)video = get_object_or_404(qs)return render(request, "viral_videos/viral_video_detail.html", {"video": video}) - 为该应用定义URL配置如下:
1234567891011# myproject/apps/viral_videos/urls.pyfrom django.urls import pathfrom .views import ViralVideoList, viral_video_detailapp_name = "viral_videos"urlpatterns = [path("", ViralVideoList.as_view(), name="viral_video_list"),path("<uuid:pk>/", viral_video_detail, name="viral_video_detail"),] - 在项目的顶级URL配置文件中添加应用URL配置如下:
1234567# myproject/urls.pyfrom django.conf.urls.i18n import i18n_patternsfrom django.urls import include, pathurlpatterns = i18n_patterns(path("viral-videos/", include("myproject.apps.viral_videos.urls", namespace="viral_videos")),) - 为爆款视频列表视图创建模板如下:
123456789101112{# viral_videos/viral_video_list.html #}{% extends "base.html" %}{% load i18n %}{% block content %}<h1>{% trans "Viral Videos" %}</h1><ul>{% for video in object_list %}<li><a href="{{ video.get_url_path }}">{{ video.title }}</a></li>{% endfor %}</ul>{% endblock %} - 为爆款视频详情视图创建模板如下:
123456789101112131415161718192021222324{# viral_videos/viral_video_detail.html #}{% extends "base.html" %}{% load i18n %}{% block content %}<h1>{{ video.title }}<span class="badge">{{ video.label }}</span></h1><div>{{ video.embed_code|safe }}</div><div><h2>{% trans "Impressions" %}</h2><ul><li>{% trans "Authenticated views" %}:{{ video.authenticated_views }}</li><li>{% trans "Anonymous views" %}:{{ video.anonymous_views }}</li><li>{% trans "Total views" %}:{{ video.total_views }}</li></ul></div>{% endblock %} - 为viral_videos应用配置后台如下,并在完成后在数据库中添加一些视频:
1234567# myproject/apps/viral_videos/admin.pyfrom django.contrib import adminfrom .models import ViralVideo@admin.register(ViralVideo)class ViralVideoAdmin(admin.ModelAdmin):list_display = ["title", "created", "modified"]
实现原理…
你可能注意到了视图中的logger.debug()语句。如果以DEBUG模式运行服务并在浏览器中访问视频(例如本地开发的 http://127.0.0.1:8000/en/viral-videos/2b14ffd3-d1f1-4699-a07b-1328421d8312/),会看到在日志(tmp/debug.log)中打印类似下面这样的SQL查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
SELECT "viral_videos_viralvideo"."created", "viral_videos_viralvideo"."modified", "viral_videos_viralvideo"."uuid", "viral_videos_viralvideo"."title", "viral_videos_viralvideo"."embed_code", "viral_videos_viralvideo"."anonymous_views", "viral_videos_viralvideo"."authenticated_views", ("viral_videos_viralvideo"."authenticated_views" + "viral_videos_viralvideo"."anonymous_views") AS "total_views", CASE WHEN ("viral_videos_viralvideo"."authenticated_views" + "viral_videos_viralvideo"."anonymous_views") > 500 THEN 'popular' WHEN "viral_videos_viralvideo"."created" > '2019-12-21T05:01:58.775441+00:00'::timestamptz THEN 'new' ELSE 'cool' END AS "label" FROM "viral_videos_viralvideo" WHERE "viral_videos_viralvideo"."uuid" = '2b14ffd3-d1f1-4699-a07b-1328421d8312'::uuid LIMIT 21; args=(500, 'popular', datetime.datetime(2019, 12, 21, 5, 1, 58, 775441, tzinfo=<UTC>), 'new', 'cool', UUID('2b14ffd3-d1f1-4699-a07b-1328421d8312')) |
然后在浏览器中可以看到显示如下内容的简单页面:
- 视频标题
- 视频标签
- 嵌入的视频
- 登录和匿名用户的浏览数以及总浏览数
类似如下图片:
TODO
Django查询集中的annotate()方法让我们可以对 SELECT SQL语句添加更多的字段,并且对通过查询集所获取的对象实时进行属性的创建。借助models.F()我们可以引用所选数据表的不同字段值。本例中,我们将创建total_views属性,它是已登录和匿名用户浏览量的总和。
通过models.Case() 和 models.When(),我们可以根据不同的条件返回值。使用models.Value()来标记这些值。本例中我们将创建SQL查询的label字段以及由QuerySet所返回的对象属性。如果浏览量大于500则会设置为popular,若在24小时之内创建则设置为new,其它情况为cool。
在视图的最后,我们调用了qs.update()方法。这些方法根据浏览视频的用户是否登录增加当前视频的authenticated_views和anonymous_views。这一增长不是在Python层面,而是在SQL层面。这解决了所谓竞态条件的问题,即两个访客同时使用该视图,又同步增长视频浏览量。
相关内容
- 使用Django shell一节
- 第2章 模型和数据库结构中的通过URL相关的方法创建模型mixin一节
- 第2章 模型和数据库结构中的创建模型mixin来处理创建和变更日期一节
slugify()的猴子补子改进国际化支持
猴子补丁(或称杂牌军补丁)是在运行时扩展或修改其它代码的一段代码。不推荐经常使用猴子补丁,有时在不创建模块的单独分支的情况下这是修改复杂第三方模块中bug的唯一途径。同时,猴子补丁还可用于在不使用复杂及耗时的数据库或文件操作的情况下,进行功能或单元测试。
本节中,我们将学习如何将默认的slugify()函数替换为第三方转译包中的那个,它更智能地处理了Unicode字符向ASCII对应字符的转化并包含很多语言包,甚至在需要时还有更多的具体转换。快速提醒一下,我们使用slugify() 来用对象标题或上传的文件名创建对用户更友好的版本。在处理时,该函数去除前后空格,将文本转化为小写,删除非字母数字字符,并将空格转化为中间杠。
准备工作
执行如下的小步骤:
- 在虚拟环境中安装transliterate如下:
1(env)$ pip install transliterate==1.10.2 - 然后在项目中创建guerrilla_patches应用并添加到配置文件的INSTALLED_APPS中。
如何实现…
在guerrilla_patches应用的models.py文件中,使用transliterate包中的slugify函数重写django.utils.text中的那个:
1 2 3 4 5 |
# myproject/apps/guerrilla_patches/models.py from django.utils import text from transliterate import slugify text.slugify = slugify |
实现原理…
默认的Django slugify() 函数对德语变音符号的处理有误。想要查看,可以试着对一个带所胡德语变音符号的长词进行url 转换。首先在Django shell中运行如下未打猴子补丁的代码:
1 2 3 4 |
(env)$ python manage.py shell >>> from django.utils.text import slugify >>> slugify("Heizölrückstoßabdämpfung") 'heizolruckstoabdampfung' |
这在德语中是不正确的,字母ß完全被去除了而没有用ss进行替换,而字母ä、ö和ü被转换成了a、o和u,实际上应该由ae、oe和ue来进行替换。
我们所创建的猴子补子在初始化时加载django.utils.text模块并对核心的slugify()函数重新赋值transliteration.slugify。这时如何在Django shell中运行同样的代码,会得到正确的结果,如下:
1 2 3 4 |
(env)$ python manage.py shell >>> from django.utils.text import slugify >>> slugify("Heizölrückstoßabdämpfung") 'heizoelrueckstossabdaempfung' |
ℹ️ 参见https://pypi.org/project/transliterate/来阅读更多有关如何使用transliterate模块的知识。
扩展知识…
在创建猴子补丁之前,我们需要完全明白希望修改后的代码如何运行。这可通过分析已有代码和查看各个变量的值来实现。有一个有用的内置Python调试器模块pdb,可以临时添加到Django代码(或第三方模块)中来在开发服务器上在任意断点处停止执行。使用如下代码来调试Python模块中不清楚部分的代码:
1 |
breakpoint() |
这会启动一个交互式shell,在其中可以输入变量名来查看值。如果输入 c 或continue,代码继承执行直至遇到下一个断点。如果输入q 或 quit,会停止管理命令。
ℹ️可以通过https://docs.python.org/3/library/pdb.html学习更多Python调试器命令以及如何查看代码回溯追踪。
另一种在开发服务器上快速查看变量值的方式是以变量作为消息抛出一个警告,如下:
1 |
raise Warning, some_variable |
在处于DEBUG模式时,Django日志器会为你提供回溯及其它本地变量。
💡在将代码提交到仓库前别忘了要删除调试代码。
如果使用PyCharm交互式开发环境,可以无需修改源代码直接用图形化界面设置断点及调试变量。
相关内容
- 使用Django shell一节
切换Debug工具栏
在使用Django进行开发时,可能会希望查看请求头部和参数,检查当前模板上下文,或度量SQL查询的性能。所有这些和更多功能都可通过Django调试工作栏实现。它是一个显示有关当前请求和响应各种调试信息的可配置面板集。本节中,我们将讲解如何根据由小书签设置的cookie值切换调试工具栏的可见性。小书签是一小段JavaScript代码的书签,可以在浏览的任意页面中运行。
准备工作
按照如下步骤来切换调试工具栏的可见性:
- 在虚拟环境中安装Django调试工具栏:
1(env)$ pip install django-debug-toolbar==2.1 - 在配置文件的INSTALLED_APPS中添加debug_toolbar:
12345# myproject/settings/_base.pyINSTALLED_APPS = [#..."debug_toolbar",]
如何实现…
按照如下步骤来配置Django调试工具栏,可使用浏览器的小标签来进行开关:
- 添加如下项目配置:
1234567891011121314151617181920# myproject/settings/_base.pyDEBUG_TOOLBAR_CONFIG = {"DISABLE_PANELS": [],"SHOW_TOOLBAR_CALLBACK": "myproject.apps.core.misc.custom_show_toolbar","SHOW_TEMPLATE_CONTEXT": True,}DEBUG_TOOLBAR_PANELS = ["debug_toolbar.panels.versions.VersionsPanel","debug_toolbar.panels.timer.TimerPanel","debug_toolbar.panels.settings.SettingsPanel","debug_toolbar.panels.headers.HeadersPanel","debug_toolbar.panels.request.RequestPanel","debug_toolbar.panels.sql.SQLPanel","debug_toolbar.panels.templates.TemplatesPanel","debug_toolbar.panels.staticfiles.StaticFilesPanel","debug_toolbar.panels.cache.CachePanel","debug_toolbar.panels.signals.SignalsPanel","debug_toolbar.panels.logging.LoggingPanel","debug_toolbar.panels.redirects.RedirectsPanel",] - 在core应用中,使用custom_show_toolbar() 函数创建一个misc.py文件,如下:
123# myproject/apps/core/misc.pydef custom_show_toolbar(request):return "1" == request.COOKIES.get("DebugToolbar", False) - 在项目的urls.py中添加如下配置规则:
123456789101112# myproject/urls.pyfrom django.conf.urls.i18n import i18n_patternsfrom django.urls import include, pathfrom django.conf import settingsimport debug_toolbarurlpatterns = i18n_patterns(#...)urlpatterns = [path('__debug__/', include(debug_toolbar.urls)),] + urlpatterns - 打开Chrome或Firefox浏览器并进入书签管理器。然后,新建包含JavaScript的两个书签。第一个急拉显示工具栏,类似下面这样:
TODO
JavaScript代码如下:
12javascript:(function(){document.cookie="DebugToolbar=1;path=/";location.reload();})(); - 第二段JavaScript链接会隐藏工具栏,效果类似下面这样:
TODO
完整的JavaScript代码如下:
12javascript:(function(){document.cookie="DebugToolbar=0;path=/";location.reload();})();
实现原理…
DEBUG_TOOLBAR_PANELS配置定义在工具栏中显示的面板。DEBUG_TOOLBAR_CONFIG字典定义工具栏的配置,包含用于查看是否显示工具样款的函数的路径。
默认,在浏览项目时,不会显示Django调试工具栏,但在点击Debug Toolbar On小书签时,DebugToolbar的cookie会设置为1,页面会进行刷新 ,就会看到带有调试面板的工具栏,例如,我们可以查看SQL语句的性能以供优化,如下图所示:
TODO
还能够查看当前视图的模板上下文变量,如下图所示:
TODO
点击第二个小书签Debug Toolbar Off,相应地会将DebugToolbar的设置为0并刷新页面,再次隐藏工具栏。
相关内容
- 第13章 维护中的通过email获取详细错误报告一节
使用ThreadLocalMiddleware
HttpRequest对象包含有关当前用户、语言、服务端变量、cookie、session等有用信息。事实上HttpRequest在视图和middleware中进行提供,可以将其(或其属性值)传递给表单、模型方法、模型管理器和模板等等。要进行简化,可以使用所谓的ThreadLocalMiddleware,它在全局可访问的Python线程中存储当前HttpRequest对象。因此,可以在模型方法、表单、信号处理器和其它那些此前不能直接访问到HttpRequest对象的地方访问它。本节中,我们将定义这一中间件。
准备工作
如未创建,请创建core应用并将其加到配置文件的INSTALLED_APPS中。
如何实现…
执行如下两步来配置ThreadLocalMiddleware,可用于在项目代码的任意函数或方法中获取当前HttpRequest或用户:
- 使用如下内容在core应用中添加middleware.py文件:
123456789101112131415161718192021222324252627282930# myproject/apps/core/middleware.pyfrom threading import local_thread_locals = local()def get_current_request():""":returns the HttpRequest object for this thread"""return getattr(_thread_locals, "request", None)def get_current_user():""":returns the current user if it exists or None otherwise """request = get_current_request()if request:return getattr(request, "user", None)class ThreadLocalMiddleware(object):"""Middleware to add the HttpRequest to thread local storage"""def __init__(self, get_response):self.get_response = get_responsedef __call__(self, request):_thread_locals.request = requestreturn self.get_response(request) - 将该中间件添加到配置文件的MIDDLEWARE中:
12345678910111213# myproject/settings/_base.pyMIDDLEWARE = ["django.middleware.security.SecurityMiddleware","django.contrib.sessions.middleware.SessionMiddleware","django.middleware.common.CommonMiddleware","django.middleware.csrf.CsrfViewMiddleware","django.contrib.auth.middleware.AuthenticationMiddleware","django.contrib.messages.middleware.MessageMiddleware","django.middleware.clickjacking.XFrameOptionsMiddleware","django.middleware.locale.LocaleMiddleware","debug_toolbar.middleware.DebugToolbarMiddleware","myproject.apps.core.middleware.ThreadLocalMiddleware",]
实现原理…
ThreadLocalMiddleware处理每个请求并存储当前线程中的HttpRequest对象。Django中的每个请求-响应环都是单线程的。我们创建了两个函数get_current_request() 和 get_current_user()。这两个函数可用在任意地方来分别获取当前HttpRequest对象或当前用户。
例如,可以使用中间件来开发、使用CreatorMixin,它会保存当前用户为模型对象的创建者,如下:
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 |
# myproject/apps/core/models.py from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ class CreatorBase(models.Model): """ ract base class with a creator """ creator = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_("creator"), editable=False, blank=True, null=True, on_delete=models.SET_NULL, ) class Meta: abstract = True def save(self, *args, **kwargs): from .middleware import get_current_user if not self.creator: self.creator = get_current_user() super().save(*args, **kwargs) save.alters_data = True |
相关内容
- 第2章 模型和数据库结构中的通过URL相关的方法创建模型mixin一节
- 第2章 模型和数据库结构中的创建模型mixin来处理创建和变更日期一节
- 第2章 模型和数据库结构中的创建模型mixin来处理元标签一节
- 第2章 模型和数据库结构中的创建模型mixin来处理通用关系一节
使用信号来通知管理员新条目
Django框架中有一个信号(signal)的概念,类似于JavaScript中的事件。有很多内置的信号。可以使用它们来在模型初始化、保存或删除实例、迁移数据库模式或处理请求等之前及之后触发一些动作。同时在可复用应用中可以创建自己的信号来在其它应用中进行处理。本节中我们学习如何使用信号来在保存指定模型时向管理员发送邮件。
准备工作
我们采用使用数据库查询表达式一节中创建的viral_videos应用。
如何实现…
按照如下步骤来为管理员创建通知:
- 使用如下内容创建文件signals.py:
123456789101112131415161718192021222324252627282930313233# myproject/apps/viral_videos/signals.pyfrom django.db.models.signals import post_savefrom django.dispatch import receiverfrom django.template.loader import render_to_stringfrom .models import ViralVideo@receiver(post_save, sender=ViralVideo)def inform_administrators(sender, **kwargs):from django.core.mail import mail_adminsinstance = kwargs["instance"]created = kwargs["created"]if created:context = {"title": instance.title, "link": instance.get_url()}subject = render_to_string("viral_videos/email/administrator/subject.txt", context)plain_text_message = render_to_string("viral_videos/email/administrator/message.txt", context)html_message = render_to_string("viral_videos/email/administrator/message.html",context)mail_admins(subject=subject.strip(),message=plain_text_message,html_message=html_message,fail_silently=True,) - 然后我们需要创建一些模型,先从模板的email主题开始:
12{# viral_videos/email/administrator/subject.txt #}New Viral Video Added - 然后创建一个普通文本消息模板,类似下面这样:
123{# viral_videos/email/administrator/message.txt #}A new viral video called "{{ title }}" has been created.You can preview it at {{ link }}. - 接着对HTML消息创建如下模板:
123{# viral_videos/email/administrator/message.html #}<p>A new viral video called "{{ title }}" has been created.</p><p>You can <a href="{{ link }}">preview it here</a>.</p> - 使用如下内容创建文件apps.py:
12345678910# myproject/apps/viral_videos/apps.pyfrom django.apps import AppConfigfrom django.utils.translation import ugettext_lazy as _class ViralVideosAppConfig(AppConfig):name = "myproject.apps.viral_videos"verbose_name = _("Viral Videos")def ready(self):from .signals import inform_administrators - 使用如下内容更新__init__.py文件:
12# myproject/apps/viral_videos/__init__.pydefault_app_config = "myproject.apps.viral_videos.apps.ViralVideosAppConfig"
确保在项目配置文件中像如下这样配置了ADMINS:
12# myproject/settings/_base.pyADMINS = [("Administrator", "admin@example.com")]
实现原理…
ViralVideosAppConfig配置类中有一个ready()方法,在所有项目模型加载到内存中的时候会被调用。根据Django官方文档,信号允许某些发送者通知一组接收者发生了某一动作。因此在ready()方法中,我们导入了inform_administrators()函数。
借助@receiver装饰器,对post_save信号注册了inform_administrators(),并且仅在发送者为ViralVideo模型时进行该信号的处理。因此,在进行ViralVideo对象保存时,会调用接收者函数。inform_administrators()函数查看视频是否为新建的。如是,它会向配置文件中ADMINS所列举的系统管理员发送邮件。
我们使用模型来生成subject、plain_text_message以及html_message的内容,这样可以在应用中对各自定义默认模板。如果把viral_videos应用对外开放,那些在自己项目中拉取它的可以按照需要对模型进行自定义,或许是将它们封装到公司邮件模板包装中去。
ℹ️可在官方文档中学习更多有关Django信号的知识。
相关内容
- 第1章 Django 3.0入门中的创建应用配置一节
- 使用数据库查询表达式一节
- 检测缺失的设置一节
检测缺失的设置
从Django 1.7开始,你可以使用可继承的系统检测框架,它替代掉了老的validate管理命令。本节中,我们将学习如何创建一个对ADMINS是否已配置的检测。类似地,我们可以检测密钥或所使用的API的连接token是否进行了设置。
准备工作
我们采用使用数据库查询表达式一节中创建并在前面小节中进行扩展了的viral_videos应用来开始我们的学习。
如何实现…
按照如下步骤来使用系统检测框架:
- 通过如下内容来创建文件checks.py:
123456789101112131415161718192021222324252627# myproject/apps/viral_videos/checks.pyfrom textwrap import dedentfrom django.core.checks import Warning, register, Tags@register(Tags.compatibility)def settings_check(app_configs, **kwargs):from django.conf import settingserrors = []if not settings.ADMINS:errors.append(Warning( dedent("""The system admins are not set in the project settings"""),obj=settings,hint=dedent("""In order to receive notifications when new videos are created, define system admins in your settings, like:ADMINS = (("Admin", "administrator@example.com"),)"""),id="viral_videos.W001",))return errors - 在应用配置的ready()方法中导入这一检测,如下:
1234567891011# myproject/apps/viral_videos/apps.pyfrom django.apps import AppConfigfrom django.utils.translation import ugettext_lazy as _class ViralVideosAppConfig(AppConfig):name = "myproject.apps.viral_videos"verbose_name = _("Viral Videos")def ready(self):from .signals import inform_administratorsfrom .checks import settings_check - 测试刚刚创建的检测,删除或注释掉ADMINS配置,然后在虚拟环境中运行check管理命令:
12345678910111213(env)$ python manage.py checkSystem check identified some issues:WARNINGS:<Settings "myproject.settings.dev">: (viral_videos.W001)The system admins are not set in the project settingsHINT:In order to receive notifications when new videos arecreated, define system admins in your settings, like:ADMINS = (("Admin", "administrator@example.com"),)System check identified 1 issue (0 silenced).
实现原理…
系统检测框架有一些模型、字段、数据库、管理认证配置、内容类型和安全配置的检测,如果在项目中未进行正确配置的话会报出错误或警告。此外,可以创建自有检测,类似于本节中所做的操作。
我们注册了settings_check()函数,如果在项目中未定义ADMINS配置的话会返回一个带有Warning的列表。
除django.core.checks模块中的Warning实例外,返回列表中还可包含Debug、Info、Error和Critical内置类或其它继承自django.core.checks.CheckMessage的类的实例。debug、info和warning级别的日志会静默略过,而error和critical级别的则会阻止项目的进一步执行。
本例中,通过传递给@register装饰器的Tags.compatibility参数来为检测添加兼容性检测的标记。Tags所包含的其它选项有:
- admin用于后台站点相关的检测
- caches用于服务端缓存相关的检测
- database用于数据库配置相关的检测
- models用于模型、模型字段或管理器相关的检测
- security用于安全相关的检测
- signals用于信号声明及处理器相关的检测
- staticfiles用于静态文件检测
- templates用于模板相关的检测
- translation用于字符串翻译相关的检测
- url用于URL配置相关的检测
ℹ️参见官方文档学习更多有关系统检测框架的知识。
相关内容
- 第1章 Django 3.0入门中的创建应用配置一节
- 使用数据库查询表达式一节
- 使用信号来通知管理员新条目一节