celery beat
This commit is contained in:
parent
64644a5a67
commit
6795e415f1
7 changed files with 131 additions and 28 deletions
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Generated by Django 5.2.6 on 2025-09-06 13:25
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('emails', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='email',
|
||||||
|
name='extras',
|
||||||
|
field=models.JSONField(blank=True, default=dict, null=True),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EmailQueue',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(max_length=255, verbose_name='title')),
|
||||||
|
('last_sent', models.DateTimeField(auto_now_add=True, verbose_name='last sent')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='email',
|
||||||
|
name='queue',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='emails.emailqueue'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import F
|
||||||
from oauth2_provider.settings import oauth2_settings
|
from oauth2_provider.settings import oauth2_settings
|
||||||
|
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
|
|
@ -6,6 +7,18 @@ from utils.models import BaseModel
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class EmailQueueQuerySet(models.query.QuerySet):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EmailQueue(models.Model):
|
||||||
|
title = models.CharField(_('title'), max_length=255)
|
||||||
|
user = models.ForeignKey(User, verbose_name=_('user'), on_delete=models.CASCADE)
|
||||||
|
last_sent = models.DateTimeField(_('last sent'), auto_now_add=True)
|
||||||
|
|
||||||
|
objects = EmailQueueQuerySet.as_manager()
|
||||||
|
|
||||||
|
|
||||||
class EmailQuerySet(models.QuerySet):
|
class EmailQuerySet(models.QuerySet):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -27,6 +40,8 @@ class Email(BaseModel):
|
||||||
priority = models.IntegerField(default=5)
|
priority = models.IntegerField(default=5)
|
||||||
extras = models.JSONField(default=dict, null=True, blank=True)
|
extras = models.JSONField(default=dict, null=True, blank=True)
|
||||||
stats = models.IntegerField(default=StateChoices.INIT, choices=StateChoices.choices)
|
stats = models.IntegerField(default=StateChoices.INIT, choices=StateChoices.choices)
|
||||||
|
queue = models.ForeignKey(EmailQueue, on_delete=models.PROTECT, related_name='+', null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
# bulk = models.ForeignKey("BulkEmail", null=True, blank=True, on_delete=models.PROTECT)
|
# bulk = models.ForeignKey("BulkEmail", null=True, blank=True, on_delete=models.PROTECT)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ class EmailSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Email
|
model = Email
|
||||||
fields = (
|
fields = (
|
||||||
|
"queue",
|
||||||
"title",
|
"title",
|
||||||
"message",
|
"message",
|
||||||
"user",
|
"user",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
|
from django.db.models import F
|
||||||
|
from django.utils import timezone
|
||||||
|
import logging
|
||||||
|
from apps.emails.models import Email, EmailQueue
|
||||||
from main import celery_app
|
from main import celery_app
|
||||||
from utils.clients import gotify
|
from utils.clients import gotify
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@celery_app.task
|
@celery_app.task
|
||||||
def send_email(email_uuid):
|
def send_email(email_uuid):
|
||||||
|
|
@ -10,3 +15,32 @@ def send_email(email_uuid):
|
||||||
|
|
||||||
email.state = email.StateChoices.DONE
|
email.state = email.StateChoices.DONE
|
||||||
email.save()
|
email.save()
|
||||||
|
|
||||||
|
|
||||||
|
@celery_app.task
|
||||||
|
def send_email_for_queued_events():
|
||||||
|
logger.info('Sending email for queued events')
|
||||||
|
for email_queue in EmailQueue.objects.all():
|
||||||
|
qs = Email.objects.filter(queue=email_queue, stats=Email.StateChoices.INIT, created_at_gte=F('queue__last_sent'))
|
||||||
|
if email_queue.last_sent <= timezone.now() - timezone.timedelta(hours=1) and qs.exists():
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
from django.conf import settings
|
||||||
|
send_mail(
|
||||||
|
"you have some queued task(s)",
|
||||||
|
f"you have {qs.count()} queued task(s)",
|
||||||
|
settings.EMAIL_HOST_USER,
|
||||||
|
["xdshia49@gmail.com"],
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
|
email_queue.last_sent = timezone.now()
|
||||||
|
email_queue.save()
|
||||||
|
|
||||||
|
|
||||||
|
celery_app.conf.beat_schedule = {
|
||||||
|
'print-every-minute': {
|
||||||
|
'task': 'apps.emails.tasks.send_email_for_queued_events',
|
||||||
|
'schedule': 10.0, # هر 60 ثانیه
|
||||||
|
# یا میتوانید از crontab استفاده کنید:
|
||||||
|
# 'schedule': crontab(minute='*/1'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ INSTALLED_APPS = [
|
||||||
'crispy_bootstrap5',
|
'crispy_bootstrap5',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
'jalali_date',
|
'jalali_date',
|
||||||
|
"django_celery_beat",
|
||||||
# 'django_minio_backend.apps.DjangoMinioBackendConfig',
|
# 'django_minio_backend.apps.DjangoMinioBackendConfig',
|
||||||
|
|
||||||
# local apps
|
# local apps
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,5 @@ httpx
|
||||||
django-redis
|
django-redis
|
||||||
django_minio_backend
|
django_minio_backend
|
||||||
xlrd
|
xlrd
|
||||||
|
django-celery-beat
|
||||||
|
# aiosmtpd #??
|
||||||
|
|
@ -1,32 +1,37 @@
|
||||||
#
|
#
|
||||||
# This file is autogenerated by pip-compile with Python 3.11
|
# This file is autogenerated by pip-compile with Python 3.13
|
||||||
# by the following command:
|
# by the following command:
|
||||||
#
|
#
|
||||||
# pip-compile
|
# pip-compile
|
||||||
#
|
#
|
||||||
|
aiosmtpd==1.4.6
|
||||||
|
# via -r requirements.in
|
||||||
amqp==5.3.1
|
amqp==5.3.1
|
||||||
# via kombu
|
# via kombu
|
||||||
anyio==4.9.0
|
anyio==4.10.0
|
||||||
# via httpx
|
# via httpx
|
||||||
argon2-cffi==25.1.0
|
argon2-cffi==25.1.0
|
||||||
# via minio
|
# via minio
|
||||||
argon2-cffi-bindings==21.2.0
|
argon2-cffi-bindings==25.1.0
|
||||||
# via argon2-cffi
|
# via argon2-cffi
|
||||||
asgiref==3.8.1
|
asgiref==3.9.1
|
||||||
# via
|
# via
|
||||||
# django
|
# django
|
||||||
# django-cors-headers
|
# django-cors-headers
|
||||||
async-timeout==5.0.1
|
atpublic==6.0.1
|
||||||
# via redis
|
# via aiosmtpd
|
||||||
attrs==25.3.0
|
attrs==25.3.0
|
||||||
# via
|
# via
|
||||||
|
# aiosmtpd
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# referencing
|
# referencing
|
||||||
billiard==4.2.1
|
billiard==4.2.1
|
||||||
# via celery
|
# via celery
|
||||||
celery==5.5.3
|
celery==5.5.3
|
||||||
# via -r requirements.in
|
# via
|
||||||
certifi==2025.6.15
|
# -r requirements.in
|
||||||
|
# django-celery-beat
|
||||||
|
certifi==2025.8.3
|
||||||
# via
|
# via
|
||||||
# httpcore
|
# httpcore
|
||||||
# httpx
|
# httpx
|
||||||
|
|
@ -36,7 +41,7 @@ cffi==1.17.1
|
||||||
# via
|
# via
|
||||||
# argon2-cffi-bindings
|
# argon2-cffi-bindings
|
||||||
# cryptography
|
# cryptography
|
||||||
charset-normalizer==3.4.2
|
charset-normalizer==3.4.3
|
||||||
# via requests
|
# via requests
|
||||||
click==8.2.1
|
click==8.2.1
|
||||||
# via
|
# via
|
||||||
|
|
@ -52,12 +57,15 @@ click-repl==0.3.0
|
||||||
# via celery
|
# via celery
|
||||||
crispy-bootstrap5==2025.6
|
crispy-bootstrap5==2025.6
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
cryptography==45.0.4
|
cron-descriptor==2.0.6
|
||||||
|
# via django-celery-beat
|
||||||
|
cryptography==45.0.7
|
||||||
# via jwcrypto
|
# via jwcrypto
|
||||||
django==5.2.3
|
django==5.2.6
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# crispy-bootstrap5
|
# crispy-bootstrap5
|
||||||
|
# django-celery-beat
|
||||||
# django-cors-headers
|
# django-cors-headers
|
||||||
# django-crispy-forms
|
# django-crispy-forms
|
||||||
# django-filter
|
# django-filter
|
||||||
|
|
@ -65,8 +73,11 @@ django==5.2.3
|
||||||
# django-minio-backend
|
# django-minio-backend
|
||||||
# django-oauth-toolkit
|
# django-oauth-toolkit
|
||||||
# django-redis
|
# django-redis
|
||||||
|
# django-timezone-field
|
||||||
# djangorestframework
|
# djangorestframework
|
||||||
# drf-spectacular
|
# drf-spectacular
|
||||||
|
django-celery-beat==2.8.1
|
||||||
|
# via -r requirements.in
|
||||||
django-cors-headers==4.7.0
|
django-cors-headers==4.7.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-crispy-forms==2.4
|
django-crispy-forms==2.4
|
||||||
|
|
@ -83,15 +94,17 @@ django-oauth-toolkit==3.0.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
django-redis==6.0.0
|
django-redis==6.0.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
djangorestframework==3.16.0
|
django-timezone-field==7.1
|
||||||
|
# via django-celery-beat
|
||||||
|
djangorestframework==3.16.1
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# drf-spectacular
|
# drf-spectacular
|
||||||
drf-spectacular==0.28.0
|
drf-spectacular==0.28.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
gevent==25.5.1
|
gevent==25.8.2
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
greenlet==3.2.3
|
greenlet==3.2.4
|
||||||
# via gevent
|
# via gevent
|
||||||
gunicorn==23.0.0
|
gunicorn==23.0.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
|
|
@ -112,7 +125,7 @@ jalali-core==1.0.0
|
||||||
# via jdatetime
|
# via jdatetime
|
||||||
jdatetime==5.2.0
|
jdatetime==5.2.0
|
||||||
# via django-jalali-date
|
# via django-jalali-date
|
||||||
jsonschema==4.24.0
|
jsonschema==4.25.1
|
||||||
# via drf-spectacular
|
# via drf-spectacular
|
||||||
jsonschema-specifications==2025.4.1
|
jsonschema-specifications==2025.4.1
|
||||||
# via jsonschema
|
# via jsonschema
|
||||||
|
|
@ -120,7 +133,7 @@ jwcrypto==1.5.6
|
||||||
# via django-oauth-toolkit
|
# via django-oauth-toolkit
|
||||||
kombu==5.5.4
|
kombu==5.5.4
|
||||||
# via celery
|
# via celery
|
||||||
minio==7.2.15
|
minio==7.2.16
|
||||||
# via django-minio-backend
|
# via django-minio-backend
|
||||||
oauthlib==3.3.1
|
oauthlib==3.3.1
|
||||||
# via django-oauth-toolkit
|
# via django-oauth-toolkit
|
||||||
|
|
@ -128,9 +141,9 @@ packaging==25.0
|
||||||
# via
|
# via
|
||||||
# gunicorn
|
# gunicorn
|
||||||
# kombu
|
# kombu
|
||||||
pillow==11.2.1
|
pillow==11.3.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
prompt-toolkit==3.0.51
|
prompt-toolkit==3.0.52
|
||||||
# via click-repl
|
# via click-repl
|
||||||
psycopg[binary,pool]==3.2.9
|
psycopg[binary,pool]==3.2.9
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
|
|
@ -142,13 +155,15 @@ pycparser==2.22
|
||||||
# via cffi
|
# via cffi
|
||||||
pycryptodome==3.23.0
|
pycryptodome==3.23.0
|
||||||
# via minio
|
# via minio
|
||||||
|
python-crontab==3.3.0
|
||||||
|
# via django-celery-beat
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
# via celery
|
# via celery
|
||||||
python-decouple==3.8
|
python-decouple==3.8
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
pyyaml==6.0.2
|
pyyaml==6.0.2
|
||||||
# via drf-spectacular
|
# via drf-spectacular
|
||||||
redis==6.2.0
|
redis==6.4.0
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# django-redis
|
# django-redis
|
||||||
|
|
@ -156,11 +171,11 @@ referencing==0.36.2
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# jsonschema-specifications
|
# jsonschema-specifications
|
||||||
requests==2.32.4
|
requests==2.32.5
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# django-oauth-toolkit
|
# django-oauth-toolkit
|
||||||
rpds-py==0.25.1
|
rpds-py==0.27.1
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# referencing
|
# referencing
|
||||||
|
|
@ -170,16 +185,16 @@ sniffio==1.3.1
|
||||||
# via anyio
|
# via anyio
|
||||||
sqlparse==0.5.3
|
sqlparse==0.5.3
|
||||||
# via django
|
# via django
|
||||||
typing-extensions==4.14.0
|
typing-extensions==4.15.0
|
||||||
# via
|
# via
|
||||||
# anyio
|
# cron-descriptor
|
||||||
# jwcrypto
|
# jwcrypto
|
||||||
# minio
|
# minio
|
||||||
# psycopg
|
|
||||||
# psycopg-pool
|
# psycopg-pool
|
||||||
# referencing
|
|
||||||
tzdata==2025.2
|
tzdata==2025.2
|
||||||
# via kombu
|
# via
|
||||||
|
# django-celery-beat
|
||||||
|
# kombu
|
||||||
uritemplate==4.2.0
|
uritemplate==4.2.0
|
||||||
# via drf-spectacular
|
# via drf-spectacular
|
||||||
urllib3==2.5.0
|
urllib3==2.5.0
|
||||||
|
|
@ -195,7 +210,7 @@ wcwidth==0.2.13
|
||||||
# via prompt-toolkit
|
# via prompt-toolkit
|
||||||
xlrd==2.0.2
|
xlrd==2.0.2
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
zope-event==5.1
|
zope-event==5.1.1
|
||||||
# via gevent
|
# via gevent
|
||||||
zope-interface==7.2
|
zope-interface==7.2
|
||||||
# via gevent
|
# via gevent
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue