├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── MANIFEST.in
├── README.md
├── django_tm
├── __init__.py
├── admin.py
├── apps.py
├── celery.py
├── celery_logs
│ └── .gitignore
├── celery_scripts
│ ├── check-db-health.py
│ ├── check-disk-free.py
│ └── clean-database.py
├── config.py
├── migrations
│ └── __init__.py
├── models.py
├── signals.py
├── tasks.py
├── templates
│ ├── static
│ │ └── .gitkeep
│ └── tasks
│ │ ├── base.html
│ │ └── index.html
├── templatetags
│ ├── __init__.py
│ └── formats.py
├── tests.py
├── urls.py
└── views.py
├── docs
└── blank.txt
├── publish.txt
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.DS_Store
3 | *.egg*
4 | /dist/
5 | /.idea
6 | /docs/_build/
7 | /node_modules/
8 | build/
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## [1.0.1] 2022-11-01
4 | ### Improvements
5 |
6 | - Update `DOCS`
7 |
8 | ## [1.0.0] 2022-10-30
9 | ### Improvements
10 |
11 | - STABLE_VERSION
12 |
13 | ## [0.0.6] 2022-10-30
14 | ### Improvements
15 |
16 | - Update `DOCS`
17 |
18 | ## [0.0.5] 2022-10-14
19 | ### Improvements
20 |
21 | - Update `DOCS`
22 | - Added sample project (step-by-step info)
23 | - [Django Tasks Manager Sample](https://github.com/app-generator/sample-django-tasks-manager)
24 |
25 | ## [0.0.4] 2022-10-13
26 | ### Improvements
27 |
28 | - Update `DOCS`
29 | - Added SShots
30 |
31 | ## [0.0.3] 2022-10-13
32 | ### Improvements
33 |
34 | - Update `README`
35 |
36 | ## [0.0.2] 2022-10-13
37 | ### Improvements
38 |
39 | - Update `README`
40 |
41 | ## [0.0.1] 2022-10-12
42 | ### Initial Release
43 |
44 | - Minimal Version
45 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 [App Generator](https://appseed.us)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE.md
2 | include README.md
3 | recursive-include django_tm/static *
4 | recursive-include django_tm/templates *
5 | recursive-include django_tm/celery_logs *
6 | recursive-include django_tm/celery_scripts *
7 | recursive-include docs *
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Django Tasks Manager](https://appseed.us/developer-tools/django-tasks-manager/)
2 |
3 | A super simple **Django & Celery** integration - This library is actively supported by [AppSeed](https://appseed.us/).
4 |
5 |
6 |
7 | > Features:
8 |
9 | - Create/Revoke `Celery Tasks`
10 | - `View LOGS` & Output
11 | - `Minimal Configuration`
12 | - Available [TASKS](https://github.com/app-generator/django-tasks-manager/blob/main/django_tm/tasks.py) (provided as starting samples)
13 | - `users_in_db()` - List all registered users
14 | - `execute_script()` - let users execute the [scripts](https://github.com/app-generator/django-tasks-manager/tree/main/django_tm/celery_scripts) defined in `CELERY_SCRIPTS_DIR` (CFG parameter)
15 | - [check-db-health.py](https://github.com/app-generator/django-tasks-manager/blob/main/django_tm/celery_scripts/check-db-health.py) (sample)
16 |
17 |
18 |
19 | 
20 |
21 |
22 |
23 | ## How to use it
24 |
25 |
26 |
27 | > **Install the package** via `PIP`
28 |
29 | ```bash
30 | $ pip install django-tasks-manager
31 | // OR
32 | $ pip install git+https://github.com/app-generator/django-tasks-manager.git
33 | ```
34 |
35 |
36 |
37 | > **Include the new routing**
38 |
39 | ```python
40 | # core/urls.py
41 |
42 | from django.urls import path, include # <-- UPDATE: Add 'include' HELPER
43 |
44 | urlpatterns = [
45 | ...
46 |
47 | path("", include("django_tm.urls")), # <-- New Routes
48 |
49 | ...
50 | ]
51 | ```
52 |
53 |
54 |
55 | > Create **Scrips & LOGS** directories - The Recomended place is in the root of the project:
56 |
57 | ```bash
58 | $ mkdir celery_scripts # Used in Settings -> CELERY_SCRIPTS_DIR
59 | $ mkdir celery_logs # Used in Settings -> CELERY_SCRIPTS_DIR
60 | ```
61 |
62 | - Make sure the user that executes the app has write permission.
63 | - Copy the [sample scripts](./django_tm/celery_scripts) in the scripts directory.
64 | - All scripts will be available in the UI, ready to be executed by the manager.
65 |
66 |
67 |
68 | > **Update Configuration** Add `os` object import
69 |
70 | ```python
71 | import os # <-- NEW
72 | ```
73 |
74 |
75 |
76 | > **Update Configuration**: Include the new APPS
77 |
78 | ```python
79 | INSTALLED_APPS = [
80 | ...
81 | 'django_tm', # Django Tasks Manager # <-- NEW
82 | 'django_celery_results', # Django Celery Results # <-- NEW
83 | ]
84 | ```
85 |
86 |
87 |
88 | > **Update Configuration**: Include the new templates
89 |
90 | ```python
91 | TEMPLATE_DIR_TASKS = os.path.join(BASE_DIR, "django_tm/templates") # <-- NEW
92 |
93 | TEMPLATES = [
94 | {
95 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
96 | 'DIRS': [TEMPLATE_DIR_TASKS], # <-- Updated
97 | 'APP_DIRS': True,
98 | },
99 | ]
100 | ```
101 |
102 |
103 |
104 | > **Update Configuration**: New **CELERY_** Section
105 |
106 | ```python
107 |
108 | #############################################################
109 | # Celery configurations
110 | # https://docs.celeryq.dev/en/stable/django/first-steps-with-django.html
111 |
112 | # !!!
113 | # BASE_DIR points to the ROOT of the project
114 | # Note: make sure you have 'os' object imported
115 | # !!!
116 |
117 | # !!!
118 | # BASE_DIR = os.path.dirname(os.path.dirname(__file__))
119 | # !!!
120 |
121 | # Working Directories required write permission
122 | CELERY_SCRIPTS_DIR = os.path.join(BASE_DIR, "celery_scripts" )
123 | CELERY_LOGS_DIR = os.path.join(BASE_DIR, "celery_logs" )
124 |
125 | CELERY_BROKER_URL = os.environ.get("CELERY_BROKER", "redis://localhost:6379")
126 | CELERY_RESULT_BACKEND = os.environ.get("CELERY_BROKER", "redis://localhost:6379")
127 |
128 | CELERY_TASK_TRACK_STARTED = True
129 | CELERY_TASK_TIME_LIMIT = 30 * 60
130 | CELERY_CACHE_BACKEND = "django-cache"
131 | CELERY_RESULT_BACKEND = "django-db"
132 | CELERY_RESULT_EXTENDED = True
133 | CELERY_RESULT_EXPIRES = 60*60*24*30 # Results expire after 1 month
134 | CELERY_ACCEPT_CONTENT = ["json"]
135 | CELERY_TASK_SERIALIZER = 'json'
136 | CELERY_RESULT_SERIALIZER = 'json'
137 |
138 | #############################################################
139 | #############################################################
140 |
141 | ```
142 |
143 |
144 |
145 |
146 | > **Start the App**
147 |
148 | ```bash
149 | $ # Set up the database
150 | $ python manage.py makemigrations
151 | $ python manage.py migrate
152 | $
153 | $ # Create the superuser
154 | $ python manage.py createsuperuser
155 | $
156 | $ # Start the application (development mode)
157 | $ python manage.py runserver # default port 8000
158 | ```
159 |
160 | - **Authenticate** as `superuser`
161 | - Access the `Tasks` page: `http://127.0.0.1:8000/tasks`
162 |
163 |
164 |
165 | > **Start the Celery Manager** (another terminal) & **Update Environment**
166 |
167 | Export `DJANGO_SETTINGS_MODULE` using the value provided in `manage.py`
168 |
169 | ```bash
170 | $ export DJANGO_SETTINGS_MODULE=cfg.settings
171 | ```
172 |
173 | The value used export should be taken from `manage.py`:
174 |
175 | ```python
176 | def main():
177 |
178 | """Run administrative tasks."""
179 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cfg.settings") # <-- VALUE to be exported
180 |
181 | try:
182 | from django.core.management import execute_from_command_line
183 | except ImportError as exc:
184 | ...
185 | ```
186 |
187 |
188 |
189 | **Note**: `Redis` server is expected on port `6379` (default). In case Redis runs on other `PORT`, please update the configuration: `CELERY_BROKER_URL` and `CELERY_RESULT_BACKEND`.
190 |
191 | ```bash
192 | $ celery --app=django_tm.celery.app worker --loglevel=info
193 | ```
194 |
195 |
196 |
197 | ## Screens
198 |
199 | > `View all tasks`
200 |
201 | 
202 |
203 |
204 |
205 | > `View Task LOG`
206 |
207 | 
208 |
209 |
210 |
211 | ## Links & Resources
212 |
213 | - Free [Support](https://appseed.us/support/) via Email and Discord
214 | - [Django Tasks Manager](https://github.com/app-generator/sample-django-tasks-manager) - `free sample` that explains:
215 | - `Project Creation` (minimal files added)
216 | - `Install` & Generate `Django` Core
217 | - Install `Django-TM` (this package)
218 | - `Update Configuration`
219 | - `Start the app`
220 | - Use the `Tasks Manager`
221 |
222 |
223 |
224 | ---
225 | [Django Tasks Manager](https://appseed.us/developer-tools/django-tasks-manager/) - Open-source library provided by **[AppSeed](https://appseed.us/)**
226 |
--------------------------------------------------------------------------------
/django_tm/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | """
3 | Copyright (c) 2019 - present AppSeed.us
4 | """
5 |
--------------------------------------------------------------------------------
/django_tm/admin.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | """
3 | Copyright (c) 2019 - present AppSeed.us
4 | """
5 |
6 | from django.contrib import admin
7 |
8 | # Register your models here.
9 |
--------------------------------------------------------------------------------
/django_tm/apps.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | """
3 | Copyright (c) 2019 - present AppSeed.us
4 | """
5 |
6 | from django.apps import AppConfig
7 |
8 | class TasksConfig(AppConfig):
9 |
10 | """
11 | See https://docs.djangoproject.com/en/2.1/ref/applications/#django.apps.AppConfig
12 | """
13 |
14 | name = "django_tm"
15 | label = "django_tm"
16 | verbose_name = "Django Tasks Manager"
17 |
18 | def ready(self):
19 | from . import signals
20 |
--------------------------------------------------------------------------------
/django_tm/celery.py:
--------------------------------------------------------------------------------
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(' ')
--------------------------------------------------------------------------------
/django_tm/celery_logs/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 |
--------------------------------------------------------------------------------
/django_tm/celery_scripts/check-db-health.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """
3 | Copyright (c) 2019 - present AppSeed.us
4 | """
5 |
6 | import os
7 | import sys
8 |
9 | def main():
10 |
11 | try:
12 |
13 | print(' EXEC -> ' + os.path.basename(__file__))
14 |
15 | # Unix ErrCode
16 | exit(0)
17 |
18 | except Exception as e:
19 |
20 | print( 'Err: ' + str( e ) )
21 | exit(1)
22 |
23 | if __name__ == '__main__':
24 | main()
25 |
--------------------------------------------------------------------------------
/django_tm/celery_scripts/check-disk-free.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """
3 | Copyright (c) 2019 - present AppSeed.us
4 | """
5 |
6 | import os
7 | import sys
8 |
9 | def main():
10 |
11 | try:
12 |
13 | print(' EXEC -> ' + os.path.basename(__file__))
14 |
15 | # Unix ErrCode
16 | exit(0)
17 |
18 | except Exception as e:
19 |
20 | print( 'Err: ' + str( e ) )
21 | exit(1)
22 |
23 | if __name__ == '__main__':
24 | main()
25 |
--------------------------------------------------------------------------------
/django_tm/celery_scripts/clean-database.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """
3 | Copyright (c) 2019 - present AppSeed.us
4 | """
5 |
6 | import os
7 | import sys
8 |
9 | def main():
10 |
11 | try:
12 |
13 | print(' EXEC -> ' + os.path.basename(__file__))
14 |
15 | # Unix ErrCode
16 | exit(0)
17 |
18 | except Exception as e:
19 |
20 | print( 'Err: ' + str( e ) )
21 | exit(1)
22 |
23 | if __name__ == '__main__':
24 | main()
25 |
--------------------------------------------------------------------------------
/django_tm/config.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | """
3 | Copyright (c) 2019 - present AppSeed.us
4 | """
5 |
6 | from django.apps import AppConfig
7 |
8 | class MyConfig(AppConfig):
9 | name = 'django_tm'
10 | label = 'django_tm'
11 |
--------------------------------------------------------------------------------
/django_tm/migrations/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | """
3 | Copyright (c) 2019 - present AppSeed.us
4 | """
5 |
--------------------------------------------------------------------------------
/django_tm/models.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | """
3 | Copyright (c) 2019 - present AppSeed.us
4 | """
5 |
6 | from django.db import models
7 |
8 | # Create your models here.
9 |
10 |
--------------------------------------------------------------------------------
/django_tm/signals.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | """
3 | Copyright (c) 2019 - present AppSeed.us
4 | """
5 |
6 | import os, json
7 |
8 | from django_celery_results.models import TaskResult
9 | from django.db.models.signals import post_save, pre_save
10 | from django.dispatch import receiver
11 | from django.conf import settings
12 |
13 |
14 | @receiver(pre_save, sender=TaskResult)
15 | def pre_result_updated(sender, **kwargs):
16 | '''
17 | Before a task result is saved, the following checks and updates are carried out if task is complete:
18 | - Check if error key & value exist in result.result, if yes upate instance.status=`FAILURE` and instance.traceback=`logs produced during task execution`
19 | '''
20 | if 'instance' not in kwargs:
21 | return
22 | instance = kwargs.pop("instance")
23 | if instance.status != "STARTED":
24 | result = json.loads(instance.result)
25 | if result.get("error"):
26 | instance.status = "FAILURE"
27 | instance.traceback = result.get("logs")
28 |
29 |
30 | @receiver(post_save, sender=TaskResult)
31 | def post_result_updated(sender, **kwargs):
32 | """
33 | After a task is completed, we store its logs in a file.
34 | """
35 | if 'instance' not in kwargs:
36 | return
37 | instance = kwargs.pop("instance")
38 | if instance.status != "STARTED":
39 | add_log(instance)
40 |
41 |
42 | def add_log(result: TaskResult):
43 | """
44 | Adds log file to celery logs with formatted date as name.
45 | :param result TaskResult: Result of executed celery task
46 | :rtype: None
47 | """
48 | if not result.task_name:
49 | return
50 |
51 | try:
52 |
53 | log_file_path = os.path.join(
54 | settings.CELERY_LOGS_DIR, f"{result.date_created.strftime(r'%Y%m%d-%H_%M')}-{result.task_id}-{result.status}.log")
55 |
56 | with open(log_file_path, "w+") as f:
57 | if result.status == "FAILURE":
58 | f.writelines([result.result, result.traceback])
59 | else:
60 | f.write(result.result)
61 |
62 | f.close()
63 |
64 | except Exception as e:
65 | print( 'Logging ERR: ' + str( e) )
66 | return
67 |
--------------------------------------------------------------------------------
/django_tm/tasks.py:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | """
3 | Copyright (c) 2019 - present AppSeed.us
4 | """
5 |
6 | import os, time, subprocess
7 | from os import listdir
8 | from os.path import isfile, join
9 |
10 | from .celery import app
11 | from celery.contrib.abortable import AbortableTask
12 |
13 | from django.contrib.auth.models import User
14 | from django.conf import settings
15 |
16 | def get_scripts():
17 | """
18 | Returns all scripts from 'ROOT_DIR/celery_scripts'
19 | """
20 | try:
21 | scripts = [f for f in listdir(settings.CELERY_SCRIPTS_DIR) if isfile(join(settings.CELERY_SCRIPTS_DIR, f))]
22 | return scripts, None
23 | except Exception as e:
24 | return None, 'Error CELERY_SCRIPTS_DIR: ' + str( e )
25 |
26 | @app.task(bind=True, base=AbortableTask)
27 | def users_in_db(self, data: dict):
28 | """
29 | Outputs all users in DB
30 | :param data dict: contains data needed for task execution.
31 | :rtype: None
32 | """
33 | users = User.objects.all()
34 | return {"output": "\n".join([u.email for u in users])}
35 |
36 | @app.task(bind=True, base=AbortableTask)
37 | def execute_script(self, data: dict):
38 | """
39 | This task executes scripts found in settings.CELERY_SCRIPTS_DIR and logs are later generated and stored in settings.CELERY_LOGS_DIR
40 | :param data dict: contains data needed for task execution. Example `input` which is the script to be executed.
41 | :rtype: None
42 | """
43 | script = data.get("input")
44 |
45 | scripts, ErrInfo = get_scripts()
46 |
47 | if script and script in scripts:
48 | # Executing related script
49 | script_path = os.path.join(settings.CELERY_SCRIPTS_DIR, script)
50 | process = subprocess.Popen(
51 | f"python {script_path}", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
52 | time.sleep(8)
53 | error = False
54 | if process.wait() == 0: # If script execution successfull
55 | logs = process.stdout.read().decode()
56 | else:
57 | logs = process.stderr.read().decode()
58 | error = True
59 |
60 | return {"logs": logs, "input": script, "error": error, "output": ""}
61 |
--------------------------------------------------------------------------------
/django_tm/templates/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/app-generator/django-tasks-manager/923dd1c0e61d460ea13070ce8cc8671177c1bcbb/django_tm/templates/static/.gitkeep
--------------------------------------------------------------------------------
/django_tm/templates/tasks/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
28 | Superusers are able to create/cancel tasks.
29 |
30 | Ordinary users can only view execution logs and running tasks (no other interactions allowed).
31 |
34 | No scripts detected - Please update the configuration (CELERY_SCRIPTS_DIR, CELERY_LOGS_DIR) 35 |
36 | {% endif %} 37 | 38 |Name | 56 |Script | 57 |STATE | 58 |59 | Input 60 | | 61 |62 | Latest EXEC 63 | | 64 | {% if request.user.is_superuser %} 65 |Action | 66 | {% endif %} 67 |
---|
Task | 201 |Input | 202 |Status | 203 |Start TS | 204 |End TS | 205 |Output | 206 |Logs | 207 |
---|---|---|---|---|---|---|
214 |
215 |
224 |
216 |
223 | 217 | {{result.id}} - {{result.task_name}} 218 |219 |220 | {{result.task_id}} 221 | 222 | |
225 |
226 |
227 | {{result|get_result_field:"input"}} 228 | |
229 |
230 |
231 | 236 | {{result.status}} 237 | 238 | |
239 |
240 |
241 | {{result.date_created|date_format}} 242 | |
243 |
244 |
245 | {{result.date_done|date_format}} 246 | |
247 |
248 |
249 | {{result|get_result_field:"output"}} 250 | |
251 |
252 | 253 | View LOG 254 | 255 | |
256 |
257 |