├── myproject ├── core │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── admin.py │ ├── tests.py │ ├── apps.py │ ├── urls.py │ ├── views.py │ ├── tasks.py │ ├── templates │ │ ├── includes │ │ │ └── nav.html │ │ ├── base.html │ │ └── index.html │ └── static │ │ └── js │ │ └── bootstrap-notify.min.js ├── __init__.py ├── wsgi.py ├── celery.py ├── urls.py └── settings.py ├── Dockerfile ├── config └── nginx │ └── app.conf ├── manage.py ├── requirements.txt ├── contrib └── env_gen.py ├── docker-compose.yml ├── README.md └── .gitignore /myproject/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /myproject/core/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /myproject/core/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /myproject/__init__.py: -------------------------------------------------------------------------------- 1 | from .celery import app as celery_app 2 | 3 | __all__ = ['celery_app'] 4 | -------------------------------------------------------------------------------- /myproject/core/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /myproject/core/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /myproject/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | name = 'myproject.core' 6 | -------------------------------------------------------------------------------- /myproject/core/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from myproject.core import views as v 4 | 5 | app_name = 'core' 6 | 7 | 8 | urlpatterns = [ 9 | path('', v.index, name='index'), 10 | path('task/print_numbers/', v.run_task, name='run_task'), 11 | ] 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | ENV DJANGO_ENV dev 5 | ENV DOCKER_CONTAINER 1 6 | 7 | RUN mkdir /app 8 | WORKDIR /app 9 | EXPOSE 8000 10 | 11 | ADD requirements.txt . 12 | RUN pip install -U pip && pip install -r requirements.txt 13 | 14 | COPY .env . 15 | COPY manage.py . 16 | COPY myproject myproject 17 | 18 | RUN python manage.py collectstatic --noinput 19 | # CMD gunicorn myproject.wsgi:application -b 0.0.0.0:8000 20 | -------------------------------------------------------------------------------- /myproject/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for myproject 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/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from dj_static import Cling 13 | from django.core.wsgi import get_wsgi_application 14 | 15 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') 16 | 17 | application = Cling(get_wsgi_application()) 18 | -------------------------------------------------------------------------------- /config/nginx/app.conf: -------------------------------------------------------------------------------- 1 | # define group app 2 | upstream app { 3 | # define server app 4 | server app:8000; 5 | } 6 | 7 | # server 8 | server { 9 | listen 8000; 10 | 11 | client_max_body_size 50M; 12 | 13 | # domain localhost 14 | 15 | server_name localhost; 16 | charset utf-8; 17 | 18 | # Handle favicon.ico 19 | location = /favicon.ico { 20 | return 204; 21 | access_log off; 22 | log_not_found off; 23 | } 24 | 25 | # Django app 26 | location / { 27 | proxy_pass http://app; 28 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 29 | proxy_set_header Host $host; 30 | proxy_redirect off; 31 | } 32 | } -------------------------------------------------------------------------------- /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', 'myproject.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 | -------------------------------------------------------------------------------- /myproject/celery.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals 2 | 3 | import logging 4 | import os 5 | 6 | from celery import Celery 7 | 8 | # from django.conf import settings 9 | 10 | logger = logging.getLogger("Celery") 11 | 12 | # set the default Django settings module for the 'celery' program. 13 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') 14 | 15 | app = Celery('myproject') 16 | 17 | app.config_from_object('django.conf:settings', namespace='CELERY') 18 | 19 | # app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) 20 | app.autodiscover_tasks() 21 | 22 | 23 | @app.task(bind=True, ignore_result=True) 24 | def debug_task(self): 25 | print(f'Request: {self.request!r}') 26 | -------------------------------------------------------------------------------- /myproject/core/views.py: -------------------------------------------------------------------------------- 1 | from decouple import config 2 | from django.http import HttpResponseRedirect 3 | from django.shortcuts import render 4 | from django.urls import reverse 5 | from django_celery_results.models import TaskResult 6 | 7 | from .tasks import print_numbers 8 | 9 | 10 | def index(request): 11 | template_name = 'index.html' 12 | object_list = TaskResult.objects.all() 13 | my_key = config('KEY') 14 | cluster = config('CLUSTER') 15 | context = { 16 | 'object_list': object_list, 17 | 'my_key': my_key, 18 | 'cluster': cluster, 19 | } 20 | return render(request, template_name, context) 21 | 22 | 23 | def run_task(request): 24 | print_numbers.delay(10) 25 | url = 'core:index' 26 | return HttpResponseRedirect(reverse(url)) 27 | -------------------------------------------------------------------------------- /myproject/core/tasks.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import pusher 4 | from celery import shared_task 5 | from celery.utils.log import get_task_logger 6 | from decouple import config 7 | 8 | logger = get_task_logger(__name__) 9 | 10 | 11 | channels_client = pusher.Pusher( 12 | app_id=config('APP_ID'), 13 | key=config('KEY'), 14 | secret=config('SECRET'), 15 | cluster=config('CLUSTER'), 16 | ssl=True 17 | ) 18 | 19 | 20 | @shared_task(queue='fila1') 21 | def print_numbers(max_number): 22 | logger.info('Creating the task..') 23 | 24 | _sec = 3 25 | logger.info('Aguardar {} seg'.format(_sec)) 26 | time.sleep(_sec) 27 | for i in range(max_number): 28 | logger.info(i) 29 | 30 | logger.info('Finishing task..') 31 | channels_client.trigger( 32 | 'my-channel', 'my-event', 33 | {'message': 'Finalizada com sucesso!'} 34 | ) 35 | return True 36 | -------------------------------------------------------------------------------- /myproject/urls.py: -------------------------------------------------------------------------------- 1 | """myproject URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import include, path 18 | 19 | urlpatterns = [ 20 | path('', include('myproject.core.urls')), 21 | path('admin/', admin.site.urls), 22 | ] 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | amqp==5.3.1 2 | asgiref==3.8.1 3 | billiard==4.2.1 4 | celery==5.4.0 5 | certifi==2024.8.30 6 | cffi==1.17.1 7 | charset-normalizer==3.4.0 8 | click==8.1.7 9 | click-didyoumean==0.3.1 10 | click-plugins==1.1.1 11 | click-repl==0.3.0 12 | cryptography==44.0.0 13 | dj-database-url==2.3.0 14 | dj-static==0.0.6 15 | Django==5.1.4 16 | django-celery-results==2.5.1 17 | django-extensions==3.2.3 18 | flower==2.0.1 19 | gunicorn==23.0.0 20 | humanize==4.11.0 21 | idna==3.10 22 | kombu==5.4.2 23 | ndg-httpsclient==0.5.1 24 | packaging==24.2 25 | prometheus_client==0.21.1 26 | prompt_toolkit==3.0.48 27 | psycopg2-binary==2.9.10 28 | pusher==3.3.2 29 | pyasn1==0.6.1 30 | pycparser==2.22 31 | PyNaCl==1.5.0 32 | pyOpenSSL==24.3.0 33 | python-dateutil==2.9.0.post0 34 | python-decouple==3.8 35 | pytz==2024.2 36 | redis==5.2.1 37 | requests==2.32.3 38 | six==1.17.0 39 | sqlparse==0.5.2 40 | static3==0.7.0 41 | tornado==6.4.2 42 | typing_extensions==4.12.2 43 | tzdata==2024.2 44 | urllib3==2.2.3 45 | vine==5.1.0 46 | wcwidth==0.2.13 47 | -------------------------------------------------------------------------------- /myproject/core/templates/includes/nav.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /contrib/env_gen.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python SECRET_KEY generator. 3 | """ 4 | import random 5 | 6 | chars = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!?@#$%^&*()" 7 | size = 50 8 | secret_key = "".join(random.sample(chars, size)) 9 | 10 | chars = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!?@#$%_" 11 | size = 20 12 | password = "".join(random.sample(chars, size)) 13 | 14 | CONFIG_STRING = """ 15 | DEBUG=True 16 | SECRET_KEY=%s 17 | ALLOWED_HOSTS=127.0.0.1,.localhost,0.0.0.0 18 | 19 | #DATABASE_URL=postgres://USER:PASSWORD@HOST:PORT/NAME 20 | POSTGRES_DB=django_celery_db 21 | POSTGRES_USER=postgres 22 | POSTGRES_PASSWORD=postgres 23 | #DB_HOST=localhost 24 | 25 | #DEFAULT_FROM_EMAIL= 26 | #EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend 27 | #EMAIL_HOST=localhost 28 | #EMAIL_PORT= 29 | #EMAIL_HOST_USER= 30 | #EMAIL_HOST_PASSWORD= 31 | #EMAIL_USE_TLS=True 32 | 33 | #Pusher 34 | APP_ID= 35 | KEY= 36 | SECRET= 37 | CLUSTER= 38 | """.strip() % (secret_key, password) 39 | 40 | # Writing our configuration file to '.env' 41 | with open('.env', 'w') as configfile: 42 | configfile.write(CONFIG_STRING) 43 | 44 | print('Success!') 45 | print('Type: cat .env') 46 | -------------------------------------------------------------------------------- /myproject/core/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% load static %} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Celery on Docker 11 | 12 | 13 | 14 | 15 | 16 | 21 | 22 | {% block css %}{% endblock css %} 23 | 24 | 25 | 26 | 27 | 28 | {% include "includes/nav.html" %} 29 | 30 |
31 | {% block content %}{% endblock content %} 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {% block js %}{% endblock js %} 46 | 47 | 48 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | db: 5 | image: postgres:16-alpine 6 | restart: always 7 | user: postgres # importante definir o usuário 8 | volumes: 9 | - pgdata:/var/lib/postgresql/data 10 | environment: 11 | - LC_ALL=C.UTF-8 12 | - POSTGRES_PASSWORD=postgres # senha padrão 13 | - POSTGRES_USER=postgres # usuário padrão 14 | - POSTGRES_DB=django_celery_db # necessário porque foi configurado assim no settings 15 | ports: 16 | - 5433:5432 # repare na porta externa 5433 17 | networks: 18 | - live 19 | 20 | redis: 21 | image: 'redis:alpine' 22 | hostname: redis 23 | ports: 24 | - 6379:6379 25 | networks: 26 | - live 27 | 28 | app: 29 | build: 30 | context: . 31 | dockerfile: Dockerfile 32 | hostname: app 33 | stdin_open: true 34 | expose: 35 | - '8000' 36 | volumes: 37 | - .env:/app/.env 38 | command: bash -c "python manage.py migrate && gunicorn myproject.wsgi:application -b 0.0.0.0:8000" 39 | depends_on: 40 | - redis 41 | - db 42 | networks: 43 | - live 44 | 45 | nginx: 46 | image: nginx 47 | hostname: nginx 48 | ports: 49 | - '82:8000' 50 | volumes: 51 | - ./config/nginx:/etc/nginx/conf.d 52 | depends_on: 53 | - app 54 | networks: 55 | - live 56 | 57 | celery: 58 | build: 59 | context: . 60 | dockerfile: Dockerfile 61 | command: "celery -A myproject worker -l info --queue=fila1" 62 | depends_on: 63 | - app 64 | - redis 65 | - db 66 | restart: on-failure 67 | networks: 68 | - live 69 | 70 | flower: 71 | build: 72 | context: . 73 | dockerfile: Dockerfile 74 | ports: 75 | - '5555:5555' 76 | command: "celery -A myproject flower --address=0.0.0.0" 77 | depends_on: 78 | - celery 79 | networks: 80 | - live 81 | 82 | volumes: 83 | pgdata: 84 | 85 | networks: 86 | live: -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-celery-redis-docker 2 | 3 | Test Django and Celery on Docker with Redis 4 | 5 | ## Como rodar o projeto? 6 | 7 | * Clone esse repositório. 8 | * Crie um virtualenv com Python 3. 9 | * Ative o virtualenv. 10 | * Instale as dependências. 11 | * Rode as migrações. 12 | 13 | ``` 14 | git clone https://github.com/rg3915/django-celery-redis-docker.git 15 | cd django-celery-redis-docker 16 | python3 -m venv .venv 17 | source .venv/bin/activate 18 | pip install -r requirements.txt 19 | python contrib/env_gen.py 20 | python manage.py migrate 21 | ``` 22 | 23 | ## Rodando com Docker 24 | 25 | ``` 26 | docker-compose up --build 27 | ``` 28 | 29 | Entre no container e rode as migrações: 30 | 31 | ``` 32 | docker container exec -ti ID python manage.py migrate 33 | docker container exec -ti ID python manage.py createsuperuser 34 | ``` 35 | 36 | Está rodando na porta `localhost:82`. Ou você pode digitar 37 | 38 | ``` 39 | docker container inspect ID 40 | ``` 41 | 42 | e pegar o `IPAddress`. 43 | 44 | ## Rodando o Celery 45 | 46 | Abra outro terminal com o virtualenv ativado e digite 47 | 48 | Dá pra fazer sem o `queue`. 49 | 50 | ``` 51 | # terminal 1 52 | celery --app=myproject worker --loglevel=INFO 53 | ``` 54 | 55 | Mas o `queue` define uma fila. 56 | 57 | ``` 58 | # terminal 1 59 | celery --app=myproject worker --loglevel=INFO --queues=fila1 60 | ``` 61 | 62 | ## Experimentando o flower 63 | 64 | O [flower](https://flower.readthedocs.io/en/latest/) serve pra monitorar o Celery em realtime. 65 | 66 | Rode num outro terminal o comando 67 | 68 | ``` 69 | # terminal 2 70 | celery -A myproject flower 71 | ``` 72 | 73 | Se for no Docker, pegue o ip do service flower 74 | 75 | ``` 76 | IP-ADDRESS:5555 77 | ``` 78 | 79 | Se quiser estressar o Celery e ver no monitor digite 80 | 81 | ``` 82 | for i in $(seq 10); do curl localhost:8000/task/print_numbers/; sleep 1; done 83 | ``` 84 | 85 | A url do monitor é http://localhost:5555/monitor 86 | 87 | 88 | ### Instalando e configurando django-celery-results 89 | 90 | https://django-celery-results.readthedocs.io/en/latest/ 91 | 92 | ``` 93 | python manage.py migrate django_celery_results 94 | ``` 95 | 96 | ### Links 97 | 98 | https://medium.com/@mdcg.dev/configurando-um-sistem-em-django-para-executar-tarefas-ass%C3%ADncronas-utilizando-celery-redis-e-53a30d0d2ec2 99 | 100 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 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 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /myproject/core/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 |
6 |
7 |

