From 165c3ab519550bd6645427d117ae4aee5a63c937 Mon Sep 17 00:00:00 2001 From: Jonatha Cardoso Date: Wed, 18 May 2022 15:37:58 -0300 Subject: [PATCH] Release: 3.1.0 --- README.rst | 12 +- docker/Dockerfile | 98 ++++++ docker/busy-wait.sh | 10 + docker/config/env_dockerfile | 21 ++ docker/config/nginx.conf | 37 +++ docker/config/saap.conf | 22 ++ docker/create_admin.py | 42 +++ docker/docker-compose.yml | 66 ++++ docker/start.sh | 111 +++++++ docs/docker.rst | 117 +++++++ requirements/requirements.txt | 2 +- saap/cerimonial/forms.py | 249 +++++++++++++- .../migrations/0081_auto_20220509_1141.py | 197 +++++++++++ .../migrations/0082_auto_20220509_1607.py | 211 ++++++++++++ .../migrations/0083_auto_20220510_1409.py | 23 ++ .../migrations/0084_auto_20220511_0929.py | 77 +++++ .../migrations/0085_auto_20220511_1511.py | 73 +++++ .../migrations/0086_auto_20220511_1554.py | 34 ++ .../migrations/0087_auto_20220511_1554.py | 26 ++ .../migrations/0088_auto_20220513_1132.py | 31 ++ .../migrations/0089_auto_20220516_1656.py | 62 ++++ .../migrations/0090_auto_20220516_1715.py | 27 ++ saap/cerimonial/models.py | 86 ++++- saap/cerimonial/reports.py | 309 +++++++++++++++++- saap/cerimonial/rules.py | 50 +-- saap/cerimonial/templatetags/dados_camara.py | 4 + saap/cerimonial/urls.py | 16 +- saap/cerimonial/utils.py | 81 +++++ saap/cerimonial/views.py | 71 +++- saap/core/forms.py | 7 +- .../migrations/0029_auto_20220516_0935.py | 19 ++ .../migrations/0030_auto_20220516_1656.py | 19 ++ .../migrations/0031_auto_20220517_1605.py | 25 ++ .../migrations/0032_auto_20220518_1534.py | 57 ++++ saap/core/models.py | 31 +- saap/core/rules.py | 34 +- saap/core/urls.py | 14 +- saap/core/views.py | 16 +- saap/crispy_layout_mixin.py | 7 +- saap/settings.py | 2 +- saap/static/img/index_agenda.png | Bin 0 -> 1646 bytes saap/static/img/index_maladireta.png | Bin 0 -> 2303 bytes saap/static/styles/app.css | 197 +++++++++-- saap/static/styles/calendar.css | 57 ++++ saap/static/styles/header.css | 13 +- saap/templates/ajuda.html | 9 + saap/templates/ajuda/agenda.html | 23 ++ saap/templates/ajuda/agendacalendario.html | 49 +++ saap/templates/ajuda/agendaevento.html | 45 +++ saap/templates/ajuda/agendalista.html | 55 ++++ saap/templates/ajuda/correspondencias.html | 2 +- saap/templates/ajuda/processosdetalhe.html | 2 +- saap/templates/ajuda/processospesquisa.html | 2 +- saap/templates/ajuda/relatorios.html | 2 +- saap/templates/base.html | 126 +++---- saap/templates/cerimonial/agenda.html | 20 ++ saap/templates/cerimonial/evento.html | 37 +++ saap/templates/cerimonial/filter_agenda.html | 63 ++++ saap/templates/core/layouts.yaml | 5 + saap/templates/index.html | 39 +++ saap/templates/novidades.html | 0 saap/templates/relatorios.html | 20 +- saap/templates/saap_sistema.html | 5 + start.sh | 77 ----- 64 files changed, 2983 insertions(+), 261 deletions(-) create mode 100644 docker/Dockerfile create mode 100644 docker/busy-wait.sh create mode 100644 docker/config/env_dockerfile create mode 100644 docker/config/nginx.conf create mode 100644 docker/config/saap.conf create mode 100644 docker/create_admin.py create mode 100644 docker/docker-compose.yml create mode 100644 docker/start.sh create mode 100644 docs/docker.rst create mode 100644 saap/cerimonial/migrations/0081_auto_20220509_1141.py create mode 100644 saap/cerimonial/migrations/0082_auto_20220509_1607.py create mode 100644 saap/cerimonial/migrations/0083_auto_20220510_1409.py create mode 100644 saap/cerimonial/migrations/0084_auto_20220511_0929.py create mode 100644 saap/cerimonial/migrations/0085_auto_20220511_1511.py create mode 100644 saap/cerimonial/migrations/0086_auto_20220511_1554.py create mode 100644 saap/cerimonial/migrations/0087_auto_20220511_1554.py create mode 100644 saap/cerimonial/migrations/0088_auto_20220513_1132.py create mode 100644 saap/cerimonial/migrations/0089_auto_20220516_1656.py create mode 100644 saap/cerimonial/migrations/0090_auto_20220516_1715.py create mode 100644 saap/cerimonial/utils.py create mode 100644 saap/core/migrations/0029_auto_20220516_0935.py create mode 100644 saap/core/migrations/0030_auto_20220516_1656.py create mode 100644 saap/core/migrations/0031_auto_20220517_1605.py create mode 100644 saap/core/migrations/0032_auto_20220518_1534.py create mode 100644 saap/static/img/index_agenda.png create mode 100644 saap/static/img/index_maladireta.png create mode 100644 saap/static/styles/calendar.css create mode 100644 saap/templates/ajuda/agenda.html create mode 100644 saap/templates/ajuda/agendacalendario.html create mode 100644 saap/templates/ajuda/agendaevento.html create mode 100644 saap/templates/ajuda/agendalista.html create mode 100644 saap/templates/cerimonial/agenda.html create mode 100644 saap/templates/cerimonial/evento.html create mode 100644 saap/templates/cerimonial/filter_agenda.html delete mode 100644 saap/templates/novidades.html delete mode 100644 start.sh diff --git a/README.rst b/README.rst index 50fa8c3..5cb0bcf 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ SAAP - Sistema de Apoio à Atividade Parlamentar Esta página reúne informações úteis sobre o desenvolvimento atual do SAAP. -Isso significa que toda a informação aqui apresentada aplica-se apenas para a versão 3.0.1 e superior. +Isso significa que toda a informação aqui apresentada aplica-se apenas para a versão 3.1.0 e superior. Desenvolvimento @@ -17,12 +17,16 @@ Deploy `Deploy SAAP com Nginx + Gunicorn `_ +Docker +========================================= + `Container SAAP + Postgres `_ + + Tradução ========================================= `Instruções para Tradução `_ - Implementação ========================================= `Instruções para Implementação e Testes `_ @@ -51,4 +55,6 @@ Referências Estes guias, o projeto e as configurações do ambiente foram desenvolvidos, corrigidos e aperfeiçoados a partir do fork de `Jonatha Cardoso `_ (servidor na `Câmara Municipal de Novo Hamburgo/RS `_). -O mesmo teve como ponto de partida o projeto original do SAAP, desenvolvido por `Leandro Silva `_ (servidor na `Câmara Municipal de Jataí/GO `_) e `Ramiro Luz `_ (servidor na `Câmara Municipal de Curitiba/PR `_) +O mesmo teve como ponto de partida o projeto original do SAAP, desenvolvido por `Leandro Silva `_ (servidor na `Câmara Municipal de Jataí/GO `_) e `Ramiro Luz `_ (servidor na `Câmara Municipal de Curitiba/PR `_). + +Para testar o SAAP, basta acessar a `versão de demonstração `. diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..bf7edfa --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,98 @@ +FROM python:3.7-slim-buster + +# Define o mantenedor da imagem +LABEL maintainer="Interlegis" + +ENV DEBIAN_FRONTEND noninteractive + +# Configura o sistema para usar UTF-8 +RUN apt-get update -y &&\ + apt-get install -y locales &&\ + rm -rf /var/lib/apt/lists/* &&\ + localedef -i pt_BR -c -f UTF-8 -A /usr/share/locale/locale.alias pt_BR.UTF-8 + +ENV LANG pt_BR.UTF-8 +ENV LC_ALL pt_BR.UTF-8 + +# Pacotes usados na construcao da imagem +ENV BUILD_PACKAGES apt-utils apt-file build-essential dialog graphviz-dev \ + libpq-dev libxml2-dev libjpeg-dev libssl-dev libcairo2-dev \ + libffi-dev libxslt1-dev libmagic-dev libfreetype6-dev \ + pkg-config python3-setuptools python3-pip python3-dev software-properties-common + +# Pacotes usados na execucao da imagem +ENV RUN_PACKAGES antiword bash curl default-jre fontconfig git graphviz jq \ + python python3-lxml python3-magic python3-psycopg2 python3-venv \ + nginx nodejs poppler-utils postgresql-client sudo ttf-dejavu tzdata vim + +# Instalacao dos pacotes +RUN apt-get update -y &&\ + apt-get upgrade -y && \ + apt-get install -y --no-install-recommends $BUILD_PACKAGES $RUN_PACKAGES &&\ + fc-cache -fv + +# Instalacao do Bower e Node.js +RUN curl -fsSL https://deb.nodesource.com/setup_12.x | sudo -E bash - &&\ + sudo apt-get install -y nodejs &&\ + curl -L https://npmjs.org/install.sh | sudo sh &&\ + sudo npm install npm -g &&\ + sudo npm install bower -g + +ENV SAAPDIR=/var/interlegis/saap + +# Instalacao do ambiente virtual +RUN pip install virtualenvwrapper + +# Clone do projeto do Github +RUN sudo mkdir -p /var/interlegis &&\ + sudo git clone https://github.com/interlegis/saap $SAAPDIR + +# Instalacao dos requerimentos +RUN pip install --ignore-installed --no-cache-dir -r /var/interlegis/saap/requirements/requirements.txt + +# Remocao dos pacotes usados na construcao da imagem +RUN SUDO_FORCE_REMOVE=yes apt-get purge -y --auto-remove $BUILD_PACKAGES &&\ + apt-get autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* + +WORKDIR /var/interlegis/saap +ADD . /var/interlegis/saap + +# Correcao da configuracao de alguns pacotes e requerimentos +CMD rm /usr/local/lib/python*/site-packages/rest_framework/* -R +RUN cp /var/interlegis/saap/config/django_db_models/base.py /usr/local/lib/python*/site-packages/django/db/models/ +RUN cp /var/interlegis/saap/config/django_core_management/base.py /usr/local/lib/python*/site-packages/django/core/management/ +RUN cp /var/interlegis/saap/config/rest_framework/* /usr/local/lib/python*/site-packages/rest_framework/ -R +RUN cp /var/interlegis/saap/config/bootstrap_admin/filter.html /usr/local/lib/python*/site-packages/bootstrap_admin/templates/admin/ +RUN cp /var/interlegis/saap/config/smart-selects/* /usr/local/lib/python*/site-packages/smart_selects/static/smart-selects/admin/js/ +RUN cp /var/interlegis/saap/config/reportlab/* /usr/local/lib/python*/site-packages/reportlab/platypus/ +RUN cp /var/interlegis/saap/config/image_cropping/* /usr/local/lib/python*/site-packages/image_cropping + +# Copia de arquivos usados na criacao da imagem +COPY start.sh $SAAPDIR +RUN chmod +x /var/interlegis/saap/start.sh +COPY busy-wait.sh $SAAPDIR +COPY create_admin.py $SAAPDIR + +# Copia do arquivo .env e dos arquivos do Nginx +COPY config/env_dockerfile $SAAPDIR/.env +COPY config/saap.conf /etc/nginx/conf.d +COPY config/nginx.conf /etc/nginx/nginx.conf + +# Configuracao dos arquivos de log +RUN ln -sf /dev/stdout /var/log/nginx/access.log &&\ + ln -sf /dev/stderr /var/log/nginx/error.log &&\ + mkdir /var/log/saap/ && touch /var/interlegis/saap/saap.log &&\ + ln -s /var/interlegis/saap/saap.log /var/log/saap/saap.log + +# Configuracao do bower e arquivos estaticos +RUN python3 manage.py bower install &&\ + python3 manage.py collectstatic --noinput --clear + +ENV DEBIAN_FRONTEND teletype + +EXPOSE 80 8000 + +VOLUME ["/var/interlegis/saap/data", "/var/interlegis/saap/media"] + +# Execucao do script de inicializacao +CMD ["/var/interlegis/saap/start.sh"] diff --git a/docker/busy-wait.sh b/docker/busy-wait.sh new file mode 100644 index 0000000..248eaf5 --- /dev/null +++ b/docker/busy-wait.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +while true; do + COUNT_PG=`psql $1 -c '\l \q' | grep saap | wc -l` + if ! [ "$COUNT_PG" -eq "0" ]; then + break + fi + echo "Waiting database setup..." + sleep 10 +done diff --git a/docker/config/env_dockerfile b/docker/config/env_dockerfile new file mode 100644 index 0000000..ee7ad6c --- /dev/null +++ b/docker/config/env_dockerfile @@ -0,0 +1,21 @@ +DATABASE_URL = sqlite:///sapl.db +SECRET_KEY = 'mzp++@i1y-6y8ez_=^sfbr!dzuyry#^@v(3g^2d1k9%f=+mhlb' +DEBUG=False +DJANGO_TOOLBAR=False +SITE_NAME='Sistema de Apoio à Atividade Parlamentar' +SITE_DOMAIN='saap.camaranh.rs.gov.br' +EMAIL_USE_TLS=True +EMAIL_PORT=25 +EMAIL_HOST= +EMAIL_SEND_USER= +EMAIL_HOST_USER= +EMAIL_HOST_PASSWORD= +DADOS_NOME='Câmara Municipal do Interlegis' +DADOS_ENDERECO='Av. N2, Bloco E - Senado Federal' +DADOS_MUNICIPIO='Brasília' +DADOS_UF='DF' +DADOS_CEP='70165-900' +DADOS_EMAIL='atendimento@interlegis.leg.br' +DADOS_TELEFONE='(61) 3303-3221' +DADOS_SITE='interlegis.leg.br' +BRASAO_PROPRIO=False diff --git a/docker/config/nginx.conf b/docker/config/nginx.conf new file mode 100644 index 0000000..2ad911e --- /dev/null +++ b/docker/config/nginx.conf @@ -0,0 +1,37 @@ +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile off; + #tcp_nopush on; + + keepalive_timeout 300; + + proxy_connect_timeout 75s; + proxy_read_timeout 300s; + + gzip on; + gzip_disable "MSIE [1-6]\\.(?!.*SV1)"; + gzip_proxied any; + gzip_comp_level 5; + gzip_types text/plain text/css text/javascript application/javascript application/x-javascript text/xml application/xml application/rss+xml image/gif image/png image/x-icon image/jpeg image/svg+xml; + gzip_vary on; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/docker/config/saap.conf b/docker/config/saap.conf new file mode 100644 index 0000000..5b96957 --- /dev/null +++ b/docker/config/saap.conf @@ -0,0 +1,22 @@ +upstream saap { + server unix:/var/interlegis/saap/run/gunicorn.sock fail_timeout=0; +} + +server { + listen 80; + server_name saap; + + client_max_body_size 4G; + + location / { + proxy_pass http://127.0.0.1:8000; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_redirect off; + } + + location /static { + alias /var/interlegis/saap/collected_static/; + } + +} diff --git a/docker/create_admin.py b/docker/create_admin.py new file mode 100644 index 0000000..683ee4c --- /dev/null +++ b/docker/create_admin.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +import os +import sys + +import django + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "saap.settings") + +def get_enviroment_admin_password(username): + password = os.environ.get('ADMIN_PASSWORD') + if not password: + #print( + # "[CREATE_SUPERUSER] Environment variable $ADMIN_PASSWORD" + # " for user %s was not set. Leaving...\n" % username) + sys.exit('MISSING_ADMIN_PASSWORD') + return password + +def create_superuser(): + from saap.core.models import User + + email = os.environ.get('ADMIN_EMAIL', '') + + if User.objects.filter(email=email).exists(): + #print("[CREATE_SUPERUSER] User %s already exists." + # " Exiting without change.\n" % email) + sys.exit('ADMIN_USER_EXISTS') + else: + password = get_enviroment_admin_password(email) + + print("[CREATE_SUPERUSER] Creating superuser...\n") + + u = User.objects.create_superuser( + email=email, password=password) + u.save() + + print("[CREATE_SUPERUSE] Done.\n") + + sys.exit(0) + +if __name__ == '__main__': + django.setup() + create_superuser() diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..459600d --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,66 @@ +version: "3.3" +services: + saapdb: + image: postgres:10.5-alpine + restart: always + container_name: postgres + labels: + NAME: "saapdb" + environment: + POSTGRES_PASSWORD: saap + POSTGRES_USER: saap + POSTGRES_DB: saap + PGDATA : /var/lib/postgresql/data/ + volumes: + - saapdb_data:/var/lib/postgresql/data/ + ports: + - "5433:5432" + networks: + - saap-net + saap: + image: interlegis/saap:latest +# build: +# context: . +# dockerfile: Dockerfile + container_name: saap + labels: + NAME: "saap" + restart: always + environment: + SITE_NAME: 'Sistema de Apoio à Atividade Parlamentar' + SITE_DOMAIN: 'saap.camaranh.rs.gov.br' + DEBUG: 'False' + DJANGO_TOOLBAR: 'False' + EMAIL_USE_TLS: 'True' + EMAIL_PORT: 25 + EMAIL_HOST: '' + EMAIL_SEND_USER: '' + EMAIL_HOST_USER: '' + EMAIL_HOST_PASSWORD: '' + DADOS_NOME: 'Câmara Municipal de Saapópolis' + DADOS_ENDERECO: 'Av. XV de Novembro, 555' + DADOS_MUNICIPIO: 'Brasília' + DADOS_UF: 'DF' + DADOS_CEP: '70165-900' + DADOS_EMAIL: 'atendimento@interlegis.leg.br' + DADOS_TELEFONE: '(61) 3303-3221' + DADOS_SITE: 'https://www12.senado.leg.br/interlegis' + BRASAO_PROPRIO: 'True' + ADMIN_PASSWORD: saap_interlegis + ADMIN_EMAIL: admin@interlegis.leg.br + volumes: + - saap_data:/var/interlegis/saap/data + - saap_media:/var/interlegis/saap/media + depends_on: + - saapdb + ports: + - "80:80" + networks: + - saap-net +networks: + saap-net: + driver: bridge +volumes: + saapdb_data: + saap_data: + saap_media: diff --git a/docker/start.sh b/docker/start.sh new file mode 100644 index 0000000..777b6ac --- /dev/null +++ b/docker/start.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash + +create_env() { + echo "[CREATE_ENV] Creating .env file..." + KEY=`python3 manage.py generate_secret_key` + FILENAME="/var/interlegis/saap/.env" + + if [ -z "${DATABASE_URL:-}" ]; then + DATABASE_URL="postgresql://saap:saap@saapdb:5432/saap" + fi + + # ALWAYS replace the content of .env variable + # If want to conditionally create only if absent then use IF below + # if [ ! -f $FILENAME ]; then + + touch $FILENAME + + # explicitly use '>' to erase any previous content + echo "SECRET_KEY="$KEY > $FILENAME + # now only appends + echo "DATABASE_URL = "$DATABASE_URL >> $FILENAME + + echo "DEBUG = ""${DEBUG-False}" >> $FILENAME + echo "DJANGO_TOOLBAR = ""${DJANGO_TOOLBAR-False}" >> $FILENAME + + echo "EMAIL_USE_TLS = ""${USE_TLS-True}" >> $FILENAME + echo "EMAIL_PORT = ""${EMAIL_PORT-25}" >> $FILENAME + echo "EMAIL_HOST = ""${EMAIL_HOST-''}" >> $FILENAME + echo "EMAIL_HOST_USER = ""${EMAIL_HOST_USER-''}" >> $FILENAME + echo "EMAIL_HOST_PASSWORD = ""${EMAIL_HOST_PASSWORD-''}" >> $FILENAME + echo "EMAIL_SEND_USER = ""${EMAIL_HOST_USER-''}" >> $FILENAME + + echo "SITE_NAME = ""${SITE_NAME-''}" >> $FILENAME + echo "SITE_DOMAIN = ""${SITE_DOMAIN-''}" >> $FILENAME + + echo "DADOS_NOME = ""${DADOS_NOME-''}" >> $FILENAME + echo "DADOS_ENDERECO = ""${DADOS_ENDERECO-''}" >> $FILENAME + echo "DADOS_MUNICIPIO = ""${DADOS_MUNICIPIO-''}" >> $FILENAME + echo "DADOS_UF = ""${DADOS_UF-''}" >> $FILENAME + echo "DADOS_CEP = ""${DADOS_CEP-''}" >> $FILENAME + echo "DADOS_EMAIL = ""${DADOS_EMAIL-''}" >> $FILENAME + echo "DADOS_TELEFONE = ""${DADOS_TELEFONE-''}" >> $FILENAME + echo "DADOS_SITE = ""${DADOS_SITE-''}" >> $FILENAME + echo "BRASAO_PROPRIO = ""${BRASAO_PROPRIO-False}" >> $FILENAME + + echo "[CREATE_ENV] Done!" +} + +load_db() { + + /bin/bash busy-wait.sh $DATABASE_URL + + export PGPASSWORD="saap" + + echo "[LOAD_DB] Creating postgres role..." + psql -U saap -c "CREATE ROLE postgres LOGIN ENCRYPTED PASSWORD 'postgres' SUPERUSER INHERIT CREATEDB NOCREATEROLE NOREPLICATION;" + psql -U saap -c "ALTER ROLE postgres VALID UNTIL 'infinity';" + + echo "[LOAD_DB] Creating database structure..." + yes yes | python3 manage.py migrate + + echo "[LOAD_DB] Loading initial data..." + python3 manage.py loaddata /var/interlegis/saap/config/initial_data/*.json + + echo "[LOAD_DB] Creating extension..." + psql -h saapdb -c "CREATE EXTENSION unaccent;" + + echo "[LOAD_DB] Done!" +} + +create_superuser(){ + + echo "[CREATE_SUPERUSER] Creating superuser..." + + user_created=$(python3 create_admin.py 2>&1) + + echo $user_created + + cmd=$(echo $user_created | grep 'ADMIN_USER_EXISTS') + user_exists=$? + + cmd=$(echo $user_created | grep 'MISSING_ADMIN_PASSWORD') + lack_pwd=$? + + if [ $user_exists -eq 0 ]; then + echo "[CREATE_SUPERUSER] User admin already exists. Not creating" + fi + + if [ $lack_pwd -eq 0 ]; then + echo "[CREATE_SUPERUSER] Environment variable $ADMIN_PASSWORD for superuser admin was not set. Leaving container" + fi + +} + +create_env + +load_db + +create_superuser + +echo "-----------------------------------" +echo "| ███████╗ █████╗ █████╗ ██████╗ |" +echo "| ██╔════╝██╔══██╗██╔══██╗██╔══██╗|" +echo "| ███████╗███████║███████║██████╔╝|" +echo "| ╚════██║██╔══██║██╔══██║██╔═══╝ |" +echo "| ███████║██║ ██║██║ ██║██║ |" +echo "| ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ |" +echo "-----------------------------------" + +gunicorn saap.wsgi:application --bind 0.0.0.0:8000 & +/usr/sbin/nginx -g "daemon off;" diff --git a/docs/docker.rst b/docs/docker.rst new file mode 100644 index 0000000..482d7cf --- /dev/null +++ b/docs/docker.rst @@ -0,0 +1,117 @@ +********************************************** +Implantação do SAAP com container Docker +*********************************************** + +Para implantar o SAAP utilizando o container docker, é necessário realizar os passos demonstrados nesse tutorial. + +1) Instalar pacotes +---------------------------------------------------------------------------------------- + +Atualize o sistema: + +:: + + sudo apt-get update && sudo apt-get upgrade -y + +Instale os pacotes: + +:: + + sudo apt-get install docker docker-compose -y + + +2) Preparar docker-compose +---------------------------------------------------------------------------------------- + +Após a instalação do docker, é necessário preparar o arquivo de configuração que fará o procedimento de instalação e configuração, tanto do SAAP quanto do banco de dados. + +Para isto, use o arquivo `docker-compose.yml `_ disponível aqui. O mesmo pode estar em qualquer localização que for desejada - nossa sugestão é mantê-lo em uma pasta /var/interlegis/docker + +É importante verificar os seguintes itens: + +- Se os mapeamentos de volume estão corretos +- Se a versão do SAAP referenciada no arquivo é a desejada - na dúvida, mantenha a expressão ``latest`` +- Se os dados da seção environment condizem com a Casa Legislativa. + + - O campo ``SITE_DOMAIN`` deve conter o endereço que será usado para acessar o SAAP + - Os campos ``EMAIL_*`` devem conter os dados corretos do servidor de e-mail, incluindo usuário/senha para envio de mensagens + - Os campos ``DADOS_*`` são usados no rodapé do site + - O campo ``BRASAO_PROPRIO`` exibe ou não um brasão personalizado. Caso ``False``, será usado o Brasão de Armas do Brasil. Caso ``True``, deverá ser feita a cópia do arquivo do brasão, conforme mostra a seção correspondente logo abaixo. + - Os campos ``ADMIN_*`` devem conter os dados do primeiro usuário que será criado, com permissão de administração. Posteriormente é possível alterar o e-mail e/ou senha do mesmo. + + +3) Instalar imagens +---------------------------------------------------------------------------------------- + +Com o docker-compose preparado, é necessário rodar o seguinte comando, dentro da pasta onde está localizado o arquivo. + +:: + + sudo docker-compose up + + +O docker iniciará o download, instalação e configuração das imagens. Após alguns minutos, ao aparecer a palavra ``SAAP``, o Ngnix e o Gunicorn iniciarão o serviço. Então, basta testar se o sistema está funcionando. + +Para rodar as imagens em background, deve-se primeiramente abortar a execução das imagens, apertando ``Ctrl + C``. + +Então, basta rodar o ``docker-compose`` com a opção ``-d``: + +:: + + sudo docker-compose up -d + + +4) Atualizar o brasão +---------------------------------------------------------------------------------------- + +Se a opção ``BRASAO_PROPRIO`` está com ``True``, é necessário atualizar a imagem do brasão. Para isto, basta colocar a imagem desejada, em formado PNG, com o nome de ``brasao-camara.png``, e rodar o comando: + +:: + + docker cp brasao-camara.png saap:/var/interlegis/saap/saap/static/img/brasao-camara.png + + +Parar imagens +---------------------------------------------------------------------------------------- + + +Para interromper a execução das imagens rodando em background, basta rodar: + +:: + + sudo docker-compose stop + + +Backup e restauração do banco +---------------------------------------------------------------------------------------- + +Para realizar o backup do banco do SAAP, basta rodar o seguinte comando: + +:: + + docker exec postgres pg_dump -U saap -v -Fc saap > ~/saap.dump + +O destino e o nome do arquivo gerado são personalizáveis. + +Para realizar a restauraçao do banco, é necessário previamente excluir o banco atual, para, então, fazer o processo: + +:: + + docker exec postgres psql -U postgres -c 'DROP DATABASE saap' + docker exec postgres psql -U postgres -c "CREATE DATABASE saap ENCODING 'UTF8' template template0" + +:: + + docker cp saap.dump postgres:/tmp/ + docker exec postgres pg_restore -v -U saap -d saap /tmp/saap.dump + +Atualizar imagem +---------------------------------------------------------------------------------------- + +Para atualizar a imagem, é necessário, primeiramente, fazer o backup do banco, conforme explicado acima. Então, basta rodar o comando abaixo: + +:: + + docker-compose up --force-recreate --build -d + +Por fim, restaurar o brasão, conforme passo 4, e restaurar o banco, conforme explicado acima. diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 3543a0a..829fa6e 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -6,7 +6,7 @@ django-braces==1.8.1 django-compressor==2.0 django-crispy-forms==1.6.0 django-debug-toolbar==1.5 -django-easy-audit +django-easy-audit==1.2.3 django-exclusivebooleanfield django-extensions==1.6.1 django-extra-views==0.7.1 diff --git a/saap/cerimonial/forms.py b/saap/cerimonial/forms.py index 4a05fec..e09607e 100644 --- a/saap/cerimonial/forms.py +++ b/saap/cerimonial/forms.py @@ -13,6 +13,7 @@ from django.db.models import Q, F, Func, Value #from django.forms.extras.widgets import SelectDateWidget from django.forms.widgets import SelectDateWidget, DateInput +from django.forms import DateInput from django.forms.models import ModelForm, ModelMultipleChoiceField from django.db.models import F @@ -21,14 +22,14 @@ MethodFilter, ModelChoiceFilter, RangeFilter,\ MultipleChoiceFilter, ModelMultipleChoiceFilter, BooleanFilter from django_filters.filterset import FilterSet -from saap.crispy_layout_mixin import SaplFormLayout, to_row +from saap.crispy_layout_mixin import SaapFormLayout, to_row from saap.core.models import Municipio, Estado from saap import settings from saap.cerimonial.models import LocalTrabalho, Endereco,\ TipoAutoridade, PronomeTratamento, Contato, Perfil, Processo, Dependente,\ IMPORTANCIA_CHOICE, AssuntoProcesso, ClassificacaoProcesso, StatusProcesso, ProcessoContato,\ - GrupoDeContatos, TopicoProcesso, Telefone, Email, EstadoCivil + GrupoDeContatos, TopicoProcesso, Telefone, Email, EstadoCivil, Evento from saap.core.forms import ListWithSearchForm from saap.core.models import Trecho, ImpressoEnderecamento, Bairro, NivelInstrucao from saap.utils import normalize, YES_NO_CHOICES, NONE_YES_NO_CHOICES @@ -90,7 +91,6 @@ def __init__(self, *args, **kwargs): self.fields['principal'].widget = forms.RadioSelect() self.fields['principal'].inline_class = True - class LocalTrabalhoForm(ModelForm): class Meta: @@ -149,6 +149,53 @@ def __init__(self, *args, **kwargs): self.fields['principal'].inline_class = True + +class EventoForm(ModelForm): + + class Meta: + model = Evento + fields = ['titulo', + 'descricao', + 'localizacao', + 'estado', + 'municipio', + 'bairro', + 'inicio', + 'termino', + 'workspace'] + + + # datetime-local is a HTML5 input type, format to make date time show on fields + widgets = { + 'inicio': DateInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%dT%H:%M'), + 'termino': DateInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%dT%H:%M'), + } + + def __init__(self, *args, **kwargs): + workspace = kwargs.pop('workspace') + + super(EventoForm, self).__init__(*args, **kwargs) + + for field in self.fields.values(): + field.widget.attrs.update({'class': 'form-control'}) + + self.workspace = workspace + self.initial['workspace'] = self.workspace + + # input_formats parses HTML5 datetime-local input to datetime field + self.fields['inicio'].input_formats = ('%Y-%m-%dT%H:%M',) + self.fields['termino'].input_formats = ('%Y-%m-%dT%H:%M',) + + self.fields['workspace'].widget = forms.HiddenInput() + + def clean(self): + + inicio = self.cleaned_data['inicio'] + termino = self.cleaned_data['termino'] + + if termino <= inicio: + self._errors['termino'] = [_('O término do evento deve ser posterior ao seu início.')] + class ContatoForm(ModelForm): class Meta: @@ -624,7 +671,7 @@ def __init__(self, *args, **kwargs): yaml_layout.append(q) self.helper = FormHelper() - self.helper.layout = SaplFormLayout(*yaml_layout) + self.helper.layout = SaapFormLayout(*yaml_layout) #self.fields['data_abertura'] = forms.DateField(widget=SelectDateWidget(), label='Joining Date', initial=date.today()) @@ -820,7 +867,7 @@ def __init__(self, *args, **kwargs): yaml_layout.append(q) self.helper = FormHelper() - self.helper.layout = SaplFormLayout(*yaml_layout) + self.helper.layout = SaapFormLayout(*yaml_layout) super(GrupoDeContatosForm, self).__init__(*args, **kwargs) @@ -3564,6 +3611,198 @@ def __init__(self, data=None, +class AgendaFilterSet(FilterSet): + + filter_overrides = {models.DateTimeField: { + 'filter_class': MethodFilter, + 'extra': lambda f: { + 'label': '%s (%s)' % (f.verbose_name, _('período')), + 'widget': RangeWidgetOverride} + }} + + + RETRATO = 'R' + PAISAGEM = 'P' + ORIENTACAO_CHOICE = ((PAISAGEM, _('Paisagem')), + (RETRATO, _('Retrato'))) + + search = MethodFilter() + + orientacao = MethodChoiceFilter( + label=_('Orientação'), + choices=ORIENTACAO_CHOICE, initial=PAISAGEM) + + bairro = MethodModelMultipleChoiceFilter( + required=False, + label=_('Bairro (' + settings.DADOS_MUNICIPIO + ')'), + queryset=None) # Será carregado no final do código + + municipio = MethodModelMultipleChoiceFilter( + required=False, + label=_('Município (' + settings.DADOS_UF + ')'), + queryset=None) # Será carregado no final do código + + def filter_orientacao(self, queryset, value): + return queryset + + def filter_search(self, queryset, value): + + query = normalize(value) + + query = query.split(' ') + if query: + q = Q() + for item in query: + if not item: + continue + # Remove acentos dos campos que estão no banco + q = q & Q(search__unaccent__icontains=item) + + if q: + queryset = queryset.filter(q) + return queryset + + def filter_search(self, queryset, value): + + query = normalize(value) + + query = query.split(' ') + if query: + q = Q() + for item in query: + if not item: + continue + # Remove acentos dos campos que estão no banco + q = q & (Q(title__unaccent__icontains=item) | + Q(description__unaccent__icontains=item) | + Q(location__unaccent__icontains=item)) + + if q: + queryset = queryset.filter(q) + return queryset + + + def filter_inicio(self, queryset, value): + + if not value[0] and not value[1]: + return queryset + + inicial = None + final = None + + if(value[0] != ''): + inicial = datetime.datetime.strptime(value[0], "%d/%m/%Y").date() + if(value[1] != ''): + final = datetime.datetime.strptime(value[1], "%d/%m/%Y").date() + + if(inicial != None and final != None): + if inicial > final: + inicial, final = final, inicial + range_select = Q(inicio__range=[inicial, final]) + elif(inicial != None): + range_select = Q(inicio__gte=inicial) + elif(final != None): + range_select = Q(inicio__lte=final) + + # Run the query. + return queryset.filter(range_select) + + def filter_termino(self, queryset, value): + + if not value[0] and not value[1]: + return queryset + + inicial = None + final = None + + if(value[0] != ''): + inicial = datetime.datetime.strptime(value[0], "%d/%m/%Y").date() + if(value[1] != ''): + final = datetime.datetime.strptime(value[1], "%d/%m/%Y").date() + + if(inicial != None and final != None): + if inicial > final: + inicial, final = final, inicial + range_select = Q(termino__range=[inicial, final]) + elif(inicial != None): + range_select = Q(termino__gte=inicial) + elif(final != None): + range_select = Q(termino__lte=final) + + # Run the query. + return queryset.filter(range_select) + + def filter_bairro(self, queryset, value): + if value: + queryset = queryset.filter( + bairro__in=value).distinct() + + return queryset + + def filter_municipio(self, queryset, value): + if value: + queryset = queryset.filter( + municipio__in=value).distinct() + + return queryset + + class Meta: + model = Evento + fields = ['search', + 'inicio', + 'termino', + ] + + def __init__(self, data=None, + queryset=None, prefix=None, strict=None, **kwargs): + + workspace = kwargs.pop('workspace') + + super(AgendaFilterSet, self).__init__( + data=data, + queryset=queryset, prefix=prefix, strict=strict, **kwargs) + + col1 = to_row([ + ('search', 12), + ('inicio', 6), + ('termino', 6), + ('bairro', 6), + ('municipio', 6), + ]) + + col2 = to_row([ + ('orientacao', 12), + ]) + + row = to_row( + [(Fieldset( + _('Pesquisa'), + col1, + to_row([(SubmitFilterPrint( + 'filter', + value=_('Filtrar'), css_class='btn-default pull-right', + type='submit'), 12)])), 9), + (Fieldset( + _('Impressão'), + col2, + to_row([(SubmitFilterPrint( + 'print', + value=_('Gerar'), css_class='btn-primary pull-right', + type='submit'), 12)])), 3)]) + + self.form.helper = FormHelper() + self.form.helper.form_method = 'GET' + self.form.helper.layout = Layout( + row, + ) + + self.form.fields['search'].label = 'Título, descrição ou localização' + self.form.fields['inicio'].label = 'Início do evento (período)' + self.form.fields['termino'].label = 'Término do evento (período)' + + self.form.fields['bairro'].queryset = Bairro.objects.filter(municipio=Municipio.objects.get(nome=settings.DADOS_MUNICIPIO, estado=Estado.objects.get(sigla=settings.DADOS_UF)).pk) + self.form.fields['municipio'].queryset = Municipio.objects.filter(estado=Estado.objects.get(sigla=settings.DADOS_UF).pk) + class ContatosFilterSet(FilterSet): filter_overrides = {models.DateField: { diff --git a/saap/cerimonial/migrations/0081_auto_20220509_1141.py b/saap/cerimonial/migrations/0081_auto_20220509_1141.py new file mode 100644 index 0000000..f8b1ce2 --- /dev/null +++ b/saap/cerimonial/migrations/0081_auto_20220509_1141.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-09 14:41 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('cerimonial', '0080_auto_20201203_1926'), + ] + + operations = [ + migrations.AlterField( + model_name='contato', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='Criação'), + ), + migrations.AlterField( + model_name='contato', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='Modificação'), + ), + migrations.AlterField( + model_name='contato', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Modificador'), + ), + migrations.AlterField( + model_name='contato', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Criador'), + ), + migrations.AlterField( + model_name='dependente', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='Criação'), + ), + migrations.AlterField( + model_name='dependente', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='Modificação'), + ), + migrations.AlterField( + model_name='dependente', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Modificador'), + ), + migrations.AlterField( + model_name='dependente', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Criador'), + ), + migrations.AlterField( + model_name='email', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='Criação'), + ), + migrations.AlterField( + model_name='email', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='Modificação'), + ), + migrations.AlterField( + model_name='email', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Modificador'), + ), + migrations.AlterField( + model_name='email', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Criador'), + ), + migrations.AlterField( + model_name='endereco', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='Criação'), + ), + migrations.AlterField( + model_name='endereco', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='Modificação'), + ), + migrations.AlterField( + model_name='endereco', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Modificador'), + ), + migrations.AlterField( + model_name='endereco', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Criador'), + ), + migrations.AlterField( + model_name='filiacaopartidaria', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='Criação'), + ), + migrations.AlterField( + model_name='filiacaopartidaria', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='Modificação'), + ), + migrations.AlterField( + model_name='filiacaopartidaria', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Modificador'), + ), + migrations.AlterField( + model_name='filiacaopartidaria', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Criador'), + ), + migrations.AlterField( + model_name='grupodecontatos', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='Criação'), + ), + migrations.AlterField( + model_name='grupodecontatos', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='Modificação'), + ), + migrations.AlterField( + model_name='grupodecontatos', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Modificador'), + ), + migrations.AlterField( + model_name='grupodecontatos', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Criador'), + ), + migrations.AlterField( + model_name='localtrabalho', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='Criação'), + ), + migrations.AlterField( + model_name='localtrabalho', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='Modificação'), + ), + migrations.AlterField( + model_name='localtrabalho', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Modificador'), + ), + migrations.AlterField( + model_name='localtrabalho', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Criador'), + ), + migrations.AlterField( + model_name='processo', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='Criação'), + ), + migrations.AlterField( + model_name='processo', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='Modificação'), + ), + migrations.AlterField( + model_name='processo', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Modificador'), + ), + migrations.AlterField( + model_name='processo', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Criador'), + ), + migrations.AlterField( + model_name='telefone', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='Criação'), + ), + migrations.AlterField( + model_name='telefone', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='Modificação'), + ), + migrations.AlterField( + model_name='telefone', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Modificador'), + ), + migrations.AlterField( + model_name='telefone', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Criador'), + ), + ] diff --git a/saap/cerimonial/migrations/0082_auto_20220509_1607.py b/saap/cerimonial/migrations/0082_auto_20220509_1607.py new file mode 100644 index 0000000..64ffaf3 --- /dev/null +++ b/saap/cerimonial/migrations/0082_auto_20220509_1607.py @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-09 19:07 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('cerimonial', '0081_auto_20220509_1141'), + ] + + operations = [ + migrations.CreateModel( + name='Agenda', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('titulo', models.CharField(max_length=200, verbose_name='Título')), + ('descricao', models.TextField(verbose_name='Descrição')), + ('localizacao', models.TextField(verbose_name='Endereço / Localização')), + ('observacoes', models.TextField(verbose_name='Observações')), + ('data_inicio', models.DateField(verbose_name='Data de início')), + ('hora_inicio', models.TimeField(verbose_name='Hora de início')), + ('data_fim', models.DateField(verbose_name='Data de término')), + ('hora_fim', models.TimeField(verbose_name='Hora de término')), + ], + ), + migrations.AlterField( + model_name='contato', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='created'), + ), + migrations.AlterField( + model_name='contato', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='modified'), + ), + migrations.AlterField( + model_name='contato', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='modifier'), + ), + migrations.AlterField( + model_name='contato', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='owner'), + ), + migrations.AlterField( + model_name='dependente', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='created'), + ), + migrations.AlterField( + model_name='dependente', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='modified'), + ), + migrations.AlterField( + model_name='dependente', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='modifier'), + ), + migrations.AlterField( + model_name='dependente', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='owner'), + ), + migrations.AlterField( + model_name='email', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='created'), + ), + migrations.AlterField( + model_name='email', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='modified'), + ), + migrations.AlterField( + model_name='email', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='modifier'), + ), + migrations.AlterField( + model_name='email', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='owner'), + ), + migrations.AlterField( + model_name='endereco', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='created'), + ), + migrations.AlterField( + model_name='endereco', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='modified'), + ), + migrations.AlterField( + model_name='endereco', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='modifier'), + ), + migrations.AlterField( + model_name='endereco', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='owner'), + ), + migrations.AlterField( + model_name='filiacaopartidaria', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='created'), + ), + migrations.AlterField( + model_name='filiacaopartidaria', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='modified'), + ), + migrations.AlterField( + model_name='filiacaopartidaria', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='modifier'), + ), + migrations.AlterField( + model_name='filiacaopartidaria', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='owner'), + ), + migrations.AlterField( + model_name='grupodecontatos', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='created'), + ), + migrations.AlterField( + model_name='grupodecontatos', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='modified'), + ), + migrations.AlterField( + model_name='grupodecontatos', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='modifier'), + ), + migrations.AlterField( + model_name='grupodecontatos', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='owner'), + ), + migrations.AlterField( + model_name='localtrabalho', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='created'), + ), + migrations.AlterField( + model_name='localtrabalho', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='modified'), + ), + migrations.AlterField( + model_name='localtrabalho', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='modifier'), + ), + migrations.AlterField( + model_name='localtrabalho', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='owner'), + ), + migrations.AlterField( + model_name='processo', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='created'), + ), + migrations.AlterField( + model_name='processo', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='modified'), + ), + migrations.AlterField( + model_name='processo', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='modifier'), + ), + migrations.AlterField( + model_name='processo', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='owner'), + ), + migrations.AlterField( + model_name='telefone', + name='created', + field=models.DateTimeField(auto_now_add=True, verbose_name='created'), + ), + migrations.AlterField( + model_name='telefone', + name='modified', + field=models.DateTimeField(auto_now=True, verbose_name='modified'), + ), + migrations.AlterField( + model_name='telefone', + name='modifier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='modifier'), + ), + migrations.AlterField( + model_name='telefone', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='owner'), + ), + ] diff --git a/saap/cerimonial/migrations/0083_auto_20220510_1409.py b/saap/cerimonial/migrations/0083_auto_20220510_1409.py new file mode 100644 index 0000000..cd0631a --- /dev/null +++ b/saap/cerimonial/migrations/0083_auto_20220510_1409.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-10 17:09 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('cerimonial', '0082_auto_20220509_1607'), + ] + + operations = [ + migrations.RenameModel( + old_name='Agenda', + new_name='Evento', + ), + migrations.AlterModelOptions( + name='evento', + options={'verbose_name': 'Agenda', 'verbose_name_plural': 'Agenda'}, + ), + ] diff --git a/saap/cerimonial/migrations/0084_auto_20220511_0929.py b/saap/cerimonial/migrations/0084_auto_20220511_0929.py new file mode 100644 index 0000000..8860992 --- /dev/null +++ b/saap/cerimonial/migrations/0084_auto_20220511_0929.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-11 12:29 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cerimonial', '0083_auto_20220510_1409'), + ] + + operations = [ + migrations.AlterModelOptions( + name='evento', + options={}, + ), + migrations.RemoveField( + model_name='evento', + name='data_fim', + ), + migrations.RemoveField( + model_name='evento', + name='data_inicio', + ), + migrations.RemoveField( + model_name='evento', + name='descricao', + ), + migrations.RemoveField( + model_name='evento', + name='hora_fim', + ), + migrations.RemoveField( + model_name='evento', + name='hora_inicio', + ), + migrations.RemoveField( + model_name='evento', + name='localizacao', + ), + migrations.RemoveField( + model_name='evento', + name='observacoes', + ), + migrations.RemoveField( + model_name='evento', + name='titulo', + ), + migrations.AddField( + model_name='evento', + name='description', + field=models.TextField(blank=True, default=''), + ), + migrations.AddField( + model_name='evento', + name='end_time', + field=models.DateTimeField(default=datetime.datetime.now), + ), + migrations.AddField( + model_name='evento', + name='location', + field=models.TextField(blank=True, default=''), + ), + migrations.AddField( + model_name='evento', + name='start_time', + field=models.DateTimeField(default=datetime.datetime.now), + ), + migrations.AddField( + model_name='evento', + name='title', + field=models.CharField(default='Evento', max_length=200), + ), + ] diff --git a/saap/cerimonial/migrations/0085_auto_20220511_1511.py b/saap/cerimonial/migrations/0085_auto_20220511_1511.py new file mode 100644 index 0000000..8f481e3 --- /dev/null +++ b/saap/cerimonial/migrations/0085_auto_20220511_1511.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-11 18:11 +from __future__ import unicode_literals + +import datetime +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('cerimonial', '0084_auto_20220511_0929'), + ] + + operations = [ + migrations.AlterModelOptions( + name='evento', + options={'verbose_name': 'Evento', 'verbose_name_plural': 'Eventos'}, + ), + migrations.AddField( + model_name='evento', + name='created', + field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2022, 5, 11, 18, 11, 17, 704399, tzinfo=utc), verbose_name='created'), + preserve_default=False, + ), + migrations.AddField( + model_name='evento', + name='modified', + field=models.DateTimeField(auto_now=True, default=datetime.datetime(2022, 5, 11, 18, 11, 26, 472073, tzinfo=utc), verbose_name='modified'), + preserve_default=False, + ), + migrations.AddField( + model_name='evento', + name='modifier', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='modifier'), + preserve_default=False, + ), + migrations.AddField( + model_name='evento', + name='owner', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='owner'), + preserve_default=False, + ), + migrations.AlterField( + model_name='evento', + name='description', + field=models.TextField(blank=True, default='', verbose_name='Descrição'), + ), + migrations.AlterField( + model_name='evento', + name='end_time', + field=models.DateTimeField(default=datetime.datetime.now, verbose_name='Término'), + ), + migrations.AlterField( + model_name='evento', + name='location', + field=models.TextField(blank=True, default='', verbose_name='Localização'), + ), + migrations.AlterField( + model_name='evento', + name='start_time', + field=models.DateTimeField(default=datetime.datetime.now, verbose_name='Início'), + ), + migrations.AlterField( + model_name='evento', + name='title', + field=models.CharField(default='Evento', max_length=200, verbose_name='Título'), + ), + ] diff --git a/saap/cerimonial/migrations/0086_auto_20220511_1554.py b/saap/cerimonial/migrations/0086_auto_20220511_1554.py new file mode 100644 index 0000000..195ea82 --- /dev/null +++ b/saap/cerimonial/migrations/0086_auto_20220511_1554.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-11 18:54 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models +import django.db.models.deletion +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0028_auto_20210218_1356'), + ('cerimonial', '0085_auto_20220511_1511'), + ] + + operations = [ + migrations.AddField( + model_name='evento', + name='workspace', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='core.AreaTrabalho', verbose_name='Área de trabalho'), + ), + migrations.AlterField( + model_name='evento', + name='end_time', + field=models.DateTimeField(default=datetime.datetime(2022, 5, 11, 18, 53, 55, 797120, tzinfo=utc), verbose_name='Término'), + ), + migrations.AlterField( + model_name='evento', + name='start_time', + field=models.DateTimeField(default=datetime.datetime(2022, 5, 11, 18, 53, 55, 797033, tzinfo=utc), verbose_name='Início'), + ), + ] diff --git a/saap/cerimonial/migrations/0087_auto_20220511_1554.py b/saap/cerimonial/migrations/0087_auto_20220511_1554.py new file mode 100644 index 0000000..080d44b --- /dev/null +++ b/saap/cerimonial/migrations/0087_auto_20220511_1554.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-11 18:54 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('cerimonial', '0086_auto_20220511_1554'), + ] + + operations = [ + migrations.AlterField( + model_name='evento', + name='end_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Término'), + ), + migrations.AlterField( + model_name='evento', + name='start_time', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Início'), + ), + ] diff --git a/saap/cerimonial/migrations/0088_auto_20220513_1132.py b/saap/cerimonial/migrations/0088_auto_20220513_1132.py new file mode 100644 index 0000000..cec742e --- /dev/null +++ b/saap/cerimonial/migrations/0088_auto_20220513_1132.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-13 14:32 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('cerimonial', '0087_auto_20220511_1554'), + ] + + operations = [ + migrations.RemoveField( + model_name='evento', + name='created', + ), + migrations.RemoveField( + model_name='evento', + name='modified', + ), + migrations.RemoveField( + model_name='evento', + name='modifier', + ), + migrations.RemoveField( + model_name='evento', + name='owner', + ), + ] diff --git a/saap/cerimonial/migrations/0089_auto_20220516_1656.py b/saap/cerimonial/migrations/0089_auto_20220516_1656.py new file mode 100644 index 0000000..74b81f4 --- /dev/null +++ b/saap/cerimonial/migrations/0089_auto_20220516_1656.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-16 19:56 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import smart_selects.db_fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0030_auto_20220516_1656'), + ('cerimonial', '0088_auto_20220513_1132'), + ] + + operations = [ + migrations.AlterModelOptions( + name='evento', + options={'ordering': ('inicio',), 'verbose_name': 'Evento', 'verbose_name_plural': 'Eventos'}, + ), + migrations.RenameField( + model_name='evento', + old_name='description', + new_name='descricao', + ), + migrations.RenameField( + model_name='evento', + old_name='start_time', + new_name='inicio', + ), + migrations.RenameField( + model_name='evento', + old_name='location', + new_name='localizacao', + ), + migrations.RenameField( + model_name='evento', + old_name='end_time', + new_name='termino', + ), + migrations.RenameField( + model_name='evento', + old_name='title', + new_name='titulo', + ), + migrations.AddField( + model_name='evento', + name='bairro', + field=smart_selects.db_fields.ChainedForeignKey(blank=True, chained_field='municipio', chained_model_field='municipio', default=5, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Bairro', verbose_name='Bairro'), + ), + migrations.AddField( + model_name='evento', + name='estado', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Estado', verbose_name='Estado'), + ), + migrations.AddField( + model_name='evento', + name='municipio', + field=smart_selects.db_fields.ChainedForeignKey(auto_choose=True, blank=True, chained_field='estado', chained_model_field='estado', null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Municipio', verbose_name='Município'), + ), + ] diff --git a/saap/cerimonial/migrations/0090_auto_20220516_1715.py b/saap/cerimonial/migrations/0090_auto_20220516_1715.py new file mode 100644 index 0000000..5f2278c --- /dev/null +++ b/saap/cerimonial/migrations/0090_auto_20220516_1715.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-16 20:15 +from __future__ import unicode_literals + +from django.db import migrations +import django.db.models.deletion +import smart_selects.db_fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('cerimonial', '0089_auto_20220516_1656'), + ] + + operations = [ + migrations.AlterField( + model_name='evento', + name='bairro', + field=smart_selects.db_fields.ChainedForeignKey(blank=True, chained_field='municipio', chained_model_field='municipio', null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Bairro', verbose_name='Bairro'), + ), + migrations.AlterField( + model_name='localtrabalho', + name='bairro', + field=smart_selects.db_fields.ChainedForeignKey(blank=True, chained_field='municipio', chained_model_field='municipio', null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Bairro', verbose_name='Bairro'), + ), + ] diff --git a/saap/cerimonial/models.py b/saap/cerimonial/models.py index 08fd854..554b3da 100644 --- a/saap/cerimonial/models.py +++ b/saap/cerimonial/models.py @@ -11,8 +11,14 @@ from smart_selects.db_fields import ChainedForeignKey +from django.utils import timezone + +from django.core.urlresolvers import reverse + from exclusivebooleanfield.fields import ExclusiveBooleanField +from pytz import timezone as pytz_timezone + FEMININO = 'F' MASCULINO = 'M' SEXO_CHOICE = ((FEMININO, _('Feminino')), @@ -203,6 +209,84 @@ class Meta(DescricaoAbstractModel.Meta): verbose_name = _('Operadora de telefonia') verbose_name_plural = _('Operadoras de telefonia') +#class Evento(SaapAuditoriaModelMixin): +class Evento(models.Model): + + titulo = models.CharField(max_length=200, default='Evento', blank=False, verbose_name=_('Título')) + + descricao = models.TextField(blank=True, default='', verbose_name=_('Descrição')) + + localizacao = models.TextField(blank=True, default='', verbose_name=_('Localização')) + + inicio = models.DateTimeField(default=timezone.now, blank=False, verbose_name=_('Início')) + + termino = models.DateTimeField(default=timezone.now, blank=False, verbose_name=_('Término')) + + workspace = models.ForeignKey( + AreaTrabalho, + verbose_name=_('Área de trabalho'), + blank=True, null=True, on_delete=PROTECT) + + estado = models.ForeignKey( + Estado, + verbose_name=_('Estado'), + blank=True, null=True) + + municipio = ChainedForeignKey( + Municipio, + chained_field="estado", + chained_model_field="estado", + null=True, blank=True, + show_all=False, + auto_choose=True, + sort=True, + verbose_name=_('Município')) + + bairro = ChainedForeignKey( + Bairro, + chained_field="municipio", + chained_model_field="municipio", + null=True, blank=True, + show_all=False, + auto_choose=False, + sort=True, + verbose_name=_('Bairro')) + + class Meta(): + verbose_name = _('Evento') + verbose_name_plural = _('Eventos') + ordering = ('inicio',) + + @cached_property + def fields_search(self): + return ['title', + 'description',] + + @property + def get_html_url(self): + url = reverse('saap.cerimonial:evento_edit', args=(self.id,)) + fmt = "%d/%m/%Y às %H:%M" + self.inicio = self.inicio.astimezone(pytz_timezone('America/Sao_Paulo')) + self.termino = self.termino.astimezone(pytz_timezone('America/Sao_Paulo')) + + if(self.bairro != None): + str_bairro = self.bairro.nome + else: + str_bairro = '' + + if(self.municipio != None): + str_municipio = self.municipio.nome + else: + str_municipio = '' + + text = "Descrição: " + self.descricao + "\n\n" + \ + "Local: " + self.localizacao + "\n" + \ + "Bairro: " + str_bairro + "\n" + \ + "Município: " + str_municipio + "\n\n" + \ + "Início: " + self.inicio.strftime(fmt) + "\n" + \ + "Término: " + self.termino.strftime(fmt) + hora_inicio = self.inicio.strftime("%H:%M") + return f' {hora_inicio} {self.titulo}' class Contato(SaapSearchMixin, SaapAuditoriaModelMixin): @@ -610,7 +694,7 @@ class LocalTrabalho(SaapAuditoriaModelMixin): Bairro, chained_field="municipio", chained_model_field="municipio", - null=True, blank=True, default=5, + null=True, blank=True, show_all=False, auto_choose=False, sort=True, diff --git a/saap/cerimonial/reports.py b/saap/cerimonial/reports.py index 9cbb7db..884e370 100644 --- a/saap/cerimonial/reports.py +++ b/saap/cerimonial/reports.py @@ -11,6 +11,7 @@ from django.template import Context, loader from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ +from django.core.urlresolvers import reverse from django_filters.views import FilterView from reportlab.lib import colors from reportlab.lib.enums import TA_CENTER, TA_RIGHT, TA_LEFT @@ -27,8 +28,10 @@ from reportlab.lib.units import mm from saap.cerimonial.forms import ImpressoEnderecamentoFilterSet,\ - ProcessosFilterSet, ContatosFilterSet, ContatosExportaFilterSet, ProcessoIndividualFilterSet, ContatoIndividualFilterSet, MalaDiretaFilterSet -from saap.cerimonial.models import Contato, Processo, Telefone, Email, GrupoDeContatos, Endereco, AssuntoProcesso, TopicoProcesso, LocalTrabalho, Dependente, FiliacaoPartidaria, Municipio, Estado, IMPORTANCIA_CHOICE + ProcessosFilterSet, ContatosFilterSet, ContatosExportaFilterSet, ProcessoIndividualFilterSet, ContatoIndividualFilterSet, MalaDiretaFilterSet, AgendaFilterSet +from saap.cerimonial.models import Contato, Processo, Telefone, Email, GrupoDeContatos, Endereco, \ + AssuntoProcesso, TopicoProcesso, LocalTrabalho, Dependente, FiliacaoPartidaria, \ + Bairro, Municipio, Estado, IMPORTANCIA_CHOICE, Evento from saap.core.models import AreaTrabalho from saap.crud.base import make_pagination from saap.utils import strip_tags, calcularIdade @@ -2919,6 +2922,307 @@ def add_relat_title(self, corpo_relatorio): corpo_relatorio.append(self.cabec) +# +# +# +# +# +# +# +# + +class RelatorioAgendaView(RelatorioProcessosView): + #permission_required = 'cerimonial.print_rel_agenda' + #permission_required = 'core.menu_agenda' + permission_required = 'core.menu_contatos' + filterset_class = AgendaFilterSet + model = Evento + template_name = 'cerimonial/filter_agenda.html' + container_field = 'workspace__operadores' + + def __init__(self): + super().__init__() + self.ctx_title = 'Agenda em PDF' + self.relat_title = 'Agenda do parlamentar ' + self.nome_objeto = 'Evento' + self.filename = 'Relatorio_Agenda' + + def get(self, request, *args, **kwargs): + filterset_class = self.get_filterset_class() + self.filterset = self.get_filterset(filterset_class) + self.object_list = self.filterset.qs + + if len(request.GET) and not len(self.filterset.form.errors)\ + and not self.object_list.exists(): + messages.error(request, _('Não existe agenda com as ' + 'condições definidas na busca!')) + + if 'print' in request.GET and self.object_list.exists(): + filename = str("Agenda_") +\ + str(datetime.datetime.fromtimestamp(time.time()).strftime('%Y_%m_%d_%H_%M_%S')) + response = HttpResponse(content_type='application/pdf') + content = 'inline; filename="%s.pdf"' % filename + #content = 'attachment; filename="%s.pdf"' % filename + response['Content-Disposition'] = content + self.build_pdf(response) + return response + + context = self.get_context_data(filter=self.filterset, + object_list=self.object_list) + + return self.render_to_response(context) + + def get_filterset(self, filterset_class): + kwargs = self.get_filterset_kwargs(filterset_class) + try: + kwargs['workspace'] = AreaTrabalho.objects.filter( + operadores=self.request.user.pk)[0] + except: + raise PermissionDenied(_('Sem permissão de acesso!')) + + self.relat_title += str(kwargs['workspace']) + + return filterset_class(**kwargs) + + def get_queryset(self): + qs = super().get_queryset() + kwargs = {} + if self.container_field: + kwargs[self.container_field] = self.request.user.pk + + return qs.filter(**kwargs) + + def get_context_data(self, **kwargs): + count = self.object_list.count() + context = super(RelatorioAgendaView, + self).get_context_data(**kwargs) + + context['count'] = count + context['title'] = _(self.ctx_title) + + paginator = context['paginator'] + page_obj = context['page_obj'] + + context['page_range'] = make_pagination( + page_obj.number, paginator.num_pages) + + qr = self.request.GET.copy() + if 'page' in qr: + del qr['page'] + context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' + + for agenda in page_obj: + fmt = "%d/%m/%Y às %H:%M" + agenda.inicio = agenda.inicio.strftime(fmt) + agenda.termino = agenda.termino.strftime(fmt) + agenda.url = reverse('saap.cerimonial:evento_edit', args=(agenda.id,)) + + return context + + def build_pdf(self, response): + TITULO = 0 + DESCRICAO = 1 + LOCALIZACAO = 2 + BAIRRO = 3 + MUNICIPIO = 4 + INICIO = 5 + TERMINO = 6 + + self.set_headings() + self.set_styles() + self.set_cabec(self.h5) + + if self.filterset.form.cleaned_data['orientacao'] == 'P': + estilo = ParagraphStyle( + name='Normal', + fontSize=8, + ) + elif self.filterset.form.cleaned_data['orientacao'] == 'R': + estilo = ParagraphStyle( + name='Normal', + fontSize=7, + ) + + corpo_relatorio = [] + self.add_relat_title(corpo_relatorio) + + registros = self.get_data() + for dados in registros: + + titulo = dados[TITULO] + descricao = dados[DESCRICAO] + + if(dados[BAIRRO] != None): + bairro = str(Bairro.objects.filter(pk=dados[BAIRRO])[0]) + else: + bairro = '' + + if(dados[MUNICIPIO] != None): + municipio = str(Municipio.objects.filter(pk=dados[MUNICIPIO])[0]) + else: + municipio = '' + + localizacao = '' + localizacao += dados[LOCALIZACAO] + localizacao += "
Bairro: " + str(bairro) + localizacao += "
Município: " + str(municipio) + + data_inicio = dados[INICIO].strftime('%d/%m/%Y') + hora_inicio = dados[INICIO].strftime('%H:%M') + data_termino = dados[TERMINO].strftime('%d/%m/%Y') + hora_termino = dados[TERMINO].strftime('%H:%M') + + item = [ + Paragraph(dados[TITULO], estilo), + Paragraph(dados[DESCRICAO], estilo), + Paragraph(localizacao, estilo), + Paragraph(data_inicio, estilo), + Paragraph(hora_inicio, estilo), + Paragraph(data_termino, estilo), + Paragraph(hora_termino, estilo), + ] + corpo_relatorio.append(item) + + style = TableStyle([ + ('FONTSIZE', (0, 0), (-1, -1), 6), + ('LEADING', (0, 0), (-1, -1), 7), + ('GRID', (0, 0), (-1, -1), 0.1, colors.black), + ('INNERGRID', (0, 0), (-1, -1), 0.1, colors.black), + ('TOPPADDING', (0, 0), (-1, -1), 3), + ('BOTTOMPADDING', (0, 0), (-1, -1), 3), + ('LEFTPADDING', (0, 0), (-1, -1), 3), + ('RIGHTPADDING', (0, 0), (-1, -1), 3), + ]) + style.add('VALIGN', (0, 0), (-1, -1), 'TOP') + + for i, value in enumerate(corpo_relatorio): + if len(value) <= 1: + style.add('SPAN', (0, i), (-1, i)) + + if len(value) == 0: + style.add('INNERGRID', (0, i), (-1, i), 0, colors.black), + style.add('GRID', (0, i), (-1, i), -1, colors.white) + style.add('LINEABOVE', (0, i), (-1, i), 0.1, colors.black) + + if len(value) == 1: + style.add('LINEABOVE', (0, i), (-1, i), 0.1, colors.black) + + if self.filterset.form.cleaned_data['orientacao'] == 'P': + rowHeights = 40 + elif self.filterset.form.cleaned_data['orientacao'] == 'R': + rowHeights = 50 + + t = LongTable(corpo_relatorio, rowHeights=None, splitByRow=True) + #t = LongTable(corpo_relatorio, rowHeights=rowHeights, splitByRow=True) + t.setStyle(style) + + if self.filterset.form.cleaned_data['orientacao'] == 'P': + t._argW[0] = 5 * cm + t._argW[1] = 7 * cm + t._argW[2] = 5 * cm + t._argW[3] = 2 * cm + t._argW[4] = 2 * cm + t._argW[5] = 2 * cm + t._argW[6] = 2 * cm + elif self.filterset.form.cleaned_data['orientacao'] == 'R': + t._argW[0] = 3.5 * cm + t._argW[1] = 4.5 * cm + t._argW[2] = 4 * cm + t._argW[3] = 2 * cm + t._argW[4] = 2 * cm + t._argW[5] = 2 * cm + t._argW[6] = 2 * cm + + #for i, value in enumerate(corpo_relatorio): + # if len(value) == 0: + # t._argH[i] = 7 + # continue + # for cell in value: + # if isinstance(cell, list): + # t._argH[i] = (height) * ( + # len(cell) - (0 if len(cell) > 1 else 0)) + # break + + elements = [t] + + if self.filterset.form.cleaned_data['orientacao'] == 'P': + orientacao = landscape(A4) + elif self.filterset.form.cleaned_data['orientacao'] == 'R': + orientacao = A4 + + doc = SimpleDocTemplate( + response, + title=self.relat_title, + pagesize=orientacao, + rightMargin=1.25 * cm, + leftMargin=1.25 * cm, + topMargin=1.1 * cm, + bottomMargin=0.8 * cm) + + doc.build(elements, canvasmaker=NumberedCanvas) + + def get_data(self): + + agrupamento = 'sem_agrupamento' + + agenda = [] + consulta_agregada = self.object_list.order_by('inicio') + consulta_agregada = consulta_agregada.values_list( + 'titulo', + 'descricao', + 'localizacao', + 'bairro', + 'municipio', + 'inicio', + 'termino', + ) + + for evento in consulta_agregada.all(): + agenda.append(evento) + + return agenda + +# def validate_data(self): +# contatos = [] +# consulta_agregada = self.object_list.order_by('nome',) +# consulta_agregada = consulta_agregada.values_list( +# 'id', +# ) +# +# total_erros = 0 +# +# for contato in consulta_agregada.all(): +# query = (Q(permite_contato=True)) +# query.add(Q(contato__id=contato[0]), Q.AND) +# +# email = Email.objects.filter(query).count() +# +# if(email == 0): +# total_erros = total_erros + 1 +# +# return total_erros + + def set_cabec(self, h5): + cabec = [Paragraph(_('Título'), h5)] + cabec.append(Paragraph(_('Descrição'), h5)) + cabec.append(Paragraph(_('Localização'), h5)) + cabec.append(Paragraph(_('Data de início'), h5)) + cabec.append(Paragraph(_('Hora de início'), h5)) + cabec.append(Paragraph(_('Data de término'), h5)) + cabec.append(Paragraph(_('Hora de término'), h5)) + self.cabec = cabec + + + def add_relat_title(self, corpo_relatorio): + tit_relatorio = _(self.relat_title) + tit_relatorio = force_text(tit_relatorio) + ' ' + + corpo_relatorio.append([Paragraph(tit_relatorio, self.h3)]) + + corpo_relatorio.append(self.cabec) + + + # # # @@ -3013,6 +3317,7 @@ def get_context_data(self, **kwargs): del qr['page'] context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' + for contato in context['page_obj']: endpref = contato.endereco_set.filter(principal=True).first() grupo = contato.grupodecontatos_set.all() diff --git a/saap/cerimonial/rules.py b/saap/cerimonial/rules.py index f5233e5..06ddeec 100644 --- a/saap/cerimonial/rules.py +++ b/saap/cerimonial/rules.py @@ -7,22 +7,22 @@ NivelInstrucao, EstadoCivil, FiliacaoPartidaria, AssuntoProcesso, Processo,\ ProcessoContato, GrupoDeContatos from saap.core.models import Trecho -from saap.core.rules import menu_contatos, menu_dados_auxiliares, search_trecho,\ - menu_processos, menu_relatorios, menu_grupocontatos +from saap.core.rules import menu_contatos, menu_processos, menu_agenda, search_trecho,\ + menu_correspondencias, menu_sistema from saap.globalrules.crud_custom import LIST, ADD, DETAIL, CHANGE, DELETE from saap.globalrules.globalrules import GROUP_SOCIAL_USERS,\ GROUP_WORKSPACE_OPER_CONTATOS, GROUP_WORKSPACE_MANAGERS,\ GROUP_WORKSPACE_OPER_PROCESSOS, GROUP_WORKSPACE_OPER_GRUPO_CONTATOS -rules_group_social_users = ( - GROUP_SOCIAL_USERS, [ - (Perfil, [ADD, DETAIL, CHANGE, DELETE]), - (EnderecoPerfil, [LIST, ADD, DETAIL, CHANGE, DELETE]), - (EmailPerfil, [LIST, ADD, DETAIL, CHANGE, DELETE]), - (TelefonePerfil, [LIST, ADD, DETAIL, CHANGE, DELETE]), - (LocalTrabalhoPerfil, [LIST, ADD, DETAIL, CHANGE, DELETE]), - (DependentePerfil, [LIST, ADD, DETAIL, CHANGE, DELETE]), ]) +#rules_group_social_users = ( +# GROUP_SOCIAL_USERS, [ +# (Perfil, [ADD, DETAIL, CHANGE, DELETE]), +# (EnderecoPerfil, [LIST, ADD, DETAIL, CHANGE, DELETE]), +# (EmailPerfil, [LIST, ADD, DETAIL, CHANGE, DELETE]), +# (TelefonePerfil, [LIST, ADD, DETAIL, CHANGE, DELETE]), +# (LocalTrabalhoPerfil, [LIST, ADD, DETAIL, CHANGE, DELETE]), +# (DependentePerfil, [LIST, ADD, DETAIL, CHANGE, DELETE]), ]) rules_group_workspace_managers = ( GROUP_WORKSPACE_MANAGERS, [ @@ -33,9 +33,10 @@ GROUP_WORKSPACE_OPER_CONTATOS, [ (get_user_model(), [ menu_contatos, - menu_dados_auxiliares, - menu_grupocontatos, - menu_relatorios]), + #menu_dados_auxiliares, + #menu_grupocontatos, + #menu_relatorios + ]), (Trecho, [LIST, DETAIL]), (OperadoraTelefonia, [LIST, DETAIL]), (NivelInstrucao, [LIST, DETAIL]), @@ -54,21 +55,22 @@ ] ) -rules_group_workspace_oper_grupo_contatos = ( - GROUP_WORKSPACE_OPER_GRUPO_CONTATOS, [ - (get_user_model(), [ - menu_contatos, - menu_grupocontatos, ]), - (GrupoDeContatos, [LIST, ADD, DETAIL, CHANGE, DELETE]), - (Contato, [LIST, DETAIL, ]), - ] -) +#rules_group_workspace_oper_grupo_contatos = ( +# GROUP_WORKSPACE_OPER_GRUPO_CONTATOS, [ +# (get_user_model(), [ +# menu_contatos, +# menu_grupocontatos, ]), +# (GrupoDeContatos, [LIST, ADD, DETAIL, CHANGE, DELETE]), +# (Contato, [LIST, DETAIL, ]), +# ] +#) rules_group_workspace_oper_processos = ( GROUP_WORKSPACE_OPER_PROCESSOS, [ (get_user_model(), [ menu_processos, - menu_dados_auxiliares, - menu_relatorios]), + #menu_dados_auxiliares, + #menu_relatorios + ]), #(AssuntoProcesso, [LIST, ADD, DETAIL, CHANGE, DELETE]), (Processo, [LIST, ADD, DETAIL, CHANGE, DELETE]), (ProcessoContato, [LIST, ADD, DETAIL, CHANGE, DELETE]), diff --git a/saap/cerimonial/templatetags/dados_camara.py b/saap/cerimonial/templatetags/dados_camara.py index 804d1cc..f8b03bb 100644 --- a/saap/cerimonial/templatetags/dados_camara.py +++ b/saap/cerimonial/templatetags/dados_camara.py @@ -34,6 +34,10 @@ def telefone_camara(): def site_camara(): return settings.DADOS_SITE +@register.simple_tag() +def versao_atual(): + return settings.VERSION + @register.simple_tag() def brasao_sistema(): if settings.BRASAO_PROPRIO == 'True': diff --git a/saap/cerimonial/urls.py b/saap/cerimonial/urls.py index ad615a0..40bd182 100644 --- a/saap/cerimonial/urls.py +++ b/saap/cerimonial/urls.py @@ -2,7 +2,8 @@ from saap.cerimonial.reports import ImpressoEnderecamentoView,\ RelatorioProcessosView, RelatorioContatosView, RelatorioContatosExportaView, \ - RelatorioContatoIndividualView, RelatorioProcessoIndividualView, MalaDiretaView + RelatorioContatoIndividualView, RelatorioProcessoIndividualView, \ + RelatorioAgendaView, MalaDiretaView from saap.cerimonial.views import ContatoCrud, TelefoneCrud, EmailCrud,\ DependenteCrud, LocalTrabalhoCrud, EnderecoCrud, FiliacaoPartidariaCrud,\ EnderecoPerfilCrud, LocalTrabalhoPerfilCrud, EmailPerfilCrud,\ @@ -14,7 +15,7 @@ ContatoFragmentFormPronomesView, StatusProcessoCrud, TopicoProcessoCrud,\ ClassificacaoProcessoCrud, ProcessoMasterCrud, AssuntoProcessoCrud,\ ContatoFragmentFormSearchView, ProcessoContatoCrud,\ - GrupoDeContatosMasterCrud + GrupoDeContatosMasterCrud, AgendaView, EventoView \ from .apps import AppConfig @@ -52,12 +53,20 @@ url(r'^processos/', include( ProcessoMasterCrud.get_urls() - )), + )), + + url(r'^agenda/$', AgendaView.as_view(), name='agenda'), + url(r'^evento/$', EventoView.event, name='evento_add'), + url(r'^evento/(?P\d+)/$', EventoView.event, name='evento_edit'), url(r'^correspondencias/enderecamentos', ImpressoEnderecamentoView.as_view(), name='print_impressoenderecamento'), + url(r'^relatorios/agenda', + RelatorioAgendaView.as_view(), + name='print_agenda'), + url(r'^relatorios/processos', RelatorioProcessosView.as_view(), name='print_rel_processos'), @@ -121,3 +130,4 @@ ] + diff --git a/saap/cerimonial/utils.py b/saap/cerimonial/utils.py new file mode 100644 index 0000000..a49fa11 --- /dev/null +++ b/saap/cerimonial/utils.py @@ -0,0 +1,81 @@ +from datetime import date, datetime, timedelta +from calendar import Calendar, LocaleHTMLCalendar + +import calendar + +class Calendar(LocaleHTMLCalendar): + + eventos = None + + def __init__(self, year=None, month=None, events=None, locale='pt_BR.UTF-8'): + self.year = year + self.month = month + super(Calendar, self).__init__() + self.setfirstweekday(6) + self.events = events + + # formats a day as a td + # filter events by day + def formatday(self, day, events): + events_per_day = events.filter(inicio__day=day) + d = '' + for event in events_per_day: + d += f'{event.get_html_url}
' + + if day != 0: + return f"{day}
{d}" + return '' + + # formats a week as a tr + def formatweek(self, theweek, events): + week = '' + for d, weekday in theweek: + week += self.formatday(d, events) + return f' {week} ' + + # formats a month as a table + # filter events by year and month + def formatmonth(self, withyear=True): + + cal = f'\n' + cal += f'{self.formatmonthname(self.year, self.month, withyear=withyear)}\n' + cal += f'{self.formatweekheader()}\n' + for week in self.monthdays2calendar(self.year, self.month): + cal += f'{self.formatweek(week, self.events)}\n' + cal += f'
' + return cal + +def get_date(req_day): + if req_day: + year, month = (int(x) for x in req_day.split('-')) + return date(year, month, day=1) + return datetime.today() + +def prev_month(d): + first = d.replace(day=1) + prev_month = first - timedelta(days=1) + month = 'mes=' + str(prev_month.year) + '-' + str(prev_month.month) + return month + +def next_month(d): + days_in_month = calendar.monthrange(d.year, d.month)[1] + last = d.replace(day=days_in_month) + next_month = last + timedelta(days=1) + month = 'mes=' + str(next_month.year) + '-' + str(next_month.month) + return month + +def this_month(): + d= date.today() + month = 'mes=' + str(d.year) + '-' + str(d.month) + return month + +def prev_year(d): + prev_year = d.year - 1 + month = 'mes=' + str(prev_year) + '-' + str(d.month) + return month + +def next_year(d): + next_year = d.year + 1 + month = 'mes=' + str(next_year) + '-' + str(d.month) + return month + diff --git a/saap/cerimonial/views.py b/saap/cerimonial/views.py index 7530d43..5fa16ff 100644 --- a/saap/cerimonial/views.py +++ b/saap/cerimonial/views.py @@ -1,11 +1,19 @@ from django.core.exceptions import PermissionDenied from django.db.models.aggregates import Max +from django.db.models import Q from django.utils.translation import ugettext_lazy as _ from django.views.generic.edit import FormView +from django.utils.safestring import mark_safe +from django.shortcuts import render, get_object_or_404 +from django.views import generic +from django.http import HttpResponseRedirect +from django.core.urlresolvers import reverse + from _functools import reduce from datetime import date, timedelta import datetime + import operator from saap.cerimonial.forms import LocalTrabalhoForm, EnderecoForm,\ @@ -13,7 +21,7 @@ ContatoFragmentPronomesForm, ContatoForm, ProcessoForm,\ ContatoFragmentSearchForm, ProcessoContatoForm,\ ListWithSearchProcessoForm, ListWithSearchContatoForm,\ - GrupoDeContatosForm, TelefoneForm, EmailForm + GrupoDeContatosForm, TelefoneForm, EmailForm, EventoForm from saap.cerimonial.models import TipoTelefone, TipoEndereco,\ TipoEmail, Parentesco, EstadoCivil, TipoAutoridade, TipoLocalTrabalho,\ NivelInstrucao, Contato, Telefone, OperadoraTelefonia, Email,\ @@ -21,18 +29,23 @@ DependentePerfil, LocalTrabalhoPerfil,\ EmailPerfil, TelefonePerfil, EnderecoPerfil, FiliacaoPartidaria,\ StatusProcesso, ClassificacaoProcesso, TopicoProcesso, Processo,\ - AssuntoProcesso, ProcessoContato, GrupoDeContatos + AssuntoProcesso, ProcessoContato, GrupoDeContatos, Evento from saap.cerimonial.rules import rules_patterns + +from saap.cerimonial.utils import Calendar, get_date, prev_month, prev_year, next_month, next_year, this_month + +from saap.utils import normalize + from saap.core.forms import ListWithSearchForm from saap.core.models import AreaTrabalho from saap.crispy_layout_mixin import CrispyLayoutFormMixin + from saap.globalrules import globalrules from saap.globalrules.crud_custom import DetailMasterCrud,\ MasterDetailCrudPermission, PerfilAbstractCrud, PerfilDetailCrudPermission -from saap.utils import normalize - -from django.db.models import Q +# Variável usada para transportar a area de trabalho na seção de agenda/evento +workspace = None globalrules.rules.config_groups(rules_patterns) @@ -396,6 +409,52 @@ def post(self, request, *args, **kwargs): # pk=self.object.pk).update(preferencial=False) return response +class AgendaView(generic.ListView): + model = Evento + template_name = 'cerimonial/agenda.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['title'] = "Agenda" + + d = get_date(self.request.GET.get('mes', None)) + + + global workspace + workspace = AreaTrabalho.objects.filter(operadores=self.request.user.pk)[0] + eventos = Evento.objects.filter(inicio__year=d.year, inicio__month=d.month, workspace=workspace).order_by('inicio') + cal = Calendar(d.year, d.month, eventos) + + # Call the formatmonth method, which returns our calendar as a table + html_cal = cal.formatmonth(withyear=True) + context['calendar'] = mark_safe(html_cal) + context['prev_month'] = prev_month(d) + context['next_month'] = next_month(d) + context['prev_year'] = prev_year(d) + context['next_year'] = next_year(d) + context['this_month'] = this_month() + return context + +class EventoView(): + + def event(request, event_id=None): + global workspace + + instance = Evento() + if event_id: + instance = get_object_or_404(Evento, pk=event_id) + else: + instance = Evento() + + form = EventoForm(request.POST or None, instance=instance, workspace=workspace) + if request.POST and form.is_valid(): + inicio = form.cleaned_data['inicio'] + #month = "mes=" + str(inicio.year) + "-" + str(inicio.month) + form.save() + #return HttpResponseRedirect(reverse('saap.cerimonial:agenda_mes', args=(month,))) + return HttpResponseRedirect(reverse('saap.cerimonial:agenda')) + return render(request, 'cerimonial/evento.html', {'form': form, 'title': "Evento"}) + class FiliacaoPartidariaCrud(MasterDetailCrudPermission): model = FiliacaoPartidaria @@ -892,11 +951,9 @@ def get_queryset(self): qs = qs.annotate(pk_unico=Max('pk')) return qs - class GrupoDeContatosMasterCrud(DetailMasterCrud): model = GrupoDeContatos container_field = 'workspace__operadores' - model_set = 'contatos' class BaseMixin(DetailMasterCrud.BaseMixin): diff --git a/saap/core/forms.py b/saap/core/forms.py index 717f51a..a640e3a 100644 --- a/saap/core/forms.py +++ b/saap/core/forms.py @@ -157,12 +157,9 @@ class UserForm(UserChangeForm): class Meta(UserChangeForm.Meta): model = User - fields = ('first_name', 'last_name', 'avatar', 'cropping') - widgets = { - 'avatar': CustomImageCropWidget(), - 'cropping': CropWidget(), - } + def __init__(self, *args, **kwargs): + super(UserForm, self).__init__(*args, **kwargs) class OperadorAreaTrabalhoForm(ModelForm): diff --git a/saap/core/migrations/0029_auto_20220516_0935.py b/saap/core/migrations/0029_auto_20220516_0935.py new file mode 100644 index 0000000..b1d8881 --- /dev/null +++ b/saap/core/migrations/0029_auto_20220516_0935.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-16 12:35 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0028_auto_20210218_1356'), + ] + + operations = [ + migrations.AlterModelOptions( + name='user', + options={'ordering': ['first_name'], 'permissions': (('menu_contatos', 'Mostrar menu de Contatos'), ('menu_processos', 'Mostrar menu de Processos'), ('menu_agenda', 'Mostrar menu de Agenda'), ('menu_correspondencias', 'Mostrar menu de Correspondências'), ('menu_tabelas_auxiliares', 'Mostrar menu de Tabelas auxiliares'), ('menu_permissoes', 'Mostrar menu de Permissões'))}, + ), + ] diff --git a/saap/core/migrations/0030_auto_20220516_1656.py b/saap/core/migrations/0030_auto_20220516_1656.py new file mode 100644 index 0000000..d71fc4f --- /dev/null +++ b/saap/core/migrations/0030_auto_20220516_1656.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-16 19:56 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0029_auto_20220516_0935'), + ] + + operations = [ + migrations.AlterModelOptions( + name='user', + options={'ordering': ['first_name'], 'permissions': (('menu_contatos', 'Mostrar menu de Contatos'), ('menu_processos', 'Mostrar menu de Processos'), ('menu_agenda', 'Mostrar menu de Agenda'), ('menu_correspondencias', 'Mostrar menu de Correspondências'), ('menu_sistema', 'Mostrar menu de Sistema'))}, + ), + ] diff --git a/saap/core/migrations/0031_auto_20220517_1605.py b/saap/core/migrations/0031_auto_20220517_1605.py new file mode 100644 index 0000000..6785b0f --- /dev/null +++ b/saap/core/migrations/0031_auto_20220517_1605.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-17 19:05 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0030_auto_20220516_1656'), + ] + + operations = [ + migrations.AlterField( + model_name='areatrabalho', + name='descricao', + field=models.CharField(blank=True, default='', max_length=254, verbose_name='Descrição'), + ), + migrations.AlterField( + model_name='areatrabalho', + name='nome', + field=models.CharField(default='', max_length=100, verbose_name='Nome'), + ), + ] diff --git a/saap/core/migrations/0032_auto_20220518_1534.py b/saap/core/migrations/0032_auto_20220518_1534.py new file mode 100644 index 0000000..50dd390 --- /dev/null +++ b/saap/core/migrations/0032_auto_20220518_1534.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2022-05-18 18:34 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone +import image_cropping.fields +import saap.core.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0031_auto_20220517_1605'), + ] + + operations = [ + migrations.AlterModelOptions( + name='user', + options={'ordering': ['first_name'], 'permissions': (('menu_contatos', 'Mostrar menu de Contatos'), ('menu_processos', 'Mostrar menu de Processos'), ('menu_agenda', 'Mostrar menu de Agenda'), ('menu_correspondencias', 'Mostrar menu de Correspondências'), ('menu_sistema', 'Mostrar menu de Sistema')), 'verbose_name': 'Usuário', 'verbose_name_plural': 'Usuários'}, + ), + migrations.AlterField( + model_name='user', + name='avatar', + field=image_cropping.fields.ImageCropField(blank=True, null=True, upload_to='avatars/', validators=[saap.core.models.avatar_validation], verbose_name='Imagem do perfil'), + ), + migrations.AlterField( + model_name='user', + name='date_joined', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Data de ingresso'), + ), + migrations.AlterField( + model_name='user', + name='email', + field=models.EmailField(max_length=254, unique=True, verbose_name='E-mail'), + ), + migrations.AlterField( + model_name='user', + name='first_name', + field=models.CharField(blank=True, max_length=30, verbose_name='Nome'), + ), + migrations.AlterField( + model_name='user', + name='is_active', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='Ativo'), + ), + migrations.AlterField( + model_name='user', + name='is_staff', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='Membro da equipe'), + ), + migrations.AlterField( + model_name='user', + name='last_name', + field=models.CharField(blank=True, max_length=30, verbose_name='Sobrenome'), + ), + ] diff --git a/saap/core/models.py b/saap/core/models.py index 28337d7..3c6f846 100644 --- a/saap/core/models.py +++ b/saap/core/models.py @@ -83,22 +83,33 @@ def avatar_validation(image): class User(AbstractBaseUser, PermissionsMixin): - email = models.EmailField(_('email address'), unique=True) - first_name = models.CharField(_('first name'), max_length=30, blank=True) - last_name = models.CharField(_('last name'), max_length=30, blank=True) + email = models.EmailField(_('E-mail'), unique=True) + + first_name = models.CharField(_('Nome'), max_length=30, blank=True) + + last_name = models.CharField(_('Sobrenome'), max_length=30, blank=True) + is_staff = models.BooleanField( - _('staff status'), default=False, + default=False, + choices=YES_NO_CHOICES, + verbose_name=_('Membro da equipe'), help_text=_('Designates whether the user can log into this admin ' 'site.')) + is_active = models.BooleanField( - _('active'), default=True, + default=False, + choices=YES_NO_CHOICES, + verbose_name=_('Ativo'), help_text=_('Designates whether this user should be treated as ' 'active. Unselect this instead of deleting accounts.')) - date_joined = models.DateTimeField(_('date joined'), default=timezone.now) + + date_joined = models.DateTimeField(_('Data de ingresso'), default=timezone.now) + avatar = ImageCropField( - _('profile picture'), upload_to="avatars/", + _('Imagem do perfil'), upload_to="avatars/", validators=[avatar_validation], null=True, blank=True) + cropping = ImageRatioField( 'avatar', '70x70', help_text=_( 'Note that the preview above will only be updated after ' @@ -112,6 +123,8 @@ class Meta(AbstractBaseUser.Meta): abstract = False permissions = MENU_PERMS_FOR_USERS ordering = ['first_name'] + verbose_name = "Usuário" + verbose_name_plural = "Usuários" def __str__(self): return self.get_display_name() @@ -449,11 +462,11 @@ def __str__(self): class AreaTrabalho(SaapAuditoriaModelMixin): - nome = models.CharField(max_length=100, blank=True, default='', + nome = models.CharField(max_length=100, blank=False, default='', verbose_name=_('Nome')) descricao = models.CharField( - default='', max_length=254, verbose_name=_('Descrição')) + default='', max_length=254, verbose_name=_('Descrição'), blank=True) parlamentar = models.ForeignKey( Parlamentar, diff --git a/saap/core/rules.py b/saap/core/rules.py index 963290f..b734331 100644 --- a/saap/core/rules.py +++ b/saap/core/rules.py @@ -1,26 +1,30 @@ from django.utils.translation import ugettext_lazy as _ -menu_dados_auxiliares = "menu_dados_auxiliares" -menu_tabelas_auxiliares = "menu_tabelas_auxiliares" -menu_area_trabalho = "menu_area_trabalho" menu_contatos = "menu_contatos" -menu_grupocontatos = "menu_grupocontatos" +#menu_grupocontatos = "menu_grupocontatos" menu_processos = "menu_processos" -menu_impresso_enderecamento = "menu_impresso_enderecamento" -menu_relatorios = "menu_relatorios" +menu_agenda = "menu_agenda" +#menu_impresso_enderecamento = "menu_impresso_enderecamento" +menu_correspondencias = "menu_correspondencias" +#menu_relatorios = "menu_relatorios" +#menu_dados_auxiliares = "menu_dados_auxiliares" +#menu_tabelas_auxiliares = "menu_tabelas_auxiliares" +#menu_area_trabalho = "menu_area_trabalho" +menu_sistema = "menu_sistema" MENU_PERMS_FOR_USERS = ( - (menu_dados_auxiliares, _('Mostrar menu de Dados auxiliares')), - (menu_tabelas_auxiliares, _('Mostrar menu de Tabelas auxiliares')), - (menu_contatos, _('Mostrar menu de de Contatos')), - (menu_grupocontatos, _('Mostrar menu de Grupos de Contatos')), + (menu_contatos, _('Mostrar menu de Contatos')), + #(menu_grupocontatos, _('Mostrar menu de Grupos de Contatos')), (menu_processos, _('Mostrar menu de Processos')), - (menu_area_trabalho, _('Mostrar menu de Áreas de trabalho')), - (menu_impresso_enderecamento, - _('Mostrar menu de Impressos de endereçamento')), - (menu_relatorios, - _('Mostrar Menu de Relatórios')), + (menu_agenda, _('Mostrar menu de Agenda')), + #(menu_impresso_enderecamento, _('Mostrar menu de Correspondências')), + (menu_correspondencias, _('Mostrar menu de Correspondências')), + #(menu_relatorios,_('Mostrar Menu de Relatórios')), + #(menu_dados_auxiliares, _('Mostrar menu de Dados auxiliares')), + #(menu_tabelas_auxiliares, _('Mostrar menu de Tabelas auxiliares')), + #(menu_area_trabalho, _('Mostrar menu de Áreas de trabalho')), + (menu_sistema, _('Mostrar menu de Sistema')), ) diff --git a/saap/core/urls.py b/saap/core/urls.py index ee2a522..aeb0094 100644 --- a/saap/core/urls.py +++ b/saap/core/urls.py @@ -7,8 +7,8 @@ from saap.core.forms import LoginForm, NewPasswordForm, ResetPasswordForm, PasswordForm from saap.core.views import CepCrud, RegiaoMunicipalCrud, DistritoCrud,\ BairroCrud, MunicipioCrud, EstadoCrud, TipoLogradouroCrud, LogradouroCrud, TrechoCrud, \ - TrechoJsonSearchView, TrechoJsonView, AreaTrabalhoCrud,\ - OperadorAreaTrabalhoCrud, PartidoCrud, ImpressoEnderecamentoCrud, HelpTopicView + TrechoJsonSearchView, TrechoJsonView, AreaTrabalhoCrud, \ + OperadorAreaTrabalhoCrud, PartidoCrud, ImpressoEnderecamentoCrud, HelpTopicView, UserCrud from .apps import AppConfig @@ -57,9 +57,12 @@ # url(r'^enderecos/', login_required( # TrechoSearchView.as_view()), name='search_view'), + url(r'^areatrabalho/', include(AreaTrabalhoCrud.get_urls() + OperadorAreaTrabalhoCrud.get_urls())), + url(r'^usuarios/', include(UserCrud.get_urls())), + url(r'^api/enderecos.json', TrechoJsonSearchView.as_view( {'get': 'list'}), name='trecho_search_rest_json'), url(r'^api/trecho.json/(?P[0-9]+)$', TrechoJsonView.as_view( @@ -82,12 +85,7 @@ url(r'^sistema/core/partido/', include(PartidoCrud.get_urls())), - url(r'^sistema/$', permission_required( - 'core.menu_tabelas_auxiliares', login_url='saap.core:login')( - TemplateView.as_view(template_name='saap_sistema.html')), - name="tabelas_auxiliares"), - - url(r'^sistema$', permission_required( + url(r'^tabelas$', permission_required( 'core.menu_tabelas_auxiliares', login_url='saap.core:login')( TemplateView.as_view(template_name='saap_sistema.html')), name="tabelas_auxiliares"), diff --git a/saap/core/views.py b/saap/core/views.py index 6d9d09b..4a2cf49 100644 --- a/saap/core/views.py +++ b/saap/core/views.py @@ -19,7 +19,7 @@ from saap.core.models import Partido, Filiacao from saap.core.forms import OperadorAreaTrabalhoForm, ImpressoEnderecamentoForm,\ - ListWithSearchForm + ListWithSearchForm, UserForm from saap.core.models import Cep, TipoLogradouro, Logradouro, RegiaoMunicipal,\ Distrito, Bairro, Municipio, Estado, Trecho, AreaTrabalho, OperadorAreaTrabalho,\ ImpressoEnderecamento, User @@ -43,6 +43,16 @@ TipoLogradouroCrud = DetailMasterCrud.build(TipoLogradouro, None, 'tipo_logradouro') LogradouroCrud = DetailMasterCrud.build(Logradouro, None, 'logradouro') +#UserCrud = DetailMasterCrud.build(User, None, 'usuario') + +class UserCrud(DetailMasterCrud): + help_text = 'usuario' + model = User + + class BaseMixin(DetailMasterCrud.BaseMixin): + list_field_names = [ + ('first_name', 'last_name'), 'email', 'groups', 'is_active'] + class TrechoCrud(DetailMasterCrud): help_text = 'trecho' model = Trecho @@ -165,11 +175,10 @@ class BaseMixin(DetailMasterCrud.BaseMixin): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['subnav_template_name'] = 'core/subnav_areatrabalho.yaml' - context['headers'] = ['Usuário', 'Grupo associado', 'Descrição'] return context class DetailView(DetailMasterCrud.DetailView): - list_field_names_set = ['user', 'grupos_associados'] + list_field_names_set = [] class OperadorAreaTrabalhoCrud(MasterDetailCrudPermission): parent_field = 'areatrabalho' @@ -240,7 +249,6 @@ def post(self, request, *args, **kwargs): return MasterDetailCrudPermission.DeleteView.post( self, request, *args, **kwargs) - class PartidoCrud(DetailMasterCrud): help_text = 'partidos' model_set = 'filiacaopartidaria_set' diff --git a/saap/crispy_layout_mixin.py b/saap/crispy_layout_mixin.py index 6ac4a2e..44d5e05 100644 --- a/saap/crispy_layout_mixin.py +++ b/saap/crispy_layout_mixin.py @@ -38,14 +38,14 @@ def form_actions(more=[], save_label=_('Salvar')): Submit('salvar', save_label, css_class='pull-right'), *more) -class SaplFormLayout(Layout): +class SaapFormLayout(Layout): def __init__(self, *fields, label_cancel=_('Cancelar')): buttons = form_actions(more=[ HTML('%s' % label_cancel)]) _fields = list(to_fieldsets(fields)) + [to_row([(buttons, 12)])] - super(SaplFormLayout, self).__init__(*_fields) + super(SaapFormLayout, self).__init__(*_fields) def get_field_display(obj, fieldname): @@ -119,7 +119,7 @@ def get_form(self, form_class=None): pass else: form.helper = FormHelper() - form.helper.layout = SaplFormLayout(*self.get_layout()) + form.helper.layout = SaapFormLayout(*self.get_layout()) return form @property @@ -166,7 +166,6 @@ def read_layout_from_yaml(yaml_layout, key): # TODO cache this at application level yaml = read_yaml_from_file(yaml_layout) base = yaml[key] - def line_to_namespans(line): split = [cell.split(':') for cell in line.split()] namespans = [[s[0], int(s[1]) if len(s) > 1 else 0] for s in split] diff --git a/saap/settings.py b/saap/settings.py index 9606778..b8e5c6c 100644 --- a/saap/settings.py +++ b/saap/settings.py @@ -57,7 +57,7 @@ DADOS_SITE = config('DADOS_SITE'); BRASAO_PROPRIO = config('BRASAO_PROPRIO'); -VERSION='3.0.5b' +VERSION='3.1.0' INSTALLED_APPS = ( #'django_admin_bootstrapped', diff --git a/saap/static/img/index_agenda.png b/saap/static/img/index_agenda.png new file mode 100644 index 0000000000000000000000000000000000000000..c189634ce7ef7395edde82658f1bdf6d4d49e275 GIT binary patch literal 1646 zcmV-!29f!RP)oV9-YLnRzA`&5D;)97vW+NsgVWc5Z zO7z2eh^U94s9ya8B8t$Lga#iX$S5jgLLn2)H%%)`17jFMC`@!5hv9m^J)Cp8`<}D! zxo4lbcl6Hmh08g2@3q%jd!4oS+IyXSU=onDOwy2~Q$J>fiu6c2twy{!1#+KD$vi7*R;T6q6j`M#zMKNN`#f^*FUxN-F6(RCHwk`4G1pU8 zx*t7xmGPKPGq0Ih4XEZR((d^nmI;2HtB%YbAfc_L+GfzeEM~A7cotYi^hA4Ox}-yo z17ABrn&BX@vIR|1oCF4P>Jwcy0{i3t6GVm?mvjmE2^a*va1HXG0VFL1J^>yqs8lNh z^x=2cGH5ll(z;ZL9vr}IU_Nj+(Zf9|X}g&XCxl446F5xq&ijNyU+kcgh3Kr|N34E>PF4D0Wnc+#v*O0L<(X@S#(F z5qJ+E9$)hV`Q~Asq(sm6feTLEE;CF1uLsoyEv6Ueg& z1|9zWvCP#@*%C=t0(qW7(MBc|XC*C3n^X0TN~iFSJPRyIizWS5g5PPA^fORqM>PuU z0)~KbU;*$7u%QI65PS#h21bZt{Ug9@)FaO)`wBC05c4}pG=oIZxy4*)pGqZ041a%E0Wy*YEFeJIq z_3+IB)7b!-zRh?8c;|q0LA(RoO|)t1p`Imnhmgz5mrgIEY~I7DrsyW(-zZTA`0c!O zcmk7jj>df8MKin2C6d$&T%miyW)+xYX5((5q^s1k#Q=Yl*#~@2WZ}fRi5DcbnM1S0p=+9NkhIOA%Opq@WwTtIE9266<7zyou3acPNf+|&GDE;?G)eP;H=Ls5 zl0qWNW=Wr#*)L_}7et{b&DKe|h+?bm||?>C00N z-8@hI<#ZS<;{ioO-B|qkEEmddQGNVtE$R~l=OfCD=`u-=$?~tYsQ1wo8iL(2sTDQ) zv^7A{2{m{tKiv!v44(nI`At#q{8Xm1J0-oED)SwO+aqbT1pj_X`%`7^;^kdQ7w{wa zP=;?MIBI5dT%s;r3Er9x!|hrLjsSns=xz%re#)#BE&%DxP%m{a8zDL;R3n>~n+Gfm zlIB?nUh&YlE5Ro`^-C3gJjI7=lAiSNZ&yMjvl8^Vbg-Mp+t)3?aA;cxmw=be?6Vd+ zwKXENEg@oN2P8cW{16vCPvowxa4^1pU}hf`klEIG$Y+370`iRJ?g&qq*~wN4sJ<7- zd$K)E?k9~h2DnesSd+RH&iw|(=W%Jw@u&0o2^L9OM}1;*e{Ok3V6%D)Rmvf&8EUc*k^w_8xV=QQ+-UGXqS=lcUS=czgvo;UOMWl+^5| zS>Fpq2e_9LeK3{ANt3h*_@+twD)q0gYR)h3@Klk*RArGK$Las>o8n}XG>hhQ-4+9o zbO>12Zf$RH90djv11toN5dQT@w_*fXYi7e005cmQdcSw{x~erbI0g(P-wm2u6Hj_u sfoF&!L2ssccVe9ARLNf8b9b5lFZdUG+?#I)N&o-=07*qoM6N<$f?9S5CjbBd literal 0 HcmV?d00001 diff --git a/saap/static/img/index_maladireta.png b/saap/static/img/index_maladireta.png new file mode 100644 index 0000000000000000000000000000000000000000..a05018281b586da249bd56d3e942f6412b67603e GIT binary patch literal 2303 zcmVsv~SmRhz z8H3aks)SOiLsUl$zKy)^swHCUNrU zGjTGhnL9r}tTS}azWdy}_q}&&LH{r?XWzBgV?Wm3XRURl|8cl>F#;Ay>XvlQId{2T zd_VL6ZUSE7eOv+d0E2TNs|VN${1~_oxUStYa+Dba_EL^Ff#q$Kw*{Dp$k_`lZI_%B zMJmNC#FPnf2+YVaS~fV2JwXZVXl6L7=VAIR)0Rj`k|Bj-G@EWz*vzz;G1kb_r& zZLygv_^txp&fqrzEUu^MBN@ylfwc_`?*#6`wn!^D3EW&F<9gt|3|>3SlQ0L7HSclyLeN?d* zsg?BxuIOLEW;XV^{dg$5pnobu|Lvi`4$?0gli0gKneuC?_0Z$Ma3jM@24Wi)WbtuV zgm!?mS9E~W_$vumpV$%L{yGZxV~)|sA!17|Zy;_924Rv&5Cpr2q+-3_@!`Zy6O`Po zu~qbZ#P|iR81M1l8t~p7q3I$06x%5kK~dnCq=5uIe&U?_PKkndO7d67DM_Do&i%85 zz9M^dT`lQjk~$?_mULdy%aUGj&Rq)V|0HQ$(KIdTTIby1fbT9zw;TNJ@>PzY{iKXMCU6^ykRy>-QqO?sv{z2-;%#gF}hBg{aZB1f3NnhQ?Jp z@{-*h{Qk3+UNJ!7>`zPjMTFPqBW!~J(6cwQIjcRb?Q zBxz0XJSFMx5xQ`jycMD8P};;!S0w!_fl!KXl~9>*S;uadHNK=+DPhv(jB_rgTbOko zi_oNAzb{o3AHf)UB5j?)bGVv58tD^TnwwR-QpQU3_>%YkWyKw3OxGCRsK`8b3S#;G z(3o(QbVd3s@o#Tf9`E}Rx|q|wT9O}p-S59I_%b4GF`TMM8c8I+HqsU<8E%P~f9a8Q zXK}6VmGrwvKPkS4@7C-=e!N82PnWcZI9^fur6Klm6Rw_510wBP%34BMeSL%Bb^?0CcN;7zz^*jAEvD0aw zD-iftKyyd%`wHr3aYiU+0vm*SvB@e{92Gs|u`v8-qAk1pjyENC0@xA=9S(j!LJ&6q z)&NHXx(RG)&ktx<0Ve|5DwgGq23fqm-_Ee(n|{YGYQxxNIE`2IfCV7JIDSpeprBg= zY{G)DrG*n%esVo<3+4kFmb_r~OQVT4TqI25Vo8ToJRJ1h9Q^(+b5JMPU;rM-(EpJb zgQcW<&noaWuWtvnS)fn z?5W3WIe8;FikEH0a(!VIX97!*CdD1FqNuGj$UPRci^$73SAkWHm_C}pkV1hzum3H? zlN0ALKeM4wyc63x-Pq-26LzG|WRg!%r?v-6=7uLrBRrNe0}H85Z#BYcyphH7gN;iapBoj9pUP#>F< zjQ51)ZiN{<&1~m54@++T67w0Mj{w^;N3k2}MgqeNK7-_?zkdXZRQNZV)FNzk1B8g^Xf}UC}+nU<5H|H z-~jn0aX!w{;6m2$lNoFl_bENjRu6B<0-a;Bim*$vz(=>Fg|ZmH5=qU?*lb*obc1v5 zg?dCu-wa`wnWvN&9UU@EX1=tRRVfwiPb{lKZ4)2Q~;DSQI8T zrr1u5W1rHs{8d^z2BI6Q)@=6EnjDN^_lh|y%WoRkU40n%Ir-)6^>DG^xq&u+8`!pQ zheH%IG$iQ;No!@bL8VvT0GDLdAqOSxmGsP9eUCh60}uk+s{^WknVOPio5o@VS^viz Z;6E|DoLRNNg;oFn002ovPDHLkV1iKvSpNV3 literal 0 HcmV?d00001 diff --git a/saap/static/styles/app.css b/saap/static/styles/app.css index 3c94822..63f7353 100644 --- a/saap/static/styles/app.css +++ b/saap/static/styles/app.css @@ -2,14 +2,14 @@ /* FONTES */ html { - font-family: "Helvetica Neue",Arial,"Noto Sans", "sans-serif","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; } body { margin: 0; - font-family: "Helvetica Neue",Arial,"Noto Sans", "sans-serif","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji } .alert > strong, @@ -28,7 +28,7 @@ label, .nav-tabs > li.active > a, h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { - font-family: "Helvetica Neue",Arial,"Noto Sans", "sans-serif","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji } .btn { @@ -119,11 +119,10 @@ a { transform: rotate(180deg); } #content { - margin-top: 20px; margin-bottom: 170px; } .navbar { - background: #001a00; + background: white; border-radius: 0; border-width: 0; margin: 0; @@ -138,7 +137,7 @@ a { background-color: white; } .navbar a.navbar-brand { font-size: 18px; - color: white; + color: #008800; display: block; position: relative; margin: 0 !important; } @@ -180,6 +179,10 @@ a { .nav-acessar { display: none; } +.smallfooter { + color: #777; +} + .masthead { margin-top: 20px; z-index: 10; } @@ -206,7 +209,7 @@ a { color: black; border-radius: 5px 5px 0 0; border-bottom: 1px solid white; - font-size: 2em; + font-size: 32px; font-weight: 400; float: left; } @@ -264,7 +267,7 @@ main .btn-default.btn-excluir:hover { padding: 0px 20px 15px 20px; margin-bottom: 15px; float: right; - background: white; + background: red; font-size: 17px; } @@ -286,6 +289,7 @@ main .btn-default.btn-excluir:hover { display: inline-block; vertical-align: middle; float: none; + margin-left: -20px; padding: 30px; } .table { @@ -430,7 +434,9 @@ fieldset { .h1, h1 { - font-size: 35px; } + font-size: 30px; + font-weight: 500; + } .h2, h2 { @@ -468,7 +474,7 @@ body { width: 100%; /* Set the fixed height of the footer here */ background: #001a00 none repeat scroll 0% 0%; - color: white; + color: #777; text-align: center; font-size: 1em; } @@ -553,7 +559,7 @@ body { margin-top: 1em; line-height: 50px; text-align: center; - font-family: "Helvetica Neue",Arial,"Noto Sans","sans-serif","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji } .container-login .btns-login .btn-login:hover { border: 1px solid #fff; @@ -575,6 +581,21 @@ body { .pagination { padding-top: 25px; } +.pagination > li > a { + color: #008800; +} + +.pagination > li > a:hover { + color: #008800; + border-color: #dee2e6; + background: #dee2e6; +} + +.pagination > li > a:focus { + color: #008800; + border-color: #008800; +} + .container, .container-fluid { transition: all 1s ease; } @@ -708,6 +729,7 @@ body { .context-actions .actions { margin: 0.5em 0; } } + main .navbar .navbar-nav li a > .dropdown-menu { background: red @@ -758,7 +780,7 @@ main .navbar .navbar-nav li a > .dropdown-menu { font-size: 1em !important; } .page-header { padding: 0.4em 0.3em 0.3em; - font-size: 1.5em; } } + font-size: 32px; } } @media screen and (min-width: 768px) { .navbar .container { @@ -780,7 +802,6 @@ main .navbar .navbar-nav li a > .dropdown-menu { /* para todo submenu */ /*background-color: $color_topo_bg; border: 1px solid rgba(255, 255, 255, 0.3);*/ - color: white; min-width: 250px; margin: 0; padding: 0; } @@ -833,6 +854,13 @@ main .navbar .navbar-nav li a > .dropdown-menu { .context-actions .search { max-width: 75%; } } +.navbar-menu { + background: #001a00; +} +.navbar-menu a { + color: #93a4aa; +} + @media screen and (max-width: 800px) { .navbar .nav.navbar-nav li { /* Para todo item e subitem*/ } @@ -939,8 +967,13 @@ main .navbar .navbar-nav li a > .dropdown-menu { -moz-transition: 0.3s ease-in; -o-transition: 0.3s ease-in; } +#firstHeading { + font-size: 30px; +} + #homeIndex { - text-align: center; } + text-align: center; +} .homeBanner span { color: white; @@ -1101,18 +1134,32 @@ nav .navbar-nav > li > a { padding-top: 0px; padding-bottom: 0px; line-height: 75px; - font-family: "Helvetica Neue",Arial,"Noto Sans","sans-serif","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji } nav .navbar-nav > li > a:hover { - background: transparent; } + background: transparent; + color: #93a4aa; +} nav .navbar-nav > li > a:focus { - background: transparent; } + background: transparent; + color: #93a4aa; +} nav .navbar-nav > li:nth-child(2) > .dropdown-menu { right: auto; } +nav .dropdown-menu > li > a { + margin-left: 0px; + margin-right: 0px; +} + +nav .dropdown-menu > li > a:hover { + background: #0B3B0B; + color: #93a4aa; +} + .nav .open > a, .nav .open > a:hover, .nav .open > a:focus { @@ -1120,15 +1167,13 @@ nav .navbar-nav > li:nth-child(2) > .dropdown-menu { border-color: #ffffff #ffffff #d6e1e5; } nav .dropdown-menu > li { - background: #1a1a00; border: 0px; - margin-right: -10px; } nav .dropdown-menu > li > a { font-size: 16px; - line-height: 0px; - color: white; } + color: #93a4aa; + } .masthead { padding: 10px; } @@ -1155,7 +1200,117 @@ nav .dropdown-menu > li > a { .navbar-brand { padding: 0px; } +#dropdown-user { + line-height: 20px; + margin-top: 15px; +} + #div_id_pk_selecionados, #id_pk_selecionados { display: none; } + +#calendar table { + width: 100%; +} + +#calendar table tr th { + text-align: center; + font-size: 16px; + background-color: #316497; + color: #99ccff; +} + +#calendar table tr td { + width: 10%; + border: 1px solid #555; + vertical-align: top; + height: 120px; + padding: 2px; +} + +#calendar td.noday { + background-color: #eee; +} + +#calendar td.filled { + background-color: #99ccff; +} + +#calendar td.today { + border: 4px solid #316497; +} + +#calendar .dayNumber { + font-size: 16px !important; + font-weight: bold; +} + +#calendar a { + font-size: 10px; +} + +.calendario { + width: 98%; + margin: 10px; + font-size: 13px; +} + +.calendario tr, .calendario td { + border: 1px solid #dcdac8; +} + +.calendario th { + padding: 10px; + text-align: center; + font-size: 18px; + background: #d6e1e5; +} + +.calendario td { + width: 200px; + height: 150px; + padding: 5px 0px 0px 5px; + vertical-align: top; + text-align: left; +} + +.calendario .mes { + font-size: 25px; +} + +.calendario .data { + font-size: 16px; + font-weight: bold; +} + +.left { + float: left; +} + +.right { + float: right; +} + +.evento { + width: 100%; +} + +.evento th { + width: 15%; + padding: 10px; +} + +.evento tr { + padding: 5px; +} + +.evento textarea { + resize: none; + height: 150px; + margin-bottom: 10px; +} + +.evento .form-control { + margin-bottom: 10px; +} diff --git a/saap/static/styles/calendar.css b/saap/static/styles/calendar.css new file mode 100644 index 0000000..677876c --- /dev/null +++ b/saap/static/styles/calendar.css @@ -0,0 +1,57 @@ +.calendar { + width: 98%; + margin: auto; + font-size: 13px; +} + +.calendar tr, .calendar td { + border: 1px solid black; +} + +.calendar th { + padding: 10px; + text-align: center; + font-size: 18px; +} + +.calendar td { + width: 200px; + height: 150px; + padding: 20px 0px 0px 5px; +} + +.calendar .month { + font-size: 25px; +} + +.calendar .date { + font-size: 16px; +} + +.calendar ul { + height: 100%; + padding: 0px 5px 0px 20px; +} + +.calendar a { + color: #17a2b8; +} + +.calendar .left { + float: left; +} + +.calendar .right { + float: right; +} + +.calendar .btn { + outline: none; + color: black; + background-color: transparent; + box-shadow: 0 0 0 0; +} + +.calendar .clearfix { + margin: 15px; +} diff --git a/saap/static/styles/header.css b/saap/static/styles/header.css index 9b78b92..84e53fa 100644 --- a/saap/static/styles/header.css +++ b/saap/static/styles/header.css @@ -3166,7 +3166,6 @@ tbody.collapse.in { background-color: #fff; border: 1px solid #ccc; border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 4px; -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); background-clip: padding-box; } @@ -3729,9 +3728,7 @@ tbody.collapse.in { .container > .navbar-header, .container > .navbar-collapse, .container-fluid > .navbar-header, - .container-fluid > .navbar-collapse { - margin-right: 0; - margin-left: 0; } } + .container-fluid > .navbar-collapse {} } .navbar-static-top { z-index: 1000; @@ -3896,9 +3893,13 @@ tbody.collapse.in { box-shadow: none; } } .navbar-nav > li > .dropdown-menu { - margin-top: 0; + margin-top: 0px; border-top-right-radius: 0; - border-top-left-radius: 0; } + border-top-left-radius: 0; + background: #1a1a00; + border-width: 0px; + right: auto; + } .navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { margin-bottom: 0; diff --git a/saap/templates/ajuda.html b/saap/templates/ajuda.html index 58b753b..38e5d43 100644 --- a/saap/templates/ajuda.html +++ b/saap/templates/ajuda.html @@ -44,6 +44,14 @@

