1660934940
Lorsque vous créez et mettez à l'échelle une application Django, vous devrez inévitablement exécuter certaines tâches périodiquement et automatiquement en arrière-plan.
Quelques exemples:
C'est l'une des rares fonctionnalités requises pour créer et faire évoluer une application Web qui ne fait pas partie du cœur de Django. Heureusement, Celery fournit une solution puissante et assez facile à mettre en œuvre appelée Celery Beat.
Dans l'article suivant, nous allons vous montrer comment configurer Django, Celery et Redis avec Docker afin d'exécuter régulièrement une commande Django Admin personnalisée avec Celery Beat.
À la fin de ce didacticiel, vous devriez être en mesure de :
Clonez le projet de base à partir du référentiel django-celery-beat , puis consultez la branche de base :
$ git clone https://github.com/testdrivenio/django-celery-beat --branch base --single-branch
$ cd django-celery-beat
Étant donné que nous devrons gérer quatre processus au total (Django, Redis, travailleur et planificateur), nous utiliserons Docker pour simplifier notre flux de travail en les connectant afin qu'ils puissent tous être exécutés à partir d'une fenêtre de terminal avec une seule commande .
À partir de la racine du projet, créez les images et lancez les conteneurs Docker :
$ docker-compose up -d --build
Ensuite, appliquez les migrations :
$ docker-compose exec web python manage.py migrate
Une fois la construction terminée, accédez à http://localhost:1337 pour vous assurer que l'application fonctionne comme prévu. Vous devriez voir le texte suivant :
Orders
No orders found!
Jetez un coup d'œil à la structure du projet avant de continuer :
├── .gitignore
├── docker-compose.yml
└── project
├── Dockerfile
├── core
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── entrypoint.sh
├── manage.py
├── orders
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── products.json
├── requirements.txt
└── templates
└── orders
└── order_list.html
Vous voulez apprendre à construire ce projet ? Consultez l' article de blog Dockerizing Django avec Postgres, Gunicorn et Nginx .
Maintenant, nous devons ajouter des conteneurs pour Celery, Celery Beat et Redis.
Nous allons commencer par ajouter les dépendances au fichier requirements.txt :
Django==3.2.4
celery==5.1.2
redis==3.5.3
Ensuite, ajoutez ce qui suit à la fin du fichier docker-compose.yml :
redis:
image: redis:alpine
celery:
build: ./project
command: celery -A core worker -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
celery-beat:
build: ./project
command: celery -A core beat -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
Nous devons également mettre à jour la section du service Webdepends_on
:
web:
build: ./project
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./project/:/usr/src/app/
ports:
- 1337:8000
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis # NEW
Le fichier docker-compose.yml complet devrait maintenant ressembler à ceci :
version: '3.8'
services:
web:
build: ./project
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./project/:/usr/src/app/
ports:
- 1337:8000
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
redis:
image: redis:alpine
celery:
build: ./project
command: celery -A core worker -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
celery-beat:
build: ./project
command: celery -A core beat -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
Avant de créer les nouveaux conteneurs, nous devons configurer Celery dans notre application Django.
Dans le répertoire "core", créez un fichier celery.py et ajoutez le code suivant :
import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
app = Celery("core")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()
Qu'est-ce qu'il se passe ici?
DJANGO_SETTINGS_MODULE
variable d'environnement afin que Celery sache comment trouver le projet Django.core
, et attribué la valeur à une variable appelée app
.django.conf
. Nous avions l'habitude namespace="CELERY"
d'empêcher les conflits avec d'autres paramètres de Django. CELERY_
En d'autres termes, tous les paramètres de configuration de Celery doivent être précédés de .app.autodiscover_tasks()
indique à Celery de rechercher des tâches Celery à partir d'applications définies dans settings.INSTALLED_APPS
.Ajoutez le code suivant à core/__init__.py :
from .celery import app as celery_app
__all__ = ("celery_app",)
Enfin, mettez à jour le fichier core/settings.py avec les paramètres Celery suivants afin qu'il puisse se connecter à Redis :
CELERY_BROKER_URL = "redis://redis:6379"
CELERY_RESULT_BACKEND = "redis://redis:6379"
Construisez les nouveaux conteneurs pour vous assurer que tout fonctionne :
$ docker-compose up -d --build
Jetez un œil aux journaux de chaque service pour voir qu'ils sont prêts, sans erreur :
$ docker-compose logs 'web'
$ docker-compose logs 'celery'
$ docker-compose logs 'celery-beat'
$ docker-compose logs 'redis'
Si tout s'est bien passé, nous avons maintenant quatre conteneurs, chacun avec des services différents.
Nous sommes maintenant prêts à créer un exemple de tâche pour voir si cela fonctionne comme il se doit.
Créez un nouveau fichier appelé core/tasks.py et ajoutez le code suivant pour un exemple de tâche qui se connecte simplement à la console :
from celery import shared_task
from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
@shared_task
def sample_task():
logger.info("The sample task just ran.")
À la fin de votre fichier settings.py , ajoutez le code suivant pour planifier sample_task
une exécution une fois par minute, en utilisant Celery Beat :
CELERY_BEAT_SCHEDULE = {
"sample_task": {
"task": "core.tasks.sample_task",
"schedule": crontab(minute="*/1"),
},
}
Ici, nous avons défini une tâche périodique à l'aide du paramètre CELERY_BEAT_SCHEDULE . Nous avons donné un nom à la tâche, sample_task
, puis avons déclaré deux paramètres :
task
déclare quelle tâche exécuter.schedule
définit l'intervalle d'exécution de la tâche. Il peut s'agir d'un entier, d'un timedelta ou d'un crontab. Nous avons utilisé un modèle crontab pour notre tâche pour lui dire de s'exécuter une fois par minute. Vous pouvez trouver plus d'informations sur la programmation de Celery ici .Assurez-vous d'ajouter les importations :
from celery.schedules import crontab
import core.tasks
Redémarrez le conteneur pour extraire les nouveaux paramètres :
$ docker-compose up -d --build
Une fois cela fait, jetez un œil aux bûches de céleri dans le conteneur :
$ docker-compose logs -f 'celery'
Vous devriez voir quelque chose de similaire à :
celery_1 | -------------- [queues]
celery_1 | .> celery exchange=celery(direct) key=celery
celery_1 |
celery_1 |
celery_1 | [tasks]
celery_1 | . core.tasks.sample_task
Nous pouvons voir que Celery a récupéré notre exemple de tâche, core.tasks.sample_task
.
Chaque minute, vous devriez voir une ligne dans le journal qui se termine par "L'exemple de tâche vient d'être exécuté." :
celery_1 | [2021-07-01 03:06:00,003: INFO/MainProcess]
Task core.tasks.sample_task[b8041b6c-bf9b-47ce-ab00-c37c1e837bc7] received
celery_1 | [2021-07-01 03:06:00,004: INFO/ForkPoolWorker-8]
core.tasks.sample_task[b8041b6c-bf9b-47ce-ab00-c37c1e837bc7]:
The sample task just ran.
Django fournit un certain nombre de django-admin
commandes intégrées, telles que :
migrate
startproject
startapp
dumpdata
makemigrations
En plus des commandes intégrées, Django nous donne également la possibilité de créer nos propres commandes personnalisées :
Les commandes de gestion personnalisées sont particulièrement utiles pour l'exécution de scripts autonomes ou pour les scripts exécutés périodiquement à partir de la crontab UNIX ou du panneau de configuration des tâches planifiées de Windows.
Donc, nous allons d'abord configurer une nouvelle commande, puis utiliser Celery Beat pour l'exécuter automatiquement.
Commencez par créer un nouveau fichier appelé orders/management/commands/my_custom_command.py . Ensuite, ajoutez le code minimal requis pour qu'il s'exécute :
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
help = "A description of the command"
def handle(self, *args, **options):
pass
Le BaseCommand
a quelques méthodes qui peuvent être remplacées, mais la seule méthode requise est handle
. handle
est le point d'entrée des commandes personnalisées. En d'autres termes, lorsque nous exécutons la commande, cette méthode est appelée.
Pour tester, nous ajoutons normalement une déclaration d'impression rapide. Cependant, il est recommandé d'utiliser à stdout.write
la place selon la documentation de Django :
Lorsque vous utilisez des commandes de gestion et souhaitez fournir une sortie de console, vous devez écrire dans self.stdout et self.stderr, au lieu d'imprimer directement dans stdout et stderr. En utilisant ces proxys, il devient beaucoup plus facile de tester votre commande personnalisée. Notez également que vous n'avez pas besoin de terminer les messages avec un caractère de nouvelle ligne, il sera ajouté automatiquement, sauf si vous spécifiez le paramètre de fin.
Alors, ajoutez une self.stdout.write
commande :
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
help = "A description of the command"
def handle(self, *args, **options):
self.stdout.write("My sample command just ran.") # NEW
Pour tester, depuis la ligne de commande, exécutez :
$ docker-compose exec web python manage.py my_custom_command
Tu devrais voir:
My sample command just ran.
Sur ce, lions le tout ensemble !
Maintenant que nous avons lancé les conteneurs, testé que nous pouvions programmer une tâche à exécuter périodiquement et écrit un exemple de commande personnalisée Django Admin, il est temps de configurer Celery Beat pour exécuter la commande personnalisée périodiquement.
Dans le projet, nous avons une application très basique appelée commandes. Il contient deux modèles, Product
et Order
. Créons une commande personnalisée qui envoie un rapport par e-mail des commandes confirmées de la journée.
Pour commencer, nous allons ajouter quelques produits et commandes à la base de données via le luminaire inclus dans ce projet :
$ docker-compose exec web python manage.py loaddata products.json
Ensuite, ajoutez quelques exemples de commandes via l'interface Django Admin. Pour ce faire, créez d'abord un superutilisateur :
$ docker-compose exec web python manage.py createsuperuser
Remplissez le nom d'utilisateur, l'e-mail et le mot de passe lorsque vous y êtes invité. Accédez ensuite à http://127.0.0.1:1337/admin dans votre navigateur Web. Connectez-vous avec le superutilisateur que vous venez de créer et créez quelques commandes. Assurez-vous qu'au moins un a un confirmed_date
d'aujourd'hui.
Créons une nouvelle commande personnalisée pour notre rapport par e-mail.
Créez un fichier nommé orders/management/commands/email_report.py :
from datetime import timedelta, time, datetime
from django.core.mail import mail_admins
from django.core.management import BaseCommand
from django.utils import timezone
from django.utils.timezone import make_aware
from orders.models import Order
today = timezone.now()
tomorrow = today + timedelta(1)
today_start = make_aware(datetime.combine(today, time()))
today_end = make_aware(datetime.combine(tomorrow, time()))
class Command(BaseCommand):
help = "Send Today's Orders Report to Admins"
def handle(self, *args, **options):
orders = Order.objects.filter(confirmed_date__range=(today_start, today_end))
if orders:
message = ""
for order in orders:
message += f"{order} \n"
subject = (
f"Order Report for {today_start.strftime('%Y-%m-%d')} "
f"to {today_end.strftime('%Y-%m-%d')}"
)
mail_admins(subject=subject, message=message, html_message=None)
self.stdout.write("E-mail Report was sent.")
else:
self.stdout.write("No orders confirmed today.")
Dans le code, nous avons interrogé la base de données pour les commandes avec un confirmed_date
d'aujourd'hui, combiné les commandes en un seul message pour le corps de l'e-mail et utilisé la mail_admins
commande intégrée de Django pour envoyer les e-mails aux administrateurs.
Ajoutez un e-mail d'administrateur factice et configurez le EMAIL_BACKEND
pour qu'il utilise le backend de la console afin que l'e-mail soit envoyé à stdout, dans le fichier de paramètres :
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
DEFAULT_FROM_EMAIL = "noreply@email.com"
ADMINS = [("testuser", "test.user@email.com"), ]
Il devrait maintenant être possible d'exécuter notre nouvelle commande depuis le terminal.
$ docker-compose exec web python manage.py email_report
Et la sortie devrait ressembler à ceci :
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Django] Order Report for 2021-07-01 to 2021-07-02
From: root@localhost
To: test.user@email.com
Date: Thu, 01 Jul 2021 12:15:50 -0000
Message-ID: <162514175053.40.5705892371538583115@5140844ebb15>
Order: 3947963f-1860-44d1-9b9a-4648fed04581 - product: Coffee
Order: ff449e6e-3dfd-48a8-9d5c-79a145d08253 - product: Rice
-------------------------------------------------------------------------------
E-mail Report was sent.
Nous devons maintenant créer une tâche périodique pour exécuter cette commande quotidiennement.
Ajoutez une nouvelle tâche à core/tasks.py :
from celery import shared_task
from celery.utils.log import get_task_logger
from django.core.management import call_command # NEW
logger = get_task_logger(__name__)
@shared_task
def sample_task():
logger.info("The sample task just ran.")
# NEW
@shared_task
def send_email_report():
call_command("email_report", )
Donc, nous avons d'abord ajouté une call_command
importation, qui est utilisée pour appeler par programme les commandes django-admin. Dans la nouvelle tâche, nous avons ensuite utilisé le call_command
avec le nom de notre commande personnalisée comme argument.
Pour planifier cette tâche, ouvrez le fichier core/settings.py et mettez à jour le CELERY_BEAT_SCHEDULE
paramètre pour inclure la nouvelle tâche :
CELERY_BEAT_SCHEDULE = {
"sample_task": {
"task": "core.tasks.sample_task",
"schedule": crontab(minute="*/1"),
},
"send_email_report": {
"task": "core.tasks.send_email_report",
"schedule": crontab(hour="*/1"),
},
}
Ici, nous avons ajouté une nouvelle entrée au fichier CELERY_BEAT_SCHEDULE
appelé send_email_report
. Comme nous l'avons fait pour notre tâche précédente, nous avons déclaré la tâche à exécuter -- par exemple, core.tasks.send_email_report
-- et utilisé un modèle crontab pour définir la récurrence.
Redémarrez les conteneurs pour vous assurer que les nouveaux paramètres deviennent actifs :
$ docker-compose up -d --build
Ouvrez les journaux associés au celery
service :
$ docker-compose logs -f 'celery'
Vous devriez voir la send_email_report
liste :
celery_1 | -------------- [queues]
celery_1 | .> celery exchange=celery(direct) key=celery
celery_1 |
celery_1 |
celery_1 | [tasks]
celery_1 | . core.tasks.sample_task
celery_1 | . core.tasks.send_email_report
Environ une minute plus tard, vous devriez voir que le rapport par e-mail est envoyé :
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] Content-Type: text/plain; charset="utf-8"
celery_1 | MIME-Version: 1.0
celery_1 | Content-Transfer-Encoding: 7bit
celery_1 | Subject: [Django] Order Report for 2021-07-01 to 2021-07-02
celery_1 | From: root@localhost
celery_1 | To: test.user@email.com
celery_1 | Date: Thu, 01 Jul 2021 12:22:00 -0000
celery_1 | Message-ID: <162514212006.17.6080459299558356876@55b9883c5414>
celery_1 |
celery_1 | Order: 3947963f-1860-44d1-9b9a-4648fed04581 - product: Coffee
celery_1 | Order: ff449e6e-3dfd-48a8-9d5c-79a145d08253 - product: Rice
celery_1 |
celery_1 |
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] -------------------------------------------------------------------------------
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8]
celery_1 |
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] E-mail Report was sent.
Dans cet article, nous vous avons guidé dans la configuration des conteneurs Docker pour Celery, Celery Beat et Redis. Nous avons ensuite montré comment créer une commande Django Admin personnalisée et une tâche périodique avec Celery Beat pour exécuter cette commande automatiquement.
Vous en voulez plus ?
Récupérez le code du repo .
Source : https://testdrive.io
#django #celery #docker #redis
1660934940
Lorsque vous créez et mettez à l'échelle une application Django, vous devrez inévitablement exécuter certaines tâches périodiquement et automatiquement en arrière-plan.
Quelques exemples:
C'est l'une des rares fonctionnalités requises pour créer et faire évoluer une application Web qui ne fait pas partie du cœur de Django. Heureusement, Celery fournit une solution puissante et assez facile à mettre en œuvre appelée Celery Beat.
Dans l'article suivant, nous allons vous montrer comment configurer Django, Celery et Redis avec Docker afin d'exécuter régulièrement une commande Django Admin personnalisée avec Celery Beat.
À la fin de ce didacticiel, vous devriez être en mesure de :
Clonez le projet de base à partir du référentiel django-celery-beat , puis consultez la branche de base :
$ git clone https://github.com/testdrivenio/django-celery-beat --branch base --single-branch
$ cd django-celery-beat
Étant donné que nous devrons gérer quatre processus au total (Django, Redis, travailleur et planificateur), nous utiliserons Docker pour simplifier notre flux de travail en les connectant afin qu'ils puissent tous être exécutés à partir d'une fenêtre de terminal avec une seule commande .
À partir de la racine du projet, créez les images et lancez les conteneurs Docker :
$ docker-compose up -d --build
Ensuite, appliquez les migrations :
$ docker-compose exec web python manage.py migrate
Une fois la construction terminée, accédez à http://localhost:1337 pour vous assurer que l'application fonctionne comme prévu. Vous devriez voir le texte suivant :
Orders
No orders found!
Jetez un coup d'œil à la structure du projet avant de continuer :
├── .gitignore
├── docker-compose.yml
└── project
├── Dockerfile
├── core
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── entrypoint.sh
├── manage.py
├── orders
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── products.json
├── requirements.txt
└── templates
└── orders
└── order_list.html
Vous voulez apprendre à construire ce projet ? Consultez l' article de blog Dockerizing Django avec Postgres, Gunicorn et Nginx .
Maintenant, nous devons ajouter des conteneurs pour Celery, Celery Beat et Redis.
Nous allons commencer par ajouter les dépendances au fichier requirements.txt :
Django==3.2.4
celery==5.1.2
redis==3.5.3
Ensuite, ajoutez ce qui suit à la fin du fichier docker-compose.yml :
redis:
image: redis:alpine
celery:
build: ./project
command: celery -A core worker -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
celery-beat:
build: ./project
command: celery -A core beat -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
Nous devons également mettre à jour la section du service Webdepends_on
:
web:
build: ./project
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./project/:/usr/src/app/
ports:
- 1337:8000
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis # NEW
Le fichier docker-compose.yml complet devrait maintenant ressembler à ceci :
version: '3.8'
services:
web:
build: ./project
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./project/:/usr/src/app/
ports:
- 1337:8000
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
redis:
image: redis:alpine
celery:
build: ./project
command: celery -A core worker -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
celery-beat:
build: ./project
command: celery -A core beat -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
Avant de créer les nouveaux conteneurs, nous devons configurer Celery dans notre application Django.
Dans le répertoire "core", créez un fichier celery.py et ajoutez le code suivant :
import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
app = Celery("core")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()
Qu'est-ce qu'il se passe ici?
DJANGO_SETTINGS_MODULE
variable d'environnement afin que Celery sache comment trouver le projet Django.core
, et attribué la valeur à une variable appelée app
.django.conf
. Nous avions l'habitude namespace="CELERY"
d'empêcher les conflits avec d'autres paramètres de Django. CELERY_
En d'autres termes, tous les paramètres de configuration de Celery doivent être précédés de .app.autodiscover_tasks()
indique à Celery de rechercher des tâches Celery à partir d'applications définies dans settings.INSTALLED_APPS
.Ajoutez le code suivant à core/__init__.py :
from .celery import app as celery_app
__all__ = ("celery_app",)
Enfin, mettez à jour le fichier core/settings.py avec les paramètres Celery suivants afin qu'il puisse se connecter à Redis :
CELERY_BROKER_URL = "redis://redis:6379"
CELERY_RESULT_BACKEND = "redis://redis:6379"
Construisez les nouveaux conteneurs pour vous assurer que tout fonctionne :
$ docker-compose up -d --build
Jetez un œil aux journaux de chaque service pour voir qu'ils sont prêts, sans erreur :
$ docker-compose logs 'web'
$ docker-compose logs 'celery'
$ docker-compose logs 'celery-beat'
$ docker-compose logs 'redis'
Si tout s'est bien passé, nous avons maintenant quatre conteneurs, chacun avec des services différents.
Nous sommes maintenant prêts à créer un exemple de tâche pour voir si cela fonctionne comme il se doit.
Créez un nouveau fichier appelé core/tasks.py et ajoutez le code suivant pour un exemple de tâche qui se connecte simplement à la console :
from celery import shared_task
from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
@shared_task
def sample_task():
logger.info("The sample task just ran.")
À la fin de votre fichier settings.py , ajoutez le code suivant pour planifier sample_task
une exécution une fois par minute, en utilisant Celery Beat :
CELERY_BEAT_SCHEDULE = {
"sample_task": {
"task": "core.tasks.sample_task",
"schedule": crontab(minute="*/1"),
},
}
Ici, nous avons défini une tâche périodique à l'aide du paramètre CELERY_BEAT_SCHEDULE . Nous avons donné un nom à la tâche, sample_task
, puis avons déclaré deux paramètres :
task
déclare quelle tâche exécuter.schedule
définit l'intervalle d'exécution de la tâche. Il peut s'agir d'un entier, d'un timedelta ou d'un crontab. Nous avons utilisé un modèle crontab pour notre tâche pour lui dire de s'exécuter une fois par minute. Vous pouvez trouver plus d'informations sur la programmation de Celery ici .Assurez-vous d'ajouter les importations :
from celery.schedules import crontab
import core.tasks
Redémarrez le conteneur pour extraire les nouveaux paramètres :
$ docker-compose up -d --build
Une fois cela fait, jetez un œil aux bûches de céleri dans le conteneur :
$ docker-compose logs -f 'celery'
Vous devriez voir quelque chose de similaire à :
celery_1 | -------------- [queues]
celery_1 | .> celery exchange=celery(direct) key=celery
celery_1 |
celery_1 |
celery_1 | [tasks]
celery_1 | . core.tasks.sample_task
Nous pouvons voir que Celery a récupéré notre exemple de tâche, core.tasks.sample_task
.
Chaque minute, vous devriez voir une ligne dans le journal qui se termine par "L'exemple de tâche vient d'être exécuté." :
celery_1 | [2021-07-01 03:06:00,003: INFO/MainProcess]
Task core.tasks.sample_task[b8041b6c-bf9b-47ce-ab00-c37c1e837bc7] received
celery_1 | [2021-07-01 03:06:00,004: INFO/ForkPoolWorker-8]
core.tasks.sample_task[b8041b6c-bf9b-47ce-ab00-c37c1e837bc7]:
The sample task just ran.
Django fournit un certain nombre de django-admin
commandes intégrées, telles que :
migrate
startproject
startapp
dumpdata
makemigrations
En plus des commandes intégrées, Django nous donne également la possibilité de créer nos propres commandes personnalisées :
Les commandes de gestion personnalisées sont particulièrement utiles pour l'exécution de scripts autonomes ou pour les scripts exécutés périodiquement à partir de la crontab UNIX ou du panneau de configuration des tâches planifiées de Windows.
Donc, nous allons d'abord configurer une nouvelle commande, puis utiliser Celery Beat pour l'exécuter automatiquement.
Commencez par créer un nouveau fichier appelé orders/management/commands/my_custom_command.py . Ensuite, ajoutez le code minimal requis pour qu'il s'exécute :
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
help = "A description of the command"
def handle(self, *args, **options):
pass
Le BaseCommand
a quelques méthodes qui peuvent être remplacées, mais la seule méthode requise est handle
. handle
est le point d'entrée des commandes personnalisées. En d'autres termes, lorsque nous exécutons la commande, cette méthode est appelée.
Pour tester, nous ajoutons normalement une déclaration d'impression rapide. Cependant, il est recommandé d'utiliser à stdout.write
la place selon la documentation de Django :
Lorsque vous utilisez des commandes de gestion et souhaitez fournir une sortie de console, vous devez écrire dans self.stdout et self.stderr, au lieu d'imprimer directement dans stdout et stderr. En utilisant ces proxys, il devient beaucoup plus facile de tester votre commande personnalisée. Notez également que vous n'avez pas besoin de terminer les messages avec un caractère de nouvelle ligne, il sera ajouté automatiquement, sauf si vous spécifiez le paramètre de fin.
Alors, ajoutez une self.stdout.write
commande :
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
help = "A description of the command"
def handle(self, *args, **options):
self.stdout.write("My sample command just ran.") # NEW
Pour tester, depuis la ligne de commande, exécutez :
$ docker-compose exec web python manage.py my_custom_command
Tu devrais voir:
My sample command just ran.
Sur ce, lions le tout ensemble !
Maintenant que nous avons lancé les conteneurs, testé que nous pouvions programmer une tâche à exécuter périodiquement et écrit un exemple de commande personnalisée Django Admin, il est temps de configurer Celery Beat pour exécuter la commande personnalisée périodiquement.
Dans le projet, nous avons une application très basique appelée commandes. Il contient deux modèles, Product
et Order
. Créons une commande personnalisée qui envoie un rapport par e-mail des commandes confirmées de la journée.
Pour commencer, nous allons ajouter quelques produits et commandes à la base de données via le luminaire inclus dans ce projet :
$ docker-compose exec web python manage.py loaddata products.json
Ensuite, ajoutez quelques exemples de commandes via l'interface Django Admin. Pour ce faire, créez d'abord un superutilisateur :
$ docker-compose exec web python manage.py createsuperuser
Remplissez le nom d'utilisateur, l'e-mail et le mot de passe lorsque vous y êtes invité. Accédez ensuite à http://127.0.0.1:1337/admin dans votre navigateur Web. Connectez-vous avec le superutilisateur que vous venez de créer et créez quelques commandes. Assurez-vous qu'au moins un a un confirmed_date
d'aujourd'hui.
Créons une nouvelle commande personnalisée pour notre rapport par e-mail.
Créez un fichier nommé orders/management/commands/email_report.py :
from datetime import timedelta, time, datetime
from django.core.mail import mail_admins
from django.core.management import BaseCommand
from django.utils import timezone
from django.utils.timezone import make_aware
from orders.models import Order
today = timezone.now()
tomorrow = today + timedelta(1)
today_start = make_aware(datetime.combine(today, time()))
today_end = make_aware(datetime.combine(tomorrow, time()))
class Command(BaseCommand):
help = "Send Today's Orders Report to Admins"
def handle(self, *args, **options):
orders = Order.objects.filter(confirmed_date__range=(today_start, today_end))
if orders:
message = ""
for order in orders:
message += f"{order} \n"
subject = (
f"Order Report for {today_start.strftime('%Y-%m-%d')} "
f"to {today_end.strftime('%Y-%m-%d')}"
)
mail_admins(subject=subject, message=message, html_message=None)
self.stdout.write("E-mail Report was sent.")
else:
self.stdout.write("No orders confirmed today.")
Dans le code, nous avons interrogé la base de données pour les commandes avec un confirmed_date
d'aujourd'hui, combiné les commandes en un seul message pour le corps de l'e-mail et utilisé la mail_admins
commande intégrée de Django pour envoyer les e-mails aux administrateurs.
Ajoutez un e-mail d'administrateur factice et configurez le EMAIL_BACKEND
pour qu'il utilise le backend de la console afin que l'e-mail soit envoyé à stdout, dans le fichier de paramètres :
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
DEFAULT_FROM_EMAIL = "noreply@email.com"
ADMINS = [("testuser", "test.user@email.com"), ]
Il devrait maintenant être possible d'exécuter notre nouvelle commande depuis le terminal.
$ docker-compose exec web python manage.py email_report
Et la sortie devrait ressembler à ceci :
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Django] Order Report for 2021-07-01 to 2021-07-02
From: root@localhost
To: test.user@email.com
Date: Thu, 01 Jul 2021 12:15:50 -0000
Message-ID: <162514175053.40.5705892371538583115@5140844ebb15>
Order: 3947963f-1860-44d1-9b9a-4648fed04581 - product: Coffee
Order: ff449e6e-3dfd-48a8-9d5c-79a145d08253 - product: Rice
-------------------------------------------------------------------------------
E-mail Report was sent.
Nous devons maintenant créer une tâche périodique pour exécuter cette commande quotidiennement.
Ajoutez une nouvelle tâche à core/tasks.py :
from celery import shared_task
from celery.utils.log import get_task_logger
from django.core.management import call_command # NEW
logger = get_task_logger(__name__)
@shared_task
def sample_task():
logger.info("The sample task just ran.")
# NEW
@shared_task
def send_email_report():
call_command("email_report", )
Donc, nous avons d'abord ajouté une call_command
importation, qui est utilisée pour appeler par programme les commandes django-admin. Dans la nouvelle tâche, nous avons ensuite utilisé le call_command
avec le nom de notre commande personnalisée comme argument.
Pour planifier cette tâche, ouvrez le fichier core/settings.py et mettez à jour le CELERY_BEAT_SCHEDULE
paramètre pour inclure la nouvelle tâche :
CELERY_BEAT_SCHEDULE = {
"sample_task": {
"task": "core.tasks.sample_task",
"schedule": crontab(minute="*/1"),
},
"send_email_report": {
"task": "core.tasks.send_email_report",
"schedule": crontab(hour="*/1"),
},
}
Ici, nous avons ajouté une nouvelle entrée au fichier CELERY_BEAT_SCHEDULE
appelé send_email_report
. Comme nous l'avons fait pour notre tâche précédente, nous avons déclaré la tâche à exécuter -- par exemple, core.tasks.send_email_report
-- et utilisé un modèle crontab pour définir la récurrence.
Redémarrez les conteneurs pour vous assurer que les nouveaux paramètres deviennent actifs :
$ docker-compose up -d --build
Ouvrez les journaux associés au celery
service :
$ docker-compose logs -f 'celery'
Vous devriez voir la send_email_report
liste :
celery_1 | -------------- [queues]
celery_1 | .> celery exchange=celery(direct) key=celery
celery_1 |
celery_1 |
celery_1 | [tasks]
celery_1 | . core.tasks.sample_task
celery_1 | . core.tasks.send_email_report
Environ une minute plus tard, vous devriez voir que le rapport par e-mail est envoyé :
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] Content-Type: text/plain; charset="utf-8"
celery_1 | MIME-Version: 1.0
celery_1 | Content-Transfer-Encoding: 7bit
celery_1 | Subject: [Django] Order Report for 2021-07-01 to 2021-07-02
celery_1 | From: root@localhost
celery_1 | To: test.user@email.com
celery_1 | Date: Thu, 01 Jul 2021 12:22:00 -0000
celery_1 | Message-ID: <162514212006.17.6080459299558356876@55b9883c5414>
celery_1 |
celery_1 | Order: 3947963f-1860-44d1-9b9a-4648fed04581 - product: Coffee
celery_1 | Order: ff449e6e-3dfd-48a8-9d5c-79a145d08253 - product: Rice
celery_1 |
celery_1 |
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] -------------------------------------------------------------------------------
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8]
celery_1 |
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] E-mail Report was sent.
Dans cet article, nous vous avons guidé dans la configuration des conteneurs Docker pour Celery, Celery Beat et Redis. Nous avons ensuite montré comment créer une commande Django Admin personnalisée et une tâche périodique avec Celery Beat pour exécuter cette commande automatiquement.
Vous en voulez plus ?
Récupérez le code du repo .
Source : https://testdrive.io
1620177818
Welcome to my blog , hey everyone in this article you learn how to customize the Django app and view in the article you will know how to register and unregister models from the admin view how to add filtering how to add a custom input field, and a button that triggers an action on all objects and even how to change the look of your app and page using the Django suit package let’s get started.
#django #create super user django #customize django admin dashboard #django admin #django admin custom field display #django admin customization #django admin full customization #django admin interface #django admin register all models #django customization
1660942320
在構建和擴展 Django 應用程序時,您不可避免地需要在後台定期自動運行某些任務。
一些例子:
這是構建和擴展不屬於 Django 核心的 Web 應用程序所需的少數功能之一。幸運的是,Celery 提供了一個強大的解決方案,它很容易實現,稱為 Celery Beat。
在下面的文章中,我們將向您展示如何使用 Docker 設置 Django、Celery 和 Redis,以便使用 Celery Beat 定期運行自定義 Django Admin 命令。
在本教程結束時,您應該能夠:
從django-celery-beat存儲庫中克隆基礎項目,然後查看基礎分支:
$ git clone https://github.com/testdrivenio/django-celery-beat --branch base --single-branch
$ cd django-celery-beat
由於我們總共需要管理四個進程(Django、Redis、worker 和調度程序),我們將使用 Docker 通過將它們連接起來來簡化我們的工作流程,以便它們都可以通過一個命令從一個終端窗口運行.
從項目根目錄,創建鏡像並啟動 Docker 容器:
$ docker-compose up -d --build
接下來,應用遷移:
$ docker-compose exec web python manage.py migrate
構建完成後,導航到http://localhost:1337以確保應用程序按預期運行。您應該看到以下文本:
Orders
No orders found!
在繼續之前快速瀏覽一下項目結構:
├── .gitignore
├── docker-compose.yml
└── project
├── Dockerfile
├── core
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── entrypoint.sh
├── manage.py
├── orders
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── products.json
├── requirements.txt
└── templates
└── orders
└── order_list.html
想學習如何構建這個項目?查看Dockerizing Django with Postgres、Gunicorn 和 Nginx博客文章。
現在,我們需要為 Celery、Celery Beat 和 Redis 添加容器。
我們首先將依賴項添加到requirements.txt文件中:
Django==3.2.4
celery==5.1.2
redis==3.5.3
接下來,將以下內容添加到docker-compose.yml文件的末尾:
redis:
image: redis:alpine
celery:
build: ./project
command: celery -A core worker -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
celery-beat:
build: ./project
command: celery -A core beat -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
我們還需要更新 Web 服務的depends_on
部分:
web:
build: ./project
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./project/:/usr/src/app/
ports:
- 1337:8000
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis # NEW
完整的 docker-compose.yml文件現在應該如下所示:
version: '3.8'
services:
web:
build: ./project
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./project/:/usr/src/app/
ports:
- 1337:8000
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
redis:
image: redis:alpine
celery:
build: ./project
command: celery -A core worker -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
celery-beat:
build: ./project
command: celery -A core beat -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
在構建新容器之前,我們需要在 Django 應用程序中配置 Celery。
在“core”目錄中,創建一個celery.py文件並添加以下代碼:
import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
app = Celery("core")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()
這裡發生了什麼事?
DJANGO_SETTINGS_MODULE
以便 Celery 知道如何找到 Django 項目。core
並將值分配給名為 的變量app
。django.conf
。我們曾經namespace="CELERY"
防止與其他 Django 設置發生衝突。CELERY_
換句話說,Celery 的所有配置設置都必須以 為前綴。app.autodiscover_tasks()
告訴 Celery 從settings.INSTALLED_APPS
.將以下代碼添加到core/__init__.py:
from .celery import app as celery_app
__all__ = ("celery_app",)
最後,使用以下 Celery 設置更新core/settings.py文件,以便它可以連接到 Redis:
CELERY_BROKER_URL = "redis://redis:6379"
CELERY_RESULT_BACKEND = "redis://redis:6379"
構建新容器以確保一切正常:
$ docker-compose up -d --build
查看每個服務的日誌以查看它們是否已準備就緒,沒有錯誤:
$ docker-compose logs 'web'
$ docker-compose logs 'celery'
$ docker-compose logs 'celery-beat'
$ docker-compose logs 'redis'
如果一切順利,我們現在有四個容器,每個容器都有不同的服務。
現在我們已經準備好創建一個示例任務來查看它是否可以正常工作。
創建一個名為core/tasks.py的新文件,並為僅記錄到控制台的示例任務添加以下代碼:
from celery import shared_task
from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
@shared_task
def sample_task():
logger.info("The sample task just ran.")
在settings.py文件的末尾,使用 Celery Beat 添加以下代碼以安排sample_task
每分鐘運行一次:
CELERY_BEAT_SCHEDULE = {
"sample_task": {
"task": "core.tasks.sample_task",
"schedule": crontab(minute="*/1"),
},
}
在這裡,我們使用CELERY_BEAT_SCHEDULE設置定義了一個週期性任務。我們為任務命名,sample_task
然後聲明了兩個設置:
task
聲明要運行的任務。schedule
設置任務應該運行的時間間隔。這可以是整數、timedelta 或 crontab。我們為我們的任務使用了一個 crontab 模式來告訴它每分鐘運行一次。您可以在此處找到有關 Celery 日程安排的更多信息。確保添加導入:
from celery.schedules import crontab
import core.tasks
重新啟動容器以引入新設置:
$ docker-compose up -d --build
完成後,查看容器中的 celery 日誌:
$ docker-compose logs -f 'celery'
您應該會看到類似於以下內容的內容:
celery_1 | -------------- [queues]
celery_1 | .> celery exchange=celery(direct) key=celery
celery_1 |
celery_1 |
celery_1 | [tasks]
celery_1 | . core.tasks.sample_task
我們可以看到 Celery 拿起了我們的示例任務,core.tasks.sample_task
.
每分鐘您都應該在日誌中看到以“剛剛運行的示例任務”結尾的一行:
celery_1 | [2021-07-01 03:06:00,003: INFO/MainProcess]
Task core.tasks.sample_task[b8041b6c-bf9b-47ce-ab00-c37c1e837bc7] received
celery_1 | [2021-07-01 03:06:00,004: INFO/ForkPoolWorker-8]
core.tasks.sample_task[b8041b6c-bf9b-47ce-ab00-c37c1e837bc7]:
The sample task just ran.
Django 提供了許多內置django-admin
命令,例如:
migrate
startproject
startapp
dumpdata
makemigrations
除了內置命令外,Django 還為我們提供了創建自己的自定義命令的選項:
自定義管理命令對於運行獨立腳本或從 UNIX crontab 或 Windows 計劃任務控制面板定期執行的腳本特別有用。
因此,我們將首先配置一個新命令,然後使用 Celery Beat 自動運行它。
首先創建一個名為orders/management/commands/my_custom_command.py的新文件。然後,添加運行所需的最少代碼:
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
help = "A description of the command"
def handle(self, *args, **options):
pass
有BaseCommand
一些方法可以被覆蓋,但唯一需要的方法是handle
. handle
是自定義命令的入口點。換句話說,當我們運行命令時,就會調用這個方法。
為了測試,我們通常只添加一個快速打印語句。但是,建議stdout.write
按照 Django 文檔使用:
當您使用管理命令並希望提供控制台輸出時,您應該寫入 self.stdout 和 self.stderr,而不是直接打印到 stdout 和 stderr。通過使用這些代理,測試您的自定義命令變得更加容易。另請注意,您不需要以換行符結束消息,它將自動添加,除非您指定結束參數。
所以,添加一個self.stdout.write
命令:
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
help = "A description of the command"
def handle(self, *args, **options):
self.stdout.write("My sample command just ran.") # NEW
要進行測試,請從命令行運行:
$ docker-compose exec web python manage.py my_custom_command
你應該看到:
My sample command just ran.
有了這個,讓我們把一切聯繫在一起!
現在我們已經啟動了容器,測試了我們可以安排任務定期運行,並編寫了自定義 Django Admin 示例命令,是時候配置 Celery Beat 以定期運行自定義命令了。
在項目中,我們有一個非常基本的應用程序,稱為訂單。它包含兩個模型,Product
和Order
。讓我們創建一個自定義命令,發送當天確認訂單的電子郵件報告。
首先,我們將通過此項目中包含的夾具將一些產品和訂單添加到數據庫中:
$ docker-compose exec web python manage.py loaddata products.json
接下來,通過 Django Admin 界面添加一些示例訂單。為此,首先創建一個超級用戶:
$ docker-compose exec web python manage.py createsuperuser
出現提示時填寫用戶名、電子郵件和密碼。然後在 Web 瀏覽器中導航到http://127.0.0.1:1337/admin 。使用您剛剛創建的超級用戶登錄並創建幾個訂單。確保至少有一個confirmed_date
今天。
讓我們為我們的電子郵件報告創建一個新的自定義命令。
創建一個名為orders/management/commands/email_report.py的文件:
from datetime import timedelta, time, datetime
from django.core.mail import mail_admins
from django.core.management import BaseCommand
from django.utils import timezone
from django.utils.timezone import make_aware
from orders.models import Order
today = timezone.now()
tomorrow = today + timedelta(1)
today_start = make_aware(datetime.combine(today, time()))
today_end = make_aware(datetime.combine(tomorrow, time()))
class Command(BaseCommand):
help = "Send Today's Orders Report to Admins"
def handle(self, *args, **options):
orders = Order.objects.filter(confirmed_date__range=(today_start, today_end))
if orders:
message = ""
for order in orders:
message += f"{order} \n"
subject = (
f"Order Report for {today_start.strftime('%Y-%m-%d')} "
f"to {today_end.strftime('%Y-%m-%d')}"
)
mail_admins(subject=subject, message=message, html_message=None)
self.stdout.write("E-mail Report was sent.")
else:
self.stdout.write("No orders confirmed today.")
在代碼中,我們使用confirmed_date
今天的 a 查詢數據庫中的訂單,將訂單組合成單個消息作為電子郵件正文,並使用 Django 的內置mail_admins
命令將電子郵件發送給管理員。
添加一個虛擬管理員電子郵件並將其設置EMAIL_BACKEND
為使用控制台後端,以便在設置文件中將電子郵件發送到標準輸出:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
DEFAULT_FROM_EMAIL = "noreply@email.com"
ADMINS = [("testuser", "test.user@email.com"), ]
現在應該可以從終端運行我們的新命令了。
$ docker-compose exec web python manage.py email_report
輸出應該類似於:
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Django] Order Report for 2021-07-01 to 2021-07-02
From: root@localhost
To: test.user@email.com
Date: Thu, 01 Jul 2021 12:15:50 -0000
Message-ID: <162514175053.40.5705892371538583115@5140844ebb15>
Order: 3947963f-1860-44d1-9b9a-4648fed04581 - product: Coffee
Order: ff449e6e-3dfd-48a8-9d5c-79a145d08253 - product: Rice
-------------------------------------------------------------------------------
E-mail Report was sent.
我們現在需要創建一個定期任務來每天運行這個命令。
向core/tasks.py添加一個新任務:
from celery import shared_task
from celery.utils.log import get_task_logger
from django.core.management import call_command # NEW
logger = get_task_logger(__name__)
@shared_task
def sample_task():
logger.info("The sample task just ran.")
# NEW
@shared_task
def send_email_report():
call_command("email_report", )
因此,首先我們添加了一個call_command
導入,用於以編程方式調用 django-admin 命令。在新任務中,我們使用call_command
自定義命令的名稱作為參數。
要安排此任務,請打開core/settings.py文件,並更新CELERY_BEAT_SCHEDULE
設置以包含新任務:
CELERY_BEAT_SCHEDULE = {
"sample_task": {
"task": "core.tasks.sample_task",
"schedule": crontab(minute="*/1"),
},
"send_email_report": {
"task": "core.tasks.send_email_report",
"schedule": crontab(hour="*/1"),
},
}
在這裡,我們向被CELERY_BEAT_SCHEDULE
調用的send_email_report
. 正如我們對之前的任務所做的那樣,我們聲明了它應該運行哪個任務——例如core.tasks.send_email_report
——並使用 crontab 模式來設置循環。
重新啟動容器以確保新設置生效:
$ docker-compose up -d --build
打開與服務關聯的日誌celery
:
$ docker-compose logs -f 'celery'
您應該看到send_email_report
列出的:
celery_1 | -------------- [queues]
celery_1 | .> celery exchange=celery(direct) key=celery
celery_1 |
celery_1 |
celery_1 | [tasks]
celery_1 | . core.tasks.sample_task
celery_1 | . core.tasks.send_email_report
大約一分鐘後,您應該會看到電子郵件報告已發送:
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] Content-Type: text/plain; charset="utf-8"
celery_1 | MIME-Version: 1.0
celery_1 | Content-Transfer-Encoding: 7bit
celery_1 | Subject: [Django] Order Report for 2021-07-01 to 2021-07-02
celery_1 | From: root@localhost
celery_1 | To: test.user@email.com
celery_1 | Date: Thu, 01 Jul 2021 12:22:00 -0000
celery_1 | Message-ID: <162514212006.17.6080459299558356876@55b9883c5414>
celery_1 |
celery_1 | Order: 3947963f-1860-44d1-9b9a-4648fed04581 - product: Coffee
celery_1 | Order: ff449e6e-3dfd-48a8-9d5c-79a145d08253 - product: Rice
celery_1 |
celery_1 |
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] -------------------------------------------------------------------------------
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8]
celery_1 |
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] E-mail Report was sent.
在本文中,我們指導您為 Celery、Celery Beat 和 Redis 設置 Docker 容器。然後,我們展示瞭如何使用 Celery Beat 創建自定義 Django Admin 命令和定期任務以自動運行該命令。
尋找更多?
從repo中獲取代碼。
1660927680
A medida que crea y escala una aplicación de Django, inevitablemente necesitará ejecutar ciertas tareas de forma periódica y automática en segundo plano.
Algunos ejemplos:
Esta es una de las pocas funciones necesarias para crear y escalar una aplicación web que no forma parte del núcleo de Django. Afortunadamente, Celery proporciona una solución poderosa, que es bastante fácil de implementar, llamada Celery Beat.
En el siguiente artículo, le mostraremos cómo configurar Django, Celery y Redis con Docker para ejecutar periódicamente un comando personalizado de administración de Django con Celery Beat.
Al final de este tutorial, debería ser capaz de:
Clone el proyecto base del repositorio django-celery-beat y luego revise la rama base :
$ git clone https://github.com/testdrivenio/django-celery-beat --branch base --single-branch
$ cd django-celery-beat
Dado que necesitaremos administrar cuatro procesos en total (Django, Redis, Worker y Scheduler), usaremos Docker para simplificar nuestro flujo de trabajo conectándolos para que todos puedan ejecutarse desde una ventana de terminal con un solo comando. .
Desde la raíz del proyecto, cree las imágenes y active los contenedores de Docker:
$ docker-compose up -d --build
A continuación, aplique las migraciones:
$ docker-compose exec web python manage.py migrate
Una vez que se complete la compilación, vaya a http://localhost:1337 para asegurarse de que la aplicación funcione como se espera. Deberías ver el siguiente texto:
Orders
No orders found!
Eche un vistazo rápido a la estructura del proyecto antes de continuar:
├── .gitignore
├── docker-compose.yml
└── project
├── Dockerfile
├── core
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── entrypoint.sh
├── manage.py
├── orders
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── products.json
├── requirements.txt
└── templates
└── orders
└── order_list.html
¿Quieres aprender a construir este proyecto? Consulte la publicación de blog Dockerizing Django con Postgres, Gunicorn y Nginx .
Ahora, necesitamos agregar contenedores para Celery, Celery Beat y Redis.
Comenzaremos agregando las dependencias al archivo requirements.txt :
Django==3.2.4
celery==5.1.2
redis==3.5.3
A continuación, agregue lo siguiente al final del archivo docker-compose.yml :
redis:
image: redis:alpine
celery:
build: ./project
command: celery -A core worker -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
celery-beat:
build: ./project
command: celery -A core beat -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
También necesitamos actualizar la depends_on
sección del servicio web:
web:
build: ./project
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./project/:/usr/src/app/
ports:
- 1337:8000
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis # NEW
El archivo completo docker-compose.yml ahora debería verse así:
version: '3.8'
services:
web:
build: ./project
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./project/:/usr/src/app/
ports:
- 1337:8000
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
redis:
image: redis:alpine
celery:
build: ./project
command: celery -A core worker -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
celery-beat:
build: ./project
command: celery -A core beat -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
Antes de construir los nuevos contenedores, debemos configurar Celery en nuestra aplicación Django.
En el directorio "núcleo", cree un archivo apio.py y agregue el siguiente código:
import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
app = Celery("core")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()
¿Que esta pasando aqui?
DJANGO_SETTINGS_MODULE
variable de entorno para que Celery sepa cómo encontrar el proyecto Django.core
y le asignamos el valor a una variable llamada app
.django.conf
. Solíamos namespace="CELERY"
evitar conflictos con otras configuraciones de Django. Todos los ajustes de configuración para Celery deben tener el prefijo CELERY_
, en otras palabras.app.autodiscover_tasks()
le dice a Celery que busque las tareas de Celery desde las aplicaciones definidas en settings.INSTALLED_APPS
.Agrega el siguiente código a core/__init__.py :
from .celery import app as celery_app
__all__ = ("celery_app",)
Por último, actualice el archivo core/settings.py con la siguiente configuración de Celery para que pueda conectarse a Redis:
CELERY_BROKER_URL = "redis://redis:6379"
CELERY_RESULT_BACKEND = "redis://redis:6379"
Construya los nuevos contenedores para asegurarse de que todo funcione:
$ docker-compose up -d --build
Eche un vistazo a los registros de cada servicio para ver que están listos, sin errores:
$ docker-compose logs 'web'
$ docker-compose logs 'celery'
$ docker-compose logs 'celery-beat'
$ docker-compose logs 'redis'
Si todo salió bien, ahora tenemos cuatro contenedores, cada uno con diferentes servicios.
Ahora estamos listos para crear una tarea de muestra para ver que funciona como debería.
Cree un nuevo archivo llamado core/tasks.py y agregue el siguiente código para una tarea de muestra que solo se registra en la consola:
from celery import shared_task
from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
@shared_task
def sample_task():
logger.info("The sample task just ran.")
Al final de su archivo settings.py , agregue el siguiente código para programar sample_task
que se ejecute una vez por minuto, usando Celery Beat:
CELERY_BEAT_SCHEDULE = {
"sample_task": {
"task": "core.tasks.sample_task",
"schedule": crontab(minute="*/1"),
},
}
Aquí, definimos una tarea periódica usando la configuración CELERY_BEAT_SCHEDULE . Le dimos a la tarea un nombre, sample_task
y luego declaramos dos configuraciones:
task
declara qué tarea ejecutar.schedule
establece el intervalo en el que debe ejecutarse la tarea. Puede ser un número entero, un timedelta o un crontab. Usamos un patrón crontab para nuestra tarea para indicarle que se ejecute una vez por minuto. Puede encontrar más información sobre la programación de Celery aquí .Asegúrate de agregar las importaciones:
from celery.schedules import crontab
import core.tasks
Reinicie el contenedor para obtener la nueva configuración:
$ docker-compose up -d --build
Una vez hecho esto, eche un vistazo a los troncos de apio en el contenedor:
$ docker-compose logs -f 'celery'
Debería ver algo similar a:
celery_1 | -------------- [queues]
celery_1 | .> celery exchange=celery(direct) key=celery
celery_1 |
celery_1 |
celery_1 | [tasks]
celery_1 | . core.tasks.sample_task
Podemos ver que Celery recogió nuestra tarea de muestra, core.tasks.sample_task
.
Cada minuto, debería ver una fila en el registro que termina con "La tarea de muestra acaba de ejecutarse":
celery_1 | [2021-07-01 03:06:00,003: INFO/MainProcess]
Task core.tasks.sample_task[b8041b6c-bf9b-47ce-ab00-c37c1e837bc7] received
celery_1 | [2021-07-01 03:06:00,004: INFO/ForkPoolWorker-8]
core.tasks.sample_task[b8041b6c-bf9b-47ce-ab00-c37c1e837bc7]:
The sample task just ran.
Django proporciona una serie de django-admin
comandos integrados, como:
migrate
startproject
startapp
dumpdata
makemigrations
Junto con los comandos incorporados, Django también nos da la opción de crear nuestros propios comandos personalizados :
Los comandos de administración personalizados son especialmente útiles para ejecutar secuencias de comandos independientes o secuencias de comandos que se ejecutan periódicamente desde el crontab de UNIX o desde el panel de control de tareas programadas de Windows.
Entonces, primero configuraremos un nuevo comando y luego usaremos Celery Beat para ejecutarlo automáticamente.
Comience creando un nuevo archivo llamado orders/management/commands/my_custom_command.py . Luego, agregue el código mínimo requerido para que se ejecute:
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
help = "A description of the command"
def handle(self, *args, **options):
pass
BaseCommand
tiene algunos métodos que se pueden anular, pero el único método que se requiere es handle
. handle
es el punto de entrada para los comandos personalizados. En otras palabras, cuando ejecutamos el comando, se llama a este método.
Para probar, normalmente agregaríamos una declaración de impresión rápida. Sin embargo, se recomienda usar en su stdout.write
lugar según la documentación de Django:
Cuando utilice comandos de administración y desee proporcionar una salida de consola, debe escribir en self.stdout y self.stderr, en lugar de imprimir directamente en stdout y stderr. Al usar estos servidores proxy, se vuelve mucho más fácil probar su comando personalizado. Tenga en cuenta también que no necesita finalizar los mensajes con un carácter de nueva línea, se agregará automáticamente, a menos que especifique el parámetro de finalización.
Entonces, agregue un self.stdout.write
comando:
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
help = "A description of the command"
def handle(self, *args, **options):
self.stdout.write("My sample command just ran.") # NEW
Para probar, desde la línea de comando, ejecute:
$ docker-compose exec web python manage.py my_custom_command
Debería ver:
My sample command just ran.
Con eso, ¡unamos todo junto!
Ahora que hicimos girar los contenedores, probamos que podemos programar una tarea para que se ejecute periódicamente y escribimos un comando de muestra de Django Admin personalizado, es hora de configurar Celery Beat para ejecutar el comando personalizado periódicamente.
En el proyecto tenemos una aplicación muy básica llamada pedidos. Contiene dos modelos, Product
y Order
. Vamos a crear un comando personalizado que envíe un informe por correo electrónico de los pedidos confirmados del día.
Para empezar, agregaremos algunos productos y pedidos a la base de datos a través del accesorio incluido en este proyecto:
$ docker-compose exec web python manage.py loaddata products.json
A continuación, agregue algunos pedidos de muestra a través de la interfaz de administración de Django. Para hacerlo, primero crea un superusuario:
$ docker-compose exec web python manage.py createsuperuser
Complete el nombre de usuario, el correo electrónico y la contraseña cuando se le solicite. Luego navegue a http://127.0.0.1:1337/admin en su navegador web. Inicie sesión con el superusuario que acaba de crear y cree un par de pedidos. Asegúrese de que al menos uno tenga una confirmed_date
de hoy.
Vamos a crear un nuevo comando personalizado para nuestro informe de correo electrónico.
Cree un archivo llamado orders/management/commands/email_report.py :
from datetime import timedelta, time, datetime
from django.core.mail import mail_admins
from django.core.management import BaseCommand
from django.utils import timezone
from django.utils.timezone import make_aware
from orders.models import Order
today = timezone.now()
tomorrow = today + timedelta(1)
today_start = make_aware(datetime.combine(today, time()))
today_end = make_aware(datetime.combine(tomorrow, time()))
class Command(BaseCommand):
help = "Send Today's Orders Report to Admins"
def handle(self, *args, **options):
orders = Order.objects.filter(confirmed_date__range=(today_start, today_end))
if orders:
message = ""
for order in orders:
message += f"{order} \n"
subject = (
f"Order Report for {today_start.strftime('%Y-%m-%d')} "
f"to {today_end.strftime('%Y-%m-%d')}"
)
mail_admins(subject=subject, message=message, html_message=None)
self.stdout.write("E-mail Report was sent.")
else:
self.stdout.write("No orders confirmed today.")
En el código, consultamos la base de datos en busca de pedidos con confirmed_date
fecha de hoy, combinamos los pedidos en un solo mensaje para el cuerpo del correo electrónico y usamos el mail_admins
comando integrado de Django para enviar los correos electrónicos a los administradores.
Agregue un correo electrónico de administrador ficticio y configure el EMAIL_BACKEND
para usar el backend de la consola , de modo que el correo electrónico se envíe a stdout, en el archivo de configuración:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
DEFAULT_FROM_EMAIL = "noreply@email.com"
ADMINS = [("testuser", "test.user@email.com"), ]
Ahora debería ser posible ejecutar nuestro nuevo comando desde la terminal.
$ docker-compose exec web python manage.py email_report
Y la salida debería ser similar a esto:
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Django] Order Report for 2021-07-01 to 2021-07-02
From: root@localhost
To: test.user@email.com
Date: Thu, 01 Jul 2021 12:15:50 -0000
Message-ID: <162514175053.40.5705892371538583115@5140844ebb15>
Order: 3947963f-1860-44d1-9b9a-4648fed04581 - product: Coffee
Order: ff449e6e-3dfd-48a8-9d5c-79a145d08253 - product: Rice
-------------------------------------------------------------------------------
E-mail Report was sent.
Ahora necesitamos crear una tarea periódica para ejecutar este comando diariamente.
Agregue una nueva tarea a core/tasks.py :
from celery import shared_task
from celery.utils.log import get_task_logger
from django.core.management import call_command # NEW
logger = get_task_logger(__name__)
@shared_task
def sample_task():
logger.info("The sample task just ran.")
# NEW
@shared_task
def send_email_report():
call_command("email_report", )
Entonces, primero agregamos una call_command
importación, que se usa para llamar mediante programación a los comandos django-admin. En la nueva tarea, usamos el call_command
con el nombre de nuestro comando personalizado como argumento.
Para programar esta tarea, abra el archivo core/settings.py y actualice la CELERY_BEAT_SCHEDULE
configuración para incluir la nueva tarea:
CELERY_BEAT_SCHEDULE = {
"sample_task": {
"task": "core.tasks.sample_task",
"schedule": crontab(minute="*/1"),
},
"send_email_report": {
"task": "core.tasks.send_email_report",
"schedule": crontab(hour="*/1"),
},
}
Aquí agregamos una nueva entrada al CELERY_BEAT_SCHEDULE
llamado send_email_report
. Como hicimos con nuestra tarea anterior, declaramos qué tarea debería ejecutar, por ejemplo, core.tasks.send_email_report
y usamos un patrón crontab para establecer la recurrencia.
Reinicie los contenedores para asegurarse de que la nueva configuración se active:
$ docker-compose up -d --build
Abra los registros asociados con el celery
servicio:
$ docker-compose logs -f 'celery'
Debería ver la send_email_report
lista:
celery_1 | -------------- [queues]
celery_1 | .> celery exchange=celery(direct) key=celery
celery_1 |
celery_1 |
celery_1 | [tasks]
celery_1 | . core.tasks.sample_task
celery_1 | . core.tasks.send_email_report
Aproximadamente un minuto después, debería ver que se envía el informe por correo electrónico:
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] Content-Type: text/plain; charset="utf-8"
celery_1 | MIME-Version: 1.0
celery_1 | Content-Transfer-Encoding: 7bit
celery_1 | Subject: [Django] Order Report for 2021-07-01 to 2021-07-02
celery_1 | From: root@localhost
celery_1 | To: test.user@email.com
celery_1 | Date: Thu, 01 Jul 2021 12:22:00 -0000
celery_1 | Message-ID: <162514212006.17.6080459299558356876@55b9883c5414>
celery_1 |
celery_1 | Order: 3947963f-1860-44d1-9b9a-4648fed04581 - product: Coffee
celery_1 | Order: ff449e6e-3dfd-48a8-9d5c-79a145d08253 - product: Rice
celery_1 |
celery_1 |
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] -------------------------------------------------------------------------------
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8]
celery_1 |
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] E-mail Report was sent.
En este artículo lo guiamos a través de la configuración de contenedores Docker para Celery, Celery Beat y Redis. Luego mostramos cómo crear un comando Django Admin personalizado y una tarea periódica con Celery Beat para ejecutar ese comando automáticamente.
¿Buscando por mas?
Tome el código del repositorio .
Fuente: https://testdriven.io
1660949640
При создании и масштабировании приложения Django вам неизбежно потребуется периодически и автоматически запускать определенные задачи в фоновом режиме.
Некоторые примеры:
Это одна из немногих функций, необходимых для создания и масштабирования веб-приложения, которое не является частью ядра Django. К счастью, Celery предоставляет мощное и достаточно простое в реализации решение под названием Celery Beat.
В следующей статье мы покажем вам, как настроить Django, Celery и Redis с помощью Docker, чтобы периодически запускать пользовательскую команду администратора Django с помощью Celery Beat.
К концу этого урока вы должны уметь:
Клонируйте базовый проект из репозитория django-celery-beat , а затем проверьте базовую ветку:
$ git clone https://github.com/testdrivenio/django-celery-beat --branch base --single-branch
$ cd django-celery-beat
Поскольку нам нужно будет управлять четырьмя процессами в общей сложности (Django, Redis, worker и планировщик), мы будем использовать Docker, чтобы упростить наш рабочий процесс, подключив их так, чтобы их можно было запускать из одного окна терминала с помощью одной команды. .
В корне проекта создайте образы и разверните контейнеры Docker:
$ docker-compose up -d --build
Затем примените миграции:
$ docker-compose exec web python manage.py migrate
После завершения сборки перейдите по адресу http://localhost:1337 , чтобы убедиться, что приложение работает должным образом. Вы должны увидеть следующий текст:
Orders
No orders found!
Прежде чем двигаться дальше, взгляните на структуру проекта:
├── .gitignore
├── docker-compose.yml
└── project
├── Dockerfile
├── core
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── entrypoint.sh
├── manage.py
├── orders
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── products.json
├── requirements.txt
└── templates
└── orders
└── order_list.html
Хотите узнать, как построить этот проект? Ознакомьтесь с сообщением в блоге Dockerizing Django с Postgres, Gunicorn и Nginx .
Теперь нам нужно добавить контейнеры для Celery, Celery Beat и Redis.
Мы начнем с добавления зависимостей в файл requirements.txt :
Django==3.2.4
celery==5.1.2
redis==3.5.3
Затем добавьте следующее в конец файла docker-compose.yml :
redis:
image: redis:alpine
celery:
build: ./project
command: celery -A core worker -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
celery-beat:
build: ./project
command: celery -A core beat -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
Нам также необходимо обновить depends_on
раздел веб-сервиса:
web:
build: ./project
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./project/:/usr/src/app/
ports:
- 1337:8000
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis # NEW
Полный файл docker-compose.yml теперь должен выглядеть так:
version: '3.8'
services:
web:
build: ./project
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./project/:/usr/src/app/
ports:
- 1337:8000
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
redis:
image: redis:alpine
celery:
build: ./project
command: celery -A core worker -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
celery-beat:
build: ./project
command: celery -A core beat -l info
volumes:
- ./project/:/usr/src/app/
environment:
- DEBUG=1
- SECRET_KEY=dbaa1_i7%*3r9-=z-+_mz4r-!qeed@(-a_r(g@k8jo8y3r27%m
- DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1]
depends_on:
- redis
Перед созданием новых контейнеров нам нужно настроить Celery в нашем приложении Django.
В каталоге «core» создайте файл celery.py и добавьте следующий код:
import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings")
app = Celery("core")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()
Что тут происходит?
DJANGO_SETTINGS_MODULE
переменной среды, чтобы Celery знал, как найти проект Django.core
и присвоили значение переменной с именем app
.django.conf
. Раньше мы namespace="CELERY"
предотвращали конфликты с другими настройками Django. CELERY_
Другими словами, все параметры конфигурации для Celery должны иметь префикс .app.autodiscover_tasks()
указывает Celery искать задачи Celery в приложениях, определенных в файлах settings.INSTALLED_APPS
.Добавьте следующий код в core/__init__.py :
from .celery import app as celery_app
__all__ = ("celery_app",)
Наконец, обновите файл core/settings.py со следующими настройками Celery, чтобы он мог подключаться к Redis:
CELERY_BROKER_URL = "redis://redis:6379"
CELERY_RESULT_BACKEND = "redis://redis:6379"
Создайте новые контейнеры, чтобы убедиться, что все работает:
$ docker-compose up -d --build
Взгляните на журналы для каждой службы, чтобы увидеть, что они готовы, без ошибок:
$ docker-compose logs 'web'
$ docker-compose logs 'celery'
$ docker-compose logs 'celery-beat'
$ docker-compose logs 'redis'
Если все прошло хорошо, у нас теперь есть четыре контейнера, каждый с разными сервисами.
Теперь мы готовы создать пример задачи, чтобы убедиться, что она работает должным образом.
Создайте новый файл с именем core/tasks.py и добавьте следующий код для примера задачи, которая просто регистрируется в консоли:
from celery import shared_task
from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
@shared_task
def sample_task():
logger.info("The sample task just ran.")
В конце файла settings.pysample_task
добавьте следующий код, чтобы запланировать запуск раз в минуту с помощью Celery Beat:
CELERY_BEAT_SCHEDULE = {
"sample_task": {
"task": "core.tasks.sample_task",
"schedule": crontab(minute="*/1"),
},
}
Здесь мы определили периодическую задачу, используя настройку CELERY_BEAT_SCHEDULE . Мы дали задаче имя, sample_task
а затем объявили две настройки:
task
объявляет, какую задачу выполнять.schedule
устанавливает интервал, в течение которого задача должна выполняться. Это может быть целое число, timedelta или crontab. Мы использовали шаблон crontab для нашей задачи, чтобы заставить ее запускаться каждую минуту. Вы можете найти больше информации о расписании Celery здесь .Обязательно добавьте импорт:
from celery.schedules import crontab
import core.tasks
Перезапустите контейнер, чтобы получить новые настройки:
$ docker-compose up -d --build
После этого взгляните на журналы сельдерея в контейнере:
$ docker-compose logs -f 'celery'
Вы должны увидеть что-то похожее на:
celery_1 | -------------- [queues]
celery_1 | .> celery exchange=celery(direct) key=celery
celery_1 |
celery_1 |
celery_1 | [tasks]
celery_1 | . core.tasks.sample_task
Мы видим, что Celery подхватил нашу тестовую задачу, core.tasks.sample_task
.
Каждую минуту вы должны видеть в журнале строку, заканчивающуюся словами «Только что запущен пример задачи»:
celery_1 | [2021-07-01 03:06:00,003: INFO/MainProcess]
Task core.tasks.sample_task[b8041b6c-bf9b-47ce-ab00-c37c1e837bc7] received
celery_1 | [2021-07-01 03:06:00,004: INFO/ForkPoolWorker-8]
core.tasks.sample_task[b8041b6c-bf9b-47ce-ab00-c37c1e837bc7]:
The sample task just ran.
Django предоставляет ряд встроенных django-admin
команд, таких как:
migrate
startproject
startapp
dumpdata
makemigrations
Наряду со встроенными командами Django также дает нам возможность создавать собственные пользовательские команды :
Пользовательские команды управления особенно полезны для запуска автономных сценариев или сценариев, которые периодически выполняются из crontab UNIX или из панели управления запланированными задачами Windows.
Итак, мы сначала настроим новую команду, а затем воспользуемся Celery Beat для ее автоматического запуска.
Начните с создания нового файла с именем orders/management/commands/my_custom_command.py . Затем добавьте минимальный необходимый код для его запуска:
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
help = "A description of the command"
def handle(self, *args, **options):
pass
Есть BaseCommand
несколько методов , которые можно переопределить, но требуется только один метод handle
. handle
является точкой входа для пользовательских команд. Другими словами, когда мы запускаем команду, вызывается этот метод.
Для проверки мы обычно просто добавляем оператор быстрой печати. Однако вместо этого рекомендуется использовать stdout.write
документацию Django:
Когда вы используете команды управления и хотите предоставить консольный вывод, вы должны писать в self.stdout и self.stderr, а не печатать напрямую в stdout и stderr. Используя эти прокси, становится намного проще протестировать пользовательскую команду. Также обратите внимание, что вам не нужно заканчивать сообщения символом новой строки, он будет добавлен автоматически, если вы не укажете конечный параметр.
Итак, добавляем self.stdout.write
команду:
from django.core.management.base import BaseCommand, CommandError
class Command(BaseCommand):
help = "A description of the command"
def handle(self, *args, **options):
self.stdout.write("My sample command just ran.") # NEW
Для проверки из командной строки запустите:
$ docker-compose exec web python manage.py my_custom_command
Тебе следует увидеть:
My sample command just ran.
С этим, давайте свяжем все вместе!
Теперь, когда мы развернули контейнеры, проверили, что мы можем запланировать периодическое выполнение задачи, и написали пример пользовательской команды администратора Django, пришло время настроить Celery Beat для периодического запуска пользовательской команды.
В проекте у нас есть очень простое приложение под названием заказы. Он содержит две модели Product
и Order
. Давайте создадим пользовательскую команду, которая отправляет по электронной почте отчет о подтвержденных заказах за день.
Для начала добавим в базу несколько товаров и заказов через фикстуру, включенную в этот проект:
$ docker-compose exec web python manage.py loaddata products.json
Затем добавьте несколько образцов заказов через интерфейс администратора Django. Для этого сначала создайте суперпользователя:
$ docker-compose exec web python manage.py createsuperuser
При появлении запроса введите имя пользователя, адрес электронной почты и пароль. Затем перейдите по адресу http://127.0.0.1:1337/admin в веб-браузере. Войдите в систему с помощью только что созданного суперпользователя и создайте пару заказов. Убедитесь, что хотя бы один confirmed_date
из них имеет сегодня.
Давайте создадим новую пользовательскую команду для нашего отчета по электронной почте.
Создайте файл с именем orders/management/commands/email_report.py :
from datetime import timedelta, time, datetime
from django.core.mail import mail_admins
from django.core.management import BaseCommand
from django.utils import timezone
from django.utils.timezone import make_aware
from orders.models import Order
today = timezone.now()
tomorrow = today + timedelta(1)
today_start = make_aware(datetime.combine(today, time()))
today_end = make_aware(datetime.combine(tomorrow, time()))
class Command(BaseCommand):
help = "Send Today's Orders Report to Admins"
def handle(self, *args, **options):
orders = Order.objects.filter(confirmed_date__range=(today_start, today_end))
if orders:
message = ""
for order in orders:
message += f"{order} \n"
subject = (
f"Order Report for {today_start.strftime('%Y-%m-%d')} "
f"to {today_end.strftime('%Y-%m-%d')}"
)
mail_admins(subject=subject, message=message, html_message=None)
self.stdout.write("E-mail Report was sent.")
else:
self.stdout.write("No orders confirmed today.")
В коде мы запросили в базе данных заказы с confirmed_date
сегодняшним днем, объединили заказы в одно сообщение для тела электронной почты и использовали встроенную mail_admins
команду Django для отправки электронных писем администраторам.
Добавьте фиктивный адрес электронной почты администратора и установите EMAIL_BACKEND
для использования серверную часть консоли , чтобы электронная почта отправлялась на стандартный вывод в файле настроек:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
DEFAULT_FROM_EMAIL = "noreply@email.com"
ADMINS = [("testuser", "test.user@email.com"), ]
Теперь можно запустить нашу новую команду из терминала.
$ docker-compose exec web python manage.py email_report
И вывод должен выглядеть примерно так:
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [Django] Order Report for 2021-07-01 to 2021-07-02
From: root@localhost
To: test.user@email.com
Date: Thu, 01 Jul 2021 12:15:50 -0000
Message-ID: <162514175053.40.5705892371538583115@5140844ebb15>
Order: 3947963f-1860-44d1-9b9a-4648fed04581 - product: Coffee
Order: ff449e6e-3dfd-48a8-9d5c-79a145d08253 - product: Rice
-------------------------------------------------------------------------------
E-mail Report was sent.
Теперь нам нужно создать периодическую задачу для ежедневного запуска этой команды.
Добавьте новую задачу в core/tasks.py :
from celery import shared_task
from celery.utils.log import get_task_logger
from django.core.management import call_command # NEW
logger = get_task_logger(__name__)
@shared_task
def sample_task():
logger.info("The sample task just ran.")
# NEW
@shared_task
def send_email_report():
call_command("email_report", )
Итак, сначала мы добавили call_command
импорт, который используется для программного вызова команд django-admin. Затем в новой задаче мы использовали call_command
имя нашей пользовательской команды в качестве аргумента.
Чтобы запланировать эту задачу, откройте файл core/settings.py и обновите CELERY_BEAT_SCHEDULE
параметр, чтобы включить новую задачу:
CELERY_BEAT_SCHEDULE = {
"sample_task": {
"task": "core.tasks.sample_task",
"schedule": crontab(minute="*/1"),
},
"send_email_report": {
"task": "core.tasks.send_email_report",
"schedule": crontab(hour="*/1"),
},
}
Здесь мы добавили новую запись в CELERY_BEAT_SCHEDULE
вызываемый файл send_email_report
. Как и для предыдущей задачи, мы объявили, какую задачу она должна запускать, например, core.tasks.send_email_report
--, и использовали шаблон crontab для установки повторения.
Перезапустите контейнеры, чтобы новые настройки стали активными:
$ docker-compose up -d --build
Откройте журналы, связанные с celery
сервисом:
$ docker-compose logs -f 'celery'
Вы должны увидеть в send_email_report
списке:
celery_1 | -------------- [queues]
celery_1 | .> celery exchange=celery(direct) key=celery
celery_1 |
celery_1 |
celery_1 | [tasks]
celery_1 | . core.tasks.sample_task
celery_1 | . core.tasks.send_email_report
Через минуту или около того вы должны увидеть, что отчет по электронной почте отправлен:
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] Content-Type: text/plain; charset="utf-8"
celery_1 | MIME-Version: 1.0
celery_1 | Content-Transfer-Encoding: 7bit
celery_1 | Subject: [Django] Order Report for 2021-07-01 to 2021-07-02
celery_1 | From: root@localhost
celery_1 | To: test.user@email.com
celery_1 | Date: Thu, 01 Jul 2021 12:22:00 -0000
celery_1 | Message-ID: <162514212006.17.6080459299558356876@55b9883c5414>
celery_1 |
celery_1 | Order: 3947963f-1860-44d1-9b9a-4648fed04581 - product: Coffee
celery_1 | Order: ff449e6e-3dfd-48a8-9d5c-79a145d08253 - product: Rice
celery_1 |
celery_1 |
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] -------------------------------------------------------------------------------
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8]
celery_1 |
celery_1 | [2021-07-01 12:22:00,071: WARNING/ForkPoolWorker-8] E-mail Report was sent.
В этой статье мы провели вас через настройку контейнеров Docker для Celery, Celery Beat и Redis. Затем мы показали, как создать пользовательскую команду администратора Django и периодическую задачу с Celery Beat для автоматического запуска этой команды.
Ищете больше?
Возьмите код из репозитория .
Источник: https://testdriven.io