Teste Django e Celery no Docker com Redis

8 |

9 | 12 |

13 |
14 |
15 | 16 |
17 |

Task Results

18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {% for object in object_list %} 29 | 30 | 31 | 32 | 33 | 34 | 35 | {% endfor %} 36 | 37 |
Task IDTask NameCompleted DatetimeTask State
{{ object.task_id }}{{ object.task_name }}{{ object.date_done }}{{ object.status }}
38 |
39 | 40 | {% endblock content %} 41 | 42 | {% block js %} 43 | 44 | 100 | 101 | {% endblock js %} -------------------------------------------------------------------------------- /myproject/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for myproject project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.2.12. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | from decouple import Csv, config 16 | from dj_database_url import parse as dburl 17 | 18 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 19 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 20 | 21 | 22 | # Quick-start development settings - unsuitable for production 23 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 24 | 25 | # SECURITY WARNING: keep the secret key used in production secret! 26 | SECRET_KEY = config('SECRET_KEY') 27 | 28 | # SECURITY WARNING: don't run with debug turned on in production! 29 | DEBUG = config('DEBUG', default=False, cast=bool) 30 | 31 | ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv()) 32 | 33 | 34 | # Application definition 35 | 36 | INSTALLED_APPS = [ 37 | 'django.contrib.admin', 38 | 'django.contrib.auth', 39 | 'django.contrib.contenttypes', 40 | 'django.contrib.sessions', 41 | 'django.contrib.messages', 42 | 'django.contrib.staticfiles', 43 | # Others apps 44 | 'django_extensions', 45 | 'django_celery_results', 46 | # My apps 47 | 'myproject.core', 48 | ] 49 | 50 | MIDDLEWARE = [ 51 | 'django.middleware.security.SecurityMiddleware', 52 | 'django.contrib.sessions.middleware.SessionMiddleware', 53 | 'django.middleware.common.CommonMiddleware', 54 | 'django.middleware.csrf.CsrfViewMiddleware', 55 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 56 | 'django.contrib.messages.middleware.MessageMiddleware', 57 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 58 | ] 59 | 60 | ROOT_URLCONF = 'myproject.urls' 61 | 62 | TEMPLATES = [ 63 | { 64 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 65 | 'DIRS': [], 66 | 'APP_DIRS': True, 67 | 'OPTIONS': { 68 | 'context_processors': [ 69 | 'django.template.context_processors.debug', 70 | 'django.template.context_processors.request', 71 | 'django.contrib.auth.context_processors.auth', 72 | 'django.contrib.messages.context_processors.messages', 73 | ], 74 | }, 75 | }, 76 | ] 77 | 78 | WSGI_APPLICATION = 'myproject.wsgi.application' 79 | 80 | # Database 81 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 82 | 83 | # default_dburl = 'sqlite:///' + os.path.join(BASE_DIR, 'db.sqlite3') 84 | # DATABASES = { 85 | # 'default': config('DATABASE_URL', default=default_dburl, cast=dburl), 86 | # } 87 | 88 | DATABASES = { 89 | 'default': { 90 | 'ENGINE': 'django.db.backends.postgresql', 91 | 'NAME': config('POSTGRES_DB', 'django_celery_db'), 92 | 'USER': config('POSTGRES_USER', 'postgres'), 93 | 'PASSWORD': config('POSTGRES_PASSWORD', 'postgres'), 94 | # é o nome do service 'db' no docker-compose. 95 | 'HOST': config('DB_HOST', 'db'), 96 | 'PORT': config('DB_PORT', '5432'), 97 | } 98 | } 99 | 100 | 101 | # Password validation 102 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 103 | 104 | AUTH_PASSWORD_VALIDATORS = [ 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 107 | }, 108 | { 109 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 110 | }, 111 | { 112 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 113 | }, 114 | { 115 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 116 | }, 117 | ] 118 | 119 | 120 | # Internationalization 121 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 122 | 123 | LANGUAGE_CODE = 'pt-br' 124 | 125 | TIME_ZONE = 'America/Sao_Paulo' 126 | 127 | USE_I18N = True 128 | 129 | USE_L10N = True 130 | 131 | USE_TZ = True 132 | 133 | # My config 134 | USE_DOCKER = False 135 | 136 | # CELERY 137 | if USE_DOCKER: 138 | CELERY_BROKER_URL = 'redis://redis:6379' # Docker 139 | else: 140 | CELERY_BROKER_URL = 'redis://localhost:6379' # Local 141 | 142 | CELERY_ACCEPT_CONTENT = ['application/json'] 143 | CELERY_TASK_SERIALIZER = 'json' 144 | CELERY_RESULT_SERIALIZER = 'json' 145 | 146 | # Django-Celery-results 147 | CELERY_RESULT_BACKEND = 'django-db' 148 | # CELERY_RESULT_BACKEND = 'redis://redis:6379' 149 | 150 | # Static files (CSS, JavaScript, Images) 151 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 152 | 153 | STATIC_ROOT = os.path.join(BASE_DIR, 'static') 154 | STATIC_URL = '/static/' 155 | -------------------------------------------------------------------------------- /myproject/core/static/js/bootstrap-notify.min.js: -------------------------------------------------------------------------------- 1 | !function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t("object"==typeof exports?require("jquery"):jQuery)}(function(t){function s(s){var e=!1;return t('[data-notify="container"]').each(function(i,n){var a=t(n),o=a.find('[data-notify="title"]').text().trim(),r=a.find('[data-notify="message"]').html().trim(),l=o===t("
"+s.settings.content.title+"
").html().trim(),d=r===t("
"+s.settings.content.message+"
").html().trim(),g=a.hasClass("alert-"+s.settings.type);return l&&d&&g&&(e=!0),!e}),e}function e(e,n,a){var o={content:{message:"object"==typeof n?n.message:n,title:n.title?n.title:"",icon:n.icon?n.icon:"",url:n.url?n.url:"#",target:n.target?n.target:"-"}};a=t.extend(!0,{},o,a),this.settings=t.extend(!0,{},i,a),this._defaults=i,"-"===this.settings.content.target&&(this.settings.content.target=this.settings.url_target),this.animations={start:"webkitAnimationStart oanimationstart MSAnimationStart animationstart",end:"webkitAnimationEnd oanimationend MSAnimationEnd animationend"},"number"==typeof this.settings.offset&&(this.settings.offset={x:this.settings.offset,y:this.settings.offset}),(this.settings.allow_duplicates||!this.settings.allow_duplicates&&!s(this))&&this.init()}var i={element:"body",position:null,type:"info",allow_dismiss:!0,allow_duplicates:!0,newest_on_top:!1,showProgressbar:!1,placement:{from:"top",align:"right"},offset:20,spacing:10,z_index:1031,delay:5e3,timer:1e3,url_target:"_blank",mouse_over:null,animate:{enter:"animated fadeInDown",exit:"animated fadeOutUp"},onShow:null,onShown:null,onClose:null,onClosed:null,icon_type:"class",template:''};String.format=function(){for(var t=arguments[0],s=1;s .progress-bar').removeClass("progress-bar-"+t.settings.type),t.settings.type=i[n],this.$ele.addClass("alert-"+i[n]).find('[data-notify="progressbar"] > .progress-bar').addClass("progress-bar-"+i[n]);break;case"icon":var a=this.$ele.find('[data-notify="icon"]');"class"===t.settings.icon_type.toLowerCase()?a.removeClass(t.settings.content.icon).addClass(i[n]):(a.is("img")||a.find("img"),a.attr("src",i[n]));break;case"progress":var o=t.settings.delay-t.settings.delay*(i[n]/100);this.$ele.data("notify-delay",o),this.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i[n]).css("width",i[n]+"%");break;case"url":this.$ele.find('[data-notify="url"]').attr("href",i[n]);break;case"target":this.$ele.find('[data-notify="url"]').attr("target",i[n]);break;default:this.$ele.find('[data-notify="'+n+'"]').html(i[n])}var r=this.$ele.outerHeight()+parseInt(t.settings.spacing)+parseInt(t.settings.offset.y);t.reposition(r)},close:function(){t.close()}}},buildNotify:function(){var s=this.settings.content;this.$ele=t(String.format(this.settings.template,this.settings.type,s.title,s.message,s.url,s.target)),this.$ele.attr("data-notify-position",this.settings.placement.from+"-"+this.settings.placement.align),this.settings.allow_dismiss||this.$ele.find('[data-notify="dismiss"]').css("display","none"),(this.settings.delay<=0&&!this.settings.showProgressbar||!this.settings.showProgressbar)&&this.$ele.find('[data-notify="progressbar"]').remove()},setIcon:function(){"class"===this.settings.icon_type.toLowerCase()?this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon):this.$ele.find('[data-notify="icon"]').is("img")?this.$ele.find('[data-notify="icon"]').attr("src",this.settings.content.icon):this.$ele.find('[data-notify="icon"]').append('Notify Icon')},styleDismiss:function(){this.$ele.find('[data-notify="dismiss"]').css({position:"absolute",right:"10px",top:"5px",zIndex:this.settings.z_index+2})},styleURL:function(){this.$ele.find('[data-notify="url"]').css({backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)",height:"100%",left:0,position:"absolute",top:0,width:"100%",zIndex:this.settings.z_index+1})},placement:function(){var s=this,e=this.settings.offset.y,i={display:"inline-block",margin:"0px auto",position:this.settings.position?this.settings.position:"body"===this.settings.element?"fixed":"absolute",transition:"all .5s ease-in-out",zIndex:this.settings.z_index},n=!1,a=this.settings;switch(t('[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])').each(function(){e=Math.max(e,parseInt(t(this).css(a.placement.from))+parseInt(t(this).outerHeight())+parseInt(a.spacing))}),this.settings.newest_on_top===!0&&(e=this.settings.offset.y),i[this.settings.placement.from]=e+"px",this.settings.placement.align){case"left":case"right":i[this.settings.placement.align]=this.settings.offset.x+"px";break;case"center":i.left=0,i.right=0}this.$ele.css(i).addClass(this.settings.animate.enter),t.each(Array("webkit-","moz-","o-","ms-",""),function(t,e){s.$ele[0].style[e+"AnimationIterationCount"]=1}),t(this.settings.element).append(this.$ele),this.settings.newest_on_top===!0&&(e=parseInt(e)+parseInt(this.settings.spacing)+this.$ele.outerHeight(),this.reposition(e)),t.isFunction(s.settings.onShow)&&s.settings.onShow.call(this.$ele),this.$ele.one(this.animations.start,function(){n=!0}).one(this.animations.end,function(){s.$ele.removeClass(s.settings.animate.enter),t.isFunction(s.settings.onShown)&&s.settings.onShown.call(this)}),setTimeout(function(){n||t.isFunction(s.settings.onShown)&&s.settings.onShown.call(this)},600)},bind:function(){var s=this;if(this.$ele.find('[data-notify="dismiss"]').on("click",function(){s.close()}),this.$ele.mouseover(function(){t(this).data("data-hover","true")}).mouseout(function(){t(this).data("data-hover","false")}),this.$ele.data("data-hover","false"),this.settings.delay>0){s.$ele.data("notify-delay",s.settings.delay);var e=setInterval(function(){var t=parseInt(s.$ele.data("notify-delay"))-s.settings.timer;if("false"===s.$ele.data("data-hover")&&"pause"===s.settings.mouse_over||"pause"!=s.settings.mouse_over){var i=(s.settings.delay-t)/s.settings.delay*100;s.$ele.data("notify-delay",t),s.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i).css("width",i+"%")}t<=-s.settings.timer&&(clearInterval(e),s.close())},s.settings.timer)}},close:function(){var s=this,e=parseInt(this.$ele.css(this.settings.placement.from)),i=!1;this.$ele.attr("data-closing","true").addClass(this.settings.animate.exit),s.reposition(e),t.isFunction(s.settings.onClose)&&s.settings.onClose.call(this.$ele),this.$ele.one(this.animations.start,function(){i=!0}).one(this.animations.end,function(){t(this).remove(),t.isFunction(s.settings.onClosed)&&s.settings.onClosed.call(this)}),setTimeout(function(){i||(s.$ele.remove(),s.settings.onClosed&&s.settings.onClosed(s.$ele))},600)},reposition:function(s){var e=this,i='[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])',n=this.$ele.nextAll(i);this.settings.newest_on_top===!0&&(n=this.$ele.prevAll(i)),n.each(function(){t(this).css(e.settings.placement.from,s),s=parseInt(s)+parseInt(e.settings.spacing)+t(this).outerHeight()})}}),t.notify=function(t,s){var i=new e(this,t,s);return i.notify},t.notifyDefaults=function(s){return i=t.extend(!0,{},i,s)},t.notifyClose=function(s){"warning"===s&&(s="danger"),"undefined"==typeof s||"all"===s?t("[data-notify]").find('[data-notify="dismiss"]').trigger("click"):"success"===s||"info"===s||"warning"===s||"danger"===s?t(".alert-"+s+"[data-notify]").find('[data-notify="dismiss"]').trigger("click"):s?t(s+"[data-notify]").find('[data-notify="dismiss"]').trigger("click"):t('[data-notify-position="'+s+'"]').find('[data-notify="dismiss"]').trigger("click")},t.notifyCloseExcept=function(s){"warning"===s&&(s="danger"),"success"===s||"info"===s||"warning"===s||"danger"===s?t("[data-notify]").not(".alert-"+s).find('[data-notify="dismiss"]').trigger("click"):t("[data-notify]").not(s).find('[data-notify="dismiss"]').trigger("click")}}); --------------------------------------------------------------------------------