Tópicos do Manual de Ajuda

+
  • + Agenda + +
  • +
  • Correspondências
  • diff --git a/saap/templates/ajuda/agenda.html b/saap/templates/ajuda/agenda.html new file mode 100644 index 0000000..a8b12d3 --- /dev/null +++ b/saap/templates/ajuda/agenda.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block base_content %} + +
    + +

    Agenda

    + +
    + +O SAAP também permite o gerenciamento de atividades, eventos e compromissos, por meio da sua agenda. É possível não apenas cadastrar as ocorrências com diversos dados, como título, descrição, localização, município, data e hora de início e término, como também navegar pelo calendário para ter a real noção das datas e horários.
    + +
    +
    + Anterior | + Índice | + Próxima +
    + +
    + +{% endblock base_content %} diff --git a/saap/templates/ajuda/agendacalendario.html b/saap/templates/ajuda/agendacalendario.html new file mode 100644 index 0000000..f271d5a --- /dev/null +++ b/saap/templates/ajuda/agendacalendario.html @@ -0,0 +1,49 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block base_content %} + +
    + +

    Calendário

    + +
    + +A agenda do SAAP permite a visualização mensal de compromissos, possuindo várias funcionalidades para auxiliar essa tarefa.
    + +
    + +A exibição dos compromissos em forma de calendário facilita a associação entre dia do mês e dia da semana- por exemplo, saber se em uma semana há muitos compromissos, ou se há compromissos no fim de semana.
    + +
    + +Os compromissos de um dia estão logo abaixo do número do dia do mês, em ordem crescente de horário. Ao posicionar o mouse em cima do título do evento, são exibidas as informações de descrição, local, bairro, município, data e hora de início, data e hora de término.
    + +
    + +Para navegar nos meses, basta usar os botões no alto do calendário:
    + +
    + +
      +
    • Ano anterior: Volta um ano em relação ao mês/ano exibido no calendário. Se o calendário mostra o mês de julho de 2022, ao clicar no botão é carregado o mês de julho de 2021. +
    • Mês anterior: Volta um mês em relação ao mês exibido no calendário. Se o calendário mostra o mês de julho de 2022, ao clicar no botão é carregado o mês de junho de 2022. +
    • Mês atual: Exibe o mês atual do calendário, independente do mês exibido no calendário. É o equivalente a clicar no menu Agenda +
    • Próximo mês: Avança um mês em relação ao mês exibido no calendário. Se o calendário mostra o mês de julho de 2022, ao clicar no botão é carregado o mês de agosto de 2022. +
    • Próximo ano: Avança um ano em relação ao mês/ano exibido no calendário. Se o calendário mostra o mês de julho de 2022, ao clicar no botão é carregado o mês de julho de 2023. +
    + +
    +Para adicionar um novo evento ou compromisso, basta clicar em Novo evento.
    + + +
    +
    + Anterior | + Índice | + Próxima +
    + +
    + +{% endblock base_content %} diff --git a/saap/templates/ajuda/agendaevento.html b/saap/templates/ajuda/agendaevento.html new file mode 100644 index 0000000..7cd02ff --- /dev/null +++ b/saap/templates/ajuda/agendaevento.html @@ -0,0 +1,45 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block base_content %} + +
    + +

    Cadastro de Contatos

    + +
    + +Para cadastrar um novo evento ou compromisso, é necessário clicar no no botão Novo evento, dentro da tela da Agenda
    + +
    + +O formulário possui diversos campos, porém apenas aqueles que estão destacados em vermelho são obrigatórios. No caso, os campos de Título, Início e Término.
    + +

    Evento

    + +
      +
    • Título: Título ou nome do evento/compromisso. Procurar ser sucinto, pois é o que aparece no calendário. +
    • Descrição +
    • Localização: Informações sobre a localização do evento, como endereço, ponto de referência e proximidade. Não é necessário informar bairro, cidade ou estado, pois os mesmos possuem campos próprios. +
    • Início: Informação da data e hora de início do evento. +
    • Término: Informação da data e hora de término do evento. Precisa ser necessariamente posterior ao seu início, mesmo que seja no minuto seguinte. +
    + +

    Editar ou Excluir

    + +Para Editar um evento, clicando no mesmo dentro do calendário, o SAAP abrirá o mesmo formulário, porém com os dados preenchidos, sendo possível sua alteração.
    + +
    + +Para Excluir um evento, basta clicar no botão Excluir, dentro do formulário de edição do evento, e o SAAP perguntará se o usuário deseja mesmo excluir o contato. Basta clicar em Confirmar para excluir, ou Cancelar a exclusão.
    + +
    +
    + Anterior | + Índice | + Próxima +
    + +
    + +{% endblock base_content %} diff --git a/saap/templates/ajuda/agendalista.html b/saap/templates/ajuda/agendalista.html new file mode 100644 index 0000000..5f63923 --- /dev/null +++ b/saap/templates/ajuda/agendalista.html @@ -0,0 +1,55 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block base_content %} + +
    + +

    Listagem de eventos da agenda

    + +
    + +Para acessar o relatório de Listagem de eventos, clique no menu Relatórios e, depois, na opção Listagem.
    + +
    + +Esta tela permite gerar uma tabela em PDF, contendo os dados dos eventos filtrados.
    + +

    Filtro de Contatos

    + +
      +
    • Título, descrição ou localização: Campo que permite pesquisar pelo título do evento, descrição ou localização (campo preenchido, não os campos de bairro ou município). +
    • Início do evento: Campos de data que permitem pesquisar um período e retornar os eventos que tem início nesse período. Por exemplo, para obter todos os eventos que ocorrerão em julho de 2022, basta informar 01/07/2022 e 31/07/2022. Ao clicar nos campos, abre-se um mini calendário, permitindo a escolha da data. Lembrando que esse campo não observa os eventos que, eventualmente, iniciam no último dia informado mas que terminam posteriormente - por exemplo, inicia em 31/07 mas termina em 04/08. +
    • Término do evento: Campos de data que permitem pesquisar um período e retornar os eventos que tem encerramento nesse período. Por exemplo, para obter todos os eventos que ocorrerão em julho de 2022, mas que terminarão em julho de 2022, basta informar 01/07/2022 no primeiro campo Início do evento e 31/07/2022 no segundo campo Término do evento. Ao clicar nos campos, abre-se um mini calendário, permitindo a escolha da data. +
    • Bairro: Permite selecionar um ou mais bairros da cidade. Para selecionar um bairro, basta clicar no mesmo. Para selecionar mais de um, basta pressionar a tecla Ctrl e ir clicando com o mouse para selecionar/desmarcar. +
    • Município: Permite selecionar um ou mais municípios do estado. Para selecionar um município, basta clicar no mesmo. Para selecionar mais de um, basta pressionar a tecla Ctrl e ir clicando com o mouse para selecionar/desmarcar. +
    + +Após preencher os filtros, clique em Filtrar. Se você não fizer o filtro antes de Gerar o relatório, ele será criado desconsiderando os filtros que você preencheu. Primeiro filtre, depois gere.
    + +

    Impressão

    + +
      +
    • Orientação: Define se as páginas serão geradas em modo Retrato (em pé) ou Paisagem (deitadas). O primeiro permite colocar mais eventos numa página, porém com colunas mais estreitas e as informações mais "apertadas". O segundo não consegue colocar tantos eventos numa página, porém as colunas ficam mais largas e as informações mais distribuídas. +
    + +Escolhidas as opções, clique em Gerar para que o relatório seja gerado.
    + +

    Imprimir relatório

    + +Recomendamos fortemente que, antes de imprimir, você baixe o arquivo, pois a impressão direto pelo navegador (Chrome, Firefox ou Edge) é mais lenta e acaba ficando com uma qualidade um pouco inferior.
    + +
    + +Escolha o local que deseja salvar, vá até o arquivo e, então, clique para abri-lo no programa leitor de PDF que você desejar ou utiliza. Para imprimir, basta clicar no ícone ou apertar as teclas Ctrl + P. A impressão é feita em tamanho A4.
    + +
    +
    + Anterior | + Índice | + Próxima +
    + +
    + +{% endblock base_content %} diff --git a/saap/templates/ajuda/correspondencias.html b/saap/templates/ajuda/correspondencias.html index 85cfd7c..e92961e 100644 --- a/saap/templates/ajuda/correspondencias.html +++ b/saap/templates/ajuda/correspondencias.html @@ -17,7 +17,7 @@

    Correspondências



    - Anterior | + Anterior | Índice | Próxima
    diff --git a/saap/templates/ajuda/processosdetalhe.html b/saap/templates/ajuda/processosdetalhe.html index 0a65a0a..765b170 100644 --- a/saap/templates/ajuda/processosdetalhe.html +++ b/saap/templates/ajuda/processosdetalhe.html @@ -62,7 +62,7 @@

    Imprimir relatório


    Anterior | Índice | - Próxima + Próxima
    diff --git a/saap/templates/ajuda/processospesquisa.html b/saap/templates/ajuda/processospesquisa.html index 936c9dd..d453038 100644 --- a/saap/templates/ajuda/processospesquisa.html +++ b/saap/templates/ajuda/processospesquisa.html @@ -57,7 +57,7 @@

    Tabela de processos


    Anterior | Índice | - Próxima + Próxima
    diff --git a/saap/templates/ajuda/relatorios.html b/saap/templates/ajuda/relatorios.html index 3188430..fe1424a 100644 --- a/saap/templates/ajuda/relatorios.html +++ b/saap/templates/ajuda/relatorios.html @@ -9,7 +9,7 @@

    Relatórios


    -Para completar o uso do SAAP, ele possui várias ferramentas para gerar relatórios dos dados. São quatro tipos de relatórios, permitindo gerar diversas listagens de contatos e processos.
    +Para completar o uso do SAAP, ele possui várias ferramentas para gerar relatórios dos dados. São vários tipos de relatórios, permitindo gerar diversas listagens de contatos, processos e compromissos da agenda.


    diff --git a/saap/templates/base.html b/saap/templates/base.html index b2f7697..4bc7303 100644 --- a/saap/templates/base.html +++ b/saap/templates/base.html @@ -32,34 +32,32 @@
    -