celery beat

This commit is contained in:
Sayyid Hamid Mahdavi 2025-09-06 17:00:21 +03:30
parent 64644a5a67
commit 6795e415f1
7 changed files with 131 additions and 28 deletions

View file

@ -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'),
),
]

View file

@ -1,4 +1,5 @@
from django.db import models
from django.db.models import F
from oauth2_provider.settings import oauth2_settings
from apps.users.models import User
@ -6,6 +7,18 @@ from utils.models import BaseModel
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):
pass
@ -27,6 +40,8 @@ class Email(BaseModel):
priority = models.IntegerField(default=5)
extras = models.JSONField(default=dict, null=True, blank=True)
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)

View file

@ -7,6 +7,7 @@ class EmailSerializer(serializers.ModelSerializer):
class Meta:
model = Email
fields = (
"queue",
"title",
"message",
"user",

View file

@ -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 utils.clients import gotify
logger = logging.getLogger(__name__)
@celery_app.task
def send_email(email_uuid):
@ -10,3 +15,32 @@ def send_email(email_uuid):
email.state = email.StateChoices.DONE
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'),
},
}

View file

@ -48,6 +48,7 @@ INSTALLED_APPS = [
'crispy_bootstrap5',
'django_filters',
'jalali_date',
"django_celery_beat",
# 'django_minio_backend.apps.DjangoMinioBackendConfig',
# local apps

View file

@ -18,4 +18,6 @@ celery
httpx
django-redis
django_minio_backend
xlrd
xlrd
django-celery-beat
# aiosmtpd #??

View file

@ -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:
#
# pip-compile
#
aiosmtpd==1.4.6
# via -r requirements.in
amqp==5.3.1
# via kombu
anyio==4.9.0
anyio==4.10.0
# via httpx
argon2-cffi==25.1.0
# via minio
argon2-cffi-bindings==21.2.0
argon2-cffi-bindings==25.1.0
# via argon2-cffi
asgiref==3.8.1
asgiref==3.9.1
# via
# django
# django-cors-headers
async-timeout==5.0.1
# via redis
atpublic==6.0.1
# via aiosmtpd
attrs==25.3.0
# via
# aiosmtpd
# jsonschema
# referencing
billiard==4.2.1
# via celery
celery==5.5.3
# via -r requirements.in
certifi==2025.6.15
# via
# -r requirements.in
# django-celery-beat
certifi==2025.8.3
# via
# httpcore
# httpx
@ -36,7 +41,7 @@ cffi==1.17.1
# via
# argon2-cffi-bindings
# cryptography
charset-normalizer==3.4.2
charset-normalizer==3.4.3
# via requests
click==8.2.1
# via
@ -52,12 +57,15 @@ click-repl==0.3.0
# via celery
crispy-bootstrap5==2025.6
# via -r requirements.in
cryptography==45.0.4
cron-descriptor==2.0.6
# via django-celery-beat
cryptography==45.0.7
# via jwcrypto
django==5.2.3
django==5.2.6
# via
# -r requirements.in
# crispy-bootstrap5
# django-celery-beat
# django-cors-headers
# django-crispy-forms
# django-filter
@ -65,8 +73,11 @@ django==5.2.3
# django-minio-backend
# django-oauth-toolkit
# django-redis
# django-timezone-field
# djangorestframework
# drf-spectacular
django-celery-beat==2.8.1
# via -r requirements.in
django-cors-headers==4.7.0
# via -r requirements.in
django-crispy-forms==2.4
@ -83,15 +94,17 @@ django-oauth-toolkit==3.0.1
# via -r requirements.in
django-redis==6.0.0
# via -r requirements.in
djangorestframework==3.16.0
django-timezone-field==7.1
# via django-celery-beat
djangorestframework==3.16.1
# via
# -r requirements.in
# drf-spectacular
drf-spectacular==0.28.0
# via -r requirements.in
gevent==25.5.1
gevent==25.8.2
# via -r requirements.in
greenlet==3.2.3
greenlet==3.2.4
# via gevent
gunicorn==23.0.0
# via -r requirements.in
@ -112,7 +125,7 @@ jalali-core==1.0.0
# via jdatetime
jdatetime==5.2.0
# via django-jalali-date
jsonschema==4.24.0
jsonschema==4.25.1
# via drf-spectacular
jsonschema-specifications==2025.4.1
# via jsonschema
@ -120,7 +133,7 @@ jwcrypto==1.5.6
# via django-oauth-toolkit
kombu==5.5.4
# via celery
minio==7.2.15
minio==7.2.16
# via django-minio-backend
oauthlib==3.3.1
# via django-oauth-toolkit
@ -128,9 +141,9 @@ packaging==25.0
# via
# gunicorn
# kombu
pillow==11.2.1
pillow==11.3.0
# via -r requirements.in
prompt-toolkit==3.0.51
prompt-toolkit==3.0.52
# via click-repl
psycopg[binary,pool]==3.2.9
# via -r requirements.in
@ -142,13 +155,15 @@ pycparser==2.22
# via cffi
pycryptodome==3.23.0
# via minio
python-crontab==3.3.0
# via django-celery-beat
python-dateutil==2.9.0.post0
# via celery
python-decouple==3.8
# via -r requirements.in
pyyaml==6.0.2
# via drf-spectacular
redis==6.2.0
redis==6.4.0
# via
# -r requirements.in
# django-redis
@ -156,11 +171,11 @@ referencing==0.36.2
# via
# jsonschema
# jsonschema-specifications
requests==2.32.4
requests==2.32.5
# via
# -r requirements.in
# django-oauth-toolkit
rpds-py==0.25.1
rpds-py==0.27.1
# via
# jsonschema
# referencing
@ -170,16 +185,16 @@ sniffio==1.3.1
# via anyio
sqlparse==0.5.3
# via django
typing-extensions==4.14.0
typing-extensions==4.15.0
# via
# anyio
# cron-descriptor
# jwcrypto
# minio
# psycopg
# psycopg-pool
# referencing
tzdata==2025.2
# via kombu
# via
# django-celery-beat
# kombu
uritemplate==4.2.0
# via drf-spectacular
urllib3==2.5.0
@ -195,7 +210,7 @@ wcwidth==0.2.13
# via prompt-toolkit
xlrd==2.0.2
# via -r requirements.in
zope-event==5.1
zope-event==5.1.1
# via gevent
zope-interface==7.2
# via gevent