diff --git a/backend/core/__init__.py b/backend/core/__init__.py index e69de29..9e0d95f 100644 --- a/backend/core/__init__.py +++ b/backend/core/__init__.py @@ -0,0 +1,3 @@ +from .celery import app as celery_app + +__all__ = ('celery_app',) \ No newline at end of file diff --git a/backend/core/celery.py b/backend/core/celery.py new file mode 100644 index 0000000..af3c387 --- /dev/null +++ b/backend/core/celery.py @@ -0,0 +1,8 @@ +import os +from celery import Celery +from django.conf import settings + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings.production') +app = Celery('core') +app.config_from_object('django.conf:settings', namespace='CELERY') +app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) \ No newline at end of file diff --git a/backend/core/settings/base.py b/backend/core/settings/base.py index 38808a3..019aedd 100644 --- a/backend/core/settings/base.py +++ b/backend/core/settings/base.py @@ -65,7 +65,7 @@ INSTALLED_APPS = [ "rest_framework.authtoken", "import_export", "django_jalali", - 'django_crontab', + 'django_celery_beat', # Custom Apps "product", "account", @@ -234,12 +234,3 @@ AWS_S3_OBJECT_PARAMETERS = { 'ACL': 'public-read', } -# ============================================================================== -# django CRONJOBS -# ============================================================================== - -CRONJOBS = [ - ('* * * * *', 'product.cron.update_product_prices', f'>> {BASE_DIR}/logfile.log 2>&1'), -] - - diff --git a/backend/core/settings/production.py b/backend/core/settings/production.py index 3ec44aa..a9e37fd 100644 --- a/backend/core/settings/production.py +++ b/backend/core/settings/production.py @@ -46,4 +46,23 @@ MEDIA_URL = 'https://c262408.parspack.net/' MEDIA_ROOT = '/app/media' STATIC_URL = '/shop_static/' -STATIC_ROOT = '/app/static' \ No newline at end of file +STATIC_ROOT = '/app/static' + + +# ============================================================================== +# django cerery +# ============================================================================== + +CELERY_BROKER_URL = "redis://redis:6379/0" +CELERY_RESULT_BACKEND = "redis://redis:6379/0" +CELERY_TIMEZONE = "UTC" +CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' + +from celery.schedules import crontab + +CELERY_BEAT_SCHEDULE = { + 'update-prices-every-minute': { + 'task': 'product.tasks.update_product_prices', + 'schedule': crontab(minute='*'), + }, +} \ No newline at end of file diff --git a/backend/dockerfile b/backend/dockerfile index 09c155a..16ceef1 100644 --- a/backend/dockerfile +++ b/backend/dockerfile @@ -13,6 +13,5 @@ COPY . /app/ CMD ["sh", "-c", "python manage.py makemigrations && \ python manage.py migrate && \ - python manage.py crontab add && \ python manage.py collectstatic --no-input && \ gunicorn core.wsgi:application --bind 0.0.0.0:8000 --workers 3"] \ No newline at end of file diff --git a/backend/home/admin.py b/backend/home/admin.py index e64e483..b4c7429 100644 --- a/backend/home/admin.py +++ b/backend/home/admin.py @@ -92,4 +92,62 @@ class HomeImageAdmin(ModelAdmin, ImportExportModelAdmin): ArrayField: { "widget": ArrayWidget, } - } \ No newline at end of file + } + + +# admin.py +from django.contrib import admin +from unfold.admin import ModelAdmin + +from django_celery_beat.models import ( + ClockedSchedule, + CrontabSchedule, + IntervalSchedule, + PeriodicTask, + SolarSchedule, +) +from django_celery_beat.admin import ClockedScheduleAdmin as BaseClockedScheduleAdmin +from django_celery_beat.admin import CrontabScheduleAdmin as BaseCrontabScheduleAdmin +from django_celery_beat.admin import PeriodicTaskAdmin as BasePeriodicTaskAdmin +from django_celery_beat.admin import PeriodicTaskForm, TaskSelectWidget +from unfold.widgets import * +admin.site.unregister(PeriodicTask) +admin.site.unregister(IntervalSchedule) +admin.site.unregister(CrontabSchedule) +admin.site.unregister(SolarSchedule) +admin.site.unregister(ClockedSchedule) + + +class UnfoldTaskSelectWidget(UnfoldAdminSelectWidget, TaskSelectWidget): + pass + + +class UnfoldPeriodicTaskForm(PeriodicTaskForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["task"].widget = UnfoldAdminTextInputWidget() + self.fields["regtask"].widget = UnfoldTaskSelectWidget() + + +@admin.register(PeriodicTask) +class PeriodicTaskAdmin(BasePeriodicTaskAdmin, ModelAdmin): + form = UnfoldPeriodicTaskForm + + +@admin.register(IntervalSchedule) +class IntervalScheduleAdmin(ModelAdmin): + pass + + +@admin.register(CrontabSchedule) +class CrontabScheduleAdmin(BaseCrontabScheduleAdmin, ModelAdmin): + pass + + +@admin.register(SolarSchedule) +class SolarScheduleAdmin(ModelAdmin): + pass + +@admin.register(ClockedSchedule) +class ClockedScheduleAdmin(BaseClockedScheduleAdmin, ModelAdmin): + pass \ No newline at end of file diff --git a/backend/product/cron.py b/backend/product/cron.py deleted file mode 100644 index 883774f..0000000 --- a/backend/product/cron.py +++ /dev/null @@ -1,5 +0,0 @@ -from product.models import ProductVariant - -def update_product_prices(): - print('calling the update product prices from cron') - ProductVariant.update_all_prices() \ No newline at end of file diff --git a/backend/product/tasks.py b/backend/product/tasks.py new file mode 100644 index 0000000..4bba6c6 --- /dev/null +++ b/backend/product/tasks.py @@ -0,0 +1,8 @@ +from celery import shared_task +from product.models import ProductVariant + +@shared_task +def update_product_prices(): + print("\033[92m Calling update product prices from Celery\033[00m") + ProductVariant.update_all_prices() + print("\033[92m its working\033[00m") diff --git a/backend/requirements.txt b/backend/requirements.txt index a3a2e7b..127a3fe 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,19 +1,27 @@ aiohappyeyeballs==2.4.0 aiohttp==3.10.5 aiosignal==1.3.1 +amqp==5.3.1 annotated-types==0.7.0 anyio==4.6.0 asgiref==3.8.1 attrs==24.2.0 az-iranian-bank-gateways==2.0.5 beautifulsoup4==4.12.3 +billiard==4.2.1 boto3==1.36.26 botocore==1.36.26 branca==0.8.1 +celery==5.4.0 certifi==2024.8.30 cffi==1.17.1 charset-normalizer==3.3.2 +click==8.1.8 +click-didyoumean==0.3.1 +click-plugins==1.1.1 +click-repl==0.3.0 colorama==0.4.6 +cron-descriptor==1.4.5 cryptography==44.0.1 defusedxml==0.8.0rc2 diff-match-patch==20230430 @@ -21,11 +29,10 @@ distro==1.9.0 Django==5.1.2 django-admin-interface==0.28.5 django-admin-persian-fonts==0.2 +django-celery-beat==2.7.0 django-cleanup==8.1.0 django-colorfield==0.11.0 django-cors-headers==4.4.0 -django-cron==0.6.0 -django-crontab==0.7.1 django-dbbackup==4.2.1 django-dirtyfields==1.9.3 django-filter==24.3 @@ -33,6 +40,7 @@ django-import-export==4.1.1 django-iranian-cities==1.0.2 django-jalali==7.3.0 django-storages==1.14.5 +django-timezone-field==7.1 django-unfold==0.48.0 djangorestframework==3.15.2 djangorestframework-simplejwt==5.3.1 @@ -65,6 +73,7 @@ jiter==0.8.2 jmespath==1.0.1 jsonschema==4.23.0 jsonschema-specifications==2024.10.1 +kombu==5.4.2 lxml==5.2.2 MarkupPy==1.14 MarkupSafe==3.0.2 @@ -78,6 +87,7 @@ openpyxl==3.1.2 packaging==24.2 pillow==10.4.0 platformdirs==4.2.2 +prompt_toolkit==3.0.50 propcache==0.2.0 psutil==6.0.0 psycopg2-binary==2.9.10 @@ -97,6 +107,7 @@ python3-openid==3.2.0 pytz==2024.2 pywebpush==2.0.3 PyYAML==6.0.2 +redis==5.2.1 referencing==0.35.1 requests==2.32.3 requests-file==2.1.0 @@ -119,6 +130,8 @@ typing_extensions==4.12.2 tzdata==2024.1 uritemplate==4.1.1 urllib3==2.2.3 +vine==5.1.0 +wcwidth==0.2.13 whitenoise==6.7.0 xlrd==2.0.1 xlwt==1.3.0 diff --git a/docker-compose.yml b/docker-compose.yml index 14dbfba..bfe3923 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,18 +18,13 @@ services: - db volumes: - ./backend:/app - - /root/vol/shop/media:/app/media - - /root/vol/shop/static:/app/static - command: - [ - "sh", - "-c", - "python manage.py migrate && python manage.py collectstatic --no-input && python manage.py crontab add && gunicorn core.wsgi:application --bind 0.0.0.0:8000 --workers 3", - ] + - media_data:/app/media + - media_data:/app/static networks: - default db: + container_name: hshop_db image: postgres:16 environment: POSTGRES_DB: hshop @@ -61,6 +56,43 @@ services: networks: - default + + redis: + image: redis:alpine + ports: + - "6379:6379" + networks: + - default + + celery_worker: + build: + context: ./backend + command: celery -A core worker --loglevel=info + depends_on: + - django + - redis + volumes: + - ./backend:/app + environment: + - CELERY_BROKER_URL=redis://redis:6379/0 + networks: + - default + + celery_beat: + build: + context: ./backend + command: celery -A core beat --loglevel=info + depends_on: + - django + - redis + volumes: + - ./backend:/app + environment: + - CELERY_BROKER_URL=redis://redis:6379/0 + networks: + - default + + volumes: postgres_data: media_data: