├── .gitignore ├── .pre-commit-config.yaml ├── .python-version ├── README.md ├── celery_project ├── __init__.py ├── asgi.py ├── celery.py ├── settings.py ├── urls.py └── wsgi.py ├── contact_form ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── tasks.py ├── templates │ └── contact_form │ │ └── contact_form.html ├── urls.py └── views.py ├── manage.py ├── pyproject.toml └── uv.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Django # 2 | *.log 3 | *.pot 4 | *.pyc 5 | __pycache__ 6 | *.sqlite3 7 | media 8 | staticfiles 9 | 10 | # Backup files # 11 | *.bak 12 | 13 | # Python # 14 | *.py[cod] 15 | *$py.class 16 | 17 | # Distribution / packaging 18 | .Python build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | .pytest_cache/ 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | 52 | # Jupyter Notebook 53 | .ipynb_checkpoints 54 | 55 | # celery 56 | celerybeat-schedule.* 57 | 58 | # SageMath parsed files 59 | *.sage.py 60 | 61 | # Environments 62 | .env 63 | .venv 64 | env/ 65 | venv/ 66 | ENV/ 67 | env.bak/ 68 | venv.bak/ 69 | 70 | # mkdocs documentation 71 | /site 72 | 73 | # mypy 74 | .mypy_cache/ 75 | 76 | # sftp configuration file 77 | sftp-config.json 78 | 79 | # Package control specific files Package 80 | Control.last-run 81 | Control.ca-list 82 | Control.ca-bundle 83 | Control.system-ca-bundle 84 | GitHub.sublime-settings 85 | 86 | # Visual Studio Code # 87 | .vscode/* 88 | !.vscode/settings.json 89 | !.vscode/tasks.json 90 | !.vscode/launch.json 91 | !.vscode/extensions.json 92 | .history 93 | 94 | # Macos yuck 95 | .DS_Store 96 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_language_version: 2 | python: python3.13 3 | 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v5.0.0 7 | hooks: 8 | - id: trailing-whitespace 9 | - id: end-of-file-fixer 10 | - id: check-yaml 11 | - id: check-toml 12 | - id: check-added-large-files 13 | - id: check-case-conflict 14 | - id: check-merge-conflict 15 | - repo: https://github.com/adamchainz/django-upgrade 16 | rev: "1.22.1" # replace with latest tag on GitHub 17 | hooks: 18 | - id: django-upgrade 19 | args: [--target-version, "5.1"] # Replace with Django version 20 | - repo: https://github.com/astral-sh/ruff-pre-commit 21 | # Ruff version. 22 | rev: v0.7.0 23 | hooks: 24 | - id: ruff 25 | args: [--fix, --exit-non-zero-on-fix] 26 | - repo: https://github.com/thibaudcolas/curlylint 27 | rev: v0.13.1 28 | hooks: 29 | - id: curlylint 30 | exclude: 'templates/snippets/timetable_search\.html' 31 | - repo: https://github.com/adamchainz/djade-pre-commit 32 | rev: "1.3.0" 33 | hooks: 34 | - id: djade 35 | args: [--target-version, "5.1"] 36 | - repo: https://github.com/rtts/djhtml 37 | rev: 3.0.7 38 | hooks: 39 | - id: djhtml 40 | args: 41 | - --tabwidth=2 42 | exclude: markdown_editor.html 43 | - repo: https://github.com/pre-commit/mirrors-prettier 44 | rev: v4.0.0-alpha.8 45 | hooks: 46 | - id: prettier 47 | types_or: 48 | - css 49 | - xml 50 | additional_dependencies: 51 | - prettier@2.5.1 52 | - "@prettier/plugin-xml@1.2.0" 53 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Basic Django Celery Example 2 | 3 | To try this out, follow these steps. 4 | 5 | - Clone the repository and change directory into the cloned directory: 6 | 7 | ```bash 8 | git clone git@github.com:stuartmaxwell/django-celery-example.git 9 | cd django-celery-example 10 | ``` 11 | 12 | - Before you start, you'll need a Redis server. If you don't have one the easiest way is through Docker with the following command: 13 | 14 | ```bash 15 | docker run --name my-redis-server -d -p 127.0.0.1:6379:6379 redis 16 | ``` 17 | 18 | - Create a virtual environment and activate it. 19 | 20 | ```bash 21 | python3 -m venv .venv 22 | source .venv/bin/activate 23 | ``` 24 | 25 | - Install the project requirements: 26 | 27 | ```bash 28 | pip install . 29 | ``` 30 | 31 | - Add some email configuration to the `settings.py` file. I use Amazon SES but substitute with our own SMTP settings. 32 | 33 | ```python 34 | # Email 35 | EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" 36 | EMAIL_HOST = "" # add your own settings here 37 | EMAIL_PORT = "" # add your own settings here 38 | EMAIL_HOST_USER = "" # add your own settings here 39 | EMAIL_HOST_PASSWORD = "" # add your own settings here 40 | EMAIL_USE_TLS = True # add your own settings here 41 | DEFAULT_FROM_EMAIL = "you@example.com" # your email address 42 | ``` 43 | 44 | - Run the database migrations and run the test server: 45 | 46 | ```bash 47 | python3 manage.py migrate 48 | python3 manage.py runserver 49 | ``` 50 | 51 | - Then in a second terminal window, navigate to your project directory, activate the virtual environment again, and then launch the Celery process - it should print out some debug information and then a `ready` message to indicate it has connected to Redis successfully and is waiting for tasks: 52 | 53 | ```bash 54 | python3 -m celery -A celery_project worker -l info -P solo 55 | ``` 56 | 57 | - Browse to and you should see a contact form. Try sending a message to see if it works. 58 | 59 | - Switch back to your terminal windows with the Celery process and you'll see some updates. It can take several seconds to send the email, but that was all done by the celery process and didn't affect the page loading time after submitting the form. 60 | -------------------------------------------------------------------------------- /celery_project/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals 2 | 3 | # This will make sure the app is always imported when 4 | # Django starts so that shared_task will use this app. 5 | from .celery import app as celery_app 6 | 7 | __all__ = ("celery_app",) 8 | -------------------------------------------------------------------------------- /celery_project/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for celery_project project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "celery_project.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /celery_project/celery.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals 2 | 3 | import os 4 | 5 | from celery import Celery 6 | 7 | # set the default Django settings module for the 'celery' program. 8 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "celery_project.settings") 9 | 10 | app = Celery("celery_project") 11 | 12 | # Using a string here means the worker doesn't have to serialize 13 | # the configuration object to child processes. 14 | # - namespace='CELERY' means all celery-related configuration keys 15 | # should have a `CELERY_` prefix. 16 | app.config_from_object("django.conf:settings", namespace="CELERY") 17 | 18 | # Load task modules from all registered Django app configs. 19 | app.autodiscover_tasks() 20 | 21 | 22 | @app.task(bind=True) 23 | def debug_task(self): 24 | print("Request: {0!r}".format(self.request)) 25 | -------------------------------------------------------------------------------- /celery_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for celery_project project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "Replace with your own secret key" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | "contact_form.apps.ContactFormConfig", 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | "django.middleware.security.SecurityMiddleware", 45 | "django.contrib.sessions.middleware.SessionMiddleware", 46 | "django.middleware.common.CommonMiddleware", 47 | "django.middleware.csrf.CsrfViewMiddleware", 48 | "django.contrib.auth.middleware.AuthenticationMiddleware", 49 | "django.contrib.messages.middleware.MessageMiddleware", 50 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 51 | ] 52 | 53 | ROOT_URLCONF = "celery_project.urls" 54 | 55 | TEMPLATES = [ 56 | { 57 | "BACKEND": "django.template.backends.django.DjangoTemplates", 58 | "DIRS": [], 59 | "APP_DIRS": True, 60 | "OPTIONS": { 61 | "context_processors": [ 62 | "django.template.context_processors.debug", 63 | "django.template.context_processors.request", 64 | "django.contrib.auth.context_processors.auth", 65 | "django.contrib.messages.context_processors.messages", 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = "celery_project.wsgi.application" 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 76 | 77 | DATABASES = { 78 | "default": { 79 | "ENGINE": "django.db.backends.sqlite3", 80 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"), 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 91 | }, 92 | { 93 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 94 | }, 95 | { 96 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 97 | }, 98 | { 99 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/3.0/topics/i18n/ 106 | 107 | LANGUAGE_CODE = "en-us" 108 | 109 | TIME_ZONE = "UTC" 110 | 111 | USE_I18N = True 112 | 113 | 114 | USE_TZ = True 115 | 116 | 117 | # Static files (CSS, JavaScript, Images) 118 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 119 | 120 | STATIC_URL = "/static/" 121 | 122 | # Celery - prefix with CELERY_ 123 | CELERY_BROKER_URL = "redis://localhost:6379" 124 | CELERY_RESULT_BACKEND = CELERY_BROKER_URL 125 | CELERY_ACCEPT_CONTENT = ["json"] 126 | CELERY_TASK_SERIALIZER = "json" 127 | CELERY_TASK_TRACK_STARTED = True 128 | CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True 129 | 130 | # Email 131 | EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" 132 | EMAIL_HOST = "" # add your own settings here 133 | EMAIL_PORT = "" # add your own settings here 134 | EMAIL_HOST_USER = "" # add your own settings here 135 | EMAIL_HOST_PASSWORD = "" # add your own settings here 136 | EMAIL_USE_TLS = True # add your own settings here 137 | DEFAULT_FROM_EMAIL = "you@example.com" # your email address 138 | -------------------------------------------------------------------------------- /celery_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | 4 | urlpatterns = [ 5 | path("admin/", admin.site.urls), 6 | path("", include("contact_form.urls")), 7 | ] 8 | -------------------------------------------------------------------------------- /celery_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for celery_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "celery_project.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /contact_form/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartmaxwell/django-celery-example/776b41ed0ce43995e329ba4037da2ef95787f4e1/contact_form/__init__.py -------------------------------------------------------------------------------- /contact_form/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import ContactForm 4 | 5 | 6 | @admin.register(ContactForm) 7 | class ContactFormAdmin(admin.ModelAdmin): 8 | list_display = ("email", "name", "subject", "created_on") 9 | -------------------------------------------------------------------------------- /contact_form/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ContactFormConfig(AppConfig): 5 | name = "contact_form" 6 | -------------------------------------------------------------------------------- /contact_form/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import ModelForm 2 | 3 | from .models import ContactForm 4 | 5 | 6 | class ContactFormModelForm(ModelForm): 7 | class Meta: 8 | model = ContactForm 9 | fields = ["name", "email", "subject", "message"] 10 | -------------------------------------------------------------------------------- /contact_form/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-04-30 09:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="ContactForm", 15 | fields=[ 16 | ( 17 | "id", 18 | models.AutoField( 19 | auto_created=True, 20 | primary_key=True, 21 | serialize=False, 22 | verbose_name="ID", 23 | ), 24 | ), 25 | ("email", models.EmailField(max_length=254)), 26 | ("name", models.CharField(max_length=64)), 27 | ("subject", models.CharField(max_length=64)), 28 | ("message", models.TextField()), 29 | ("created_on", models.DateTimeField(auto_now=True)), 30 | ], 31 | options={ 32 | "verbose_name": "contact form message", 33 | "verbose_name_plural": "contact form messages", 34 | "db_table": "contactform", 35 | }, 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /contact_form/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stuartmaxwell/django-celery-example/776b41ed0ce43995e329ba4037da2ef95787f4e1/contact_form/migrations/__init__.py -------------------------------------------------------------------------------- /contact_form/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class ContactForm(models.Model): 5 | email = models.EmailField() 6 | name = models.CharField(max_length=64) 7 | subject = models.CharField(max_length=64) 8 | message = models.TextField() 9 | created_on = models.DateTimeField(auto_now=True) 10 | 11 | class Meta: 12 | db_table = "contactform" 13 | verbose_name = "contact form message" 14 | verbose_name_plural = "contact form messages" 15 | 16 | def __str__(self): 17 | return self.email 18 | -------------------------------------------------------------------------------- /contact_form/tasks.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals 2 | 3 | from celery import shared_task 4 | from celery.utils.log import get_task_logger 5 | from django.core.mail import BadHeaderError, send_mail 6 | 7 | from celery_project.settings import DEFAULT_FROM_EMAIL 8 | 9 | logger = get_task_logger(__name__) 10 | 11 | 12 | @shared_task(bind=True, max_retries=3) 13 | def send_email_task(self, to, subject, message): 14 | logger.info(f"from={DEFAULT_FROM_EMAIL}, {to=}, {subject=}, {message=}") 15 | try: 16 | logger.info("About to send_mail") 17 | send_mail(subject, message, DEFAULT_FROM_EMAIL, [to]) 18 | except BadHeaderError: 19 | logger.info("BadHeaderError") 20 | except Exception as e: 21 | logger.error(e) 22 | -------------------------------------------------------------------------------- /contact_form/templates/contact_form/contact_form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Contact Form 6 | 7 | 8 |

Contact Form

9 |
10 | {% csrf_token %} 11 | {{ form.as_p }} 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /contact_form/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import ContactFormView 4 | 5 | app_name = "contact_form" 6 | urlpatterns = [ 7 | path("", ContactFormView.as_view(), name="contact_form"), 8 | ] 9 | -------------------------------------------------------------------------------- /contact_form/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import FormView 2 | 3 | from .forms import ContactFormModelForm 4 | from .tasks import send_email_task 5 | 6 | 7 | class ContactFormView(FormView): 8 | form_class = ContactFormModelForm 9 | template_name = "contact_form/contact_form.html" 10 | success_url = "/" 11 | 12 | def form_valid(self, form): 13 | form.save() 14 | self.send_email(form.cleaned_data) 15 | 16 | return super().form_valid(form) 17 | 18 | def send_email(self, valid_data): 19 | email = valid_data["email"] 20 | subject = "Contact form sent from website" 21 | message = ( 22 | f"You have received a contact form.\n" 23 | f"Email: {valid_data['email']}\n" 24 | f"Name: {valid_data['name']}\n" 25 | f"Subject: {valid_data['subject']}\n" 26 | f"{valid_data['message']}\n" 27 | ) 28 | send_email_task.delay( 29 | email, subject, message, 30 | ) 31 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "celery_project.settings") 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == "__main__": 21 | main() 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "django-celery-example" 3 | version = "0.1.0" 4 | description = "Basic Django Celery Example" 5 | readme = "README.md" 6 | requires-python = ">=3.9" 7 | dependencies = ["celery>=5.4.0", "django>=4.2.0", "redis>=5.2.0"] 8 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.9" 3 | 4 | [[package]] 5 | name = "amqp" 6 | version = "5.2.0" 7 | source = { registry = "https://pypi.org/simple" } 8 | dependencies = [ 9 | { name = "vine" }, 10 | ] 11 | sdist = { url = "https://files.pythonhosted.org/packages/32/2c/6eb09fbdeb3c060b37bd33f8873832897a83e7a428afe01aad333fc405ec/amqp-5.2.0.tar.gz", hash = "sha256:a1ecff425ad063ad42a486c902807d1482311481c8ad95a72694b2975e75f7fd", size = 128754 } 12 | wheels = [ 13 | { url = "https://files.pythonhosted.org/packages/b3/f0/8e5be5d5e0653d9e1d02b1144efa33ff7d2963dfad07049e02c0fa9b2e8d/amqp-5.2.0-py3-none-any.whl", hash = "sha256:827cb12fb0baa892aad844fd95258143bce4027fdac4fccddbc43330fd281637", size = 50917 }, 14 | ] 15 | 16 | [[package]] 17 | name = "asgiref" 18 | version = "3.8.1" 19 | source = { registry = "https://pypi.org/simple" } 20 | dependencies = [ 21 | { name = "typing-extensions", marker = "python_full_version < '3.11'" }, 22 | ] 23 | sdist = { url = "https://files.pythonhosted.org/packages/29/38/b3395cc9ad1b56d2ddac9970bc8f4141312dbaec28bc7c218b0dfafd0f42/asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590", size = 35186 } 24 | wheels = [ 25 | { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828 }, 26 | ] 27 | 28 | [[package]] 29 | name = "async-timeout" 30 | version = "5.0.0" 31 | source = { registry = "https://pypi.org/simple" } 32 | sdist = { url = "https://files.pythonhosted.org/packages/61/1f/44d9efc904bbe4d9967433522b691a9c4f1e81c2c64fbe44bad63d5de646/async_timeout-5.0.0.tar.gz", hash = "sha256:49675ec889daacfe65ff66d2dde7dd1447a6f4b2f23721022e4ba121f8772a85", size = 8951 } 33 | wheels = [ 34 | { url = "https://files.pythonhosted.org/packages/f3/df/32887f4a54676cf151301faed0261fbae969284cd673744371da67452967/async_timeout-5.0.0-py3-none-any.whl", hash = "sha256:904719a4bd6e0520047d0ddae220aabee67b877f7ca17bf8cea20f67f6247ae0", size = 6064 }, 35 | ] 36 | 37 | [[package]] 38 | name = "billiard" 39 | version = "4.2.1" 40 | source = { registry = "https://pypi.org/simple" } 41 | sdist = { url = "https://files.pythonhosted.org/packages/7c/58/1546c970afcd2a2428b1bfafecf2371d8951cc34b46701bea73f4280989e/billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f", size = 155031 } 42 | wheels = [ 43 | { url = "https://files.pythonhosted.org/packages/30/da/43b15f28fe5f9e027b41c539abc5469052e9d48fd75f8ff094ba2a0ae767/billiard-4.2.1-py3-none-any.whl", hash = "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb", size = 86766 }, 44 | ] 45 | 46 | [[package]] 47 | name = "celery" 48 | version = "5.4.0" 49 | source = { registry = "https://pypi.org/simple" } 50 | dependencies = [ 51 | { name = "billiard" }, 52 | { name = "click" }, 53 | { name = "click-didyoumean" }, 54 | { name = "click-plugins" }, 55 | { name = "click-repl" }, 56 | { name = "kombu" }, 57 | { name = "python-dateutil" }, 58 | { name = "tzdata" }, 59 | { name = "vine" }, 60 | ] 61 | sdist = { url = "https://files.pythonhosted.org/packages/8a/9c/cf0bce2cc1c8971bf56629d8f180e4ca35612c7e79e6e432e785261a8be4/celery-5.4.0.tar.gz", hash = "sha256:504a19140e8d3029d5acad88330c541d4c3f64c789d85f94756762d8bca7e706", size = 1575692 } 62 | wheels = [ 63 | { url = "https://files.pythonhosted.org/packages/90/c4/6a4d3772e5407622feb93dd25c86ce3c0fee746fa822a777a627d56b4f2a/celery-5.4.0-py3-none-any.whl", hash = "sha256:369631eb580cf8c51a82721ec538684994f8277637edde2dfc0dacd73ed97f64", size = 425983 }, 64 | ] 65 | 66 | [[package]] 67 | name = "click" 68 | version = "8.1.7" 69 | source = { registry = "https://pypi.org/simple" } 70 | dependencies = [ 71 | { name = "colorama", marker = "platform_system == 'Windows'" }, 72 | ] 73 | sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } 74 | wheels = [ 75 | { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, 76 | ] 77 | 78 | [[package]] 79 | name = "click-didyoumean" 80 | version = "0.3.1" 81 | source = { registry = "https://pypi.org/simple" } 82 | dependencies = [ 83 | { name = "click" }, 84 | ] 85 | sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089 } 86 | wheels = [ 87 | { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631 }, 88 | ] 89 | 90 | [[package]] 91 | name = "click-plugins" 92 | version = "1.1.1" 93 | source = { registry = "https://pypi.org/simple" } 94 | dependencies = [ 95 | { name = "click" }, 96 | ] 97 | sdist = { url = "https://files.pythonhosted.org/packages/5f/1d/45434f64ed749540af821fd7e42b8e4d23ac04b1eda7c26613288d6cd8a8/click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", size = 8164 } 98 | wheels = [ 99 | { url = "https://files.pythonhosted.org/packages/e9/da/824b92d9942f4e472702488857914bdd50f73021efea15b4cad9aca8ecef/click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8", size = 7497 }, 100 | ] 101 | 102 | [[package]] 103 | name = "click-repl" 104 | version = "0.3.0" 105 | source = { registry = "https://pypi.org/simple" } 106 | dependencies = [ 107 | { name = "click" }, 108 | { name = "prompt-toolkit" }, 109 | ] 110 | sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449 } 111 | wheels = [ 112 | { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289 }, 113 | ] 114 | 115 | [[package]] 116 | name = "colorama" 117 | version = "0.4.6" 118 | source = { registry = "https://pypi.org/simple" } 119 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 120 | wheels = [ 121 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 122 | ] 123 | 124 | [[package]] 125 | name = "django" 126 | version = "4.2.16" 127 | source = { registry = "https://pypi.org/simple" } 128 | dependencies = [ 129 | { name = "asgiref" }, 130 | { name = "sqlparse" }, 131 | { name = "tzdata", marker = "sys_platform == 'win32'" }, 132 | ] 133 | sdist = { url = "https://files.pythonhosted.org/packages/65/d8/a607ee443b54a4db4ad28902328b906ae6218aa556fb9b3ac45c0bcb313d/Django-4.2.16.tar.gz", hash = "sha256:6f1616c2786c408ce86ab7e10f792b8f15742f7b7b7460243929cb371e7f1dad", size = 10436023 } 134 | wheels = [ 135 | { url = "https://files.pythonhosted.org/packages/94/2c/6b6c7e493d5ea789416918658ebfa16be7a64c77610307497ed09a93c8c4/Django-4.2.16-py3-none-any.whl", hash = "sha256:1ddc333a16fc139fd253035a1606bb24261951bbc3a6ca256717fa06cc41a898", size = 7992936 }, 136 | ] 137 | 138 | [[package]] 139 | name = "django-celery-example" 140 | version = "0.1.0" 141 | source = { virtual = "." } 142 | dependencies = [ 143 | { name = "celery" }, 144 | { name = "django" }, 145 | { name = "redis" }, 146 | ] 147 | 148 | [package.metadata] 149 | requires-dist = [ 150 | { name = "celery", specifier = ">=5.4.0" }, 151 | { name = "django", specifier = ">=4.2.0" }, 152 | { name = "redis", specifier = ">=5.2.0" }, 153 | ] 154 | 155 | [[package]] 156 | name = "kombu" 157 | version = "5.4.2" 158 | source = { registry = "https://pypi.org/simple" } 159 | dependencies = [ 160 | { name = "amqp" }, 161 | { name = "typing-extensions", marker = "python_full_version < '3.10'" }, 162 | { name = "tzdata" }, 163 | { name = "vine" }, 164 | ] 165 | sdist = { url = "https://files.pythonhosted.org/packages/38/4d/b93fcb353d279839cc35d0012bee805ed0cf61c07587916bfc35dbfddaf1/kombu-5.4.2.tar.gz", hash = "sha256:eef572dd2fd9fc614b37580e3caeafdd5af46c1eff31e7fba89138cdb406f2cf", size = 442858 } 166 | wheels = [ 167 | { url = "https://files.pythonhosted.org/packages/87/ec/7811a3cf9fdfee3ee88e54d08fcbc3fabe7c1b6e4059826c59d7b795651c/kombu-5.4.2-py3-none-any.whl", hash = "sha256:14212f5ccf022fc0a70453bb025a1dcc32782a588c49ea866884047d66e14763", size = 201349 }, 168 | ] 169 | 170 | [[package]] 171 | name = "prompt-toolkit" 172 | version = "3.0.48" 173 | source = { registry = "https://pypi.org/simple" } 174 | dependencies = [ 175 | { name = "wcwidth" }, 176 | ] 177 | sdist = { url = "https://files.pythonhosted.org/packages/2d/4f/feb5e137aff82f7c7f3248267b97451da3644f6cdc218edfe549fb354127/prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90", size = 424684 } 178 | wheels = [ 179 | { url = "https://files.pythonhosted.org/packages/a9/6a/fd08d94654f7e67c52ca30523a178b3f8ccc4237fce4be90d39c938a831a/prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e", size = 386595 }, 180 | ] 181 | 182 | [[package]] 183 | name = "python-dateutil" 184 | version = "2.9.0.post0" 185 | source = { registry = "https://pypi.org/simple" } 186 | dependencies = [ 187 | { name = "six" }, 188 | ] 189 | sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } 190 | wheels = [ 191 | { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, 192 | ] 193 | 194 | [[package]] 195 | name = "redis" 196 | version = "5.2.0" 197 | source = { registry = "https://pypi.org/simple" } 198 | dependencies = [ 199 | { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, 200 | ] 201 | sdist = { url = "https://files.pythonhosted.org/packages/53/17/2f4a87ffa4cd93714cf52edfa3ea94589e9de65f71e9f99cbcfa84347a53/redis-5.2.0.tar.gz", hash = "sha256:0b1087665a771b1ff2e003aa5bdd354f15a70c9e25d5a7dbf9c722c16528a7b0", size = 4607878 } 202 | wheels = [ 203 | { url = "https://files.pythonhosted.org/packages/12/f5/ffa560ecc4bafbf25f7961c3d6f50d627a90186352e27e7d0ba5b1f6d87d/redis-5.2.0-py3-none-any.whl", hash = "sha256:ae174f2bb3b1bf2b09d54bf3e51fbc1469cf6c10aa03e21141f51969801a7897", size = 261428 }, 204 | ] 205 | 206 | [[package]] 207 | name = "six" 208 | version = "1.16.0" 209 | source = { registry = "https://pypi.org/simple" } 210 | sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } 211 | wheels = [ 212 | { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, 213 | ] 214 | 215 | [[package]] 216 | name = "sqlparse" 217 | version = "0.5.1" 218 | source = { registry = "https://pypi.org/simple" } 219 | sdist = { url = "https://files.pythonhosted.org/packages/73/82/dfa23ec2cbed08a801deab02fe7c904bfb00765256b155941d789a338c68/sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e", size = 84502 } 220 | wheels = [ 221 | { url = "https://files.pythonhosted.org/packages/5d/a5/b2860373aa8de1e626b2bdfdd6df4355f0565b47e51f7d0c54fe70faf8fe/sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4", size = 44156 }, 222 | ] 223 | 224 | [[package]] 225 | name = "typing-extensions" 226 | version = "4.12.2" 227 | source = { registry = "https://pypi.org/simple" } 228 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 229 | wheels = [ 230 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 231 | ] 232 | 233 | [[package]] 234 | name = "tzdata" 235 | version = "2024.2" 236 | source = { registry = "https://pypi.org/simple" } 237 | sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 } 238 | wheels = [ 239 | { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, 240 | ] 241 | 242 | [[package]] 243 | name = "vine" 244 | version = "5.1.0" 245 | source = { registry = "https://pypi.org/simple" } 246 | sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980 } 247 | wheels = [ 248 | { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636 }, 249 | ] 250 | 251 | [[package]] 252 | name = "wcwidth" 253 | version = "0.2.13" 254 | source = { registry = "https://pypi.org/simple" } 255 | sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } 256 | wheels = [ 257 | { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, 258 | ] 259 | --------------------------------------------------------------------------------