├── proj ├── __init__.py ├── urls.py ├── asgi.py ├── celery.py ├── wsgi.py └── settings.py ├── status_nfe ├── __init__.py ├── migrations │ ├── __init__.py │ └── 0001_initial.py ├── apps.py ├── serializers.py ├── urls.py ├── tasks.py ├── models.py ├── viewsets.py └── utils.py ├── .dockerignore ├── requirements.txt ├── Dockerfile ├── manage.py ├── README.md ├── docker-compose.yml └── .gitignore /proj/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /status_nfe/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .git -------------------------------------------------------------------------------- /status_nfe/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /proj/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | 3 | 4 | urlpatterns = [ 5 | path('api/v1/', include('status_nfe.urls')), 6 | ] 7 | -------------------------------------------------------------------------------- /status_nfe/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class StatusNfeConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'status_nfe' 7 | -------------------------------------------------------------------------------- /status_nfe/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from status_nfe.models import StatusNfe 4 | 5 | 6 | class StatusNfeSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = StatusNfe 9 | fields = '__all__' 10 | -------------------------------------------------------------------------------- /status_nfe/urls.py: -------------------------------------------------------------------------------- 1 | from rest_framework import routers 2 | from status_nfe.viewsets import StatusNfeViewSet 3 | 4 | 5 | router = routers.DefaultRouter() 6 | 7 | router.register('statusnfe', StatusNfeViewSet, basename='statusnfe') 8 | 9 | urlpatterns = router.urls 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django==3.2.19 2 | djangorestframework==3.14.0 3 | psycopg2-binary==2.9.5 4 | celery[redis] 5 | django-celery-beat==2.4.0 6 | django-celery-results==2.4.0 7 | django-environ==0.9.0 8 | django-redis 9 | beautifulsoup4==4.11.1 10 | requests 11 | gunicorn 12 | gevent -------------------------------------------------------------------------------- /proj/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for proj 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.2/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', 'proj.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /proj/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | from celery import Celery 3 | 4 | 5 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings') 6 | 7 | app = Celery('proj') 8 | 9 | app.config_from_object('django.conf:settings', namespace='CELERY') 10 | 11 | app.autodiscover_tasks() 12 | 13 | 14 | app.conf.beat_schedule = { 15 | 'status_portal_nfe': { 16 | 'task': 'task_status_portal_nfe', 17 | 'schedule': 60 * 10 # 10 minutes 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /proj/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for proj 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.2/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', 'proj.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | 5 | LABEL name="statusnfe" 6 | LABEL version="0.0.1" 7 | LABEL description="Status NFe" 8 | LABEL org.lucrorural.vendor="Lucro Rural" 9 | 10 | ENV LC_ALL=C.UTF-8 11 | ENV LANG=C.UTF-8 12 | 13 | COPY ./requirements.txt /app/requirements.txt 14 | RUN pip install -r /app/requirements.txt 15 | 16 | COPY . /app/ 17 | WORKDIR /app/ 18 | 19 | EXPOSE 8000 20 | 21 | CMD ["gunicorn", "-b", "0.0.0.0:8000", "proj.wsgi:application", "-k", "gevent"] -------------------------------------------------------------------------------- /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 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /status_nfe/tasks.py: -------------------------------------------------------------------------------- 1 | from celery import shared_task 2 | from django.core.cache import cache 3 | 4 | from status_nfe.models import StatusNfe 5 | from status_nfe.utils import DisponibilidadeNFe 6 | 7 | 8 | @shared_task(name='task_status_portal_nfe') 9 | def status_portal_nfe(): 10 | disp_nfe = DisponibilidadeNFe() 11 | data = disp_nfe.get_status() 12 | 13 | for item in data: 14 | autorizador = item['autorizador'] 15 | ultima_verificacao = item['ultima_verificacao'] 16 | 17 | obj = StatusNfe.objects.filter(autorizador=autorizador) 18 | if obj.exists(): 19 | obj.update(**item) 20 | else: 21 | obj.create(**item) 22 | 23 | print(f'update {autorizador} {ultima_verificacao}') 24 | 25 | cache.clear() 26 | return 'OK' 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # status-nfe 2 | 3 | `status-nfe` é um serviço que consulta como estão os status dos Webservices da Sefaz de todos os Estados do Brasil através do Portal NFe (http://www.nfe.fazenda.gov.br/portal/disponibilidade.aspx) 4 | 5 | ## Como funciona 6 | 7 | * A consulta ao site do Portal NFe é feita a cada 10 minutos com os serviços do Celery e Celery Beat. 8 | * O resultado da consulta é enviada para a fila de processamento do Redis. 9 | * O status de cada serviço e de cada UF é gravada no banco de dados Postgres. 10 | * Após a consulta e gravação é criado ou atualizado o cache dos dados no Redis. 11 | * O endpoint da consulta: `api/v1/statusnfe` 12 | 13 | ## Iniciando os serviços 14 | 15 | * Constroe as imagens de todos os serviços envolvidos: `docker-compose up -d --build` 16 | * Atualiza os modelos do banco de dados: `docker-compose exec web python manage.py migrate` 17 | * Inicia todos os serviços: `docker-compose up` 18 | * Monitoramento do cache: `docker exec -it container_id redis-cli monitor` 19 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '1' 2 | 3 | services: 4 | 5 | database: 6 | image: postgres:13-alpine 7 | volumes: 8 | - postgres_data:/var/lib/postgresql/data/ 9 | environment: 10 | POSTGRES_DB: statusnfe 11 | POSTGRES_USER: status 12 | POSTGRES_PASSWORD: status 13 | expose: 14 | - 5432 15 | ports: 16 | - "5432:5432" 17 | 18 | redis: 19 | image: redis:6-alpine 20 | ports: 21 | - "6379:6379" 22 | 23 | web: 24 | build: . 25 | volumes: 26 | - .:/app 27 | ports: 28 | - "8000:8000" 29 | depends_on: 30 | - database 31 | - redis 32 | 33 | celery: 34 | build: . 35 | command: celery -A proj worker -l info 36 | volumes: 37 | - .:/app 38 | depends_on: 39 | - database 40 | - redis 41 | 42 | celery-beat: 43 | build: . 44 | command: celery -A proj beat -l info 45 | volumes: 46 | - .:/app 47 | depends_on: 48 | - database 49 | - redis 50 | 51 | volumes: 52 | postgres_data: -------------------------------------------------------------------------------- /status_nfe/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | 4 | 5 | class StatusNfe(models.Model): 6 | autorizador = models.CharField(max_length=20, blank=True, null=True) 7 | autorizacao = models.CharField(max_length=20, blank=True, null=True) 8 | retorno_autorizacao = models.CharField(max_length=20, blank=True, null=True) 9 | inutilizacao = models.CharField(max_length=20, blank=True, null=True) 10 | consulta_protocolo = models.CharField(max_length=20, blank=True, null=True) 11 | status_servico = models.CharField(max_length=20, blank=True, null=True) 12 | tempo_medio = models.CharField(max_length=20, blank=True, null=True) 13 | consulta_cadastro = models.CharField(max_length=20, blank=True, null=True) 14 | recepcao_evento = models.CharField(max_length=20, blank=True, null=True) 15 | ultima_verificacao = models.DateTimeField(null=True, blank=True, default=timezone.now) 16 | 17 | def __str__(self): 18 | return f'{self.autorizador} - autorizacao={self.autorizacao} ultima={self.ultima_verificacao}' 19 | 20 | class Meta: 21 | managed = True 22 | db_table = 'statusnfe' 23 | verbose_name = 'statusnfe' 24 | verbose_name_plural = 'statusnfe' 25 | -------------------------------------------------------------------------------- /status_nfe/viewsets.py: -------------------------------------------------------------------------------- 1 | from django.utils.decorators import method_decorator 2 | from django.views.decorators.cache import cache_page 3 | 4 | from rest_framework.filters import SearchFilter, OrderingFilter 5 | from rest_framework.viewsets import ReadOnlyModelViewSet 6 | 7 | from status_nfe.serializers import StatusNfeSerializer 8 | from status_nfe.models import StatusNfe 9 | 10 | 11 | webservice = { 12 | 'AM': 'AM', 13 | 'BA': 'BA', 14 | 'GO': 'GO', 15 | 'MG': 'MG', 16 | 'MS': 'MS', 17 | 'MT': 'MT', 18 | 'PE': 'PE', 19 | 'PR': 'PR', 20 | 'RS': 'RS', 21 | 'SP': 'SP', 22 | 'MA': 'SVAN', 23 | 'AC': 'SVRS', 24 | 'AL': 'SVRS', 25 | 'AP': 'SVRS', 26 | 'CE': 'SVRS', 27 | 'DF': 'SVRS', 28 | 'ES': 'SVRS', 29 | 'PA': 'SVRS', 30 | 'PB': 'SVRS', 31 | 'PI': 'SVRS', 32 | 'RJ': 'SVRS', 33 | 'RN': 'SVRS', 34 | 'RO': 'SVRS', 35 | 'RR': 'SVRS', 36 | 'SC': 'SVRS', 37 | 'SE': 'SVRS', 38 | 'TO': 'SVRS', 39 | } 40 | 41 | CACHE_TIME = 60 * 10 # 10 minutes 42 | 43 | @method_decorator(cache_page(CACHE_TIME), name='dispatch') 44 | class StatusNfeViewSet(ReadOnlyModelViewSet): 45 | serializer_class = StatusNfeSerializer 46 | queryset = StatusNfe.objects.all().order_by('autorizador') 47 | permission_classes = () 48 | pagination_class = None 49 | filter_backends = (SearchFilter, OrderingFilter,) 50 | filterset_fields = '__all__' 51 | ordering_fields = '__all__' 52 | 53 | def get_queryset(self): 54 | qs = self.queryset 55 | uf = self.request.query_params.get('uf', None) 56 | if uf: 57 | if uf.upper() not in webservice.keys(): 58 | return qs 59 | return qs.filter(autorizador=webservice[uf.upper()]) 60 | return qs -------------------------------------------------------------------------------- /status_nfe/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2022-07-13 18:35 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='StatusNfe', 17 | fields=[ 18 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('autorizador', models.CharField(blank=True, max_length=20, null=True)), 20 | ('autorizacao', models.CharField(blank=True, max_length=20, null=True)), 21 | ('retorno_autorizacao', models.CharField(blank=True, max_length=20, null=True)), 22 | ('inutilizacao', models.CharField(blank=True, max_length=20, null=True)), 23 | ('consulta_protocolo', models.CharField(blank=True, max_length=20, null=True)), 24 | ('status_servico', models.CharField(blank=True, max_length=20, null=True)), 25 | ('tempo_medio', models.CharField(blank=True, max_length=20, null=True)), 26 | ('consulta_cadastro', models.CharField(blank=True, max_length=20, null=True)), 27 | ('recepcao_evento', models.CharField(blank=True, max_length=20, null=True)), 28 | ('ultima_verificacao', models.DateTimeField(blank=True, default=django.utils.timezone.now, null=True)), 29 | ], 30 | options={ 31 | 'verbose_name': 'statusnfe', 32 | 'verbose_name_plural': 'statusnfe', 33 | 'db_table': 'statusnfe', 34 | 'managed': True, 35 | }, 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /status_nfe/utils.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | import requests 4 | from bs4 import BeautifulSoup 5 | 6 | 7 | class DisponibilidadeNFe(object): 8 | 9 | def __init__(self): 10 | self.__URL = 'https://www.nfe.fazenda.gov.br/portal/disponibilidade.aspx' 11 | 12 | self.headers = { 13 | 'Connection': 'keep-alive', 14 | 'Cache-Control': 'max-age=0', 15 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36', # noqa 16 | 'DNT': '1', 17 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', # noqa 18 | 'Accept-Encoding': 'gzip, deflate, br', 19 | 'Accept-Language': 'pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,mt;q=0.6,gl;q=0.5,he;q=0.4,ru;q=0.3,pl;q=0.2,la;q=0.1,es;q=0.1,fr;q=0.1,de;q=0.1,cy;q=0.1,und;q=0.1' # noqa 20 | } 21 | 22 | def _request(self): 23 | try: 24 | data = requests.get( 25 | self.__URL, 26 | headers=self.headers, 27 | timeout=None 28 | ) 29 | except Exception as error: 30 | raise error 31 | return data 32 | 33 | def get_status(self): 34 | request = self._request() 35 | html = request.content 36 | 37 | sefaz_data = [] 38 | # Fazer o parse do HTML 39 | try: 40 | soup = BeautifulSoup(html, 'html.parser') 41 | table = soup.find(id="ctl00_ContentPlaceHolder1_gdvDisponibilidade2") 42 | 43 | for tr in table.find_all("tr"): 44 | # Eliminar o cabeçalho 45 | if tr.th is not None: 46 | continue 47 | 48 | # Fazer parse do TD 49 | info = [] 50 | for td in tr.find_all("td"): 51 | if td.img is not None: 52 | info.append(td.img["src"].split("_")[1]) 53 | continue 54 | 55 | info.append(td.get_text()) 56 | 57 | autorizador = info[0] 58 | autorizacao = info[1] 59 | retorno_autorizacao = info[2] 60 | inutilizacao = info[3] 61 | consulta_protocolo = info[4] 62 | status_servico = info[5] 63 | tempo_medio = info[6] 64 | consulta_cadastro = info[7] 65 | recepcao_evento = info[8] 66 | ultima_verificacao = table.caption.span.get_text().split(" - ")[1].split(": ")[1] 67 | 68 | # “13/07/2022 16:17:21” -> YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ].'] 69 | ultima_verificacao_format = datetime.strptime(ultima_verificacao, "%d/%m/%Y %H:%M:%S") 70 | 71 | sefaz_data.append( 72 | { 73 | 'autorizador': autorizador, 74 | 'autorizacao': autorizacao, 75 | 'retorno_autorizacao': retorno_autorizacao, 76 | 'inutilizacao': inutilizacao, 77 | 'consulta_protocolo': consulta_protocolo, 78 | 'status_servico': status_servico, 79 | 'tempo_medio': tempo_medio, 80 | 'consulta_cadastro': consulta_cadastro, 81 | 'recepcao_evento': recepcao_evento, 82 | 'ultima_verificacao': ultima_verificacao_format 83 | } 84 | ) 85 | return sefaz_data 86 | 87 | except BaseException as err: 88 | return err 89 | -------------------------------------------------------------------------------- /proj/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | import environ 4 | 5 | 6 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 7 | # BASE_DIR = Path(__file__).resolve().parent.parent 8 | ROOT_DIR = environ.Path(__file__) - 2 9 | 10 | # always read .env file 11 | env = environ.Env() 12 | env.read_env(str(ROOT_DIR.path('.env'))) 13 | 14 | # Quick-start development settings - unsuitable for production 15 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 16 | 17 | # SECURITY WARNING: keep the secret key used in production secret! 18 | SECRET_KEY = 'django-insecure-1o91lmmc*%z^nh@zpj8^=3c6qqzz4$&sp9g5fu3%+0#d$0h_w2' 19 | 20 | SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 21 | SESSION_COOKIE_SECURE = True 22 | CSRF_COOKIE_SECURE = True 23 | SECURE_HSTS_SECONDS = 60 24 | SECURE_HSTS_INCLUDE_SUBDOMAINS = True 25 | SECURE_HSTS_PRELOAD = True 26 | SECURE_CONTENT_TYPE_NOSNIFF = True 27 | SECURE_BROWSER_XSS_FILTER = True 28 | SECURE_REFERRER_POLICY = 'origin-when-cross-origin' 29 | 30 | # SECURITY WARNING: don't run with debug turned on in production! 31 | DEBUG = True 32 | 33 | ALLOWED_HOSTS = ['*'] 34 | 35 | # Application definition 36 | 37 | INSTALLED_APPS = [ 38 | 'django.contrib.auth', 39 | 'django.contrib.contenttypes', 40 | 'django.contrib.sessions', 41 | 'django.contrib.messages', 42 | 'django.contrib.staticfiles', 43 | 'rest_framework', 44 | 'django_celery_beat', 45 | 'django_celery_results', 46 | 'status_nfe' 47 | ] 48 | 49 | CORS_ORIGIN_ALLOW_ALL = True 50 | 51 | CORS_EXPOSE_HEADERS = ( 52 | 'Access-Control-Allow-Origin: *', 53 | ) 54 | 55 | REST_FRAMEWORK = { 56 | 'REQUEST_DEFAULT_FORMAT': 'json', 57 | 'DEFAULT_RENDERER_CLASSES': ( 58 | 'rest_framework.renderers.JSONRenderer', 59 | ), 60 | } 61 | 62 | 63 | MIDDLEWARE = [ 64 | 'django.middleware.security.SecurityMiddleware', 65 | 'django.contrib.sessions.middleware.SessionMiddleware', 66 | 'django.middleware.common.CommonMiddleware', 67 | 'django.middleware.csrf.CsrfViewMiddleware', 68 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 69 | 'django.contrib.messages.middleware.MessageMiddleware', 70 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 71 | ] 72 | 73 | ROOT_URLCONF = 'proj.urls' 74 | 75 | TEMPLATES = [ 76 | { 77 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 78 | 'DIRS': [], 79 | 'APP_DIRS': True, 80 | 'OPTIONS': { 81 | 'context_processors': [ 82 | 'django.template.context_processors.debug', 83 | 'django.template.context_processors.request', 84 | 'django.contrib.auth.context_processors.auth', 85 | 'django.contrib.messages.context_processors.messages', 86 | ], 87 | }, 88 | }, 89 | ] 90 | 91 | WSGI_APPLICATION = 'proj.wsgi.application' 92 | 93 | 94 | # Database 95 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 96 | 97 | DATABASES = { 98 | 'default': { 99 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 100 | 'NAME': os.getenv('POSTGRES_DB'), 101 | 'USER': os.getenv('POSTGRES_USER'), 102 | 'PASSWORD': os.getenv('POSTGRES_PASSWORD'), 103 | 'HOST': 'database', 104 | 'PORT': 5432, 105 | } 106 | } 107 | 108 | 109 | # Password validation 110 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 111 | 112 | AUTH_PASSWORD_VALIDATORS = [ 113 | { 114 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 115 | }, 116 | { 117 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 118 | }, 119 | { 120 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 121 | }, 122 | { 123 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 124 | }, 125 | ] 126 | 127 | 128 | # Internationalization 129 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 130 | 131 | LANGUAGE_CODE = 'pt-br' 132 | TIME_ZONE = 'America/Cuiaba' 133 | USE_I18N = True 134 | USE_L10N = True 135 | USE_TZ = True 136 | 137 | 138 | # STATIC 139 | # ------------------------------------------------------------------------------ 140 | # https://docs.djangoproject.com/en/dev/ref/settings/#static-root 141 | STATIC_ROOT = os.path.join(ROOT_DIR, "staticfiles") 142 | # https://docs.djangoproject.com/en/dev/ref/settings/#static-url 143 | STATIC_URL = "/static/" 144 | 145 | # MEDIA 146 | # ------------------------------------------------------------------------------ 147 | # https://docs.djangoproject.com/en/dev/ref/settings/#media-root 148 | MEDIA_ROOT = str(ROOT_DIR('media')) 149 | # https://docs.djangoproject.com/en/dev/ref/settings/#media-url 150 | MEDIA_URL = '/media/' 151 | 152 | # Default primary key field type 153 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 154 | 155 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 156 | 157 | # Celery settings 158 | CELERY_BROKER_URL = 'redis://redis:6379' 159 | CELERY_RESULT_BACKEND = 'redis://redis:6379' 160 | CELERY_ACCEPT_CONTENT = ['application/json'] 161 | CELERY_TASK_SERIALIZER = 'json' 162 | CELERY_RESULT_SERIALIZER = 'json' 163 | CELERY_TIMEZONE = TIME_ZONE 164 | CELERYD_LOG_FILE = "celery.log" 165 | CELERYD_LOG_LEVEL = "INFO" 166 | 167 | CACHES = { 168 | "default": { 169 | "BACKEND": "django_redis.cache.RedisCache", 170 | "LOCATION": "redis://redis:6379/1", 171 | "OPTIONS": { 172 | "CLIENT_CLASS": "django_redis.client.DefaultClient", 173 | }, 174 | 'KEY_PREFIX': 'status_nfe' 175 | } 176 | } --------------------------------------------------------------------------------