Skip to content

Commit 29ec712

Browse files
add celery
1 parent f15d7af commit 29ec712

23 files changed

+3789
-2897
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ env
1515
.env
1616
node_modules
1717
yarn.lock
18+
19+
tasks_logs/
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 4.2.5 on 2023-10-08 08:05
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
initial = True
9+
10+
dependencies = [
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='Product',
16+
fields=[
17+
('id', models.AutoField(primary_key=True, serialize=False)),
18+
('name', models.CharField(max_length=100)),
19+
('info', models.CharField(default='', max_length=100)),
20+
('price', models.IntegerField(blank=True, null=True)),
21+
],
22+
),
23+
]

celerybeat-schedule

16 KB
Binary file not shown.

core/settings.py

+22
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
"apps.charts",
5353
"apps.tables",
5454
"apps.tasks",
55+
56+
"django_celery_results",
5557
]
5658

5759
MIDDLEWARE = [
@@ -144,3 +146,23 @@
144146
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
145147

146148
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
149+
150+
151+
152+
CELERY_SCRIPTS_DIR = os.path.join(BASE_DIR, "tasks_scripts" )
153+
154+
CELERY_LOGS_URL = "/tasks_logs/"
155+
CELERY_LOGS_DIR = os.path.join(BASE_DIR, "tasks_logs" )
156+
157+
CELERY_BROKER_URL = os.environ.get("CELERY_BROKER", "redis://localhost:6379")
158+
CELERY_RESULT_BACKEND = os.environ.get("CELERY_BROKER", "redis://localhost:6379")
159+
160+
CELERY_TASK_TRACK_STARTED = True
161+
CELERY_TASK_TIME_LIMIT = 30 * 60
162+
CELERY_CACHE_BACKEND = "django-cache"
163+
CELERY_RESULT_BACKEND = "django-db"
164+
CELERY_RESULT_EXTENDED = True
165+
CELERY_RESULT_EXPIRES = 60*60*24*30 # Results expire after 1 month
166+
CELERY_ACCEPT_CONTENT = ["json"]
167+
CELERY_TASK_SERIALIZER = 'json'
168+
CELERY_RESULT_SERIALIZER = 'json'

core/urls.py

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
"""
1717
from django.contrib import admin
1818
from django.urls import include, path
19+
from django.conf import settings
20+
from django.conf.urls.static import static
1921

2022
urlpatterns = [
2123
path("", include("home.urls")),
@@ -26,3 +28,4 @@
2628
path("tables/", include("apps.tables.urls")),
2729
path("tasks/", include("apps.tasks.urls")),
2830
]
31+
urlpatterns += static(settings.CELERY_LOGS_URL, document_root=settings.CELERY_LOGS_DIR)

docs/CELERY.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
## Celery docs
2+
3+
- Install the dependencies
4+
```bash
5+
$ pip install -r requirements.txt
6+
```
7+
8+
- In the base directory inside `tasks_scripts` folder you need to write your scripts file.
9+
- Run the celery command from the CLI.
10+
```bash
11+
$ celery -A home worker -l info -B
12+
```
13+
- Run the django server
14+
```bash
15+
$ python manage.py runserver
16+
```
17+
- You will see a new route `Tasks` in the sidebar.
18+
- You can start and cancel any task from the UI.

home/celery.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# -*- encoding: utf-8 -*-
2+
"""
3+
Copyright (c) 2019 - present AppSeed.us
4+
"""
5+
6+
import os
7+
8+
from celery import Celery
9+
10+
if os.environ.get('DJANGO_SETTINGS_MODULE'):
11+
12+
app = Celery('core')
13+
14+
# Using a string here means the worker doesn't have to serialize
15+
# the configuration object to child processes.
16+
# - namespace='CELERY' means all celery-related configuration keys
17+
# should have a `CELERY_` prefix.
18+
app.config_from_object('django.conf:settings', namespace='CELERY')
19+
20+
# Load task modules from all registered Django apps.
21+
app.autodiscover_tasks()
22+
23+
else:
24+
print(' ')
25+
print('Celery Configuration ERROR: ')
26+
print(' > "DJANGO_SETTINGS_MODULE" not set in environment (value in manage.py)')
27+
print(' Hint: export DJANGO_SETTINGS_MODULE=project.settings ')
28+
print(' ')

home/migrations/0001_initial.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 4.2.5 on 2023-10-08 08:05
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
initial = True
9+
10+
dependencies = [
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='FileInfo',
16+
fields=[
17+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18+
('path', models.URLField()),
19+
('info', models.CharField(max_length=255)),
20+
],
21+
),
22+
]

home/models.py

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
from django.db import models
22

33
# Create your models here.
4+
5+
class FileInfo(models.Model):
6+
path = models.URLField()
7+
info = models.CharField(max_length=255)
8+
9+
def __str__(self):
10+
return self.path

home/tasks.py

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import os, time, subprocess
2+
import datetime
3+
from os import listdir
4+
from os.path import isfile, join
5+
6+
from .celery import app
7+
from celery.contrib.abortable import AbortableTask
8+
from django_celery_results.models import TaskResult
9+
10+
from django.contrib.auth.models import User
11+
from django.conf import settings
12+
from celery.exceptions import Ignore, TaskError
13+
14+
15+
def get_scripts():
16+
"""
17+
Returns all scripts from 'ROOT_DIR/celery_scripts'
18+
"""
19+
raw_scripts = []
20+
scripts = []
21+
ignored_ext = ['db', 'txt']
22+
23+
try:
24+
raw_scripts = [f for f in listdir(settings.CELERY_SCRIPTS_DIR) if isfile(join(settings.CELERY_SCRIPTS_DIR, f))]
25+
except Exception as e:
26+
return None, 'Error CELERY_SCRIPTS_DIR: ' + str( e )
27+
28+
for filename in raw_scripts:
29+
30+
ext = filename.split(".")[-1]
31+
if ext not in ignored_ext:
32+
scripts.append( filename )
33+
34+
return scripts, None
35+
36+
def write_to_log_file(logs, script_name):
37+
"""
38+
Writes logs to a log file with formatted name in the CELERY_LOGS_DIR directory.
39+
"""
40+
script_base_name = os.path.splitext(script_name)[0] # Remove the .py extension
41+
current_time = datetime.datetime.now().strftime("%y%m%d-%H%M%S")
42+
log_file_name = f"{script_base_name}-{current_time}.log"
43+
log_file_path = os.path.join(settings.CELERY_LOGS_DIR, log_file_name)
44+
45+
with open(log_file_path, 'w') as log_file:
46+
log_file.write(logs)
47+
48+
return log_file_path
49+
50+
@app.task(bind=True, base=AbortableTask)
51+
def execute_script(self, data: dict):
52+
"""
53+
This task executes scripts found in settings.CELERY_SCRIPTS_DIR and logs are later generated and stored in settings.CELERY_LOGS_DIR
54+
:param data dict: contains data needed for task execution. Example `input` which is the script to be executed.
55+
:rtype: None
56+
"""
57+
script = data.get("script")
58+
args = data.get("args")
59+
60+
print( '> EXEC [' + script + '] -> ('+args+')' )
61+
62+
scripts, ErrInfo = get_scripts()
63+
64+
if script and script in scripts:
65+
# Executing related script
66+
script_path = os.path.join(settings.CELERY_SCRIPTS_DIR, script)
67+
process = subprocess.Popen(
68+
f"python {script_path} {args}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
69+
time.sleep(8)
70+
71+
exit_code = process.wait()
72+
error = False
73+
status = "STARTED"
74+
if exit_code == 0: # If script execution successfull
75+
logs = process.stdout.read().decode()
76+
status = "SUCCESS"
77+
else:
78+
logs = process.stderr.read().decode()
79+
error = True
80+
status = "FAILURE"
81+
82+
83+
log_file = write_to_log_file(logs, script)
84+
85+
return {"logs": logs, "input": script, "error": error, "output": "", "status": status, "log_file": log_file}

home/templatetags/__init__.py

Whitespace-only changes.

home/templatetags/file_extension.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from django import template
2+
import os
3+
from urllib.parse import quote
4+
5+
register = template.Library()
6+
7+
@register.filter
8+
def file_extension(value):
9+
_, extension = os.path.splitext(value)
10+
return extension.lower()
11+
12+
13+
@register.filter
14+
def encoded_file_path(path):
15+
return path.replace('/', '%slash%')
16+
17+
@register.filter
18+
def encoded_path(path):
19+
return path.replace('\\', '/')

home/templatetags/formats.py

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# -*- encoding: utf-8 -*-
2+
"""
3+
Copyright (c) 2019 - present AppSeed.us
4+
"""
5+
6+
import json
7+
import os
8+
9+
from django import template
10+
from django.conf import settings
11+
12+
register = template.Library()
13+
14+
def date_format(date):
15+
"""
16+
Returns a formatted date string
17+
Format: `Year-Month-Day-Hour-Minute-Second`
18+
Example: `2022-10-10-00-20-33`
19+
:param date datetime: Date object to be formatted
20+
:rtype: str
21+
"""
22+
try:
23+
return date.strftime(r'%Y-%m-%d-%H-%M-%S')
24+
except:
25+
return date
26+
27+
register.filter("date_format", date_format)
28+
29+
def get_result_field(result, field: str):
30+
"""
31+
Returns a field from the content of the result attibute in result
32+
Example: `result.result['field']`
33+
:param result AbortableAsyncResult: Result object to get field from
34+
:param field str: Field to return from result object
35+
:rtype: str
36+
"""
37+
result = json.loads(result.result)
38+
if result:
39+
return result.get(field)
40+
41+
register.filter("get_result_field", get_result_field)
42+
43+
44+
45+
def log_file_path(path):
46+
file_path = path.split("tasks_logs")[1]
47+
return file_path
48+
49+
register.filter("log_file_path", log_file_path)
50+
51+
52+
def log_to_text(path):
53+
path = path.lstrip('/')
54+
55+
full_path = os.path.join(settings.CELERY_LOGS_DIR, path)
56+
57+
try:
58+
with open(full_path, 'r') as file:
59+
text = file.read()
60+
61+
return text
62+
except:
63+
return 'NO LOGS'
64+
65+
register.filter("log_to_text", log_to_text)

home/templatetags/info_value.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from django import template
2+
from home.models import FileInfo
3+
4+
register = template.Library()
5+
6+
@register.filter
7+
def info_value(path):
8+
file_info = FileInfo.objects.filter(path=path)
9+
if file_info.exists():
10+
return file_info.first().info
11+
else:
12+
return ""

home/urls.py

+8
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,12 @@
55
urlpatterns = [
66
path("", views.index, name="index"),
77
path("starter/", views.starter, name="starter"),
8+
9+
# Celery
10+
path('tasks/', views.tasks, name="tasks"),
11+
path('tasks/run/<str:task_name>' , views.run_task, name="run-task" ),
12+
path('tasks/cancel/<str:task_id>' , views.cancel_task, name="cancel-task" ),
13+
path('tasks/output/' , views.task_output, name="task-output" ),
14+
path('tasks/log/' , views.task_log, name="task-log" ),
15+
path('download-log-file/<str:file_path>/', views.download_log_file, name='download_log_file'),
816
]

0 commit comments

Comments
 (0)