Django快速开发进阶

Django框架 / 2021-05-27

此处衔接我在个人wiki上面写的三篇Django快速开发的博客

一、Django集成Sentry

Sentry是一款开源的日志聚合系统,它支持很多平台,也可以监控Django运行产生的日志,其中最需要关注的就是自动上报未捕获的错误日志!使用Sentry服务有两种方式:

  1. SentryDocker官方服务,量大需要付费,优点是使用方便。
  2. 自建服务:使用Docker来部署Sentry,优点是免费,生产环境可以考虑使用官方收费服务。

1.1、Docker环境配置

已安装请跳过并直接开始1.2小节。

1.1.1、Docker安装

本次示例为ArchLinux系列的Manjaro21系统,其它系统请自行更换安装命令。

sudo pacman -S docker docker-compose  # 安装docker
sudo mkdir /etc/docker
sudo vim /etc/docker/daemon.json
1.1.2、配置加速服务

加入以下配置到/etc/docker/daemon.json,感谢中国科技大学提供的Docker镜像源免费加速服务,大家也可以使用国内其它加速服务。

{
  "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn/"]
}
1.1.3、配置Docker服务
sudo systemctl daemon-reload  # 重载系统服务配置
sudo systemctl enable docker
sudo systemctl restart docker
sudo usermod -a sanxi -G docker  # 将sanxi加入docker组,即可无须sudo也能执行docker命令,重新登陆才生效。

1.2、Sentry部署

本文以部署Sentry 21.4.1版本为例,先去GitHub下载Sentryon-premise版本,这也是官方推荐的部署方式!安装Sentry前请先确保本地docker服务正在运行及docker-compose已安装!

1.2.1、环境准备
wget https://github.com/getsentry/onpremise/archive/refs/tags/21.4.1.tar.gz  # 下载
tar -xvf 21.4.1.tar.gz  # 解压
cd onpremise-21.4.1/
bash install.sh  # 开始自动部署,耗时较长,请耐心等待
1.2.2、启动sentry服务

注意:必须保证在onpremise目录下,有docker-compose.yml文件,如果前面运行脚本失败这里是不会有此文件的。

docker-compose up -d  # -d,后台运行

1.3、Django集成sentry

1.3.1、登陆sentry

默认的sentry端口是9000,访问对应地址后,界面说实话还挺好看的。

添加sentry用户

如果在一开始执行install.sh时忘记了添加sentry用户,可以使用以下格式命令进行用户的添加:

[sanxi@sanxi-aero15sa onpremise-21.4.1]$ docker-compose run --rm web createuser --help
Creating sentry_onpremise_web_run ... done
Usage: sentry createuser [OPTIONS]

  Create a new user.

Options:
  --email TEXT
  --password TEXT
  --superuser / --no-superuser
  --no-password
  --no-input
  --force-update
  --help 
# 按照上面的选项添加用户
[sanxi@sanxi-aero15sa onpremise-21.4.1]$ docker-compose run --rm web createuser --email admin@qq.com --password admin --superuser
Creating sentry_onpremise_web_run ... done
08:17:09 [INFO] sentry.plugins.github: apps-not-configured
Added to organization: sentry
User created: admin@qq.com

第一次登陆成功后,会弹出一个配置后台窗口,我们是本地环境,直接默认即可。

1.3.2、创建project

图片已经弄丢了,反正都是图形界面,点两下就知道了。

1.3.3、settings配置

创建完project后,sentry会给出Configure Django信息,按照信息所说的来配置Django即可。

首先,是安装sentry的SDK。

pip install --upgrade sentry-sdk

接着,在Django项目的settings里面配置,一定要记得必须是Configure Django给的代码才行,因为里面含有你刚才创建的项目的密钥,这是最关键的信息,你也可以自己在sentry的setting里面查看每个project的密钥信息。

import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration

sentry_sdk.init(
    dsn="http://44b110ca269a468cbc54d97da336a35a@127.0.0.1:9000/3",
    integrations=[DjangoIntegration()],

    # Set traces_sample_rate to 1.0 to capture 100%
    # of transactions for performance monitoring.
    # We recommend adjusting this value in production.
    traces_sample_rate=1.0,  # 这个是日志采集率,1.0就是100%,生产环境要改,不然影响性能。

    # If you wish to associate users to errors (assuming you are using
    # django.contrib.auth) you may enable sending PII data.
    send_default_pii=True
)
1.3.4、测试sentry

配置完sentry后,可以在urls.py中添加以下代码进行测试sentry是否可用!然后访问sentry-debug,比如我服务是在本地,所以是访问:http://127.0.0.1:8000/sentry-debug/,因为Python中1不能除以0,所以一定会报错,这时候回到sentry就可以看到错误日志了。

from django.urls import path

def trigger_error(request):
    division_by_zero = 1 / 0

urlpatterns = [
    path('sentry-debug/', trigger_error),
    # ...
]

中间件捕获异常上报到钉钉

使用Django自定义中间件的process_exception

二、集成Rest Framework

Django Rest Framework,简称drf,基于Django框架进行二次开发,是对Rest API的一种实现!基础部分可以看我这篇博客:《RESTful与django》。

2.1、安装drf包

如果不是默认安装到当前用户家目录,则需要使用--user选项指定安装包到本地,这样就不存在无权限访问的错误。

 pip install djangorestframework

2.2、创建序列化器

为项目创建序列化器,建议单独模块的方式存放于utils目录。

from rest_framework import serializers
from interview.models import Candidate  # 导入表


class InterviewSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Candidate  # 将要序列化的表
        fields = ('name', 'gender', 'degree')  # 展示的字段

2.3、视图类

drf视图层的基础可以看我这篇博客:《Rest Framework视图层》:https://blog.sanxi.info/archives/6-6--r-e-s-t--f-r-a-m-e-w-o-r-k-shi-tu-lei, viewset是更进一步的封装。

from django.contrib.auth.models import User
from rest_framework import viewsets
from rest_framework import permissions
from utils.serializers import InterviewSerializer

# Create your views here.


class InterviewViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = Candidate.objects.all()
    serializer_class = InterviewSerializer
    permission_classes = [permissions.IsAuthenticated]

2.4、路由配置

urls.py中配置参考如下配置:

from rest_framework import routersfrom interview.views import InterviewViewSetrouter = routers.DefaultRouter()router.register(r'interview', InterviewViewSet)urlpatterns = [    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),    path('api/', include(router.urls)),]

2.5、注册drf

需要在settings里面将drf注册到APP中

INSTALLED_APPS = [    ...    'rest_framework',]

2.6、分页器

分页器可以看看我这篇博客:Django Rest Framework组件

