Browse Source

2486 django channels (#2523)

* config inicial

* impl channels tutorial 1 e 2

* corrige versão do redis

* impl tutorial 3

* impl time-refresh websocket

* add view para apps frontend

* corrige teste de app do sapl

* renomeia view para entry_point_view

* add redis no docker compose e no travis

* configura ativação para channels

* add comunicação websocket ao nginx

* change frontend

* corrige porta do postgres

* descomenta código de configuração do logging

* rename view and urls from app frontend

* corrige teste e url para app online

* adequa leitura de query_string para asgirequest

*  nova versão do frontend

* inclui teste de https no ws
pull/2601/head
Leandro Roberto Silva 6 years ago
committed by Edward
parent
commit
f4239fdc59
  1. 1
      .travis.yml
  2. 35
      config/nginx/sapl.conf
  3. 46
      daphne_start.sh
  4. 9
      docker-compose.yml
  5. 42
      docs/instalacao31.rst
  6. 4
      requirements/requirements.txt
  7. 14
      sapl/asgi.py
  8. 139
      sapl/base/consumers.py
  9. 9
      sapl/base/routing.py
  10. 27
      sapl/base/urls.py
  11. 125
      sapl/base/views.py
  12. 4
      sapl/protocoloadm/views.py
  13. 12
      sapl/routing.py
  14. 58
      sapl/rules/apps.py
  15. 2
      sapl/sessao/views.py
  16. 16
      sapl/settings.py
  17. 1
      sapl/static/sapl/css/chunk-3e2c11a1.e4f7f867.css
  18. BIN
      sapl/static/sapl/css/chunk-3e2c11a1.e4f7f867.css.gz
  19. 8
      sapl/static/sapl/css/chunk-45646c50.b20a1ea4.css
  20. BIN
      sapl/static/sapl/css/chunk-45646c50.b20a1ea4.css.gz
  21. 1
      sapl/static/sapl/css/chunk-4cf2dae1.c632bec6.css
  22. BIN
      sapl/static/sapl/css/chunk-4cf2dae1.c632bec6.css.gz
  23. 2
      sapl/static/sapl/css/chunk-vendors.2ce8185b.css
  24. BIN
      sapl/static/sapl/css/chunk-vendors.2ce8185b.css.gz
  25. BIN
      sapl/static/sapl/css/chunk-vendors.3c9fe6b4.css.gz
  26. 1
      sapl/static/sapl/css/online.3d6220ca.css
  27. BIN
      sapl/static/sapl/img/icon_normas_juridicas.52266702.png
  28. BIN
      sapl/static/sapl/img/icon_plenarias.f182e226.png
  29. 1
      sapl/static/sapl/js/chunk-2d0c4a82.6ddbd5d4.js
  30. BIN
      sapl/static/sapl/js/chunk-2d0c4a82.6ddbd5d4.js.gz
  31. 1
      sapl/static/sapl/js/chunk-2d0e8be2.bf2356b1.js
  32. BIN
      sapl/static/sapl/js/chunk-2d0e8be2.bf2356b1.js.gz
  33. 1
      sapl/static/sapl/js/chunk-3e2c11a1.8d622491.js
  34. BIN
      sapl/static/sapl/js/chunk-3e2c11a1.8d622491.js.gz
  35. 1
      sapl/static/sapl/js/chunk-45646c50.856e60c0.js
  36. BIN
      sapl/static/sapl/js/chunk-45646c50.856e60c0.js.gz
  37. 1
      sapl/static/sapl/js/chunk-4cf2dae1.89120e74.js
  38. BIN
      sapl/static/sapl/js/chunk-4cf2dae1.89120e74.js.gz
  39. 278
      sapl/static/sapl/js/chunk-vendors.0003dc37.js
  40. BIN
      sapl/static/sapl/js/chunk-vendors.0003dc37.js.gz
  41. 304
      sapl/static/sapl/js/chunk-vendors.0b0a5dbb.js
  42. BIN
      sapl/static/sapl/js/chunk-vendors.0b0a5dbb.js.gz
  43. 1
      sapl/static/sapl/js/compilacao.7b04bca9.js
  44. BIN
      sapl/static/sapl/js/compilacao.7b04bca9.js.gz
  45. 7
      sapl/static/sapl/js/global.742e068f.js
  46. BIN
      sapl/static/sapl/js/global.742e068f.js.gz
  47. 1
      sapl/static/sapl/js/online.50eb7f8e.js
  48. BIN
      sapl/static/sapl/js/online.50eb7f8e.js.gz
  49. 6
      sapl/templates/base.html
  50. 27
      sapl/templates/base/channel_index.html
  51. 47
      sapl/templates/base/channel_room.html
  52. 34
      sapl/templates/base/time_refresh_log_test.html
  53. 7
      sapl/test_urls.py
  54. 2
      sapl/webpack-stats.json
  55. 2
      setup.py
  56. 7
      start.sh

1
.travis.yml

@ -5,6 +5,7 @@ python:
services:
- postgresql
- redis-server
install:
- pip install -r requirements/test-requirements.txt

35
config/nginx/sapl.conf

@ -1,37 +1,58 @@
upstream sapl_server {
server unix:/var/interlegis/sapl/run/gunicorn.sock fail_timeout=0;
}
upstream channels_server {
server unix:/var/interlegis/sapl/run/daphne.sock;
}
server {
listen 80;
server_name sapl.test;
client_max_body_size 4G;
location /static/ {
alias /var/interlegis/sapl/collected_static/;
alias /var/interlegis/sapl/collected_static/;
}
location /media/ {
alias /var/interlegis/sapl/media/;
alias /var/interlegis/sapl/media/;
}
location / {
try_files $uri @proxy_to_app;
}
location /ws/ {
try_files $uri @proxyto_ws;
}
location @proxyto_ws {
proxy_pass http://channels_server;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
location @proxy_to_app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
if (!-f $request_filename) {
proxy_pass http://sapl_server;
break;
}
}
error_page 500 502 503 504 /500.html;
location = /500.html {
root /var/interlegis/sapl/sapl/static/;

46
daphne_start.sh

@ -0,0 +1,46 @@
#!/bin/bash
# As seen in http://tutos.readthedocs.org/en/latest/source/ndg.html
SAPL_DIR="/var/interlegis/sapl"
# Seta um novo diretório foi passado como raiz para o SAPL
# caso esse tenha sido passado como parâmetro
if [ "$1" ]
then
SAPL_DIR="$1"
fi
NAME="SAPL" # Name of the application (*)
DJANGODIR=/var/interlegis/sapl/ # Django project directory (*)
SOCKFILE=/var/interlegis/sapl/run/daphne.sock # we will communicate using this unix socket (*)
USER=`whoami` # the user to run as (*)
GROUP=`whoami` # the group to run as (*)
NUM_WORKERS=3 # how many worker processes should Gunicorn spawn (*)
# NUM_WORKERS = 2 * CPUS + 1
TIMEOUT=60
MAX_REQUESTS=100 # number of requests before restarting worker
DJANGO_SETTINGS_MODULE=sapl.settings # which settings file should Django use (*)
DJANGO_ASGI_MODULE=sapl.asgi # WSGI module name (*)
echo "Starting $NAME as `whoami` on base dir $SAPL_DIR"
# parameter can be passed to run without virtualenv
if [[ "$@" != "no-venv" ]]; then
# Activate the virtual environment
cd $DJANGODIR
source /var/interlegis/.virtualenvs/sapl/bin/activate
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH
fi
# Create the run directory if it doesn't exist
RUNDIR=$(dirname $SOCKFILE)
test -d $RUNDIR || mkdir -p $RUNDIR
# Start your Django Unicorn
exec daphne \
-u $SOCKFILE ${DJANGO_ASGI_MODULE}:application \
--access-log /var/log/sapl/daphne_access.log \
-v2

9
docker-compose.yml

@ -10,6 +10,11 @@ sapldb:
- sapldb_data:/var/lib/postgresql/data/
ports:
- "5432:5432"
saplredis:
image: redis:5.0.3-stretch
restart: always
ports:
- "6379:6379"
sapl:
image: interlegis/sapl:3.1.146
restart: always
@ -23,11 +28,15 @@ sapl:
EMAIL_HOST_USER: usuariosmtp
EMAIL_SEND_USER: usuariosmtp
EMAIL_HOST_PASSWORD: senhasmtp
USE_CHANNEL_LAYERS: 'True'
PORT_CHANNEL_LAYERS: 6379
HOST_CHANNEL_LAYERS: saplredis
TZ: America/Sao_Paulo
volumes:
- sapl_data:/var/interlegis/sapl/data
- sapl_media:/var/interlegis/sapl/media
links:
- sapldb
- saplredis
ports:
- "80:80"

42
docs/instalacao31.rst

@ -145,9 +145,18 @@ Criação da `SECRET_KEY <https://docs.djangoproject.com/es/1.9/ref/settings/#st
SERVER_EMAIL = [Insira este parâmetro]
SOLR_URL = '[Insira este parâmetro]'
USE_SOLR = '[Insira este parâmetro]'
SOLR_COLLECTION = '[Insira este parâmetro]'
FRONTEND_CUSTOM = [True/False]
USE_CHANNEL_LAYERS = True
HOST_CHANNEL_LAYERS: localhost
PORT_CHANNEL_LAYERS: 6379
SITE_URL = '[Insira este parâmetro]'
TZ = 'America/Sao_Paulo'
* Uma configuração mínima para atender os procedimentos acima seria::
@ -282,3 +291,36 @@ Feito isso, e você ativando a variável de ambiente FRONTEND_CUSTOM=True (vide
**Deste ponto em diante, é exigido o conhecimento que você pode adquirir em https://cli.vuejs.org/guide/ e em https://vuejs.org/v2/guide/ para colaborar com sapl-frontend**
**OBS: após a separação do sapl para o sapl-frontend, o conteúdo da pasta static é compilado e minificado. É gerado pelo build do sapl-frontend e não deve-se tentar customizar ou criar elementos manipulando diretamente informações na pasta static.**
Django-Channels
===============
Para ativar Django-Channels e a comunicação via websockets utilizada pelo entry-point sessao/online de sapl-frontend coloque no arquivo .env a variável:
USE_CHANNEL_LAYERS = True
HOST_CHANNEL_LAYERS: localhost
PORT_CHANNEL_LAYERS: 6379
Ao ativar o channels, no ambiente de desenvolvimento é necessário ativar um servidor redis. Utilize/Instale o docker e execute:
sudo docker run -p 6379:6379 -d redis:5.0.3-stretch
No caso de ambiente de produção, o container do docker sapl para produção já está configurado com redis.
Testes do channels
------------------
Existe uma interface mínima de comunicação para testes dentro do sapl. Pode ser acessada utilizando:
http://localhost:8001/sapl/time-refresh/
Se clicar em `pull`, o websocket de teste deve responder:
{"message": "OK"}
De outro modo, ficando com esta interface aberta, abra outra janela e altere/inclua/apague algo em seu sapl. Será enviado um json que segue este padrão:
{"message": {"action": "post_save", "id": 16923, "app": "materia", "model": "autoria"}}
O papel de /sapl/time-refresh/ é apenas isto, informar que houve ação no registro `id`, da `app` e `model`. Além de `action` que pode ser `post_save` ou `pre-delete`.

4
requirements/requirements.txt

@ -33,5 +33,9 @@ whoosh==2.7.4
pyoai==2.5.0
daphne==2.2.5
channels_redis==2.3.2
channels==2.1.7
git+git://github.com/interlegis/trml2pdf.git
git+git://github.com/interlegis/django-admin-bootstrapped

14
sapl/asgi.py

@ -0,0 +1,14 @@
"""
ASGI entrypoint. Configures Django and then runs the application
defined in the ASGI_APPLICATION setting.
"""
import os
from channels.routing import get_default_application
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sapl.settings")
django.setup()
application = get_default_application()

139
sapl/base/consumers.py

@ -0,0 +1,139 @@
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import AsyncWebsocketConsumer, WebsocketConsumer
from channels.layers import get_channel_layer
from django.db.models.signals import post_save, post_delete, pre_delete
from django.dispatch.dispatcher import receiver
class TimeRefreshConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = 'time_refresh_channel'
self.room_group_name = 'group_%s' % self.room_name
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'time_refresh_message',
'message': message
}
)
# Receive message from room group
async def time_refresh_message(self, event):
message = event['message']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message
}))
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
async def chat_message(self, event):
message = event['message']
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': message
}))
class ChatConsumer__tutorial2(WebsocketConsumer):
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Receive message from room group
def chat_message(self, event):
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message
}))

9
sapl/base/routing.py

@ -0,0 +1,9 @@
from django.conf.urls import url
from . import consumers
websocket_urlpatterns = [
url(r'^ws/chat/(?P<room_name>[^/]+)/$', consumers.ChatConsumer),
url(r'^ws/time-refresh/$', consumers.TimeRefreshConsumer),
]

27
sapl/base/urls.py

@ -8,6 +8,7 @@ from django.contrib.auth.views import (password_reset, password_reset_complete,
password_reset_done)
from django.views.generic.base import RedirectView, TemplateView
from sapl import base
from sapl.base.views import AutorCrud, ConfirmarEmailView, TipoAutorCrud
from sapl.settings import EMAIL_SEND_USER, MEDIA_URL
@ -42,9 +43,12 @@ app_name = AppConfig.name
admin_user = [
url(r'^sistema/usuario/$', PesquisarUsuarioView.as_view(), name='usuario'),
url(r'^sistema/usuario/create$', CreateUsuarioView.as_view(), name='user_create'),
url(r'^sistema/usuario/(?P<pk>\d+)/edit$', EditUsuarioView.as_view(), name='user_edit'),
url(r'^sistema/usuario/(?P<pk>\d+)/delete$', DeleteUsuarioView.as_view(), name='user_delete')
url(r'^sistema/usuario/create$',
CreateUsuarioView.as_view(), name='user_create'),
url(r'^sistema/usuario/(?P<pk>\d+)/edit$',
EditUsuarioView.as_view(), name='user_edit'),
url(r'^sistema/usuario/(?P<pk>\d+)/delete$',
DeleteUsuarioView.as_view(), name='user_delete')
]
alterar_senha = [
@ -83,6 +87,19 @@ recuperar_senha = [
name='recuperar_senha_completo'),
]
channels_url = [
url(r'^sapl/channel$', base.views.chanel_index, name='channel_index'),
url(r'^sapl/channel/(?P<room_name>[^/]+)/$',
base.views.chanel_room, name='channel_room'),
url(r'^sapl/time-refresh/$',
base.views.time_refresh_log_test, name='time_refresh_log_test_index'),
url(r'^online',
base.views.online_app_view, name='online_app_url'),
]
urlpatterns = [
url(r'^sistema/autor/tipo/', include(TipoAutorCrud.get_urls())),
@ -97,7 +114,7 @@ urlpatterns = [
url(r'^sistema/app-config/', include(AppConfigCrud.get_urls())),
# TODO mover estas telas para a app 'relatorios'
url(r'^sistema/relatorios/$',
url(r'^sistema/relatorios/$',
RelatoriosListView.as_view(), name='relatorios_list'),
url(r'^sistema/relatorios/materia-por-autor$',
RelatorioMateriasPorAutorView.as_view(), name='materia_por_autor'),
@ -185,4 +202,4 @@ urlpatterns = [
LogotipoView.as_view(), name='logotipo'),
] + recuperar_senha + alterar_senha + admin_user
] + recuperar_senha + alterar_senha + admin_user + channels_url

125
sapl/base/views.py

@ -1,6 +1,7 @@
import collections
import itertools
import datetime
import itertools
import json
import logging
import os
@ -15,11 +16,13 @@ from django.core.urlresolvers import reverse, reverse_lazy
from django.db import connection
from django.db.models import Count, Q, ProtectedError
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render
from django.template import TemplateDoesNotExist
from django.template.loader import get_template
from django.utils import timezone
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.utils.safestring import mark_safe
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
from django.views.generic import (CreateView, DeleteView, FormView, ListView,
@ -61,6 +64,25 @@ from .forms import (AlterarSenhaForm, CasaLegislativaForm,
from .models import AppConfig, CasaLegislativa
def chanel_index(request):
return render(request, 'base/channel_index.html', {})
def chanel_room(request, room_name):
return render(request, 'base/channel_room.html', {
'room_name_json': mark_safe(json.dumps(room_name))
})
def time_refresh_log_test(request):
return render(request, 'base/time_refresh_log_test.html', {})
def online_app_view(request):
return render(request, 'online_app.html')
def filtra_url_materias_em_tramitacao(qr, qs, campo_url, local_ou_status):
id_materias = []
filtro_url = qr[campo_url]
@ -289,7 +311,7 @@ class AutorCrud(CrudAux):
class RelatoriosListView(TemplateView):
template_name='base/relatorios_list.html'
template_name = 'base/relatorios_list.html'
def get_context_data(self, **kwargs):
context = super(TemplateView, self).get_context_data(**kwargs)
@ -799,15 +821,15 @@ class RelatorioNormasPublicadasMesView(FilterView):
context['ano'] = self.request.GET['ano']
normas_mes = collections.OrderedDict()
meses = {1: 'Janeiro', 2: 'Fevereiro', 3:'Março', 4: 'Abril', 5: 'Maio', 6:'Junho',
7: 'Julho', 8: 'Agosto', 9:'Setembro', 10:'Outubro', 11:'Novembro', 12:'Dezembro'}
meses = {1: 'Janeiro', 2: 'Fevereiro', 3: 'Março', 4: 'Abril', 5: 'Maio', 6: 'Junho',
7: 'Julho', 8: 'Agosto', 9: 'Setembro', 10: 'Outubro', 11: 'Novembro', 12: 'Dezembro'}
for norma in context['object_list']:
if not meses[norma.data.month] in normas_mes:
normas_mes[meses[norma.data.month]] = []
normas_mes[meses[norma.data.month]].append(norma)
context['normas_mes'] = normas_mes
quant_normas_mes = {}
for key in normas_mes.keys():
quant_normas_mes[key] = len(normas_mes[key])
@ -833,19 +855,20 @@ class RelatorioNormasVigenciaView(FilterView):
vigencia = kwargs['data']['vigencia']
if ano:
qs = qs.filter(ano=ano)
if vigencia == 'True':
qs_dt_not_null = qs.filter(data_vigencia__isnull=True)
qs = (qs_dt_not_null | qs.filter(data_vigencia__gte=datetime.datetime.now().date())).distinct()
qs = (qs_dt_not_null | qs.filter(
data_vigencia__gte=datetime.datetime.now().date())).distinct()
else:
qs = qs.filter(data_vigencia__lt=datetime.datetime.now().date())
qs = qs.filter(
data_vigencia__lt=datetime.datetime.now().date())
kwargs.update({
'queryset': qs
})
return kwargs
def get_context_data(self, **kwargs):
context = super(RelatorioNormasVigenciaView,
self).get_context_data(**kwargs)
@ -855,17 +878,20 @@ class RelatorioNormasVigenciaView(FilterView):
if not self.filterset.form.is_valid():
return context
normas_totais = NormaJuridica.objects.filter(ano=self.request.GET['ano'])
normas_totais = NormaJuridica.objects.filter(
ano=self.request.GET['ano'])
context['quant_total'] = len(normas_totais)
if self.request.GET['vigencia'] == 'True':
context['vigencia'] = 'Vigente'
context['quant_vigente'] = len(context['object_list'])
context['quant_nao_vigente'] = context['quant_total'] - context['quant_vigente']
context['quant_nao_vigente'] = context['quant_total'] - \
context['quant_vigente']
else:
context['vigencia'] = 'Não vigente'
context['quant_nao_vigente'] = len(context['object_list'])
context['quant_vigente'] = context['quant_total'] - context['quant_nao_vigente']
context['quant_vigente'] = context['quant_total'] - \
context['quant_nao_vigente']
qr = self.request.GET.copy()
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
@ -891,7 +917,7 @@ class EstatisticasAcessoNormas(TemplateView):
return self.render_to_response(context)
context['ano'] = self.request.GET['ano']
query = '''
select norma_id, ano, extract(month from horario_acesso) as mes, count(*)
from norma_normaestatisticas
@ -904,20 +930,21 @@ class EstatisticasAcessoNormas(TemplateView):
rows = cursor.fetchall()
normas_mes = collections.OrderedDict()
meses = {1: 'Janeiro', 2: 'Fevereiro', 3:'Março', 4: 'Abril', 5: 'Maio', 6:'Junho',
7: 'Julho', 8: 'Agosto', 9:'Setembro', 10:'Outubro', 11:'Novembro', 12:'Dezembro'}
meses = {1: 'Janeiro', 2: 'Fevereiro', 3: 'Março', 4: 'Abril', 5: 'Maio', 6: 'Junho',
7: 'Julho', 8: 'Agosto', 9: 'Setembro', 10: 'Outubro', 11: 'Novembro', 12: 'Dezembro'}
for row in rows:
if not meses[int(row[2])] in normas_mes:
normas_mes[meses[int(row[2])]] = []
norma_est = [NormaJuridica.objects.get(id=row[0]), row[3]]
normas_mes[meses[int(row[2])]].append(norma_est)
# Ordena por acesso e limita em 5
for n in normas_mes:
sorted_by_value = sorted(normas_mes[n], key=lambda kv: kv[1], reverse=True)
sorted_by_value = sorted(
normas_mes[n], key=lambda kv: kv[1], reverse=True)
normas_mes[n] = sorted_by_value[0:5]
context['normas_mes'] = normas_mes
return self.render_to_response(context)
@ -952,8 +979,8 @@ class ListarInconsistenciasView(PermissionRequiredMixin, ListView):
tabela.append(
('mandato_sem_data_inicio',
'Mandatos sem data inicial',
len(mandato_sem_data_inicio())
)
len(mandato_sem_data_inicio())
)
)
tabela.append(
('parlamentares_mandatos_intersecao',
@ -977,7 +1004,7 @@ class ListarInconsistenciasView(PermissionRequiredMixin, ListView):
('legislatura_infindavel',
'Legislaturas sem data fim',
len(legislatura_infindavel())
)
)
)
return tabela
@ -1000,14 +1027,14 @@ class ListarLegislaturaInfindavelView(PermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs):
context = super(
ListarLegislaturaInfindavelView, self
).get_context_data(**kwargs)
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhuma encontrada.'
] = 'Nenhuma encontrada.'
return context
@ -1050,14 +1077,14 @@ class ListarBancadaComissaoAutorExternoView(PermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs):
context = super(
ListarBancadaComissaoAutorExternoView, self
).get_context_data(**kwargs)
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
] = 'Nenhum encontrado.'
return context
@ -1084,7 +1111,7 @@ class ListarAutoresDuplicadosView(PermissionRequiredMixin, ListView):
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
] = 'Nenhum encontrado.'
return context
@ -1097,10 +1124,12 @@ def parlamentares_mandatos_intersecao():
for c in combinacoes:
data_inicio_mandato1 = c[0].data_inicio_mandato
data_fim_mandato1 = c[0].data_fim_mandato if c[0].data_fim_mandato else timezone.now().date()
data_fim_mandato1 = c[0].data_fim_mandato if c[0].data_fim_mandato else timezone.now(
).date()
data_inicio_mandato2 = c[1].data_inicio_mandato
data_fim_mandato2 = c[1].data_fim_mandato if c[1].data_fim_mandato else timezone.now().date()
data_fim_mandato2 = c[1].data_fim_mandato if c[1].data_fim_mandato else timezone.now(
).date()
if data_inicio_mandato1 and data_inicio_mandato2:
exists = intervalos_tem_intersecao(
@ -1131,7 +1160,7 @@ class ListarParlMandatosIntersecaoView(PermissionRequiredMixin, ListView):
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
] = 'Nenhum encontrado.'
return context
@ -1152,14 +1181,14 @@ class ListarMandatoSemDataInicioView(PermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs):
context = super(
ListarMandatoSemDataInicioView, self
).get_context_data(**kwargs)
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrada.'
] = 'Nenhum encontrada.'
return context
@ -1187,27 +1216,27 @@ class ListarMatProtocoloInexistenteView(PermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs):
context = super(
ListarMatProtocoloInexistenteView, self
).get_context_data(**kwargs)
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhuma encontrada.'
] = 'Nenhuma encontrada.'
return context
def protocolos_com_materias():
protocolos = {}
for m in MateriaLegislativa.objects.filter(numero_protocolo__isnull=False).order_by('-ano', 'numero_protocolo'):
if Protocolo.objects.filter(numero=m.numero_protocolo, ano=m.ano).exists():
key = "{}/{}".format(m.numero_protocolo, m.ano)
val = protocolos.get(key, list())
val.append(m)
protocolos[key] = val
return [(v[0], len(v)) for (k, v) in protocolos.items() if len(v) > 1]
@ -1230,7 +1259,7 @@ class ListarProtocolosComMateriasView(PermissionRequiredMixin, ListView):
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
] = 'Nenhum encontrado.'
return context
@ -1244,6 +1273,7 @@ def protocolos_duplicados():
return [(v[0], len(v)) for (k, v) in protocolos.items() if len(v) > 1]
class ListarProtocolosDuplicadosView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/protocolos_duplicados.html'
@ -1263,7 +1293,7 @@ class ListarProtocolosDuplicadosView(PermissionRequiredMixin, ListView):
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
] = 'Nenhum encontrado.'
return context
@ -1295,9 +1325,9 @@ class PesquisarUsuarioView(PermissionRequiredMixin, FilterView):
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context['NO_ENTRIES_MSG'] = 'Nenhum usuário encontrado!'
context['title'] = _('Usuários')
return context
@ -1308,7 +1338,7 @@ class PesquisarUsuarioView(PermissionRequiredMixin, FilterView):
data = self.filterset.data
url = ''
if data:
url = "&" + str(self.request.environ['QUERY_STRING'])
url = "&" + str(self.request.META['QUERY_STRING'])
if url.startswith("&page"):
ponto_comeco = url.find('username=') - 1
url = url[ponto_comeco:]
@ -1366,13 +1396,14 @@ class DeleteUsuarioView(PermissionRequiredMixin, DeleteView):
template_name = "crud/confirm_delete.html"
permission_required = ('base.delete_appconfig',)
success_url = reverse_lazy('sapl.base:usuario')
success_message = "Usuário removido com sucesso!"
success_message = "Usuário removido com sucesso!"
def delete(self, request, *args, **kwargs):
def delete(self, request, *args, **kwargs):
try:
super(DeleteUsuarioView, self).delete(request, *args, **kwargs)
except ProtectedError as exception:
error_url = reverse_lazy('sapl.base:user_delete', kwargs={'pk': self.kwargs['pk']})
error_url = reverse_lazy('sapl.base:user_delete', kwargs={
'pk': self.kwargs['pk']})
error_message = "O usuário não pode ser removido, pois é referenciado por:<br><ul>"
for e in exception.protected_objects:
@ -1389,7 +1420,7 @@ class DeleteUsuarioView(PermissionRequiredMixin, DeleteView):
@property
def cancel_url(self):
return reverse('sapl.base:user_edit',
kwargs={'pk': self.kwargs['pk']})
kwargs={'pk': self.kwargs['pk']})
class EditUsuarioView(PermissionRequiredMixin, UpdateView):

4
sapl/protocoloadm/views.py

@ -404,7 +404,7 @@ class ProtocoloPesquisaView(PermissionRequiredMixin, FilterView):
# Então a ordem da URL está diferente
data = self.filterset.data
if data and data.get('numero') is not None:
url = "&" + str(self.request.environ['QUERY_STRING'])
url = "&" + str(self.request.META['QUERY_STRING'])
if url.startswith("&page"):
ponto_comeco = url.find('numero=') - 1
url = url[ponto_comeco:]
@ -882,7 +882,7 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
# Então a ordem da URL está diferente
data = self.filterset.data
if data and data.get('tipo') is not None:
url = "&" + str(self.request.environ['QUERY_STRING'])
url = "&" + str(self.request.META['QUERY_STRING'])
if url.startswith("&page"):
ponto_comeco = url.find('tipo=') - 1
url = url[ponto_comeco:]

12
sapl/routing.py

@ -0,0 +1,12 @@
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import sapl.base.routing
application = ProtocolTypeRouter({
'websocket': AuthMiddlewareStack(
URLRouter(
sapl.base.routing.websocket_urlpatterns
)
),
})

58
sapl/rules/apps.py

@ -1,13 +1,17 @@
from builtins import LookupError
import logging
import django
from django.apps import apps
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
import django.apps
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.management import _get_all_permissions
from django.core import exceptions
from django.db import models, router
from django.db.models.signals import post_save
from django.db.utils import DEFAULT_DB_ALIAS
from django.dispatch.dispatcher import receiver
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
import reversion
@ -34,7 +38,7 @@ def create_proxy_permissions(
try:
logger.info("Tentando obter modelo de permissão do app.")
Permission = apps.get_model('auth', 'Permission')
Permission = django.apps.apps.get_model('auth', 'Permission')
except LookupError as e:
logger.error(str(e))
return
@ -253,16 +257,62 @@ def cria_usuarios_padrao():
rules.cria_usuarios_padrao()
def send_signal_for_websocket_time_refresh(inst, action):
if not settings.USE_CHANNEL_LAYERS:
return
if hasattr(inst, '_meta') and not inst._meta.app_config is None and \
inst._meta.app_config.name[:4] == 'sapl':
# um mensagem não deve ser enviada se é post_save mas originou se de
# revision_pre_delete_signal
funcs = []
if action == 'post_save':
import inspect
funcs = list(filter(lambda x: x == 'revision_pre_delete_signal',
map(lambda x: x[3], inspect.stack())))
if not funcs:
try:
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
"group_time_refresh_channel", {
"type": "time_refresh.message",
'message': {
'action': action,
'id': inst.id,
'app': inst._meta.app_label,
'model': inst._meta.model_name
}
}
)
except Exception as e:
logger = logging.getLogger(__name__)
logger.info(_("Erro na comunicação com o backend do redis. "
"Certifique se possuir um servidor de redis "
"ativo funcionando como configurado em "
"CHANNEL_LAYERS"))
def revision_pre_delete_signal(sender, **kwargs):
send_signal_for_websocket_time_refresh(kwargs['instance'], 'pre_delete')
with reversion.create_revision():
kwargs['instance'].save()
reversion.set_comment("Deletado pelo sinal.")
@receiver(post_save, dispatch_uid='sapl_post_save_signal')
def sapl_post_save_signal(sender, instance, using, **kwargs):
send_signal_for_websocket_time_refresh(instance, 'post_save')
models.signals.post_migrate.connect(
receiver=update_groups)
models.signals.post_migrate.connect(
receiver=create_proxy_permissions,
dispatch_uid="django.contrib.auth.management.create_permissions")

2
sapl/sessao/views.py

@ -2977,7 +2977,7 @@ class PesquisarSessaoPlenariaView(FilterView):
# Então a ordem da URL está diferente
data = self.filterset.data
if data and data.get('data_inicio__year') is not None:
url = "&" + str(self.request.environ['QUERY_STRING'])
url = "&" + str(self.request.META['QUERY_STRING'])
if url.startswith("&page"):
ponto_comeco = url.find('data_inicio__year=') - 1
url = url[ponto_comeco:]

16
sapl/settings.py

@ -100,6 +100,8 @@ INSTALLED_APPS = (
'webpack_loader',
'channels',
) + SAPL_APPS
# FTS = Full Text Search
@ -204,7 +206,21 @@ TEMPLATES = [
WSGI_APPLICATION = 'sapl.wsgi.application'
ASGI_APPLICATION = "sapl.routing.application"
USE_CHANNEL_LAYERS = config('USE_CHANNEL_LAYERS', cast=bool, default=False)
HOST_CHANNEL_LAYERS = config('HOST_CHANNEL_LAYERS', cast=str, default='localhost')
PORT_CHANNEL_LAYERS = config('PORT_CHANNEL_LAYERS', cast=int, default=6379)
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [(HOST_CHANNEL_LAYERS, PORT_CHANNEL_LAYERS)],
},
},
}
# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases

1
sapl/static/sapl/css/chunk-3e2c11a1.e4f7f867.css

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/css/chunk-3e2c11a1.e4f7f867.css.gz

Binary file not shown.

8
sapl/static/sapl/css/chunk-45646c50.b20a1ea4.css

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/css/chunk-45646c50.b20a1ea4.css.gz

Binary file not shown.

1
sapl/static/sapl/css/chunk-4cf2dae1.c632bec6.css

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/css/chunk-4cf2dae1.c632bec6.css.gz

Binary file not shown.

2
sapl/static/sapl/css/chunk-vendors.3c9fe6b4.css → sapl/static/sapl/css/chunk-vendors.2ce8185b.css

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/css/chunk-vendors.2ce8185b.css.gz

Binary file not shown.

BIN
sapl/static/sapl/css/chunk-vendors.3c9fe6b4.css.gz

Binary file not shown.

1
sapl/static/sapl/css/online.3d6220ca.css

@ -0,0 +1 @@
.container-messages[data-v-26b3e554]{position:fixed;bottom:0;right:1rem;z-index:10001}

BIN
sapl/static/sapl/img/icon_normas_juridicas.52266702.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
sapl/static/sapl/img/icon_plenarias.f182e226.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

1
sapl/static/sapl/js/chunk-2d0c4a82.6ddbd5d4.js

@ -0,0 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([["chunk-2d0c4a82"],{"3c84":function(e,n,s){"use strict";s.r(n);var a={name:"sessao-plenaria-module"},t=s("0c7c"),c=Object(t.a)(a,function(){var e=this.$createElement,n=this._self._c||e;return n("div",{staticClass:"online-sessaolenaria"},[n("router-view")],1)},[],!1,null,null,null);n.default=c.exports}}]);

BIN
sapl/static/sapl/js/chunk-2d0c4a82.6ddbd5d4.js.gz

Binary file not shown.

1
sapl/static/sapl/js/chunk-2d0e8be2.bf2356b1.js

@ -0,0 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([["chunk-2d0e8be2"],{"8b24":function(n,e,t){"use strict";t.r(e);var a={name:"index",data:function(){return{}}},i=t("0c7c"),c=Object(i.a)(a,function(){var n=this.$createElement;return(this._self._c||n)("div",[this._v("\n teste de página para dentro do main - INDEX INDEX\n")])},[],!1,null,null,null);e.default=c.exports}}]);

BIN
sapl/static/sapl/js/chunk-2d0e8be2.bf2356b1.js.gz

Binary file not shown.

1
sapl/static/sapl/js/chunk-3e2c11a1.8d622491.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/js/chunk-3e2c11a1.8d622491.js.gz

Binary file not shown.

1
sapl/static/sapl/js/chunk-45646c50.856e60c0.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/js/chunk-45646c50.856e60c0.js.gz

Binary file not shown.

1
sapl/static/sapl/js/chunk-4cf2dae1.89120e74.js

@ -0,0 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([["chunk-4cf2dae1"],{3713:function(t,s,a){"use strict";(function(t){var e=a("975e");s.a={name:"sessao-plenaria-item-list",props:["sessao"],data:function(){return{utils:e.a.Utils,app:["sessao","parlamentares"],model:["sessaoplenaria","sessaolegislativa","tiposessaoplenaria","legislatura"],data_inicio:new Date,sessao_legislativa:{numero:""},tipo:{nome:""},legislatura:{numero:""},metadata:{sessao_legislativa:{app:"parlamentares",model:"sessaolegislativa",id:this.sessao.sessao_legislativa},legislatura:{app:"parlamentares",model:"legislatura",id:this.sessao.legislatura},tipo:{app:"sessao",model:"tiposessaoplenaria",id:this.sessao.tipo}}}},watch:{sessao:function(t){this.updateSessao(),this.fetch()}},computed:{titulo:function(){var t=this.sessao,s=this.tipo,a=this.data_inicio;return"".concat(t.numero,"ª ").concat(s.nome," da \n ").concat(a.getDate()>15?2:1,"ª Quizena do Mês de \n ").concat(this.month_text(a.getMonth())," de \n ").concat(a.getFullYear(),"\n ")},subtitulo:function(){return"".concat(this.sessao_legislativa.numero,"ª Sessão Legislativa da \n ").concat(this.legislatura.numero,"ª Legislatura")},date_text:function(){return"".concat(this.data_inicio.getDate()," de \n ").concat(this.month_text(this.data_inicio.getMonth())," de\n ").concat(this.data_inicio.getFullYear()," – ").concat(this.sessao.hora_inicio)}},methods:{sendStore:function(){this.insertInState({app:"sessao",model:"sessaoplenaria",id:this.sessao.id,value:this.sessao})},month_text:function(t){return["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"][t]},fetch:function(){var s=this;t.mapKeys(s.metadata,function(t,a){var e=s.metadata[a];e.component=s;var i=s.getModel(e);null===i?s.insertInState(e).then(function(t){s[a]=s.getModel(e)[e.id]}):void 0===i[e.id]?s.$nextTick().then(function(){setTimeout(function(){s.fetch()},100)}):s[a]=i[e.id]})},updateSessao:function(){this.data_inicio=this.stringToDate(this.sessao.data_inicio,"yyyy-mm-dd","-"),this.metadata.sessao_legislativa.id=this.sessao.sessao_legislativa,this.metadata.tipo.id=this.sessao.tipo,this.metadata.legislatura.id=this.sessao.legislatura}},mounted:function(){this.updateSessao(),this.fetch()}}}).call(this,a("2ef0"))},4358:function(t,s,a){"use strict";var e=a("3713").a,i=(a("e94f"),a("0c7c")),n=Object(i.a)(e,function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("router-link",{class:"sessao-plenaria-item-list",attrs:{to:{name:"sessao_plenaria_online_link",params:{id:t.sessao.id}}},nativeOn:{click:function(s){return t.sendStore(s)}}},[a("h5",{staticClass:"tit"},[t._v("\n "+t._s(t.titulo)+"\n ")]),a("div",{staticClass:"subtitulo"},[a("span",[t._v(t._s(t.subtitulo))]),a("span",{staticClass:"separator"},[t._v(" – ")]),a("span",[t._v(t._s(t.date_text))])])])},[],!1,null,null,null);s.a=n.exports},"4a7e":function(t,s,a){"use strict";a.r(s);var e=a("4358"),i=a("975e"),n={name:"sessao-plenaria-online",components:{SessaoPlenariaItemList:e.a},data:function(){return{utils:i.a.Utils,sessao:null,app:["sessao"],model:["sessaoplenaria"]}},mounted:function(){this.fetchSessao()},methods:{fetchSessao:function(){var t=this,s=t.$route.params.id,a={app:t.app[0],model:t.model[0],id:s},e=t.getModel(a);null!==e&&e.hasOwnProperty(s)?t.sessao=e[s]:t.$nextTick().then(function(){t.insertInState(a).then(function(){e=t.getModel(a),t.sessao=e[s]})})},fetch:function(){this.fetchSessao()}}},o=(a("8e03"),a("0c7c")),l=Object(o.a)(n,function(){var t=this.$createElement,s=this._self._c||t;return s("div",{staticClass:"sessao-plenaria-online"},[this.sessao?s("div",[s("sessao-plenaria-item-list",{attrs:{sessao:this.sessao}}),this._v("\nteste\n ")],1):this._e()])},[],!1,null,null,null);s.default=l.exports},"8e03":function(t,s,a){"use strict";var e=a("e8c9");a.n(e).a},e8c9:function(t,s,a){},e94f:function(t,s,a){"use strict";var e=a("f16d");a.n(e).a},f16d:function(t,s,a){}}]);

BIN
sapl/static/sapl/js/chunk-4cf2dae1.89120e74.js.gz

Binary file not shown.

278
sapl/static/sapl/js/chunk-vendors.0003dc37.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/js/chunk-vendors.0003dc37.js.gz

Binary file not shown.

304
sapl/static/sapl/js/chunk-vendors.0b0a5dbb.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/js/chunk-vendors.0b0a5dbb.js.gz

Binary file not shown.

1
sapl/static/sapl/js/compilacao.7b04bca9.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/js/compilacao.7b04bca9.js.gz

Binary file not shown.

7
sapl/static/sapl/js/global.742e068f.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/js/global.742e068f.js.gz

Binary file not shown.

1
sapl/static/sapl/js/online.50eb7f8e.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/js/online.50eb7f8e.js.gz

Binary file not shown.

6
sapl/templates/base.html

@ -27,7 +27,7 @@
</head>
<body>
<div class="page fadein">
<div id="app-frontend-body" class="page fadein">
{% if not request|has_iframe %}
{% block navigation %}
@ -152,10 +152,8 @@
</div>
{% endblock base_header %}
{# Content per se #}
{% block vue_content %}<div id="app"></div>{% endblock %}
{% block vue_content %}<div id="app-frontend-base-content"></div>{% endblock %}
{# Content per se #}
{% block base_content %}{% endblock %}
</div>

27
sapl/templates/base/channel_index.html

@ -0,0 +1,27 @@
<!-- chat/templates/chat/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Chat Rooms</title>
</head>
<body>
What chat room would you like to enter?<br/>
<input id="room-name-input" type="text" size="100"/><br/>
<input id="room-name-submit" type="button" value="Enter"/>
<script>
document.querySelector('#room-name-input').focus();
document.querySelector('#room-name-input').onkeyup = function(e) {
if (e.keyCode === 13) { // enter, return
document.querySelector('#room-name-submit').click();
}
};
document.querySelector('#room-name-submit').onclick = function(e) {
var roomName = document.querySelector('#room-name-input').value;
window.location.pathname = '/channel/' + roomName + '/';
};
</script>
</body>
</html>

47
sapl/templates/base/channel_room.html

@ -0,0 +1,47 @@
<!-- chat/templates/chat/room.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Chat Room</title>
</head>
<body>
<textarea id="chat-log" cols="100" rows="20"></textarea><br/>
<input id="chat-message-input" type="text" size="100"/><br/>
<input id="chat-message-submit" type="button" value="Send"/>
</body>
<script>
var roomName = {{ room_name_json }};
var chatSocket = new WebSocket(
'ws://' + window.location.host +
'/ws/chat/' + roomName + '/');
chatSocket.onmessage = function(e) {
var data = JSON.parse(e.data);
var message = data['message'];
document.querySelector('#chat-log').value += (message + '\n');
};
chatSocket.onclose = function(e) {
console.error('Chat socket closed unexpectedly');
};
document.querySelector('#chat-message-input').focus();
document.querySelector('#chat-message-input').onkeyup = function(e) {
if (e.keyCode === 13) { // enter, return
document.querySelector('#chat-message-submit').click();
}
};
document.querySelector('#chat-message-submit').onclick = function(e) {
var messageInputDom = document.querySelector('#chat-message-input');
var message = messageInputDom.value;
chatSocket.send(JSON.stringify({
'message': message
}));
messageInputDom.value = '';
};
</script>
</html>

34
sapl/templates/base/time_refresh_log_test.html

@ -0,0 +1,34 @@
<!-- chat/templates/chat/room.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Chat Room</title>
</head>
<body>
<textarea id="chat-log" cols="100" rows="20"></textarea><br/>
<input id="chat-message-submit" type="button" value="Pull"/>
</body>
<script>
var chatSocket = new WebSocket(
(window.location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.host +
'/ws/time-refresh/');
chatSocket.onmessage = function(e) {
var data = JSON.parse(e.data);
var message = data['message'];
document.querySelector('#chat-log').value += (e.data + '\n');
};
chatSocket.onclose = function(e) {
console.error('Chat socket closed unexpectedly');
};
document.querySelector('#chat-message-submit').onclick = function(e) {
chatSocket.send(JSON.stringify({
'message': 'OK'
}));
};
</script>
</html>

7
sapl/test_urls.py

@ -1,12 +1,12 @@
import pytest
from django.apps import apps
from django.contrib.auth import get_user_model
from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.db import transaction
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
import pytest
from sapl.crud.base import PermissionRequiredForAppCrudMixin
from sapl.rules.apps import AppConfig, update_groups
@ -14,6 +14,7 @@ from scripts.lista_urls import lista_urls
from .settings import SAPL_APPS
pytestmark = pytest.mark.django_db
sapl_appconfs = [apps.get_app_config(n[5:]) for n in SAPL_APPS]
@ -166,6 +167,8 @@ apps_url_patterns_prefixs_and_users = {
'/email',
'/recuperar-senha',
'/sapl',
'/app',
'/online',
'/XSLT',
]},
'comissoes': {

2
sapl/webpack-stats.json

@ -1 +1 @@
{"status":"done","publicPath":"/static/sapl/","chunks":{"chunk-vendors":[{"name":"css/chunk-vendors.3c9fe6b4.css","publicPath":"/static/sapl/css/chunk-vendors.3c9fe6b4.css","path":"../sapl/sapl/static/sapl/css/chunk-vendors.3c9fe6b4.css"},{"name":"js/chunk-vendors.0003dc37.js","publicPath":"/static/sapl/js/chunk-vendors.0003dc37.js","path":"../sapl/sapl/static/sapl/js/chunk-vendors.0003dc37.js"},{"name":"css/chunk-vendors.3c9fe6b4.css.map","publicPath":"/static/sapl/css/chunk-vendors.3c9fe6b4.css.map","path":"../sapl/sapl/static/sapl/css/chunk-vendors.3c9fe6b4.css.map"}],"compilacao":[{"name":"css/compilacao.3372b760.css","publicPath":"/static/sapl/css/compilacao.3372b760.css","path":"../sapl/sapl/static/sapl/css/compilacao.3372b760.css"},{"name":"js/compilacao.9853b958.js","publicPath":"/static/sapl/js/compilacao.9853b958.js","path":"../sapl/sapl/static/sapl/js/compilacao.9853b958.js"},{"name":"css/compilacao.3372b760.css.map","publicPath":"/static/sapl/css/compilacao.3372b760.css.map","path":"../sapl/sapl/static/sapl/css/compilacao.3372b760.css.map"}],"global":[{"name":"css/global.f7113e2c.css","publicPath":"/static/sapl/css/global.f7113e2c.css","path":"../sapl/sapl/static/sapl/css/global.f7113e2c.css"},{"name":"js/global.081db1b5.js","publicPath":"/static/sapl/js/global.081db1b5.js","path":"../sapl/sapl/static/sapl/js/global.081db1b5.js"},{"name":"css/global.f7113e2c.css.map","publicPath":"/static/sapl/css/global.f7113e2c.css.map","path":"../sapl/sapl/static/sapl/css/global.f7113e2c.css.map"}],"painel":[{"name":"css/painel.baa845ab.css","publicPath":"/static/sapl/css/painel.baa845ab.css","path":"../sapl/sapl/static/sapl/css/painel.baa845ab.css"},{"name":"js/painel.f4adb91b.js","publicPath":"/static/sapl/js/painel.f4adb91b.js","path":"../sapl/sapl/static/sapl/js/painel.f4adb91b.js"},{"name":"css/painel.baa845ab.css.map","publicPath":"/static/sapl/css/painel.baa845ab.css.map","path":"../sapl/sapl/static/sapl/css/painel.baa845ab.css.map"}]}}
{"status":"done","publicPath":"/static/sapl/","chunks":{"null":[{"name":"css/chunk-4cf2dae1.c632bec6.css","publicPath":"/static/sapl/css/chunk-4cf2dae1.c632bec6.css","path":"../sapl/sapl/static/sapl/css/chunk-4cf2dae1.c632bec6.css"},{"name":"js/chunk-4cf2dae1.89120e74.js","publicPath":"/static/sapl/js/chunk-4cf2dae1.89120e74.js","path":"../sapl/sapl/static/sapl/js/chunk-4cf2dae1.89120e74.js"},{"name":"css/chunk-4cf2dae1.c632bec6.css.map","publicPath":"/static/sapl/css/chunk-4cf2dae1.c632bec6.css.map","path":"../sapl/sapl/static/sapl/css/chunk-4cf2dae1.c632bec6.css.map"}],"chunk-vendors":[{"name":"css/chunk-vendors.2ce8185b.css","publicPath":"/static/sapl/css/chunk-vendors.2ce8185b.css","path":"../sapl/sapl/static/sapl/css/chunk-vendors.2ce8185b.css"},{"name":"js/chunk-vendors.0b0a5dbb.js","publicPath":"/static/sapl/js/chunk-vendors.0b0a5dbb.js","path":"../sapl/sapl/static/sapl/js/chunk-vendors.0b0a5dbb.js"},{"name":"css/chunk-vendors.2ce8185b.css.map","publicPath":"/static/sapl/css/chunk-vendors.2ce8185b.css.map","path":"../sapl/sapl/static/sapl/css/chunk-vendors.2ce8185b.css.map"}],"compilacao":[{"name":"css/compilacao.3372b760.css","publicPath":"/static/sapl/css/compilacao.3372b760.css","path":"../sapl/sapl/static/sapl/css/compilacao.3372b760.css"},{"name":"js/compilacao.7b04bca9.js","publicPath":"/static/sapl/js/compilacao.7b04bca9.js","path":"../sapl/sapl/static/sapl/js/compilacao.7b04bca9.js"},{"name":"css/compilacao.3372b760.css.map","publicPath":"/static/sapl/css/compilacao.3372b760.css.map","path":"../sapl/sapl/static/sapl/css/compilacao.3372b760.css.map"}],"global":[{"name":"css/global.e4ae5421.css","publicPath":"/static/sapl/css/global.e4ae5421.css","path":"../sapl/sapl/static/sapl/css/global.e4ae5421.css"},{"name":"js/global.742e068f.js","publicPath":"/static/sapl/js/global.742e068f.js","path":"../sapl/sapl/static/sapl/js/global.742e068f.js"},{"name":"css/global.e4ae5421.css.map","publicPath":"/static/sapl/css/global.e4ae5421.css.map","path":"../sapl/sapl/static/sapl/css/global.e4ae5421.css.map"}],"online":[{"name":"css/online.3d6220ca.css","publicPath":"/static/sapl/css/online.3d6220ca.css","path":"../sapl/sapl/static/sapl/css/online.3d6220ca.css"},{"name":"js/online.50eb7f8e.js","publicPath":"/static/sapl/js/online.50eb7f8e.js","path":"../sapl/sapl/static/sapl/js/online.50eb7f8e.js"},{"name":"css/online.3d6220ca.css.map","publicPath":"/static/sapl/css/online.3d6220ca.css.map","path":"../sapl/sapl/static/sapl/css/online.3d6220ca.css.map"}],"painel":[{"name":"css/painel.baa845ab.css","publicPath":"/static/sapl/css/painel.baa845ab.css","path":"../sapl/sapl/static/sapl/css/painel.baa845ab.css"},{"name":"js/painel.f4adb91b.js","publicPath":"/static/sapl/js/painel.f4adb91b.js","path":"../sapl/sapl/static/sapl/js/painel.f4adb91b.js"},{"name":"css/painel.baa845ab.css.map","publicPath":"/static/sapl/css/painel.baa845ab.css.map","path":"../sapl/sapl/static/sapl/css/painel.baa845ab.css.map"}]}}

2
setup.py

@ -41,6 +41,8 @@ install_requires = [
'pysolr==3.6.0',
'whoosh==2.7.4',
'channels==2.1.7',
# 'git+git://github.com/interlegis/trml2pdf.git',
# 'git+git://github.com/interlegis/django-admin-bootstrapped',
]

7
start.sh

@ -39,8 +39,9 @@ create_env() {
echo "USE_SOLR = ""${USE_SOLR-False}" >> $FILENAME
echo "SOLR_COLLECTION = ""${SOLR_COLLECTION-sapl}" >> $FILENAME
echo "SOLR_URL = ""${SOLR_URL-http://localhost:8983}" >> $FILENAME
echo "USE_CHANNEL_LAYERS = ""${USE_CHANNEL_LAYERS-True}" >> $FILENAME
echo "PORT_CHANNEL_LAYERS = ""${PORT_CHANNEL_LAYERS-6379}" >> $FILENAME
echo "HOST_CHANNEL_LAYERS = ""${HOST_CHANNEL_LAYERS-'saplredis'}" >> $FILENAME
echo "[ENV FILE] done."
}
@ -109,5 +110,5 @@ echo "| ███████║██║ ██║██║ ████
echo "| ╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝ |"
echo "-------------------------------------"
/bin/sh gunicorn_start.sh no-venv &
/bin/sh gunicorn_start.sh no-venv & /bin/sh daphne_start.sh no-venv &
/usr/sbin/nginx -g "daemon off;"

Loading…
Cancel
Save