REST_FRAMEWORK = {    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',    'PAGE_SIZE': 10  # 每页展示条数}

2.7、验证

这时候我们再去访问127.0.0.1:8000/api/interview/,就可以看到返回相应的数据了。

{
    "count": 5,
    "next": null,
    "previous": null,
    "results": [
        {
            "name": "张三",
            "gender": "男",
            "degree": "本科"
        },
        {
            "name": "刘倩",
            "gender": "女",
            "degree": "本科"
        },
        {
            "name": "陈欣欣",
            "gender": "女",
            "degree": "硕士"
        },
        {
            "name": "刘德华",
            "gender": "男",
            "degree": "硕士"
        },
        {
            "name": "李小龙",
            "gender": "男",
            "degree": "本科"
        }
    ]
}

三、Django集成redis

django-redis 基于 BSD 许可, 是一个使 Django 支持 Redis cache/session 后端的全功能组件。更多详情请参阅django-redis官方文档:https://django-redis-chs.readthedocs.io/zh_CN/latest/

3.1、安装

pip install django-redis  # 安装redis集成包
sudo pacman -S redis
systemctl start redis

3.2、作为缓存的配置

CACHES = {    "default": {        "BACKEND": "django_redis.cache.RedisCache",        "LOCATION": "redis://127.0.0.1:6379/1",        "TIMEOUT": 300,        "OPTIONS": {            "CLIENT_CLASS": "django_redis.client.DefaultClient",            "SOCKET_CONNECT_TIMEOUT": 5,  # 创建redis连接超时时间            "SOCKET_TIMEOUT": 5,  # 读写超时时间        }    }}CACHE_MIDDLEWARE_SECONDS = 600

3.3、整站缓存

在common中间件前后各加一个中间件,启用后访问时看页面的response headers中的cache-control就可以判定是否缓存成功,也可以加个日志做。

MIDDLEWARE = [    'django.middleware.security.SecurityMiddleware',    'django.contrib.sessions.middleware.SessionMiddleware',    'django.middleware.locale.LocaleMiddleware',    'django.middleware.cache.UpdateCacheMiddleware',  # common前面    'django.middleware.common.CommonMiddleware',    'django.middleware.cache.FetchFromCacheMiddleware',  # common后面    'django.middleware.csrf.CsrfViewMiddleware',    'django.contrib.auth.middleware.AuthenticationMiddleware',    'django.contrib.messages.middleware.MessageMiddleware',    'django.middleware.clickjacking.XFrameOptionsMiddleware',]

3.4、验证

当我们访问后台时,页面返回的response headers如下所示:

HTTP/1.1 200 OK
Date: Fri, 14 May 2021 06:22:58 GMT
Server: WSGIServer/0.2 CPython/3.9.4
Content-Type: text/html; charset=utf-8
Vary: Accept, Cookie, Accept-Language
Allow: GET, POST, HEAD, OPTIONS
X-Frame-Options: SAMEORIGIN
Content-Length: 10428
Expires: Fri, 14 May 2021 06:30:15 GMT
Cache-Control: max-age=600
Content-Language: zh-hans

四、Django集成celery

参考celery官方文档:https://docs.celeryproject.org/en/stable/django/first-steps-with-django.html

celery是一种基于Python开发的分布式消息队列,在**celery4.0+**开始集成Django了,它可以很方便地在Django中执行异步任务。

pip install celery

4.1、celery异步任务

本次目的为学习如何在Django项目中使用celery来执行异步任务

4.1.1、目录结构
[sanxi@sanxi-aero15sa ~]$ tree recruitment2/ -I static
recruitment2/
├── db.sqlite3
├── interview
├── manage.py
├── recruitment  # 项目根目录,也是默认的settings.py所在目录,我这里因为删了,所以没有。
│   ├── celery.py  # celery必须放在项目根目录
│   ├── __init__.py  # 在此处导入celery的app
│   ├── urls.py
│   └── wsgi.py
└── utils
4.1.2、celery异步执行任务

在项目根目录下创建名为celery.py的文件,代码如下所示:

import osfrom celery import Celery# set the default Django settings module for the 'celery' program.os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.base_settings')app = Celery('recruitment')# Using a string here means the worker doesn't have to serialize# the configuration object to child processes.# - namespace='CELERY' means all celery-related configuration keys#   should have a `CELERY_` prefix.# namespace固定了settings配置格式必须是CELERY开头app.config_from_object('django.conf:settings', namespace='CELERY')# Load task modules from all registered Django app configs.app.autodiscover_tasks()@app.task(bind=True)def debug_task(self):    print(f'Request: {self.request!r}')
4.1.3、导入app

celery.py同目录下的__init__.py中导入上面创建的celery模块中celery对象:app

# This will make sure the app is always imported when# Django starts so that shared_task will use this app.from .celery import app as celery_app__all__ = ('celery_app',)
4.1.4、settings配置

backend是异步任务运行结果存储位置,broker是存储任务的系统的代理,也是一个消息队列。

# Celery Configuration Options
CELERY_TIMEZONE = "Asia/Shanghai"
CELERY_TASK_TRACK_STARTED = True
CELERY_TASK_TIME_LIMIT = 30 * 60
CELERY_BROKER_URL = "redis://localhost:6379/0"
CELERY_RESULT_BACKEND = "redis://localhost:6379/1"
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_RESULT_SERIALIZER = 'json'
CELERYD_MAX_TASKS_PER_CHILD = 10
CELERYD_LOG_FILE = os.path.join(BASE_DIR, "logs", "celery_worker.log")
CELERYBEAT_LOG_FILE = os.path.join(BASE_DIR, "logs", "celery_beat.log")
4.1.5、创建tasks

在Django的app下面创建tasks.py,在这里编写需要执行的异步任务。

# Create your tasks here
from celery import shared_task
from dingtalkchatbot.chatbot import DingtalkChatbot  # 导入钉钉官方SDK
from settings.base_settings import WEBHOOK, SECRET  # 自定义的配置项,可以放在任何地方。


@shared_task  # 共享任务,用以异步执行。
def send_dingtalk_message(message):
    xiaoding = DingtalkChatbot(WEBHOOK, secret=SECRET, pc_slide=True)
    xiaoding.send_text(msg=message, is_at_all=True)
4.1.6、修改代码为异步执行

再次提醒,异步任务不要滥用,只应该在产生I/O操作的地方使用异步任务,我们的项目中发送消息到钉钉会产生网络I/O,可以在这里改一改原来的代码,将它从同步执行改为celery异步执行。

from .tasks import send_dingtalk_message  # 导入tasks


def first_interview(modelsadmin, request, queryset):
    for field in queryset:
        seeker_list.append(field.name)
        interviewer_list.append(field.first_interviewer_user.username)
    send_dingtalk_message.delay(f'候选人 {seeker_list} 已到达,请第一轮面试官 {interviewer_list} 做好准备!')
4.1.7、启动Redis
sudo systemctl atart redis
4.1.8、启动celery

celery命令用法:celery [OPTIONS] COMMAND [ARGS]...

  • OPTIONS:-A或--app,指定Django的app,也就是默认settings.py所在目录

注意,如果Django不是用默认的settings.py,需要使用选项指定setting文件DJANGO_SETTINGS_MODULE=your settings

[sanxi@sanxi-aero15sa recruitment2]$ celery -A recruitment worker -l info
 
 -------------- celery@sanxi-aero15sa v5.0.5 (singularity)
--- ***** ----- 
-- ******* ---- Linux-5.10.34-1-MANJARO-x86_64-with-glibc2.33 2021-05-16 22:14:07
- *** --- * --- 
- ** ---------- [config]
- ** ---------- .> app:         recruitment:0x7f6dfc8ba7f0
- ** ---------- .> transport:   redis://localhost:6379/0
- ** ---------- .> results:     redis://localhost:6379/1
- *** --- * --- .> concurrency: 12 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery
                

[tasks]
  . interview.tasks.send_dingtalk_message
  . recruitment.celery.debug_task

[2021-05-16 22:14:07,546: INFO/MainProcess] Connected to redis://localhost:6379/0
[2021-05-16 22:14:07,551: INFO/MainProcess] mingle: searching for neighbors
[2021-05-16 22:14:08,563: INFO/MainProcess] mingle: all alone
[2021-05-16 22:14:08,569: WARNING/MainProcess] /home/sanxi/.local/lib/python3.9/site-packages/celery/fixups/django.py:203: UserWarning: Using settings.DEBUG leads to a memory
            leak, never use this setting in production environments!
  warnings.warn('''Using settings.DEBUG leads to a memory

[2021-05-16 22:14:08,569: INFO/MainProcess] celery@sanxi-aero15sa ready.
4.1.10、flower监控
pip install flower
celery flower --broker=redis://@localhost:6379/0  # 接着启动flower服务,默认5555端口。
4.1.11、验证

此时可以去后台系统,点击通知面试官按钮即可在钉钉群收到对应消息。

4.2、celery定时任务

4.2.1、定时任务简述

celery定时任务由三部分组成:心跳管理、调度器、存储

  • beat:调度进程,用于管理和调度定时任务。
  • scheduler:调度器,查找任务并检测任务的执行状态,如到点执行,则scheduler执行此任务。
  • storage:存储任务的执行状态,有两种方式
    • 文件形式存储,也是默认的方式;Default Persistenstorage。
    • 数据库存储,将任务执行状态信息存储到数据库中。

图片来源于吕召刚老师

image-20210513221418940

4.2.2、环境准备

安装beat包

pip install django-celery-beat

注册

INSTALLED_APPS = [
    'django_celery_beat'
]

数据库迁移

python manage.py makemigrations
python manage.py migrate
4.2.3、Periodic Tasks

此时再打开后台管理界面,会发现多了一个Periodic Tasks界面,此处即为celery的周期任务管理界面,它有以下模块:

  • Intervals,用于管理任务运行的周期。
  • Crontab,用crontab的格式来定期执行任务。
  • Periodic tasks,具体要执行的任务列表。
  • Solar events,根据当地日出日落时间来执行任务,这个跟智能家具可以结合使用,比如日落时自动打开灯之前的。
  • Clocked,跟Intervals有点类似,在执行时间执行任务。
4.2.4、添加定时任务

其实最方便的是在web界面添加,比代码方便多了。

管理celery定时任务有几种方式:

  • 启动时自动注册定时任务
  • 直接添加定时任务
  • 动态添加定时任务
自动注册

调用DJango的信号添加定时任务,这是代码运行时自动注册定时任务

from celery.schedules import crontab


@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    # Calls test('hello') every 10 seconds.
    sender.add_periodic_task(10.0, test.s('hello'), name='add every 10')

    # Calls test('world') every 30 seconds
    sender.add_periodic_task(30.0, test.s('world'), expires=10)

    # Executes every Monday morning at 7:30 a.m.
    sender.add_periodic_task(
        crontab(hour=7, minute=30, day_of_week=1),
        test.s('Happy Mondays!'),
    )
    
    
@app.task
def test(args):
    print(args)
世间微尘里 独爱茶酒中