Browse Source

Merge branch '3.1.x' of https://github.com/interlegis/sapl into 1895-numeracao-oradoes-do-expediente

pull/1989/head
Talitha 8 years ago
parent
commit
1d7f375e8a
  1. 6
      .travis.yml
  2. 2
      docker-compose.yml
  3. 6
      docs/instalacao31.rst
  4. 4
      gunicorn_start.sh
  5. 45
      release.sh
  6. 2
      requirements/migration-requirements.txt
  7. 2
      requirements/requirements.txt
  8. 20
      sapl/audiencia/views.py
  9. 12
      sapl/base/forms.py
  10. 20
      sapl/base/migrations/0017_appconfig_cronometro_consideracoes.py
  11. 5
      sapl/base/models.py
  12. 19
      sapl/base/urls.py
  13. 25
      sapl/base/views.py
  14. 3
      sapl/comissoes/forms.py
  15. 23
      sapl/comissoes/migrations/0014_auto_20180503_1055.py
  16. 2
      sapl/comissoes/models.py
  17. 7
      sapl/comissoes/views.py
  18. 3
      sapl/hashers.py
  19. 24
      sapl/legacy/management/commands/migracao_25_31.py
  20. 88
      sapl/legacy/migracao.py
  21. 216
      sapl/legacy/migracao_dados.py
  22. 214
      sapl/legacy/migracao_documentos.py
  23. 8
      sapl/legacy/migracao_usuarios.py
  24. 2
      sapl/legacy/models.py
  25. 4
      sapl/legacy/router.py
  26. 222
      sapl/legacy/scripts/exporta_zope/exporta_zope.py
  27. 7
      sapl/legacy/scripts/exporta_zope/requirements.txt
  28. 4
      sapl/legacy/scripts/exporta_zope/variaveis_comuns.py
  29. 26
      sapl/legacy/scripts/migra_um_db.sh
  30. 40
      sapl/legacy/scripts/normaliza_dump_mysql.py
  31. 28
      sapl/legacy/scripts/normaliza_dump_mysql.sh
  32. 5
      sapl/legacy/scripts/recria_um_db_postgres.sh
  33. 10
      sapl/legacy_migration_settings.py
  34. 65
      sapl/materia/forms.py
  35. 20
      sapl/materia/migrations/0028_auto_20180418_1629.py
  36. 16
      sapl/materia/models.py
  37. 49
      sapl/materia/views.py
  38. 13
      sapl/norma/forms.py
  39. 25
      sapl/norma/views.py
  40. 20
      sapl/painel/migrations/0002_auto_20180523_1430.py
  41. 3
      sapl/painel/models.py
  42. 1
      sapl/painel/views.py
  43. 4
      sapl/parlamentares/forms.py
  44. 2
      sapl/parlamentares/views.py
  45. 165
      sapl/protocoloadm/forms.py
  46. 20
      sapl/protocoloadm/migrations/0004_documentoadministrativo_numero_externo.py
  47. 4
      sapl/protocoloadm/models.py
  48. 8
      sapl/protocoloadm/urls.py
  49. 59
      sapl/protocoloadm/views.py
  50. 3
      sapl/relatorios/views.py
  51. 42
      sapl/sessao/forms.py
  52. 147
      sapl/sessao/views.py
  53. 2
      sapl/settings.py
  54. 11
      sapl/static/XSLT/HTML/.objects
  55. 95
      sapl/static/XSLT/HTML/estilo.css
  56. 51
      sapl/static/XSLT/HTML/indicacao.xsl
  57. 41
      sapl/static/XSLT/HTML/mocao.xsl
  58. 45
      sapl/static/XSLT/HTML/mocao2.xsl
  59. 41
      sapl/static/XSLT/HTML/parecer.xsl
  60. 47
      sapl/static/XSLT/HTML/pedido.xsl
  61. 53
      sapl/static/XSLT/HTML/pedido2.xsl
  62. 105
      sapl/static/XSLT/HTML/pl.xsl
  63. 100
      sapl/static/XSLT/HTML/pl2.xsl
  64. 52
      sapl/static/XSLT/HTML/requerimento.xsl
  65. 57
      sapl/static/XSLT/HTML/requerimento2.xsl
  66. 2
      sapl/templates/base/layouts.yaml
  67. 2
      sapl/templates/materia/layouts.yaml
  68. 38
      sapl/templates/materia/materialegislativa_form.html
  69. 2
      sapl/templates/norma/normajuridica_detail.html
  70. 3
      sapl/templates/norma/subnav.yaml
  71. 30
      sapl/templates/painel/index.html
  72. 12
      sapl/templates/parlamentares/layouts.yaml
  73. 10
      sapl/templates/protocoloadm/comprovante.html
  74. 1
      sapl/templates/protocoloadm/layouts.yaml
  75. 2
      sapl/templates/protocoloadm/protocoloadm_detail.html
  76. 6
      sapl/templates/sessao/blocos_resumo/conteudo_multimidia.html
  77. 2
      sapl/templates/sessao/blocos_resumo/expedientes.html
  78. 1
      sapl/templates/sessao/expediente.html
  79. 2
      sapl/templates/sessao/mesa.html
  80. 92
      sapl/templates/sessao/painel.html
  81. 4
      sapl/test_urls.py
  82. 5
      sapl/urls.py
  83. 13
      sapl/utils.py
  84. 10
      scripts/django/check_migrations.sh
  85. 4
      scripts/django/check_qa.sh
  86. 2
      scripts/django/fix_qa.sh
  87. 3
      scripts/django/gerar_grafico_apps.sh
  88. 5
      scripts/django/reset_all_migrations.sh
  89. 5
      scripts/django/test_and_check_qa.sh
  90. 2
      scripts/hooks/pre-commit
  91. 14
      scripts/redbaron.py
  92. 7
      scripts_docker/remove-all-containers.sh
  93. 2
      setup.py

6
.travis.yml

@ -1,7 +1,7 @@
language: python language: python
python: python:
- 3.4.3 - 3.5
services: services:
- postgresql - postgresql
@ -14,13 +14,13 @@ before_script:
- cp sapl/.env_test sapl/.env - cp sapl/.env_test sapl/.env
- psql -c "CREATE USER sapl WITH PASSWORD 'sapl'" -U postgres; - psql -c "CREATE USER sapl WITH PASSWORD 'sapl'" -U postgres;
- psql -c "CREATE DATABASE sapl OWNER sapl;" -U postgres - psql -c "CREATE DATABASE sapl OWNER sapl;" -U postgres
- ./check_migrations.sh - ./scripts/django/check_migrations.sh
script: script:
- ./manage.py migrate - ./manage.py migrate
- ./manage.py bower install - ./manage.py bower install
- py.test --create-db - py.test --create-db
# - ./test_and_check_qa.sh # - ./scripts/django/test_and_check_qa.sh
addons: addons:
hosts: hosts:

2
docker-compose.yml

@ -11,7 +11,7 @@ sapldb:
ports: ports:
- "5432:5432" - "5432:5432"
sapl: sapl:
image: interlegis/sapl:3.1.72 image: interlegis/sapl:3.1.84
restart: always restart: always
environment: environment:
ADMIN_PASSWORD: interlegis ADMIN_PASSWORD: interlegis

6
docs/instalacao31.rst

@ -28,10 +28,10 @@ Instalar as seguintes dependências do sistema::
pkg-config postgresql postgresql-contrib pgadmin3 python-psycopg2 \ pkg-config postgresql postgresql-contrib pgadmin3 python-psycopg2 \
software-properties-common build-essential libxml2-dev libjpeg-dev \ software-properties-common build-essential libxml2-dev libjpeg-dev \
libmysqlclient-dev libssl-dev libffi-dev libxslt1-dev python3-setuptools \ libmysqlclient-dev libssl-dev libffi-dev libxslt1-dev python3-setuptools \
python3-pip curl poppler-utils antiword default-jre python3-pip curl poppler-utils antiword default-jre python3-venv
sudo -i sudo -i
curl -sL https://deb.nodesource.com/setup_6.x | bash - curl -sL https://deb.nodesource.com/setup_8.x | bash -
exit exit
sudo apt-get install nodejs sudo apt-get install nodejs
@ -184,6 +184,8 @@ Copie a chave que aparecerá, edite o arquivo .env e altere o valor do parâmetr
* Instalar as dependências do ``bower``:: * Instalar as dependências do ``bower``::
eval $(echo "sudo chown -R $USER:$USER /home/$USER/") eval $(echo "sudo chown -R $USER:$USER /home/$USER/")
sudo chown -R $USER:$GROUP ~/.npm
sudo chown -R $USER:$GROUP ~/.config
./manage.py bower install ./manage.py bower install
* Atualizar e/ou criar as tabelas da base de dados para refletir o modelo da versão clonada:: * Atualizar e/ou criar as tabelas da base de dados para refletir o modelo da versão clonada::

4
gunicorn_start.sh

@ -16,8 +16,9 @@ DJANGODIR=/var/interlegis/sapl/ # Django project directory (*
SOCKFILE=/var/interlegis/sapl/run/gunicorn.sock # we will communicate using this unix socket (*) SOCKFILE=/var/interlegis/sapl/run/gunicorn.sock # we will communicate using this unix socket (*)
USER=`whoami` # the user to run as (*) USER=`whoami` # the user to run as (*)
GROUP=`whoami` # the group to run as (*) GROUP=`whoami` # the group to run as (*)
NUM_WORKERS=9 # how many worker processes should Gunicorn spawn (*) NUM_WORKERS=4 # how many worker processes should Gunicorn spawn (*)
# NUM_WORKERS = 2 * CPUS + 1 # NUM_WORKERS = 2 * CPUS + 1
MAX_REQUESTS=100 # number of requests before restarting worker
DJANGO_SETTINGS_MODULE=sapl.settings # which settings file should Django use (*) DJANGO_SETTINGS_MODULE=sapl.settings # which settings file should Django use (*)
DJANGO_WSGI_MODULE=sapl.wsgi # WSGI module name (*) DJANGO_WSGI_MODULE=sapl.wsgi # WSGI module name (*)
@ -41,6 +42,7 @@ test -d $RUNDIR || mkdir -p $RUNDIR
exec gunicorn ${DJANGO_WSGI_MODULE}:application \ exec gunicorn ${DJANGO_WSGI_MODULE}:application \
--name $NAME \ --name $NAME \
--workers $NUM_WORKERS \ --workers $NUM_WORKERS \
--max-requests $MAX_REQUESTS \
--user $USER \ --user $USER \
--access-logfile - \ --access-logfile - \
--error-logfile - \ --error-logfile - \

45
release.sh

@ -0,0 +1,45 @@
#/bin/bash
VERSION=`git describe --tags --abbrev=0`
LAST_DIGIT=`echo $VERSION | cut -f 3 -d '.'`
MAIN_REV=`echo $VERSION | cut -f 1,2 -d '.'`
NEXT_NUMBER=$(($LAST_DIGIT + 1))
NEXT_VERSION=$MAIN_REV'.'$NEXT_NUMBER
function bump_version {
sed -e s/$VERSION/$NEXT_VERSION/g docker-compose.yml > tmp1
mv tmp1 docker-compose.yml
sed -e s/$VERSION/$NEXT_VERSION/g setup.py > tmp2
mv tmp2 setup.py
}
function commit_and_push {
echo "committing..."
git add docker-compose.yml setup.py
git commit -m "Release: $NEXT_VERSION"
git tag $NEXT_VERSION
echo "sending to github..."
git push origin $NEXT_VERSION
git push origin
echo "done."
}
case "$1" in
--dry-run)
echo "Dry run"
bump_version
echo "done."
echo "Run git checkout -- docker-compose.yml setup.py to undo the files"
exit 0
;;
--publish)
echo "generating release"
bump_version
commit_and_push
esac

2
requirements/migration-requirements.txt

@ -1,2 +1,4 @@
-r dev-requirements.txt -r dev-requirements.txt
GitPython
mysqlclient==1.3.12 mysqlclient==1.3.12
pyaml

2
requirements/requirements.txt

@ -22,7 +22,7 @@ easy-thumbnails==2.3
django-image-cropping==1.1.0 django-image-cropping==1.1.0
git+git://github.com/interlegis/trml2pdf.git git+git://github.com/interlegis/trml2pdf.git
libsass==0.11.1 libsass==0.11.1
psycopg2==2.7.3 psycopg2-binary==2.7.4
python-decouple==3.0 python-decouple==3.0
pytz==2016.4 pytz==2016.4
pyyaml==3.11 pyyaml==3.11

20
sapl/audiencia/views.py

@ -1,15 +1,10 @@
from django.shortcuts import render
from django.http import HttpResponse from django.http import HttpResponse
from django.core.urlresolvers import reverse
from django.db.models import F
from django.views.decorators.clickjacking import xframe_options_exempt from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import ListView from django.views.generic import UpdateView
from sapl.comissoes.forms import ParticipacaoCreateForm, ParticipacaoEditForm from sapl.crud.base import RP_DETAIL, RP_LIST, Crud
from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, CrudAux, MasterDetailCrud
from sapl.materia.models import MateriaLegislativa
from .forms import AudienciaForm from .forms import AudienciaForm
from .models import (AudienciaPublica, TipoAudienciaPublica) from .models import AudienciaPublica
def index(request): def index(request):
return HttpResponse("Audiência Pública") return HttpResponse("Audiência Pública")
@ -36,10 +31,11 @@ class AudienciaCrud(Crud):
form_class = AudienciaForm form_class = AudienciaForm
def get_initial(self): def get_initial(self):
self.initial['tipo_materia'] = self.object.materia.tipo.id initial = super(UpdateView, self).get_initial()
self.initial['numero_materia'] = self.object.materia.numero initial['tipo_materia'] = self.object.materia.tipo.id
self.initial['ano_materia'] = self.object.materia.ano initial['numero_materia'] = self.object.materia.numero
return self.initial initial['ano_materia'] = self.object.materia.ano
return initial
class DeleteView(Crud.DeleteView): class DeleteView(Crud.DeleteView):
pass pass

12
sapl/base/forms.py

@ -525,6 +525,11 @@ class RelatorioAtasFilterSet(django_filters.FilterSet):
model = SessaoPlenaria model = SessaoPlenaria
fields = ['data_inicio'] fields = ['data_inicio']
@property
def qs(self):
parent = super(RelatorioAtasFilterSet, self).qs
return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(RelatorioAtasFilterSet, self).__init__( super(RelatorioAtasFilterSet, self).__init__(
*args, **kwargs) *args, **kwargs)
@ -588,7 +593,7 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
@property @property
def qs(self): def qs(self):
parent = super(RelatorioHistoricoTramitacaoFilterSet, self).qs parent = super(RelatorioHistoricoTramitacaoFilterSet, self).qs
return parent.distinct().order_by('-ano', 'tipo', 'numero') return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero')
class Meta: class Meta:
model = MateriaLegislativa model = MateriaLegislativa
@ -628,7 +633,7 @@ class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet):
@property @property
def qs(self): def qs(self):
parent = super(RelatorioDataFimPrazoTramitacaoFilterSet, self).qs parent = super(RelatorioDataFimPrazoTramitacaoFilterSet, self).qs
return parent.distinct().order_by('-ano', 'tipo', 'numero') return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero')
class Meta: class Meta:
model = MateriaLegislativa model = MateriaLegislativa
@ -829,6 +834,7 @@ class ConfiguracoesAppForm(ModelForm):
'cronometro_discurso', 'cronometro_discurso',
'cronometro_aparte', 'cronometro_aparte',
'cronometro_ordem', 'cronometro_ordem',
'cronometro_consideracoes',
'mostrar_brasao_painel', 'mostrar_brasao_painel',
'receber_recibo_proposicao'] 'receber_recibo_proposicao']
@ -837,6 +843,8 @@ class ConfiguracoesAppForm(ModelForm):
self.fields['cronometro_discurso'].widget.attrs['class'] = 'cronometro' self.fields['cronometro_discurso'].widget.attrs['class'] = 'cronometro'
self.fields['cronometro_aparte'].widget.attrs['class'] = 'cronometro' self.fields['cronometro_aparte'].widget.attrs['class'] = 'cronometro'
self.fields['cronometro_ordem'].widget.attrs['class'] = 'cronometro' self.fields['cronometro_ordem'].widget.attrs['class'] = 'cronometro'
self.fields['cronometro_consideracoes'].widget.attrs['class'] = 'cronometro'
def clean_mostrar_brasao_painel(self): def clean_mostrar_brasao_painel(self):
mostrar_brasao_painel = self.cleaned_data.get( mostrar_brasao_painel = self.cleaned_data.get(

20
sapl/base/migrations/0017_appconfig_cronometro_consideracoes.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-05-23 17:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0016_auto_20180326_1840'),
]
operations = [
migrations.AddField(
model_name='appconfig',
name='cronometro_consideracoes',
field=models.TimeField(blank=True, null=True, verbose_name='Cronômetro de Considerações Finais'),
),
]

5
sapl/base/models.py

@ -113,6 +113,11 @@ class AppConfig(models.Model):
blank=True, blank=True,
null=True) null=True)
cronometro_consideracoes = models.TimeField(
verbose_name=_('Cronômetro de Considerações Finais'),
blank=True,
null=True)
mostrar_brasao_painel = models.BooleanField( mostrar_brasao_painel = models.BooleanField(
default=False, default=False,
verbose_name=_('Mostrar brasão da Casa no painel?')) verbose_name=_('Mostrar brasão da Casa no painel?'))

19
sapl/base/urls.py

@ -1,20 +1,22 @@
import os
from django.conf.urls import include, url from django.conf.urls import include, url
from django.contrib.auth import views from django.contrib.auth import views
from django.contrib.auth.decorators import permission_required from django.contrib.auth.decorators import permission_required
from django.contrib.auth.views import (password_reset, password_reset_complete, from django.contrib.auth.views import (password_reset, password_reset_complete,
password_reset_confirm, password_reset_confirm,
password_reset_done) password_reset_done)
from django.views.generic.base import TemplateView from django.views.generic.base import RedirectView, TemplateView
from sapl.base.views import AutorCrud, ConfirmarEmailView, TipoAutorCrud from sapl.base.views import AutorCrud, ConfirmarEmailView, TipoAutorCrud
from sapl.settings import EMAIL_SEND_USER from sapl.settings import EMAIL_SEND_USER, MEDIA_URL
from .apps import AppConfig from .apps import AppConfig
from .forms import LoginForm, NovaSenhaForm, RecuperarSenhaForm from .forms import LoginForm, NovaSenhaForm, RecuperarSenhaForm
from .views import (AlterarSenha, AppConfigCrud, CasaLegislativaCrud, from .views import (AlterarSenha, AppConfigCrud, CasaLegislativaCrud,
CreateUsuarioView, DeleteUsuarioView, EditUsuarioView, CreateUsuarioView, DeleteUsuarioView, EditUsuarioView,
HelpTopicView, ListarUsuarioView, RelatorioAtasView, HelpTopicView, ListarUsuarioView, LogotipoView,
RelatorioDataFimPrazoTramitacaoView, RelatorioAtasView, RelatorioDataFimPrazoTramitacaoView,
RelatorioHistoricoTramitacaoView, RelatorioHistoricoTramitacaoView,
RelatorioMateriasPorAnoAutorTipoView, RelatorioMateriasPorAnoAutorTipoView,
RelatorioMateriasPorAutorView, RelatorioMateriasPorAutorView,
@ -120,4 +122,13 @@ urlpatterns = [
url(r'^sistema/search/', SaplSearchView(), name='haystack_search'), url(r'^sistema/search/', SaplSearchView(), name='haystack_search'),
# Folhas XSLT e extras referenciadas por documentos migrados do sapl 2.5
url(r'^(sapl/)?XSLT/HTML/(?P<path>.*)$', RedirectView.as_view(
url=os.path.join(MEDIA_URL, 'sapl/public/XSLT/HTML/%(path)s'),
permanent=False)),
# url do logotipo usada em documentos migrados do sapl 2.5
url(r'^(sapl/)?sapl_documentos/props_sapl/logo_casa',
LogotipoView.as_view(), name='logotipo'),
] + recuperar_senha + alterar_senha + admin_user ] + recuperar_senha + alterar_senha + admin_user

25
sapl/base/views.py

@ -1,5 +1,6 @@
from django.conf import settings import os
from django.contrib.auth import get_user_model, update_session_auth_hash
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
@ -12,14 +13,15 @@ from django.template import TemplateDoesNotExist
from django.template.loader import get_template from django.template.loader import get_template
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import (CreateView, DeleteView, DetailView, FormView, from django.utils.translation import string_concat
ListView, UpdateView) from django.views.generic import (CreateView, DeleteView, FormView, ListView,
from django.views.generic.base import TemplateView UpdateView)
from django.views.generic.base import RedirectView, TemplateView
from django_filters.views import FilterView from django_filters.views import FilterView
from haystack.views import SearchView from haystack.views import SearchView
from sapl import settings
from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm
from sapl.base.models import Autor, TipoAutor from sapl.base.models import Autor, TipoAutor
from sapl.crud.base import CrudAux, make_pagination from sapl.crud.base import CrudAux, make_pagination
@ -759,3 +761,14 @@ class AlterarSenha(FormView):
user.save() user.save()
return super().form_valid(form) return super().form_valid(form)
STATIC_LOGO = os.path.join(settings.STATIC_URL, 'img/logo.png')
class LogotipoView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
casa = get_casalegislativa()
logo = casa and casa.logotipo and casa.logotipo.name
return os.path.join(settings.MEDIA_URL, logo) if logo else STATIC_LOGO

3
sapl/comissoes/forms.py

@ -241,6 +241,9 @@ class ComissaoForm(forms.ModelForm):
if not self.is_valid(): if not self.is_valid():
return self.cleaned_data return self.cleaned_data
if len(self.cleaned_data['nome']) > 50:
msg = _('Nome da Comissão deve ter no máximo 50 caracteres.')
raise ValidationError(msg)
if self.cleaned_data['data_extincao']: if self.cleaned_data['data_extincao']:
if (self.cleaned_data['data_extincao'] < if (self.cleaned_data['data_extincao'] <
self.cleaned_data['data_criacao']): self.cleaned_data['data_criacao']):

23
sapl/comissoes/migrations/0014_auto_20180503_1055.py

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2018-05-03 13:55
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('comissoes', '0013_auto_20180312_1533'),
]
operations = [
migrations.AlterModelOptions(
name='composicao',
options={'ordering': ['periodo'], 'verbose_name': 'Composição de Comissão', 'verbose_name_plural': 'Composições de Comissão'},
),
migrations.AlterModelOptions(
name='periodo',
options={'ordering': ['-data_inicio', '-data_fim'], 'verbose_name': 'Período de composição de Comissão', 'verbose_name_plural': 'Períodos de composição de Comissão'},
),
]

2
sapl/comissoes/models.py

@ -105,6 +105,7 @@ class Periodo(models.Model): # PeriodoCompComissao
class Meta: class Meta:
verbose_name = _('Período de composição de Comissão') verbose_name = _('Período de composição de Comissão')
verbose_name_plural = _('Períodos de composição de Comissão') verbose_name_plural = _('Períodos de composição de Comissão')
ordering = ['-data_inicio', '-data_fim']
def __str__(self): def __str__(self):
if self.data_inicio and self.data_fim: if self.data_inicio and self.data_fim:
@ -140,6 +141,7 @@ class Composicao(models.Model): # IGNORE
class Meta: class Meta:
verbose_name = _('Composição de Comissão') verbose_name = _('Composição de Comissão')
verbose_name_plural = _('Composições de Comissão') verbose_name_plural = _('Composições de Comissão')
ordering = ['periodo']
def __str__(self): def __str__(self):
return '%s: %s' % (self.comissao.sigla, self.periodo) return '%s: %s' % (self.comissao.sigla, self.periodo)

7
sapl/comissoes/views.py

@ -51,6 +51,9 @@ class PeriodoComposicaoCrud(CrudAux):
class UpdateView(CrudAux.UpdateView): class UpdateView(CrudAux.UpdateView):
form_class = PeriodoForm form_class = PeriodoForm
# class ListView(CrudAux.ListView):
class ParticipacaoCrud(MasterDetailCrud): class ParticipacaoCrud(MasterDetailCrud):
model = Participacao model = Participacao
parent_field = 'composicao__comissao' parent_field = 'composicao__comissao'
@ -112,7 +115,9 @@ class ComposicaoCrud(MasterDetailCrud):
composicao_pk = self.take_composicao_pk() composicao_pk = self.take_composicao_pk()
if composicao_pk == 0: if composicao_pk == 0:
ultima_composicao = context['composicao_list'].last() # Composicao eh ordenada por Periodo, que por sua vez esta em
# ordem descrescente de data de inicio (issue #1920)
ultima_composicao = context['composicao_list'].first()
if ultima_composicao: if ultima_composicao:
context['composicao_pk'] = ultima_composicao.pk context['composicao_pk'] = ultima_composicao.pk
else: else:

3
sapl/hashers.py

@ -46,11 +46,12 @@ ZOPE_SHA1_PREFIX = '{SSHA}'
def zope_encoded_password_to_django(encoded): def zope_encoded_password_to_django(encoded):
"Migra um hash de senha do zope para uso com o ZopeSHA1PasswordHasher" "Migra um hash de senha do zope para uso com o ZopeSHA1PasswordHasher"
if encoded.startswith(ZOPE_SHA1_PREFIX): if encoded and encoded.startswith(ZOPE_SHA1_PREFIX):
data = encoded[len(ZOPE_SHA1_PREFIX):] data = encoded[len(ZOPE_SHA1_PREFIX):]
salt = get_salt_from_zope_sha1(data) salt = get_salt_from_zope_sha1(data)
hasher = ZopeSHA1PasswordHasher() hasher = ZopeSHA1PasswordHasher()
return super(ZopeSHA1PasswordHasher, hasher).encode(data, salt) return super(ZopeSHA1PasswordHasher, hasher).encode(data, salt)
else: else:
# assume it's a plain password and use the default hashing # assume it's a plain password and use the default hashing
# a None password blocks login, forcing a password reset
return make_password(encoded) return make_password(encoded)

24
sapl/legacy/management/commands/migracao_25_31.py

@ -1,33 +1,13 @@
from django.core import management from django.core import management
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from sapl.legacy.migracao import migrar, migrar_dados from sapl.legacy.migracao import migrar
class Command(BaseCommand): class Command(BaseCommand):
help = 'Migração de dados do SAPL 2.5 para o SAPL 3.1' help = 'Migração de dados do SAPL 2.5 para o SAPL 3.1'
def add_arguments(self, parser):
parser.add_argument(
'--force',
action='store_true',
default=False,
dest='force',
help='Não interativa: pula confirmação de exclusão dos dados',
)
parser.add_argument(
'--dados',
action='store_true',
default=False,
dest='dados',
help='migra somente dados',
)
def handle(self, *args, **options): def handle(self, *args, **options):
management.call_command('migrate') management.call_command('migrate')
somente_dados, interativo = options['dados'], not options['force'] migrar(interativo=False)
if somente_dados:
migrar_dados(interativo=interativo)
else:
migrar(interativo=interativo)

88
sapl/legacy/migracao.py

@ -1,42 +1,76 @@
import subprocess import subprocess
import tarfile from getpass import getpass
from django.conf import settings import requests
from unipath import Path
from sapl.legacy.migracao_dados import migrar_dados from sapl.legacy.migracao_dados import (REPO, TAG_MARCO, gravar_marco, info,
migrar_dados)
from sapl.legacy.migracao_documentos import migrar_documentos from sapl.legacy.migracao_documentos import migrar_documentos
from sapl.legacy.migracao_usuarios import migrar_usuarios from sapl.legacy.migracao_usuarios import migrar_usuarios
from sapl.legacy.scripts.exporta_zope.variaveis_comuns import TAG_ZOPE
from sapl.legacy_migration_settings import DIR_REPO, NOME_BANCO_LEGADO
from sapl.materia.models import Proposicao
def migrar(interativo=False): def adornar_msg(msg):
migrar_dados(interativo=interativo) return '\n{1}\n{0}\n{1}'.format(msg, '#' * len(msg))
migrar_usuarios()
migrar_documentos()
# fonte: https://stackoverflow.com/a/17081026/1877490 def migrar(interativo=False):
def make_tarfile(output_filename, source_dir): if TAG_MARCO in REPO.tags:
with tarfile.open(output_filename, "w:gz") as tar: info('A migração já está feita.')
tar.add(source_dir, arcname=os.path.basename(source_dir)) return
assert TAG_ZOPE in REPO.tags, adornar_msg(
'Antes de migrar '
'é necessário fazer a exportação de documentos do zope')
migrar_dados(interativo=interativo)
migrar_usuarios(REPO.working_dir)
migrar_documentos(REPO)
gravar_marco()
compactar_media()
def gerar_pacote():
banco = settings.DATABASES['legacy']['NAME']
# backup do banco def compactar_media():
print('Gerando backup do banco... ', end='', flush=True)
arq_backup = settings.MEDIA_ROOT.child('{}.backup'.format(banco))
backup_cmd = '''
pg_dump --host localhost --port 5432 --username postgres --no-password
--format custom --blobs --verbose --file {} {}'''.format(
arq_backup, banco)
subprocess.check_output(backup_cmd.split(), stderr=subprocess.DEVNULL)
print('SUCESSO')
# tar de media/sapl # tar de media/sapl
print('Criando tar de media... ', end='', flush=True) print('Criando tar de media... ', end='', flush=True)
tar_media = settings.MEDIA_ROOT.child('{}.media.tgz'.format(banco)) arq_tar = DIR_REPO.child('{}.media.tar'.format(NOME_BANCO_LEGADO))
dir_media = settings.MEDIA_ROOT.child('sapl') arq_tar.remove()
with tarfile.open(tar_media, "w:gz") as tar: subprocess.check_output(['tar', 'cfh', arq_tar, '-C', DIR_REPO, 'sapl'])
tar.add(dir_media, arcname=dir_media.name)
print('SUCESSO') print('SUCESSO')
PROPOSICAO_UPLOAD_TO = Proposicao._meta.get_field('texto_original').upload_to
def salva_conteudo_do_sde(proposicao, conteudo):
caminho_relativo = PROPOSICAO_UPLOAD_TO(
proposicao, 'proposicao_sde_{}.xml'.format(proposicao.pk))
caminho_absoluto = Path(REPO.working_dir, caminho_relativo)
caminho_absoluto.parent.mkdir(parents=True)
with open(caminho_absoluto, 'wb') as arq:
arq.write(conteudo)
proposicao.texto_original = caminho_relativo
proposicao.save()
def scrap_sde(url, usuario, senha=None):
if not senha:
senha = getpass()
# login
session = requests.session()
res = session.post('{}?retry=1'.format(url),
{'__ac_name': usuario, '__ac_password': senha})
assert res.status_code == 200
url_proposicao = '{}/sapl_documentos/proposicao/{}/renderXML?xsl=__default__' # noqa
total = Proposicao.objects.count()
for num, proposicao in enumerate(Proposicao.objects.all()):
pk = proposicao.pk
res = session.get(url_proposicao.format(url, pk))
print("pk: {} status: {} (progresso: {:.2%})".format(
pk, res.status_code, num / total))
if res.status_code == 200:
salva_conteudo_do_sde(proposicao, res.content)

216
sapl/legacy/migracao_dados.py

@ -1,4 +1,7 @@
import datetime
import os
import re import re
import subprocess
import traceback import traceback
from collections import OrderedDict, defaultdict, namedtuple from collections import OrderedDict, defaultdict, namedtuple
from datetime import date from datetime import date
@ -7,10 +10,13 @@ from itertools import groupby
from operator import xor from operator import xor
from subprocess import PIPE, call from subprocess import PIPE, call
import git
import pkg_resources import pkg_resources
import pyaml
import pytz import pytz
import reversion import reversion
import yaml import yaml
from bs4 import BeautifulSoup
from django.apps import apps from django.apps import apps
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
@ -18,13 +24,18 @@ from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import connections, transaction from django.db import connections, transaction
from django.db.models import Max, Q from django.db.models import Max, Q
from pyaml import UnsafePrettyYAMLDumper
from unipath import Path from unipath import Path
from sapl.base.models import AppConfig as AppConf from sapl.base.models import AppConfig as AppConf
from sapl.base.models import Autor, TipoAutor, cria_models_tipo_autor from sapl.base.models import Autor, TipoAutor, cria_models_tipo_autor
from sapl.comissoes.models import Comissao, Composicao, Participacao from sapl.comissoes.models import Comissao, Composicao, Participacao
from sapl.legacy import scripts
from sapl.legacy.models import NormaJuridica as OldNormaJuridica from sapl.legacy.models import NormaJuridica as OldNormaJuridica
from sapl.legacy.models import TipoNumeracaoProtocolo from sapl.legacy.models import TipoNumeracaoProtocolo
from sapl.legacy_migration_settings import (DATABASES, DIR_DADOS_MIGRACAO,
DIR_REPO, NOME_BANCO_LEGADO,
PROJECT_DIR)
from sapl.materia.models import (AcompanhamentoMateria, MateriaLegislativa, from sapl.materia.models import (AcompanhamentoMateria, MateriaLegislativa,
Proposicao, StatusTramitacao, TipoDocumento, Proposicao, StatusTramitacao, TipoDocumento,
TipoMateriaLegislativa, TipoProposicao, TipoMateriaLegislativa, TipoProposicao,
@ -35,11 +46,11 @@ from sapl.parlamentares.models import (Legislatura, Mandato, Parlamentar,
Partido, TipoAfastamento) Partido, TipoAfastamento)
from sapl.protocoloadm.models import (DocumentoAdministrativo, Protocolo, from sapl.protocoloadm.models import (DocumentoAdministrativo, Protocolo,
StatusTramitacaoAdministrativo) StatusTramitacaoAdministrativo)
from sapl.sessao.models import (ExpedienteMateria, OrdemDia, RegistroVotacao, from sapl.sessao.models import (ExpedienteMateria, ExpedienteSessao, OrdemDia,
TipoResultadoVotacao) RegistroVotacao, TipoResultadoVotacao)
from sapl.settings import DATABASES, PROJECT_DIR
from sapl.utils import normalize from sapl.utils import normalize
from .scripts.normaliza_dump_mysql import normaliza_dump_mysql
from .timezonesbrasil import get_timezone from .timezonesbrasil import get_timezone
# BASE ###################################################################### # BASE ######################################################################
@ -136,6 +147,7 @@ for nome_novo, nome_antigo in (('comissao', 'cod_comissao'),
class CampoVirtual(namedtuple('CampoVirtual', 'model related_model')): class CampoVirtual(namedtuple('CampoVirtual', 'model related_model')):
null = True null = True
CAMPOS_VIRTUAIS_PROPOSICAO = { CAMPOS_VIRTUAIS_PROPOSICAO = {
TipoMateriaLegislativa: CampoVirtual(Proposicao, MateriaLegislativa), TipoMateriaLegislativa: CampoVirtual(Proposicao, MateriaLegislativa),
TipoDocumento: CampoVirtual(Proposicao, DocumentoAdministrativo) TipoDocumento: CampoVirtual(Proposicao, DocumentoAdministrativo)
@ -143,6 +155,15 @@ CAMPOS_VIRTUAIS_PROPOSICAO = {
for campo_virtual in CAMPOS_VIRTUAIS_PROPOSICAO.values(): for campo_virtual in CAMPOS_VIRTUAIS_PROPOSICAO.values():
campos_novos_para_antigos[campo_virtual] = 'cod_mat_ou_doc' campos_novos_para_antigos[campo_virtual] = 'cod_mat_ou_doc'
CAMPOS_VIRTUAIS_TIPO_PROPOSICAO = {
'M': CampoVirtual(TipoProposicao, TipoMateriaLegislativa),
'D': CampoVirtual(TipoProposicao, TipoDocumento)
}
for campo_virtual in CAMPOS_VIRTUAIS_TIPO_PROPOSICAO.values():
campos_novos_para_antigos[campo_virtual] = 'tip_mat_ou_doc'
# campos virtuais de Autor para funcionar com get_fk_related # campos virtuais de Autor para funcionar com get_fk_related
CAMPOS_VIRTUAIS_AUTOR = {related: CampoVirtual(Autor, related) CAMPOS_VIRTUAIS_AUTOR = {related: CampoVirtual(Autor, related)
for related in (Parlamentar, Comissao, Partido)} for related in (Parlamentar, Comissao, Partido)}
@ -159,6 +180,7 @@ for related, campo_antigo in [(Parlamentar, 'cod_parlamentar'),
def info(msg): def info(msg):
print('INFO: ' + msg) print('INFO: ' + msg)
ocorrencias = defaultdict(list) ocorrencias = defaultdict(list)
@ -201,7 +223,7 @@ class ForeignKeyFaltando(ObjectDoesNotExist):
campo = campos_novos_para_antigos[self.field] campo = campos_novos_para_antigos[self.field]
_, tabela, campos_pk = get_estrutura_legado(self.field.model) _, tabela, campos_pk = get_estrutura_legado(self.field.model)
pk = {c: getattr(self.old, c) for c in campos_pk} pk = {c: getattr(self.old, c) for c in campos_pk}
sql = 'select * from {} where {}'.format( sql = 'select * from {} where {};'.format(
tabela, tabela,
' and '.join(['{} = {}'.format(k, v) for k, v in pk.items()])) ' and '.join(['{} = {}'.format(k, v) for k, v in pk.items()]))
return OrderedDict((('campo', campo), return OrderedDict((('campo', campo),
@ -494,6 +516,8 @@ PROPAGACOES_DE_EXCLUSAO = [
('parlamentar', 'dependente', 'cod_parlamentar'), ('parlamentar', 'dependente', 'cod_parlamentar'),
('parlamentar', 'filiacao', 'cod_parlamentar'), ('parlamentar', 'filiacao', 'cod_parlamentar'),
('parlamentar', 'mandato', 'cod_parlamentar'), ('parlamentar', 'mandato', 'cod_parlamentar'),
('parlamentar', 'composicao_mesa', 'cod_parlamentar'),
('parlamentar', 'composicao_comissao', 'cod_parlamentar'),
# comissao # comissao
('comissao', 'composicao_comissao', 'cod_comissao'), ('comissao', 'composicao_comissao', 'cod_comissao'),
@ -518,6 +542,11 @@ PROPAGACOES_DE_EXCLUSAO = [
('materia_legislativa', 'anexada', 'cod_materia_principal'), ('materia_legislativa', 'anexada', 'cod_materia_principal'),
('materia_legislativa', 'anexada', 'cod_materia_anexada'), ('materia_legislativa', 'anexada', 'cod_materia_anexada'),
('materia_legislativa', 'documento_acessorio', 'cod_materia'), ('materia_legislativa', 'documento_acessorio', 'cod_materia'),
('materia_legislativa', 'numeracao', 'cod_materia'),
# norma
('norma_juridica', 'vinculo_norma_juridica', 'cod_norma_referente'),
('norma_juridica', 'vinculo_norma_juridica', 'cod_norma_referida'),
# documento administrativo # documento administrativo
('documento_administrativo', 'tramitacao_administrativo', 'cod_documento'), ('documento_administrativo', 'tramitacao_administrativo', 'cod_documento'),
@ -548,6 +577,9 @@ def uniformiza_banco():
garante_coluna_no_legado('tipo_materia_legislativa', garante_coluna_no_legado('tipo_materia_legislativa',
'quorum_minimo_votacao int(11) NULL') 'quorum_minimo_votacao int(11) NULL')
garante_coluna_no_legado('materia_legislativa',
'txt_resultado TEXT NULL')
# Cria campos cod_presenca_sessao (sendo a nova PK da tabela) # Cria campos cod_presenca_sessao (sendo a nova PK da tabela)
# e dat_sessao em sessao_plenaria_presenca # e dat_sessao em sessao_plenaria_presenca
if not existe_coluna_no_legado('sessao_plenaria_presenca', if not existe_coluna_no_legado('sessao_plenaria_presenca',
@ -695,31 +727,26 @@ def fill_dados_basicos():
appconf.save() appconf.save()
def get_last_pk(model):
last_value = model.objects.all().aggregate(Max('pk'))
return last_value['pk__max'] or 0
def reinicia_sequence(model, id): def reinicia_sequence(model, id):
sequence_name = '%s_id_seq' % model._meta.db_table sequence_name = '%s_id_seq' % model._meta.db_table
exec_sql('ALTER SEQUENCE %s RESTART WITH %s MINVALUE -1;' % ( exec_sql('ALTER SEQUENCE %s RESTART WITH %s MINVALUE -1;' % (
sequence_name, id)) sequence_name, id))
DIR_DADOS_MIGRACAO = Path('~/migracao_sapl/').expand() REPO = git.Repo.init(DIR_REPO)
PATH_TABELA_TIMEZONES = DIR_DADOS_MIGRACAO.child('tabela_timezones.yaml')
DIR_RESULTADOS = DIR_DADOS_MIGRACAO.child('resultados')
def dict_representer(dumper, data): def dict_representer(dumper, data):
return dumper.represent_dict(data.items()) return dumper.represent_dict(data.items())
yaml.add_representer(OrderedDict, dict_representer) yaml.add_representer(OrderedDict, dict_representer)
# configura timezone de migração # configura timezone de migração
nome_banco_legado = DATABASES['legacy']['NAME'] match = re.match('sapl_cm_(.*)', NOME_BANCO_LEGADO)
match = re.match('sapl_cm_(.*)', nome_banco_legado)
sigla_casa = match.group(1) sigla_casa = match.group(1)
PATH_TABELA_TIMEZONES = DIR_DADOS_MIGRACAO.child('tabela_timezones.yaml')
with open(PATH_TABELA_TIMEZONES, 'r') as arq: with open(PATH_TABELA_TIMEZONES, 'r') as arq:
tabela_timezones = yaml.load(arq) tabela_timezones = yaml.load(arq)
municipio, uf, nome_timezone = tabela_timezones[sigla_casa] municipio, uf, nome_timezone = tabela_timezones[sigla_casa]
@ -762,7 +789,27 @@ def populate_renamed_fields(new, old):
setattr(new, field.name, value) setattr(new, field.name, value)
def roda_comando_shell(cmd):
res = os.system(cmd)
assert res == 0, 'O comando falhou: {}'.format(cmd)
def migrar_dados(interativo=True): def migrar_dados(interativo=True):
# restaura dump
arq_dump = Path(DIR_DADOS_MIGRACAO.child(
'dumps_mysql', '{}.sql'.format(NOME_BANCO_LEGADO)))
assert arq_dump.exists(), 'Dump do mysql faltando: {}'.format(arq_dump)
info('Restaurando dump mysql de [{}]'.format(arq_dump))
normaliza_dump_mysql(arq_dump)
roda_comando_shell('mysql -uroot < {}'.format(arq_dump))
# executa ajustes pré-migração, se existirem
arq_ajustes_pre_migracao = DIR_DADOS_MIGRACAO.child(
'ajustes_pre_migracao', '{}.sql'.format(sigla_casa))
if arq_ajustes_pre_migracao.exists():
exec_legado(arq_ajustes_pre_migracao.read_file())
uniformiza_banco() uniformiza_banco()
# excluindo database antigo. # excluindo database antigo.
@ -789,19 +836,16 @@ def migrar_dados(interativo=True):
info('Começando migração: ...') info('Começando migração: ...')
try: try:
ocorrencias.clear() ocorrencias.clear()
dir_ocorrencias = DIR_RESULTADOS.child(date.today().isoformat())
dir_ocorrencias.mkdir(parents=True)
migrar_todos_os_models() migrar_todos_os_models()
except Exception as e: except Exception as e:
ocorrencias['traceback'] = str(traceback.format_exc()) ocorrencias['traceback'] = str(traceback.format_exc())
raise e raise e
finally: finally:
# grava ocorrências # grava ocorrências
arq_ocorrencias = dir_ocorrencias.child( arq_ocorrencias = Path(REPO.working_dir, 'ocorrencias.yaml')
nome_banco_legado + '.yaml')
with open(arq_ocorrencias, 'w') as arq: with open(arq_ocorrencias, 'w') as arq:
dump = yaml.dump(dict(ocorrencias), allow_unicode=True) pyaml.dump(ocorrencias, arq, vspacing=1)
arq.write(dump.replace('\n- ', '\n\n- ')) REPO.git.add([arq_ocorrencias.name])
info('Ocorrências salvas em\n {}'.format(arq_ocorrencias)) info('Ocorrências salvas em\n {}'.format(arq_ocorrencias))
# recria tipos de autor padrão que não foram criados pela migração # recria tipos de autor padrão que não foram criados pela migração
@ -816,7 +860,7 @@ def move_para_depois_de(lista, movido, referencias):
return lista return lista
def migrar_todos_os_models(): def get_models_a_migrar():
models = [model for app in appconfs for model in app.models.values() models = [model for app in appconfs for model in app.models.values()
if model in field_renames] if model in field_renames]
# Devido à referência TipoProposicao.tipo_conteudo_related # Devido à referência TipoProposicao.tipo_conteudo_related
@ -829,7 +873,11 @@ def migrar_todos_os_models():
move_para_depois_de(models, Proposicao, move_para_depois_de(models, Proposicao,
[MateriaLegislativa, DocumentoAdministrativo]) [MateriaLegislativa, DocumentoAdministrativo])
for model in models: return models
def migrar_todos_os_models():
for model in get_models_a_migrar():
migrar_model(model) migrar_model(model)
@ -852,10 +900,14 @@ def migrar_model(model):
def get_id_do_legado(old): def get_id_do_legado(old):
return getattr(old, nome_pk) return getattr(old, nome_pk)
ultima_pk_legado = model_legado.objects.all().aggregate(
Max('pk'))['pk__max'] or 0
else: else:
# a pk no legado tem mais de um campo # a pk no legado tem mais de um campo
old_records = iter_sql_records(tabela_legado) old_records = iter_sql_records(tabela_legado)
get_id_do_legado = None get_id_do_legado = None
ultima_pk_legado = model_legado.objects.count()
ajuste_antes_salvar = AJUSTE_ANTES_SALVAR.get(model) ajuste_antes_salvar = AJUSTE_ANTES_SALVAR.get(model)
ajuste_depois_salvar = AJUSTE_DEPOIS_SALVAR.get(model) ajuste_depois_salvar = AJUSTE_DEPOIS_SALVAR.get(model)
@ -898,10 +950,13 @@ def migrar_model(model):
if ajuste_depois_salvar: if ajuste_depois_salvar:
ajuste_depois_salvar() ajuste_depois_salvar()
# se configuramos ids explicitamente devemos reiniciar a sequence # reiniciamos a sequence logo após a última pk do legado
#
# É importante que seja do legado (e não da nova base),
# pois numa nova versão da migração podemos inserir registros
# não migrados antes sem conflito com pks criadas até lá
if get_id_do_legado: if get_id_do_legado:
last_pk = get_last_pk(model) reinicia_sequence(model, ultima_pk_legado + 1)
reinicia_sequence(model, last_pk + 1)
# apaga registros migrados do legado # apaga registros migrados do legado
if sql_delete_legado: if sql_delete_legado:
@ -1061,22 +1116,16 @@ def adjust_tipoafastamento(new, old):
new.indicador = 'F' new.indicador = 'F'
TIPO_MATERIA_OU_TIPO_DOCUMENTO = {'M': TipoMateriaLegislativa, def set_generic_fk(new, campo_virtual, old):
'D': TipoDocumento} new.content_type = content_types[campo_virtual.related_model]
new.object_id = get_fk_related(campo_virtual, old)
def adjust_tipoproposicao(new, old): def adjust_tipoproposicao(new, old):
"Aponta para o tipo relacionado de matéria ou documento" "Aponta para o tipo relacionado de matéria ou documento"
value = old.tip_mat_ou_doc if old.tip_mat_ou_doc:
model_tipo = TIPO_MATERIA_OU_TIPO_DOCUMENTO[old.ind_mat_ou_doc] campo_virtual = CAMPOS_VIRTUAIS_TIPO_PROPOSICAO[old.ind_mat_ou_doc]
tipo = model_tipo.objects.filter(pk=value) set_generic_fk(new, campo_virtual, old)
if tipo:
new.tipo_conteudo_related = tipo[0]
else:
raise ForeignKeyFaltando(
field=TipoProposicao.tipo_conteudo_related,
value=(model_tipo.__name__, value),
label={'ind_mat_ou_doc': old.ind_mat_ou_doc})
def adjust_proposicao_antes_salvar(new, old): def adjust_proposicao_antes_salvar(new, old):
@ -1085,8 +1134,7 @@ def adjust_proposicao_antes_salvar(new, old):
if old.cod_mat_ou_doc: if old.cod_mat_ou_doc:
tipo_mat_ou_doc = type(new.tipo.tipo_conteudo_related) tipo_mat_ou_doc = type(new.tipo.tipo_conteudo_related)
campo_virtual = CAMPOS_VIRTUAIS_PROPOSICAO[tipo_mat_ou_doc] campo_virtual = CAMPOS_VIRTUAIS_PROPOSICAO[tipo_mat_ou_doc]
new.content_type = content_types[campo_virtual.related_model] set_generic_fk(new, campo_virtual, old)
new.object_id = get_fk_related(campo_virtual, old)
def adjust_statustramitacao(new, old): def adjust_statustramitacao(new, old):
@ -1162,10 +1210,9 @@ def adjust_autor(new, old):
break break
if old.col_username: if old.col_username:
user_model = get_user_model() user, created = get_user_model().objects.get_or_create(
if not user_model.objects.filter(username=old.col_username).exists(): username=old.col_username)
# cria um novo ususaŕio para o autor if created:
user = user_model(username=old.col_username)
# gera uma senha inutilizável, que precisará ser trocada # gera uma senha inutilizável, que precisará ser trocada
user.set_password(None) user.set_password(None)
with reversion.create_revision(): with reversion.create_revision():
@ -1173,8 +1220,9 @@ def adjust_autor(new, old):
reversion.set_comment( reversion.set_comment(
'Usuário criado pela migração para o autor {}'.format( 'Usuário criado pela migração para o autor {}'.format(
old.cod_autor)) old.cod_autor))
grupo_autor = Group.objects.get(name="Autor") grupo_autor = Group.objects.get(name="Autor")
user.groups.add(grupo_autor) user.groups.add(grupo_autor)
new.user = user
def adjust_comissao(new, old): def adjust_comissao(new, old):
@ -1199,6 +1247,21 @@ def adjust_tiporesultadovotacao(new, old):
{'pk': new.pk, 'nome': new.nome}) {'pk': new.pk, 'nome': new.nome})
def remove_style(conteudo):
if 'style' not in conteudo:
return conteudo # atalho que acelera muito os casos sem style
soup = BeautifulSoup(conteudo, 'html.parser')
for tag in soup.recursiveChildGenerator():
if hasattr(tag, 'attrs'):
tag.attrs = {k: v for k, v in tag.attrs.items() if k != 'style'}
return str(soup)
def adjust_expediente_sessao(new, old):
new.conteudo = remove_style(new.conteudo)
AJUSTE_ANTES_SALVAR = { AJUSTE_ANTES_SALVAR = {
Autor: adjust_autor, Autor: adjust_autor,
TipoAutor: adjust_tipo_autor, TipoAutor: adjust_tipo_autor,
@ -1220,10 +1283,71 @@ AJUSTE_ANTES_SALVAR = {
StatusTramitacaoAdministrativo: adjust_statustramitacaoadm, StatusTramitacaoAdministrativo: adjust_statustramitacaoadm,
Tramitacao: adjust_tramitacao, Tramitacao: adjust_tramitacao,
TipoResultadoVotacao: adjust_tiporesultadovotacao, TipoResultadoVotacao: adjust_tiporesultadovotacao,
ExpedienteSessao: adjust_expediente_sessao,
} }
AJUSTE_DEPOIS_SALVAR = { AJUSTE_DEPOIS_SALVAR = {
NormaJuridica: adjust_normajuridica_depois_salvar, NormaJuridica: adjust_normajuridica_depois_salvar,
} }
# CHECKS ####################################################################
# MARCO ######################################################################
TIME_FORMAT = '%H:%M:%S'
# permite a gravação de tempos puros pelo pretty-yaml
def time_representer(dumper, data):
return dumper.represent_scalar('!time', data.strftime(TIME_FORMAT))
UnsafePrettyYAMLDumper.add_representer(datetime.time, time_representer)
# permite a leitura de tempos puros pelo pyyaml (no padrão gravado acima)
def time_constructor(loader, node):
value = loader.construct_scalar(node)
return datetime.datetime.strptime(value, TIME_FORMAT).time()
yaml.add_constructor(u'!time', time_constructor)
TAG_MARCO = 'marco'
def gravar_marco():
"""Grava um dump de todos os dados como arquivos yaml no repo de marco
"""
# prepara ou localiza repositorio
dir_dados = Path(REPO.working_dir, 'dados')
# exporta dados como arquivos yaml
user_model = get_user_model()
models = get_models_a_migrar() + [
Composicao, user_model, Group, ContentType]
for model in models:
info('Gravando marco de [{}]'.format(model.__name__))
dir_model = dir_dados.child(model._meta.app_label, model.__name__)
dir_model.mkdir(parents=True)
for data in model.objects.all().values():
nome_arq = Path(dir_model, '{}.yaml'.format(data['id']))
with open(nome_arq, 'w') as arq:
pyaml.dump(data, arq)
# backup do banco
print('Gerando backup do banco... ', end='', flush=True)
arq_backup = DIR_REPO.child('{}.backup'.format(NOME_BANCO_LEGADO))
arq_backup.remove()
backup_cmd = '''
pg_dump --host localhost --port 5432 --username postgres --no-password
--format custom --blobs --verbose --file {} {}'''.format(
arq_backup, NOME_BANCO_LEGADO)
subprocess.check_output(backup_cmd.split(), stderr=subprocess.DEVNULL)
print('SUCESSO')
# salva mudanças
REPO.git.add([dir_dados.name])
if 'master' not in REPO.heads or REPO.index.diff('HEAD'):
# se de fato existe mudança
REPO.index.commit('Grava marco')
REPO.git.execute('git tag -f'.split() + [TAG_MARCO])

214
sapl/legacy/migracao_documentos.py

@ -1,12 +1,14 @@
import mimetypes
import os import os
import re import re
from glob import glob from glob import glob
from os.path import join
import yaml import yaml
from django.db import transaction
from image_cropping.fields import ImageCropField
from sapl.base.models import CasaLegislativa from sapl.base.models import CasaLegislativa
from sapl.legacy.migracao_dados import exec_legado, warn from sapl.legacy.migracao_dados import exec_legado
from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa, from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa,
Proposicao) Proposicao)
from sapl.norma.models import NormaJuridica from sapl.norma.models import NormaJuridica
@ -14,107 +16,56 @@ from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import (DocumentoAcessorioAdministrativo, from sapl.protocoloadm.models import (DocumentoAcessorioAdministrativo,
DocumentoAdministrativo) DocumentoAdministrativo)
from sapl.sessao.models import SessaoPlenaria from sapl.sessao.models import SessaoPlenaria
from sapl.settings import MEDIA_ROOT
# MIGRAÇÃO DE DOCUMENTOS ################################################### # MIGRAÇÃO DE DOCUMENTOS ###################################################
def get_ano(obj):
return [obj.ano]
def ___(obj):
return []
DOCS = { DOCS = {
CasaLegislativa: [ CasaLegislativa: [('logotipo', 'props_sapl/{}.*')],
('logotipo', Parlamentar: [('fotografia', 'parlamentar/fotos/{}_foto_parlamentar')],
'props_sapl/{}.*', MateriaLegislativa: [('texto_original', 'materia/{}_texto_integral')],
'public/casa/logotipo/', DocumentoAcessorio: [('arquivo', 'materia/{}')],
___) NormaJuridica: [('texto_integral', 'norma_juridica/{}_texto_integral')],
], SessaoPlenaria: [('upload_pauta', 'pauta_sessao/{}_pauta_sessao'),
Parlamentar: [ ('upload_ata', 'ata_sessao/{}_ata_sessao'),
('fotografia', ('upload_anexo', 'anexo_sessao/{}_texto_anexado')],
'parlamentar/fotos/{}_foto_parlamentar', Proposicao: [('texto_original', 'proposicao/{}')],
'public/parlamentar/{0}/', DocumentoAdministrativo: [('texto_integral',
___) 'administrativo/{}_texto_integral')],
], DocumentoAcessorioAdministrativo: [('arquivo', 'administrativo/{}')],
MateriaLegislativa: [
('texto_original',
'materia/{}_texto_integral',
'public/materialegislativa/{1}/{0}/',
get_ano)
],
DocumentoAcessorio: [
('arquivo',
'materia/{}',
'public/documentoacessorio/{1}/{0}/',
lambda obj: [obj.materia.ano])
],
NormaJuridica: [
('texto_integral',
'norma_juridica/{}_texto_integral',
'public/normajuridica/{1}/{0}/',
get_ano)
],
SessaoPlenaria: [
('upload_pauta',
'pauta_sessao/{}_pauta_sessao',
'public/sessaoplenaria/{0}/pauta/',
___),
('upload_ata',
'ata_sessao/{}_ata_sessao',
'public/sessaoplenaria/{0}/ata/',
___),
('upload_anexo',
'anexo_sessao/{}_texto_anexado',
'public/sessaoplenaria/{0}/anexo/',
___)
],
Proposicao: [
('texto_original',
'proposicao/{}',
'private/proposicao/{0}/',
get_ano)
],
DocumentoAdministrativo: [
('texto_integral',
'administrativo/{}_texto_integral',
'private/documentoadministrativo/{0}/',
get_ano)
],
DocumentoAcessorioAdministrativo: [
('arquivo',
'administrativo/{}',
'private/documentoacessorioadministrativo/{0}/',
___)
],
} }
DOCS = {model: [(campo, DOCS = {model: [(campo, join('sapl_documentos', origem))
os.path.join('sapl_documentos', origem), for campo, origem, in campos]
os.path.join('sapl', destino),
get_extra_args)
for campo, origem, destino, get_extra_args in campos]
for model, campos in DOCS.items()} for model, campos in DOCS.items()}
def em_media(caminho): def mover_documento(repo, origem, destino):
return os.path.join(MEDIA_ROOT, caminho) origem, destino = [join(repo.working_dir, c) if not os.path.isabs(c) else c
def mover_documento(origem, destino):
origem, destino = [em_media(c) if not os.path.isabs(c) else c
for c in (origem, destino)] for c in (origem, destino)]
os.makedirs(os.path.dirname(destino), exist_ok=True) os.makedirs(os.path.dirname(destino), exist_ok=True)
os.rename(origem, destino) repo.git.mv(origem, destino)
def migrar_propriedades_da_casa(): def migrar_logotipo(repo, casa, propriedades):
print('.... Migrando logotipo da casa ....')
[(campo, origem)] = DOCS[CasaLegislativa]
# a extensão do logo pode ter sido ajustada pelo tipo real do arquivo
nome_nas_propriedades = os.path.splitext(propriedades['id_logo'])[0]
arquivos = glob(join(repo.working_dir, origem.format(nome_nas_propriedades)))
if arquivos:
assert len(arquivos) == 1, 'Há mais de um logotipo para a casa'
[logo] = arquivos
destino = join(CasaLegislativa._meta.get_field(campo).upload_to,
os.path.basename(logo))
mover_documento(repo, logo, destino)
casa.logotipo = destino
def migrar_propriedades_da_casa(repo):
print('#### Migrando propriedades da casa ####') print('#### Migrando propriedades da casa ####')
caminho = em_media('sapl_documentos/propriedades.yaml') caminho = join(repo.working_dir, 'sapl_documentos/propriedades.yaml')
repo.git.execute('git annex get'.split() + [caminho])
with open(caminho, 'r') as arquivo: with open(caminho, 'r') as arquivo:
propriedades = yaml.safe_load(arquivo) propriedades = yaml.safe_load(arquivo)
casa = CasaLegislativa.objects.first() casa = CasaLegislativa.objects.first()
@ -134,67 +85,73 @@ def migrar_propriedades_da_casa():
for campo, prop in campos_para_propriedades: for campo, prop in campos_para_propriedades:
setattr(casa, campo, propriedades[prop]) setattr(casa, campo, propriedades[prop])
# Localidade # localidade
sql_localidade = ''' sql_localidade = '''
select nom_localidade, sgl_uf from localidade select nom_localidade, sgl_uf from localidade
where cod_localidade = {}'''.format(propriedades['cod_localidade']) where cod_localidade = {}'''.format(propriedades['cod_localidade'])
[(casa.municipio, casa.uf)] = exec_legado(sql_localidade) [(casa.municipio, casa.uf)] = exec_legado(sql_localidade)
print('.... Migrando logotipo da casa ....') # logotipo
[(_, origem, destino, __)] = DOCS[CasaLegislativa] migrar_logotipo(repo, casa, propriedades)
# a extensão do logo pode ter sido ajustada pelo tipo real do arquivo
id_logo = os.path.splitext(propriedades['id_logo'])[0]
[origem] = glob(em_media(origem.format(id_logo)))
destino = os.path.join(destino, os.path.basename(origem))
mover_documento(origem, destino)
casa.logotipo = destino
casa.save() casa.save()
os.remove(caminho) repo.git.rm(caminho)
def migrar_docs_por_ids(model): def migrar_docs_por_ids(repo, model):
for campo, base_origem, base_destino, get_extra_args in DOCS[model]: for campo, base_origem in DOCS[model]:
print('#### Migrando {} de {} ####'.format(campo, model.__name__)) print('#### Migrando {} de {} ####'.format(campo, model.__name__))
dir_origem, nome_origem = os.path.split(em_media(base_origem)) dir_origem, nome_origem = os.path.split(
join(repo.working_dir, base_origem))
nome_origem = nome_origem.format('(\d+)') nome_origem = nome_origem.format('(\d+)')
pat = re.compile('^{}\.\w+$'.format(nome_origem)) pat = re.compile('^{}\.\w+$'.format(nome_origem))
if not os.path.isdir(dir_origem): if not os.path.isdir(dir_origem):
print(' >>> O diretório {} não existe! Abortado.'.format( print(' >>> O diretório {} não existe! Abortado.'.format(
dir_origem)) dir_origem))
continue continue
for arq in os.listdir(dir_origem): matches = [pat.match(arq) for arq in os.listdir(dir_origem)]
match = pat.match(arq) ids_origens = [(int(m.group(1)),
if match: join(dir_origem, m.group(0)))
for m in matches if m]
objetos = {obj.id: obj for obj in model.objects.all()}
upload_to = model._meta.get_field(campo).upload_to
tem_cropping = isinstance(model._meta.get_field(campo), ImageCropField)
with transaction.atomic():
for id, origem in ids_origens:
# associa documento ao objeto # associa documento ao objeto
origem = os.path.join(dir_origem, match.group(0)) obj = objetos.get(id)
id = match.group(1) if obj:
try: destino = upload_to(obj, os.path.basename(origem))
obj = model.objects.get(pk=id) mover_documento(repo, origem, destino)
except model.DoesNotExist:
msg = ' {} (pk={}) não encontrado para documento em [{}]'
print(msg.format(model.__name__, id, origem))
else:
destino = os.path.join(
base_destino.format(id, *get_extra_args(obj)),
os.path.basename(origem))
mover_documento(origem, destino)
setattr(obj, campo, destino) setattr(obj, campo, destino)
if tem_cropping:
# conserta link do git annex (antes do commit)
# pois o conteúdo das imagens é acessado pelo cropping
repo.git.execute('git annex fix'.split() + [destino])
obj.save() obj.save()
else:
msg = ' {} (pk={}) não encontrado para documento em [{}]'
print(msg.format(model.__name__, id, origem))
def migrar_documentos(): def migrar_documentos(repo):
# aqui supomos que uma pasta chamada sapl_documentos está em MEDIA_ROOT # aqui supomos que as pastas XSLT e sapl_documentos estão em
# com o conteúdo da pasta de mesmo nome do zope # <repo.working_dir> com o conteúdo exportado do zope
# Os arquivos da pasta serão MOVIDOS para a nova estrutura! # Os arquivos das pastas serão (git) MOVIDOS para a nova estrutura!
# A pasta, após conferência do que não foi migrado, deve ser apagada.
# #
# Isto significa que para rodar novamente esta função é preciso # Isto significa que para rodar novamente esta função é preciso
# restaurar a pasta sapl_documentos ao estado inicial # restaurar o repo ao estado anterior
mover_documento(repo, 'XSLT', 'sapl/public/XSLT')
migrar_propriedades_da_casa(repo)
migrar_propriedades_da_casa() # garante que o conteúdo das fotos dos parlamentares esteja presente
# (necessário para o cropping de imagem)
repo.git.execute('git annex get sapl_documentos/parlamentar'.split())
for model in [ for model in [
Parlamentar, Parlamentar,
@ -206,14 +163,13 @@ def migrar_documentos():
DocumentoAdministrativo, DocumentoAdministrativo,
DocumentoAcessorioAdministrativo, DocumentoAcessorioAdministrativo,
]: ]:
migrar_docs_por_ids(model) migrar_docs_por_ids(repo, model)
sobrando = [os.path.join(dir, file) sobrando = [join(dir, file)
for (dir, _, files) in os.walk(em_media('sapl_documentos')) for (dir, _, files) in os.walk(join(repo.working_dir,
'sapl_documentos'))
for file in files] for file in files]
if sobrando: if sobrando:
print('\n#### Encerrado ####\n\n' print('\n#### Encerrado ####\n\n'
'{} documentos sobraram sem ser migrados!!!'.format( '{} documentos sobraram sem ser migrados!!!'.format(
len(sobrando))) len(sobrando)))
for doc in sobrando:
print(' {}'. format(doc))

8
sapl/legacy/migracao_usuarios.py

@ -1,8 +1,8 @@
import yaml import yaml
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from unipath import Path
from sapl.hashers import zope_encoded_password_to_django from sapl.hashers import zope_encoded_password_to_django
from sapl.settings import MEDIA_ROOT
PERFIL_LEGADO_PARA_NOVO = {legado: Group.objects.get(name=novo) PERFIL_LEGADO_PARA_NOVO = {legado: Group.objects.get(name=novo)
for legado, novo in [ for legado, novo in [
@ -44,9 +44,9 @@ def decode_nome(nome):
return nome return nome
def migrar_usuarios(): def migrar_usuarios(dir_repo):
""" """
o arquivo media/usuarios.yaml e importa os usuários nele listados, o arquivo <dir_repo>/usuarios.yaml e importa os usuários nele listados,
com senhas e perfis. com senhas e perfis.
Os usuários são criados se necessário e seus perfis ajustados. Os usuários são criados se necessário e seus perfis ajustados.
@ -68,7 +68,7 @@ def migrar_usuarios():
Também podemos assumir que essa é uma tarefa de um administrador Também podemos assumir que essa é uma tarefa de um administrador
""" """
ARQUIVO_USUARIOS = MEDIA_ROOT.child('usuarios.yaml') ARQUIVO_USUARIOS = Path(dir_repo).child('usuarios.yaml')
with open(ARQUIVO_USUARIOS, 'r') as f: with open(ARQUIVO_USUARIOS, 'r') as f:
usuarios = yaml.load(f) usuarios = yaml.load(f)
# conferimos de que só há um nome de usuário # conferimos de que só há um nome de usuário

2
sapl/legacy/models.py

@ -433,7 +433,7 @@ class MateriaLegislativa(models.Model):
cod_regime_tramitacao = models.IntegerField() cod_regime_tramitacao = models.IntegerField()
dat_publicacao = models.DateField(blank=True, null=True) dat_publicacao = models.DateField(blank=True, null=True)
tip_origem_externa = models.IntegerField(blank=True, null=True) tip_origem_externa = models.IntegerField(blank=True, null=True)
num_origem_externa = models.CharField(max_length=5, blank=True, null=True) num_origem_externa = models.CharField(max_length=10, blank=True, null=True)
ano_origem_externa = models.SmallIntegerField(blank=True, null=True) ano_origem_externa = models.SmallIntegerField(blank=True, null=True)
dat_origem_externa = models.DateField(blank=True, null=True) dat_origem_externa = models.DateField(blank=True, null=True)
cod_local_origem_externa = models.IntegerField(blank=True, null=True) cod_local_origem_externa = models.IntegerField(blank=True, null=True)

4
sapl/legacy/router.py

@ -16,7 +16,7 @@ class LegacyRouter:
return True return True
return None return None
def allow_migrate(self, db, model): def allow_migrate(self, db, app_label, model_name=None, **hints):
if model._meta.app_label == 'legacy': if app_label == 'legacy':
return False return False
return None return None

222
sapl/legacy/scripts/exporta_zope/exporta_zope.py

@ -5,6 +5,8 @@
# Esse script precisa rodar em python 2 # Esse script precisa rodar em python 2
# e depende apenas do descrito no arquivo requiments.txt # e depende apenas do descrito no arquivo requiments.txt
import cStringIO
import hashlib
import mimetypes import mimetypes
import os import os
import sys import sys
@ -12,21 +14,32 @@ from collections import defaultdict
from contextlib import contextmanager from contextlib import contextmanager
from functools import partial from functools import partial
import git
import magic import magic
import yaml import yaml
import ZODB.DB import ZODB.DB
import ZODB.FileStorage import ZODB.FileStorage
from unipath import Path
from ZODB.broken import Broken from ZODB.broken import Broken
from variaveis_comuns import DIR_DADOS_MIGRACAO, TAG_ZOPE
EXTENSOES = { EXTENSOES = {
'application/msword': '.doc', 'application/msword': '.doc',
'application/pdf': '.pdf', 'application/pdf': '.pdf',
'application/vnd.oasis.opendocument.text': '.odt', 'application/vnd.oasis.opendocument.text': '.odt',
'application/vnd.ms-excel': '.xls', 'application/vnd.ms-excel': '.xls',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx', # noqa 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx', # noqa
'application/vnd.oasis.opendocument.text-template': '.ott',
'application/vnd.ms-powerpoint': '.ppt',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx', # noqa
'application/vnd.oasis.opendocument.spreadsheet': '.ods',
'application/xml': '.xml', 'application/xml': '.xml',
'text/xml': '.xml', 'text/xml': '.xml',
'application/zip': '.zip', 'application/zip': '.zip',
'application/x-rar': '.rar',
'image/jpeg': '.jpeg', 'image/jpeg': '.jpeg',
'image/png': '.png', 'image/png': '.png',
'image/gif': '.gif', 'image/gif': '.gif',
@ -38,6 +51,11 @@ EXTENSOES = {
'image/tiff': '.tiff', 'image/tiff': '.tiff',
'application/tiff': '.tiff', 'application/tiff': '.tiff',
'audio/x-wav': '.wav', 'audio/x-wav': '.wav',
'video/mp4': '.mp4',
'image/x-icon': '.ico',
'image/x-ms-bmp': '.bmp',
'video/x-ms-asf': '.asf',
'audio/mpeg': '.mp3',
# TODO rever... # TODO rever...
'text/richtext': '.rtf', 'text/richtext': '.rtf',
@ -45,7 +63,9 @@ EXTENSOES = {
# sem extensao # sem extensao
'application/octet-stream': '', # binário 'application/octet-stream': '', # binário
'inode/x-empty': '', # vazio 'inode/x-empty': '', # vazio
'text/x-unknown-content-type': '', 'application/x-empty': '', # vazio
'text/x-unknown-content-type': '', # desconhecido
'application/CDFV2-unknown': '', # desconhecido
} }
@ -56,29 +76,26 @@ def br(obj):
return obj return obj
def guess_extension(caminho): def guess_extension(fullname, buffer):
mime = magic.from_file(caminho, mime=True) mime = magic.from_buffer(buffer, mime=True)
try: extensao = EXTENSOES.get(mime)
return EXTENSOES[mime] if extensao is not None:
except KeyError as e: return extensao
msg = '\n'.join([ else:
'Extensão não conhecida para o arquivo:', possibilidades = '\n'.join(
caminho,
'E mimetype:',
mime,
' Algumas possibilidades são:', ] +
[" '{}': '{}',".format(mime, ext) [" '{}': '{}',".format(mime, ext)
for ext in mimetypes.guess_all_extensions(mime)] + for ext in mimetypes.guess_all_extensions(mime)])
['Atualize o código do dicionário EXTENSOES!'] print('''Extensão não conhecida para o arquivo: {}
) e mimetype: {}
print(msg) Algumas possibilidades são:
raise Exception(msg, e) {}
Atualize o código do dicionário EXTENSOES!
'''.format(fullname, mime, possibilidades)
def dump_file(doc, path): )
name = doc['__name__'] return '.DESCONHECIDO.{}'.format(mime.replace('/', '__'))
fullname = os.path.join(path, name)
def get_conteudo_file(doc):
# A partir daqui usamos dict.pop('...') nos __Broken_state__ # A partir daqui usamos dict.pop('...') nos __Broken_state__
# para contornar um "vazamento" de memória que ocorre # para contornar um "vazamento" de memória que ocorre
# ao percorrer a árvore de objetos # ao percorrer a árvore de objetos
@ -95,25 +112,28 @@ def dump_file(doc, path):
doc['data'] = pdata doc['data'] = pdata
pdata = doc pdata = doc
with open(fullname, 'w') as arq: output = cStringIO.StringIO()
while pdata: while pdata:
arq.write(pdata.pop('data')) output.write(pdata.pop('data'))
pdata = br(pdata.pop('next', None)) pdata = br(pdata.pop('next', None))
return output.getvalue()
base, original_extension = os.path.splitext(fullname)
extension = guess_extension(fullname)
if extension == '.xml' and original_extension in ['.xsl', '.xslt']:
# não trocamos as extensões XSL e XSLT
final_name = fullname
else:
# trocamos a extensão pela adivinhada
final_name = base + extension
os.rename(fullname, final_name)
print(final_name)
def dump_file(doc, path, salvar, get_conteudo=get_conteudo_file):
name = doc['__name__']
fullname = os.path.join(path, name)
conteudo = get_conteudo(doc)
if conteudo:
# pula arquivos vazios
salvar(fullname, conteudo)
return name return name
def get_conteudo_dtml_method(doc):
return doc['raw']
def enumerate_by_key_list(folder, key_list, type_key): def enumerate_by_key_list(folder, key_list, type_key):
for entry in folder.get(key_list, []): for entry in folder.get(key_list, []):
id, meta_type = entry['id'], entry[type_key] id, meta_type = entry['id'], entry[type_key]
@ -131,11 +151,16 @@ enumerate_properties = partial(enumerate_by_key_list,
def enumerate_btree(folder): def enumerate_btree(folder):
contagem_esperada = folder['_count'].value contagem_esperada = folder['_count'].value
tree = folder['_tree'] tree = folder['_tree']
contagem_real = 0 # para o caso em que não haja itens
for contagem_real, (id, obj) in enumerate(tree.iteritems(), start=1): for contagem_real, (id, obj) in enumerate(tree.iteritems(), start=1):
obj, meta_type = br(obj), type(obj).__name__ obj, meta_type = br(obj), type(obj).__name__
yield id, obj, meta_type yield id, obj, meta_type
# verificação de consistência # verificação de consistência
assert contagem_esperada == contagem_real if contagem_esperada != contagem_real:
print('ATENÇÃO: contagens diferentes na btree: '
'{} esperada: {} real: {}'.format(folder['title'],
contagem_esperada,
contagem_real))
nao_identificados = defaultdict(list) nao_identificados = defaultdict(list)
@ -148,14 +173,14 @@ def logando_nao_identificados():
if nao_identificados: if nao_identificados:
print('#' * 80) print('#' * 80)
print('#' * 80) print('#' * 80)
print(u'FORAM ENCONTRADOS ARQUIVOS DE FORMATO NÃO IDENTIFICADO!!!') print('FORAM ENCONTRADOS ARQUIVOS DE FORMATO NÃO IDENTIFICADO!!!')
print(u'REFAÇA A EXPORTAÇÃO\n') print('REFAÇA A EXPORTAÇÃO\n')
print(nao_identificados) print(nao_identificados)
print('#' * 80) print('#' * 80)
print('#' * 80) print('#' * 80)
def dump_folder(folder, path='', enum=enumerate_folder): def dump_folder(folder, path, salvar, enum=enumerate_folder):
name = folder['id'] name = folder['id']
path = os.path.join(path, name) path = os.path.join(path, name)
if not os.path.exists(path): if not os.path.exists(path):
@ -165,7 +190,7 @@ def dump_folder(folder, path='', enum=enumerate_folder):
if dump == '?': if dump == '?':
nao_identificados[meta_type].append(path + '/' + id) nao_identificados[meta_type].append(path + '/' + id)
elif dump: elif dump:
id_interno = dump(obj, path) id_interno = dump(obj, path, salvar)
assert id == id_interno assert id == id_interno
return name return name
@ -201,24 +226,24 @@ def read_sde(element):
return data return data
def save_as_yaml(path, name, obj): def save_as_yaml(path, name, obj, salvar):
fullname = os.path.join(path, name) fullname = os.path.join(path, name)
with open(fullname, 'w') as arquivo: conteudo = yaml.safe_dump(obj, allow_unicode=True)
yaml.safe_dump(obj, arquivo, allow_unicode=True) salvar(fullname, conteudo)
print(fullname)
return fullname
def dump_sde(strdoc, path, tipo): def dump_sde(strdoc, path, salvar, tipo):
id = strdoc['id'] id = strdoc['id']
sde = read_sde(strdoc) sde = read_sde(strdoc)
save_as_yaml(path, '{}.{}.yaml'.format(id, tipo), sde) save_as_yaml(path, '{}.{}.yaml'.format(id, tipo), sde, salvar)
return id return id
DUMP_FUNCTIONS = { DUMP_FUNCTIONS = {
'File': dump_file, 'File': dump_file,
'Image': dump_file, 'Image': dump_file,
'DTML Method': partial(dump_file,
get_conteudo=get_conteudo_dtml_method),
'Folder': partial(dump_folder, enum=enumerate_folder), 'Folder': partial(dump_folder, enum=enumerate_folder),
'BTreeFolder2': partial(dump_folder, enum=enumerate_btree), 'BTreeFolder2': partial(dump_folder, enum=enumerate_btree),
'SDE-Document': partial(dump_sde, tipo='sde.document'), 'SDE-Document': partial(dump_sde, tipo='sde.document'),
@ -233,7 +258,7 @@ DUMP_FUNCTIONS = {
def get_app(data_fs_path): def get_app(data_fs_path):
storage = ZODB.FileStorage.FileStorage(data_fs_path) storage = ZODB.FileStorage.FileStorage(data_fs_path, read_only=True)
db = ZODB.DB(storage) db = ZODB.DB(storage)
connection = db.open() connection = db.open()
root = connection.root() root = connection.root()
@ -255,42 +280,115 @@ def find_sapl(app):
return sapl return sapl
def dump_propriedades(docs, path, encoding='iso-8859-1'): def dump_propriedades(docs, path, salvar, encoding='iso-8859-1'):
props_sapl = br(docs['props_sapl']) props_sapl = br(docs['props_sapl'])
ids = [p['id'] for p in props_sapl['_properties']] ids = [p['id'] for p in props_sapl['_properties']]
props = {id: props_sapl[id] for id in ids} props = {id: props_sapl[id] for id in ids}
props = {id: p.decode(encoding) if isinstance(p, str) else p props = {id: p.decode(encoding) if isinstance(p, str) else p
for id, p in props.items()} for id, p in props.items()}
save_as_yaml(path, 'sapl_documentos/propriedades.yaml', props) save_as_yaml(path, 'sapl_documentos/propriedades.yaml', props, salvar)
def dump_usuarios(sapl, path): def dump_usuarios(sapl, path, salvar):
users = br(br(sapl['acl_users'])['data']) users = br(br(sapl['acl_users'])['data'])
users = {k: br(v) for k, v in users['data'].items()} users = {k: br(v) for k, v in users['data'].items()}
save_as_yaml(path, 'usuarios.yaml', users) save_as_yaml(path, 'usuarios.yaml', users, salvar)
def dump_sapl(data_fs_path, destino='../../../../media'): def _dump_sapl(data_fs_path, destino, salvar):
assert Path(data_fs_path).exists()
app, close_db = get_app(data_fs_path) app, close_db = get_app(data_fs_path)
try: try:
sapl = find_sapl(app) sapl = find_sapl(app)
# extrai folhas XSLT # extrai folhas XSLT
dump_folder(br(sapl['XSLT']), destino) dump_folder(br(sapl['XSLT']), destino, salvar)
# extrai usuários com suas senhas e perfis # extrai usuários com suas senhas e perfis
dump_usuarios(sapl, destino) dump_usuarios(sapl, destino, salvar)
# extrai documentos # extrai documentos
docs = br(sapl['sapl_documentos']) docs = br(sapl['sapl_documentos'])
with logando_nao_identificados(): with logando_nao_identificados():
dump_folder(docs, destino) dump_folder(docs, destino, salvar)
dump_propriedades(docs, destino) dump_propriedades(docs, destino, salvar)
finally: finally:
close_db() close_db()
def repo_execute(repo, cmd, *args):
return repo.git.execute(cmd.split() + list(args))
def get_annex_hashes(repo):
hashes = repo_execute(
repo, 'git annex find', '--format=${keyname}\n', '--include=*')
return {os.path.splitext(h)[0] for h in hashes.splitlines()}
def ajusta_extensao(fullname, conteudo):
base, extensao = os.path.splitext(fullname)
if extensao not in ['.xsl', '.xslt', '.yaml', '.css']:
extensao = guess_extension(fullname, conteudo)
return base + extensao
def build_salvar(repo):
"""Constroi função salvar que pula arquivos que já estão no annex
"""
hashes = get_annex_hashes(repo)
def salvar(fullname, conteudo):
sha = hashlib.sha256()
sha.update(conteudo)
if sha.hexdigest() in hashes:
print('- hash encontrado - {}'.format(fullname))
else:
fullname = ajusta_extensao(fullname, conteudo)
if os.path.exists(fullname):
# destrava arquivo pré-existente (o conteúdo mudou)
repo_execute(repo, 'git annex unlock', fullname)
with open(fullname, 'w') as arq:
arq.write(conteudo)
print(fullname)
return salvar
def dump_sapl(sigla):
sigla = sigla[-3:] # ignora prefixo (por ex. 'sapl_cm_')
data_fs_path = DIR_DADOS_MIGRACAO.child('datafs',
'Data_cm_{}.fs'.format(sigla))
assert data_fs_path.exists(), 'Origem não existe: {}'.format(data_fs_path)
nome_banco_legado = 'sapl_cm_{}'.format(sigla)
destino = DIR_DADOS_MIGRACAO.child('repos', nome_banco_legado)
destino.mkdir(parents=True)
repo = git.Repo.init(destino)
if TAG_ZOPE in repo.tags:
print('{}: A exportação de documentos já está feita -- abortando'.format(sigla))
return
repo_execute(repo, 'git annex init')
repo_execute(repo, 'git config annex.thin true')
salvar = build_salvar(repo)
try:
finalizado = False
_dump_sapl(data_fs_path, destino, salvar)
finalizado = True
finally:
# grava mundaças
repo_execute(repo, 'git annex add sapl_documentos')
repo.git.add(A=True)
if 'master' not in repo.heads or repo.index.diff('HEAD'):
# se de fato existe mudança
status = 'completa' if finalizado else 'parcial'
repo.index.commit(u'Exportação do zope {}'.format(status))
if finalizado:
repo.git.execute('git tag -f'.split() + [TAG_ZOPE])
if __name__ == "__main__": if __name__ == "__main__":
if len(sys.argv) == 2: if len(sys.argv) == 2:
data_fs_path = sys.argv[1] sigla = sys.argv[1]
dump_sapl(data_fs_path) dump_sapl(sigla)
else: else:
print('Uso: python exporta_zope <caminho p Data.fs>') print('Uso: python exporta_zope <sigla>')

7
sapl/legacy/scripts/exporta_zope/requirements.txt

@ -1,3 +1,8 @@
# ZODB version 3.7.4 # ZODB version 3.7.4
PyYAML==3.12
ZODB==5.3.0 ZODB==5.3.0
PyYAML
Unipath
GitPython
pyaml
python-magic
ipython

4
sapl/legacy/scripts/exporta_zope/variaveis_comuns.py

@ -0,0 +1,4 @@
from unipath import Path
DIR_DADOS_MIGRACAO = Path('~/migracao_sapl/').expand()
TAG_ZOPE = 'zope'

26
sapl/legacy/scripts/migra_um_db.sh

@ -1,10 +1,7 @@
#!/bin/bash #!/bin/bash
# rodar esse script na raiz do projeto # rodar esse script na raiz do projeto
if [ $# -ge 2 ]; then if [ $# -eq 1 ]; then
# proteje pasta com dumps de alterações acidentais
# chmod -R -w ~/migracao_sapl/sapl_dumps
DIR_MIGRACAO=~/migracao_sapl DIR_MIGRACAO=~/migracao_sapl
@ -20,28 +17,11 @@ if [ $# -ge 2 ]; then
echo "########################################" | tee -a $LOG echo "########################################" | tee -a $LOG
echo >> $LOG echo >> $LOG
if [ $3 ]; then
# se há senha do mysql
mysql -u$2 -p"$3" -N -s -e "DROP DATABASE IF EXISTS $1; CREATE DATABASE $1;"
mysql -u$2 -p"$3" < $DIR_MIGRACAO/dumps_mysql/$1.sql
else
# se não há senha do mysql
mysql -u$2 -N -s -e "DROP DATABASE IF EXISTS $1; CREATE DATABASE $1;"
mysql -u$2 < $DIR_MIGRACAO/dumps_mysql/$1.sql
fi;
echo "O banco legado foi restaurado" |& tee -a $LOG
echo >> $LOG
echo "--- DJANGO MIGRATE ---" | tee -a $LOG
echo >> $LOG
DATABASE_NAME=$1 ./manage.py migrate --settings sapl.legacy_migration_settings
echo >> $LOG
echo "--- MIGRACAO ---" | tee -a $LOG echo "--- MIGRACAO ---" | tee -a $LOG
echo >> $LOG echo >> $LOG
DATABASE_NAME=$1 ./manage.py migracao_25_31 --force --dados --settings sapl.legacy_migration_settings 2>&1 | tee -a $LOG DATABASE_NAME=$1 ./manage.py migracao_25_31 --settings sapl.legacy_migration_settings 2>&1 | tee -a $LOG
echo >> $LOG echo >> $LOG
else else
echo "USO:" echo "USO:"
echo " $0 <nome_database> <usuário mysql> [senha mysql]" echo " $0 <nome_database>"
fi; fi;

40
sapl/legacy/scripts/normaliza_dump_mysql.py

@ -0,0 +1,40 @@
#!/usr/bin/env python
import re
import sys
from unipath import Path
cabecalho = '''
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*!40000 DROP DATABASE IF EXISTS `{banco}`*/;
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `{banco}` /*!40100 DEFAULT CHARACTER SET latin1 */;
USE `{banco}`;
'''
def normaliza_dump_mysql(nome_arquivo):
arquivo = Path(nome_arquivo).expand()
banco = arquivo.stem
conteudo = arquivo.read_file()
inicio = re.finditer('--\n-- Table structure for table .*\n--\n', conteudo)
inicio = next(inicio).start()
conteudo = cabecalho.format(banco=banco) + conteudo[inicio:]
arquivo.write_file(conteudo)
if __name__ == "__main__":
nome_aquivo = sys.argv[1]
normaliza_dump_mysql(nome_aquivo)

28
sapl/legacy/scripts/normaliza_dump_mysql.sh

@ -1,28 +0,0 @@
#!/bin/bash
ARQUIVO=$1
BANCO=`basename $1 | cut -f1 -d.`
TMP=__tmp.sql
cat << EOF > $TMP
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*!40000 DROP DATABASE IF EXISTS \`$BANCO\`*/;
CREATE DATABASE /*!32312 IF NOT EXISTS*/ \`$BANCO\` /*!40100 DEFAULT CHARACTER SET latin1 */;
USE \`$BANCO\`;
EOF
sed 1,`grep -n '^USE ' $ARQUIVO |cut -f1 -d:`d $ARQUIVO >> $TMP
mv $TMP $ARQUIVO

5
sapl/legacy/scripts/recria_um_db_postgres.sh

@ -4,3 +4,8 @@
echo "Database $1" echo "Database $1"
sudo -u postgres psql -c "drop DATABASE if exists $1" sudo -u postgres psql -c "drop DATABASE if exists $1"
sudo -u postgres psql -c "CREATE DATABASE $1 WITH OWNER = sapl ENCODING = 'UTF8' TABLESPACE = pg_default LC_COLLATE = 'pt_BR.UTF-8' LC_CTYPE = 'pt_BR.UTF-8' CONNECTION LIMIT = -1 TEMPLATE template0;" sudo -u postgres psql -c "CREATE DATABASE $1 WITH OWNER = sapl ENCODING = 'UTF8' TABLESPACE = pg_default LC_COLLATE = 'pt_BR.UTF-8' LC_CTYPE = 'pt_BR.UTF-8' CONNECTION LIMIT = -1 TEMPLATE template0;"
echo "--- DJANGO MIGRATE ---" | tee -a $LOG
DATABASE_NAME=$1 ./manage.py migrate --settings sapl.legacy_migration_settings

10
sapl/legacy_migration_settings.py

@ -3,6 +3,9 @@ import os
from decouple import Config, RepositoryEnv from decouple import Config, RepositoryEnv
from dj_database_url import parse as db_url from dj_database_url import parse as db_url
from sapl.legacy.scripts.exporta_zope.variaveis_comuns import \
DIR_DADOS_MIGRACAO
from .settings import * # flake8: noqa from .settings import * # flake8: noqa
config = Config(RepositoryEnv(BASE_DIR.child('legacy', '.env'))) config = Config(RepositoryEnv(BASE_DIR.child('legacy', '.env')))
@ -33,3 +36,10 @@ DEBUG = True
# delisga indexação fulltext em tempo real # delisga indexação fulltext em tempo real
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.BaseSignalProcessor' HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.BaseSignalProcessor'
SHELL_PLUS_DONT_LOAD = ['legacy']
NOME_BANCO_LEGADO = DATABASES['legacy']['NAME']
DIR_REPO = Path(DIR_DADOS_MIGRACAO, 'repos', NOME_BANCO_LEGADO)
MEDIA_ROOT = DIR_REPO

65
sapl/materia/forms.py

@ -36,7 +36,7 @@ from sapl.materia.models import (AssuntoMateria, Autoria, MateriaAssunto,
from sapl.norma.models import (LegislacaoCitada, NormaJuridica, from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
TipoNormaJuridica) TipoNormaJuridica)
from sapl.parlamentares.models import Legislatura from sapl.parlamentares.models import Legislatura
from sapl.protocoloadm.models import Protocolo from sapl.protocoloadm.models import Protocolo, DocumentoAdministrativo
from sapl.settings import MAX_DOC_UPLOAD_SIZE from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
ChoiceWithoutValidationField, ChoiceWithoutValidationField,
@ -162,6 +162,11 @@ class MateriaSimplificadaForm(ModelForm):
class MateriaLegislativaForm(ModelForm): class MateriaLegislativaForm(ModelForm):
tipo_autor = ModelChoiceField(label=_('Tipo Autor'),
required=False,
queryset=TipoAutor.objects.all(),
empty_label=_('------'), )
autor = forms.ModelChoiceField(required=False, autor = forms.ModelChoiceField(required=False,
empty_label='------', empty_label='------',
queryset=Autor.objects.all() queryset=Autor.objects.all()
@ -172,6 +177,15 @@ class MateriaLegislativaForm(ModelForm):
exclude = ['texto_articulado', 'autores', 'proposicao', exclude = ['texto_articulado', 'autores', 'proposicao',
'anexadas', 'data_ultima_atualizacao'] 'anexadas', 'data_ultima_atualizacao']
def __init__(self, *args, **kwargs):
super(MateriaLegislativaForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
self.fields['tipo_autor'] = forms.CharField(required=False,
widget=forms.TextInput(attrs={'disabled': 'disabled'}))
self.fields['autor'] = forms.CharField(required=False,
widget=forms.TextInput(attrs={'disabled': 'disabled'}))
def clean(self): def clean(self):
super(MateriaLegislativaForm, self).clean() super(MateriaLegislativaForm, self).clean()
@ -182,29 +196,54 @@ class MateriaLegislativaForm(ModelForm):
data_apresentacao = cleaned_data['data_apresentacao'] data_apresentacao = cleaned_data['data_apresentacao']
ano = cleaned_data['ano'] ano = cleaned_data['ano']
protocolo = cleaned_data['numero_protocolo']
protocolo_antigo = self.instance.numero_protocolo
if protocolo:
if not Protocolo.objects.filter(numero=protocolo,ano=ano).exists():
raise ValidationError(_('Protocolo %s/%s não'
' existe' % (protocolo, ano)))
if protocolo_antigo != protocolo:
exist_materia = MateriaLegislativa.objects.filter(
numero_protocolo=protocolo,
ano=ano).exists()
exist_doc = DocumentoAdministrativo.objects.filter(
protocolo_id=protocolo,
ano=ano).exists()
if exist_materia or exist_doc:
raise ValidationError(_('Protocolo %s/%s ja possui'
' documento vinculado'
% (protocolo, ano)))
if data_apresentacao.year != ano: if data_apresentacao.year != ano:
raise ValidationError("O ano da matéria não pode ser " raise ValidationError(_("O ano da matéria não pode ser "
"diferente do ano na data de apresentação") "diferente do ano na data de apresentação"))
ano_origem_externa = cleaned_data['ano_origem_externa'] ano_origem_externa = cleaned_data['ano_origem_externa']
data_origem_externa = cleaned_data['data_origem_externa'] data_origem_externa = cleaned_data['data_origem_externa']
if ano_origem_externa and data_origem_externa and \ if ano_origem_externa and data_origem_externa and \
ano_origem_externa != data_origem_externa.year: ano_origem_externa != data_origem_externa.year:
raise ValidationError("O ano de origem externa da matéria não " raise ValidationError(_("O ano de origem externa da matéria não "
"pode ser diferente do ano na data de " "pode ser diferente do ano na data de "
"origem externa") "origem externa"))
return cleaned_data return cleaned_data
def save(self, commit=False): def save(self, commit=False):
if not self.instance.pk:
primeiro_autor = True
else:
primeiro_autor = False
materia = super(MateriaLegislativaForm, self).save(commit) materia = super(MateriaLegislativaForm, self).save(commit)
materia.save() materia.save()
if self.cleaned_data['autor']: if self.cleaned_data['autor']:
autoria = Autoria() autoria = Autoria()
autoria.primeiro_autor = True autoria.primeiro_autor = primeiro_autor
autoria.materia = materia autoria.materia = materia
autoria.autor = self.cleaned_data['autor'] autoria.autor = self.cleaned_data['autor']
autoria.save() autoria.save()
@ -582,8 +621,18 @@ class AnexadaForm(ModelForm):
msg = _('A matéria a ser anexada não existe no cadastro' msg = _('A matéria a ser anexada não existe no cadastro'
' de matérias legislativas.') ' de matérias legislativas.')
raise ValidationError(msg) raise ValidationError(msg)
else:
cleaned_data['materia_anexada'] = materia_anexada materia_principal = self.instance.materia_principal
if materia_principal == materia_anexada:
raise ValidationError(_('Matéria não pode ser anexada a si mesma'))
is_anexada = Anexada.objects.filter(materia_principal=materia_principal,
materia_anexada=materia_anexada
).exists()
if is_anexada:
raise ValidationError(_('Materia já se encontra anexada'))
cleaned_data['materia_anexada'] = materia_anexada
return cleaned_data return cleaned_data

20
sapl/materia/migrations/0028_auto_20180418_1629.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-04-18 19:29
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0027_auto_20180409_1443'),
]
operations = [
migrations.AlterField(
model_name='materialegislativa',
name='numero_origem_externa',
field=models.CharField(blank=True, max_length=10, verbose_name='Número'),
),
]

16
sapl/materia/models.py

@ -167,7 +167,7 @@ class MateriaLegislativa(models.Model):
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Tipo')) verbose_name=_('Tipo'))
numero_origem_externa = models.CharField( numero_origem_externa = models.CharField(
max_length=5, blank=True, verbose_name=_('Número')) max_length=10, blank=True, verbose_name=_('Número'))
ano_origem_externa = models.PositiveSmallIntegerField( ano_origem_externa = models.PositiveSmallIntegerField(
blank=True, null=True, verbose_name=_('Ano'), choices=RANGE_ANOS) blank=True, null=True, verbose_name=_('Ano'), choices=RANGE_ANOS)
data_origem_externa = models.DateField( data_origem_externa = models.DateField(
@ -325,9 +325,17 @@ class AcompanhamentoMateria(models.Model):
verbose_name_plural = _('Acompanhamentos de Matéria') verbose_name_plural = _('Acompanhamentos de Matéria')
def __str__(self): def __str__(self):
# FIXME str should be human readable, using hash is very strange if self.data_cadastro is None:
return _('%(materia)s - #%(hash)s') % { return _('%(materia)s - %(email)s') % {
'materia': self.materia, 'hash': self.hash} 'materia': self.materia,
'email': self.email
}
else:
return _('%(materia)s - %(email)s - Registrado em: %(data)s') % {
'materia': self.materia,
'email': self.email,
'data': str(self.data_cadastro.strftime('%d/%m/%Y'))
}
@reversion.register() @reversion.register()

49
sapl/materia/views.py

@ -17,7 +17,7 @@ from django.shortcuts import get_object_or_404, redirect
from django.template import RequestContext, loader from django.template import RequestContext, loader
from django.utils import formats, timezone from django.utils import formats, timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import CreateView, ListView, TemplateView, UpdateView from django.views.generic import FormView, ListView, TemplateView, CreateView, UpdateView
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django_filters.views import FilterView from django_filters.views import FilterView
@ -937,7 +937,8 @@ class RelatoriaCrud(MasterDetailCrud):
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
else: else:
composicao = comissao.composicao_set.last() composicao = comissao.composicao_set.order_by(
'-periodo__data_inicio').first()
participacao = Participacao.objects.filter( participacao = Participacao.objects.filter(
composicao=composicao) composicao=composicao)
@ -1012,18 +1013,19 @@ class TramitacaoCrud(MasterDetailCrud):
'pk': self.kwargs['pk']}) 'pk': self.kwargs['pk']})
def get_initial(self): def get_initial(self):
initial = super(CreateView, self).get_initial()
local = MateriaLegislativa.objects.get( local = MateriaLegislativa.objects.get(
pk=self.kwargs['pk']).tramitacao_set.order_by( pk=self.kwargs['pk']).tramitacao_set.order_by(
'-data_tramitacao', '-data_tramitacao',
'-id').first() '-id').first()
if local: if local:
self.initial['unidade_tramitacao_local' initial['unidade_tramitacao_local'
] = local.unidade_tramitacao_destino.pk ] = local.unidade_tramitacao_destino.pk
else: else:
self.initial['unidade_tramitacao_local'] = '' initial['unidade_tramitacao_local'] = ''
self.initial['data_tramitacao'] = timezone.now().date() initial['data_tramitacao'] = timezone.now().date()
return self.initial return initial
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
@ -1153,9 +1155,10 @@ class DocumentoAcessorioCrud(MasterDetailCrud):
super(MasterDetailCrud.CreateView, self).__init__(**kwargs) super(MasterDetailCrud.CreateView, self).__init__(**kwargs)
def get_initial(self): def get_initial(self):
self.initial['data'] = timezone.now().date() initial = super(CreateView, self).get_initial()
initial['data'] = timezone.now().date()
return self.initial return initial
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super( context = super(
@ -1282,10 +1285,11 @@ class LegislacaoCitadaCrud(MasterDetailCrud):
form_class = LegislacaoCitadaForm form_class = LegislacaoCitadaForm
def get_initial(self): def get_initial(self):
self.initial['tipo'] = self.object.norma.tipo.id initial = super(UpdateView, self).get_initial()
self.initial['numero'] = self.object.norma.numero initial['tipo'] = self.object.norma.tipo.id
self.initial['ano'] = self.object.norma.ano initial['numero'] = self.object.norma.numero
return self.initial initial['ano'] = self.object.norma.ano
return initial
class DetailView(MasterDetailCrud.DetailView): class DetailView(MasterDetailCrud.DetailView):
@ -1318,10 +1322,11 @@ class AnexadaCrud(MasterDetailCrud):
form_class = AnexadaForm form_class = AnexadaForm
def get_initial(self): def get_initial(self):
self.initial['tipo'] = self.object.materia_anexada.tipo.id initial = super(UpdateView, self).get_initial()
self.initial['numero'] = self.object.materia_anexada.numero initial['tipo'] = self.object.materia_anexada.tipo.id
self.initial['ano'] = self.object.materia_anexada.ano initial['numero'] = self.object.materia_anexada.numero
return self.initial initial['ano'] = self.object.materia_anexada.ano
return initial
class DetailView(MasterDetailCrud.DetailView): class DetailView(MasterDetailCrud.DetailView):
@ -1343,16 +1348,18 @@ class MateriaAssuntoCrud(MasterDetailCrud):
form_class = MateriaAssuntoForm form_class = MateriaAssuntoForm
def get_initial(self): def get_initial(self):
self.initial['materia'] = self.kwargs['pk'] initial = super(CreateView, self).get_initial()
return self.initial initial['materia'] = self.kwargs['pk']
return initial
class UpdateView(MasterDetailCrud.UpdateView): class UpdateView(MasterDetailCrud.UpdateView):
form_class = MateriaAssuntoForm form_class = MateriaAssuntoForm
def get_initial(self): def get_initial(self):
self.initial['materia'] = self.get_object().materia initial = super(UpdateView, self).get_initial()
self.initial['assunto'] = self.get_object().assunto initial['materia'] = self.get_object().materia
return self.initial initial['assunto'] = self.get_object().assunto
return initial
class MateriaLegislativaCrud(Crud): class MateriaLegislativaCrud(Crud):

13
sapl/norma/forms.py

@ -83,16 +83,19 @@ class NormaJuridicaForm(ModelForm):
label='Matéria', label='Matéria',
required=False, required=False,
queryset=TipoMateriaLegislativa.objects.all(), queryset=TipoMateriaLegislativa.objects.all(),
empty_label='Selecione' empty_label='Selecione',
widget=forms.Select(attrs={'autocomplete': 'off'})
) )
numero_materia = forms.CharField( numero_materia = forms.CharField(
label='Número Matéria', label='Número Matéria',
required=False required=False,
widget=forms.TextInput(attrs={'autocomplete': 'off'})
) )
ano_materia = forms.ChoiceField( ano_materia = forms.ChoiceField(
label='Ano Matéria', label='Ano Matéria',
required=False, required=False,
choices=ANO_CHOICES, choices=ANO_CHOICES,
widget=forms.Select(attrs={'autocomplete': 'off'})
) )
class Meta: class Meta:
@ -122,7 +125,11 @@ class NormaJuridicaForm(ModelForm):
if not self.is_valid(): if not self.is_valid():
return cleaned_data return cleaned_data
norma = NormaJuridica.objects.filter(ano=cleaned_data['ano'],
numero=cleaned_data['numero'],
tipo=cleaned_data['tipo']).exists()
if norma:
raise ValidationError("Já existe uma norma de mesmo Tipo, Ano e Número no sistema")
if (cleaned_data['tipo_materia'] and if (cleaned_data['tipo_materia'] and
cleaned_data['numero_materia'] and cleaned_data['numero_materia'] and
cleaned_data['ano_materia']): cleaned_data['ano_materia']):

25
sapl/norma/views.py

@ -7,7 +7,7 @@ from django.http import HttpResponse, JsonResponse
from django.template import RequestContext, loader from django.template import RequestContext, loader
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import CreateView, ListView, TemplateView, UpdateView from django.views.generic import TemplateView, UpdateView
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django_filters.views import FilterView from django_filters.views import FilterView
@ -40,10 +40,9 @@ class NormaRelacionadaCrud(MasterDetailCrud):
model = NormaRelacionada model = NormaRelacionada
parent_field = 'norma_principal' parent_field = 'norma_principal'
help_topic = 'norma_juridica' help_topic = 'norma_juridica'
public = [RP_LIST, RP_DETAIL]
class BaseMixin(MasterDetailCrud.BaseMixin): class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['norma_relacionada'] list_field_names = ['norma_relacionada', 'tipo_vinculo']
class CreateView(MasterDetailCrud.CreateView): class CreateView(MasterDetailCrud.CreateView):
form_class = NormaRelacionadaForm form_class = NormaRelacionadaForm
@ -52,11 +51,12 @@ class NormaRelacionadaCrud(MasterDetailCrud):
form_class = NormaRelacionadaForm form_class = NormaRelacionadaForm
def get_initial(self): def get_initial(self):
self.initial['tipo'] = self.object.norma_relacionada.tipo.id initial = super(UpdateView, self).get_initial()
self.initial['numero'] = self.object.norma_relacionada.numero initial['tipo'] = self.object.norma_relacionada.tipo.id
self.initial['ano'] = self.object.norma_relacionada.ano initial['numero'] = self.object.norma_relacionada.numero
self.initial['ementa'] = self.object.norma_relacionada.ementa initial['ano'] = self.object.norma_relacionada.ano
return self.initial initial['ementa'] = self.object.norma_relacionada.ementa
return initial
class DetailView(MasterDetailCrud.DetailView): class DetailView(MasterDetailCrud.DetailView):
@ -172,12 +172,13 @@ class NormaCrud(Crud):
layout_key = 'NormaJuridicaCreate' layout_key = 'NormaJuridicaCreate'
def get_initial(self): def get_initial(self):
initial = super(UpdateView, self).get_initial()
norma = NormaJuridica.objects.get(id=self.kwargs['pk']) norma = NormaJuridica.objects.get(id=self.kwargs['pk'])
if norma.materia: if norma.materia:
self.initial['tipo_materia'] = norma.materia.tipo initial['tipo_materia'] = norma.materia.tipo
self.initial['ano_materia'] = norma.materia.ano initial['ano_materia'] = norma.materia.ano
self.initial['numero_materia'] = norma.materia.numero initial['numero_materia'] = norma.materia.numero
return self.initial.copy() return initial
def recuperar_norma(request): def recuperar_norma(request):

20
sapl/painel/migrations/0002_auto_20180523_1430.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-05-23 17:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('painel', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='cronometro',
name='tipo',
field=models.CharField(choices=[('A', 'Aparte'), ('D', 'Discurso'), ('O', 'Ordem do dia'), ('C', 'Considerações finais')], max_length=1, verbose_name='Tipo Cronômetro'),
),
]

3
sapl/painel/models.py

@ -26,7 +26,8 @@ class Cronometro(models.Model):
CRONOMETRO_TYPES = ( CRONOMETRO_TYPES = (
('A', _('Aparte')), ('A', _('Aparte')),
('D', _('Discurso')), ('D', _('Discurso')),
('O', _('Ordem do dia')) ('O', _('Ordem do dia')),
('C', _('Considerações finais'))
) )
CRONOMETRO_STATUS = ( CRONOMETRO_STATUS = (

1
sapl/painel/views.py

@ -464,6 +464,7 @@ def get_dados_painel(request, pk):
'cronometro_aparte': get_cronometro_status(request, 'aparte'), 'cronometro_aparte': get_cronometro_status(request, 'aparte'),
'cronometro_discurso': get_cronometro_status(request, 'discurso'), 'cronometro_discurso': get_cronometro_status(request, 'discurso'),
'cronometro_ordem': get_cronometro_status(request, 'ordem'), 'cronometro_ordem': get_cronometro_status(request, 'ordem'),
'cronometro_consideracoes': get_cronometro_status(request, 'consideracoes'),
'status_painel': sessao.painel_aberto, 'status_painel': sessao.painel_aberto,
'brasao': brasao 'brasao': brasao
} }

4
sapl/parlamentares/forms.py

@ -261,6 +261,8 @@ class FiliacaoForm(ModelForm):
fields = ['partido', fields = ['partido',
'data', 'data',
'data_desfiliacao'] 'data_desfiliacao']
widgets = {'data': forms.DateInput(attrs={'autocomplete': 'off'}),
'data_desfiliacao': forms.DateInput(attrs={'autocomplete': 'off'})}
def clean(self): def clean(self):
super(FiliacaoForm, self).clean() super(FiliacaoForm, self).clean()
@ -321,7 +323,7 @@ class FrenteForm(ModelForm):
frente = super(FrenteForm, self).save(commit) frente = super(FrenteForm, self).save(commit)
content_type = ContentType.objects.get_for_model(Frente) content_type = ContentType.objects.get_for_model(Frente)
object_id = frente.pk object_id = frente.pk
tipo = TipoAutor.objects.get(descricao='Frente Parlamentar') tipo = TipoAutor.objects.get(descricao__icontains='Frente')
Autor.objects.create( Autor.objects.create(
content_type=content_type, content_type=content_type,
object_id=object_id, object_id=object_id,

2
sapl/parlamentares/views.py

@ -192,7 +192,7 @@ class ColigacaoCrud(CrudAux):
help_topic = 'coligacao' help_topic = 'coligacao'
class ListView(CrudAux.ListView): class ListView(CrudAux.ListView):
ordering = ('-numero_votos', 'nome') ordering = ('legislatura', '-nome')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(ColigacaoCrud.ListView, self).get_context_data( context = super(ColigacaoCrud.ListView, self).get_context_data(

165
sapl/protocoloadm/forms.py

@ -155,6 +155,7 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
fields = ['tipo', fields = ['tipo',
'numero', 'numero',
'protocolo__numero', 'protocolo__numero',
'numero_externo',
'data', 'data',
'tramitacaoadministrativo__unidade_tramitacao_destino', 'tramitacaoadministrativo__unidade_tramitacao_destino',
'tramitacaoadministrativo__status'] 'tramitacaoadministrativo__status']
@ -173,7 +174,8 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
row2 = to_row( row2 = to_row(
[('ano', 4), [('ano', 4),
('protocolo__numero', 4), ('protocolo__numero', 2),
('numero_externo', 2),
('data', 4)]) ('data', 4)])
row3 = to_row( row3 = to_row(
@ -645,6 +647,7 @@ class DocumentoAdministrativoForm(ModelForm):
'tramitacao', 'tramitacao',
'dias_prazo', 'dias_prazo',
'data_fim_prazo', 'data_fim_prazo',
'numero_externo',
'observacao', 'observacao',
'texto_integral', 'texto_integral',
'protocolo', 'protocolo',
@ -655,9 +658,6 @@ class DocumentoAdministrativoForm(ModelForm):
def clean(self): def clean(self):
super(DocumentoAdministrativoForm, self).clean() super(DocumentoAdministrativoForm, self).clean()
if not self.is_valid():
return self.cleaned_data
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
if not self.is_valid(): if not self.is_valid():
@ -665,14 +665,22 @@ class DocumentoAdministrativoForm(ModelForm):
numero_protocolo = self.data['numero_protocolo'] numero_protocolo = self.data['numero_protocolo']
ano_protocolo = self.data['ano_protocolo'] ano_protocolo = self.data['ano_protocolo']
numero_documento = self.cleaned_data['numero'] numero_documento = int(self.cleaned_data['numero'])
tipo_documento = self.data['tipo'] tipo_documento = int(self.data['tipo'])
ano_documento = int(self.data['ano'])
documento = DocumentoAdministrativo.objects.filter(numero=numero_documento,
tipo=tipo_documento, ano=ano_protocolo) # não permite atualizar para numero/ano/tipo existente
if self.instance.pk:
if documento: mudanca_doc = numero_documento != self.instance.numero \
raise ValidationError('Documento já existente') or ano_documento != self.instance.ano \
or tipo_documento != self.instance.tipo.pk
if not self.instance.pk or mudanca_doc:
doc_exists = DocumentoAdministrativo.objects.filter(numero=numero_documento,
tipo=tipo_documento,
ano=ano_protocolo).exists()
if doc_exists:
raise ValidationError('Documento já existente')
# campos opcionais, mas que se informados devem ser válidos # campos opcionais, mas que se informados devem ser válidos
if numero_protocolo and ano_protocolo: if numero_protocolo and ano_protocolo:
@ -690,6 +698,22 @@ class DocumentoAdministrativoForm(ModelForm):
numero_protocolo, ano_protocolo)) numero_protocolo, ano_protocolo))
raise ValidationError(msg) raise ValidationError(msg)
inst = self.instance.protocolo
protocolo_antigo = inst.numero if inst else None
if str(protocolo_antigo) != numero_protocolo:
exist_materia = MateriaLegislativa.objects.filter(
numero_protocolo=numero_protocolo,
ano=ano_protocolo).exists()
exist_doc = DocumentoAdministrativo.objects.filter(
protocolo_id=numero_protocolo,
ano=ano_protocolo).exists()
if exist_materia or exist_doc:
raise ValidationError(_('Protocolo %s/%s ja possui'
' documento vinculado'
% (numero_protocolo, ano_protocolo)))
return self.cleaned_data return self.cleaned_data
def save(self, commit=True): def save(self, commit=True):
@ -720,7 +744,7 @@ class DocumentoAdministrativoForm(ModelForm):
[('texto_integral', 12)]) [('texto_integral', 12)])
row6 = to_row( row6 = to_row(
[('dias_prazo', 6), ('data_fim_prazo', 6)]) [('numero_externo', 4), ('dias_prazo', 6), ('data_fim_prazo', 2)])
row7 = to_row( row7 = to_row(
[('observacao', 12)]) [('observacao', 12)])
@ -733,3 +757,118 @@ class DocumentoAdministrativoForm(ModelForm):
row6, row7)) row6, row7))
super(DocumentoAdministrativoForm, self).__init__( super(DocumentoAdministrativoForm, self).__init__(
*args, **kwargs) *args, **kwargs)
class DesvincularDocumentoForm(ModelForm):
numero = forms.CharField(required=True,
label=DocumentoAdministrativo._meta.
get_field('numero').verbose_name
)
ano = forms.ChoiceField(required=True,
label=DocumentoAdministrativo._meta.
get_field('ano').verbose_name,
choices=RANGE_ANOS,
widget=forms.Select(attrs={'class': 'selector'}))
def clean(self):
super(DesvincularDocumentoForm, self).clean()
cleaned_data = self.cleaned_data
if not self.is_valid():
return cleaned_data
numero = cleaned_data['numero']
ano = cleaned_data['ano']
tipo = cleaned_data['tipo']
try:
documento = DocumentoAdministrativo.objects.get(numero=numero, ano=ano, tipo=tipo)
if not documento.protocolo:
raise forms.ValidationError(
_("%s %s/%s não se encontra vinculado a nenhum protocolo" % (tipo, numero, ano)))
except ObjectDoesNotExist:
raise forms.ValidationError(
_("%s %s/%s não existe" % (tipo, numero, ano)))
return cleaned_data
class Meta:
model = DocumentoAdministrativo
fields = ['tipo',
'numero',
'ano',
]
def __init__(self, *args, **kwargs):
row1 = to_row(
[('numero', 4),
('ano', 4),
('tipo', 4)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação do Documento'),
row1,
HTML("&nbsp;"),
form_actions(label='Desvincular')
)
)
super(DesvincularDocumentoForm, self).__init__(
*args, **kwargs)
class DesvincularMateriaForm(forms.Form):
numero = forms.CharField(required=True,
label=_('Número da Matéria'))
ano = forms.ChoiceField(required=True,
label=_('Ano da Matéria'),
choices=RANGE_ANOS,
widget=forms.Select(attrs={'class': 'selector'}))
tipo = forms.ModelChoiceField(label=_('Tipo de Matéria'),
required=True,
queryset=TipoMateriaLegislativa.objects.all(),
empty_label='------')
def clean(self):
super(DesvincularMateriaForm, self).clean()
cleaned_data = self.cleaned_data
if not self.is_valid():
return cleaned_data
numero = cleaned_data['numero']
ano = cleaned_data['ano']
tipo = cleaned_data['tipo']
try:
materia = MateriaLegislativa.objects.get(numero=numero, ano=ano, tipo=tipo)
if not materia.numero_protocolo:
raise forms.ValidationError(
_("%s %s/%s não se encontra vinculada a nenhum protocolo" % (tipo, numero, ano)))
except ObjectDoesNotExist:
raise forms.ValidationError(
_("%s %s/%s não existe" % (tipo, numero, ano)))
return cleaned_data
def __init__(self, *args, **kwargs):
super(DesvincularMateriaForm, self).__init__(*args, **kwargs)
row1 = to_row(
[('numero', 4),
('ano', 4),
('tipo', 4)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação da Matéria'),
row1,
HTML("&nbsp;"),
form_actions(label='Desvincular')
)
)

20
sapl/protocoloadm/migrations/0004_documentoadministrativo_numero_externo.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2018-04-25 18:40
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0003_auto_20180103_1343'),
]
operations = [
migrations.AddField(
model_name='documentoadministrativo',
name='numero_externo',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Número Externo'),
),
]

4
sapl/protocoloadm/models.py

@ -133,6 +133,10 @@ class DocumentoAdministrativo(models.Model):
verbose_name=_('Em Tramitação?'), verbose_name=_('Em Tramitação?'),
choices=YES_NO_CHOICES) choices=YES_NO_CHOICES)
assunto = models.TextField(verbose_name=_('Assunto')) assunto = models.TextField(verbose_name=_('Assunto'))
numero_externo = models.PositiveIntegerField(
blank=True,
null=True,
verbose_name=_('Número Externo'))
observacao = models.TextField( observacao = models.TextField(
blank=True, verbose_name=_('Observação')) blank=True, verbose_name=_('Observação'))
texto_integral = models.FileField( texto_integral = models.FileField(

8
sapl/protocoloadm/urls.py

@ -15,7 +15,9 @@ from sapl.protocoloadm.views import (AnularProtocoloAdmView,
TipoDocumentoAdministrativoCrud, TipoDocumentoAdministrativoCrud,
TramitacaoAdmCrud, TramitacaoAdmCrud,
atualizar_numero_documento, atualizar_numero_documento,
doc_texto_integral) doc_texto_integral,
DesvincularDocumentoView,
DesvincularMateriaView)
from .apps import AppConfig from .apps import AppConfig
@ -61,6 +63,10 @@ urlpatterns_protocolo = [
url(r'^protocoloadm/anular-protocolo', url(r'^protocoloadm/anular-protocolo',
AnularProtocoloAdmView.as_view(), name='anular_protocolo'), AnularProtocoloAdmView.as_view(), name='anular_protocolo'),
url(r'^protocoloadm/desvincular-documento',
DesvincularDocumentoView.as_view(), name='desvincular_documento'),
url(r'^protocoloadm/desvincular-materia',
DesvincularMateriaView.as_view(), name='desvincular_materia'),
url(r'^protocoloadm/protocolar-mat', url(r'^protocoloadm/protocolar-mat',
ProtocoloMateriaView.as_view(), name='protocolar_mat'), ProtocoloMateriaView.as_view(), name='protocolar_mat'),

59
sapl/protocoloadm/views.py

@ -11,8 +11,9 @@ from django.http.response import HttpResponseRedirect
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import CreateView, ListView from django.views.generic import ListView, CreateView
from django.views.generic.base import RedirectView, TemplateView from django.views.generic.base import RedirectView, TemplateView
from django.views.generic.edit import FormView
from django_filters.views import FilterView from django_filters.views import FilterView
import sapl import sapl
@ -30,7 +31,7 @@ from .forms import (AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm,
DocumentoAdministrativoFilterSet, DocumentoAdministrativoFilterSet,
DocumentoAdministrativoForm, ProtocoloDocumentForm, DocumentoAdministrativoForm, ProtocoloDocumentForm,
ProtocoloFilterSet, ProtocoloMateriaForm, ProtocoloFilterSet, ProtocoloMateriaForm,
TramitacaoAdmEditForm, TramitacaoAdmForm) TramitacaoAdmEditForm, TramitacaoAdmForm, DesvincularDocumentoForm, DesvincularMateriaForm)
from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo, from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo,
StatusTramitacaoAdministrativo, StatusTramitacaoAdministrativo,
TipoDocumentoAdministrativo, TramitacaoAdministrativo) TipoDocumentoAdministrativo, TramitacaoAdministrativo)
@ -451,10 +452,11 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
if not protocolo.numero: if not protocolo.numero:
protocolo.numero = (numero['numero__max'] + 1) if numero['numero__max'] else 1 protocolo.numero = (numero['numero__max'] + 1) if numero['numero__max'] else 1
if protocolo.numero < (numero['numero__max'] + 1): if numero['numero__max']:
msg = _('Número de protocolo deve ser maior que {}').format(numero['numero__max']) if protocolo.numero < (numero['numero__max'] + 1):
messages.add_message(self.request, messages.ERROR, msg) msg = _('Número de protocolo deve ser maior que {}').format(numero['numero__max'])
return self.render_to_response(self.get_context_data()) messages.add_message(self.request, messages.ERROR, msg)
return self.render_to_response(self.get_context_data())
protocolo.ano = timezone.now().year protocolo.ano = timezone.now().year
protocolo.data = timezone.now().date() protocolo.data = timezone.now().date()
protocolo.hora = timezone.now().time() protocolo.hora = timezone.now().time()
@ -608,18 +610,19 @@ class TramitacaoAdmCrud(MasterDetailCrud):
form_class = TramitacaoAdmForm form_class = TramitacaoAdmForm
def get_initial(self): def get_initial(self):
initial = super(CreateView, self).get_initial()
local = DocumentoAdministrativo.objects.get( local = DocumentoAdministrativo.objects.get(
pk=self.kwargs['pk']).tramitacaoadministrativo_set.order_by( pk=self.kwargs['pk']).tramitacaoadministrativo_set.order_by(
'-data_tramitacao', '-data_tramitacao',
'-id').first() '-id').first()
if local: if local:
self.initial['unidade_tramitacao_local' initial['unidade_tramitacao_local'
] = local.unidade_tramitacao_destino.pk ] = local.unidade_tramitacao_destino.pk
else: else:
self.initial['unidade_tramitacao_local'] = '' initial['unidade_tramitacao_local'] = ''
self.initial['data_tramitacao'] = timezone.now().date() initial['data_tramitacao'] = timezone.now().date()
return self.initial return initial
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
@ -720,3 +723,39 @@ def atualizar_numero_documento(request):
{'numero': 1, 'ano': ano}) {'numero': 1, 'ano': ano})
return response return response
class DesvincularDocumentoView(PermissionRequiredMixin, CreateView):
template_name = 'protocoloadm/anular_protocoloadm.html'
form_class = DesvincularDocumentoForm
form_valid_message = _('Documento desvinculado com sucesso!')
permission_required = ('protocoloadm.action_anular_protocolo', )
def get_success_url(self):
return reverse('sapl.protocoloadm:protocolo')
def form_valid(self, form):
documento = DocumentoAdministrativo.objects.get(numero=form.cleaned_data['numero'],
ano=form.cleaned_data['ano'],
tipo=form.cleaned_data['tipo'])
documento.protocolo = None
documento.save()
return redirect(self.get_success_url())
class DesvincularMateriaView(PermissionRequiredMixin, FormView):
template_name = 'protocoloadm/anular_protocoloadm.html'
form_class = DesvincularMateriaForm
form_valid_message = _('Matéria desvinculado com sucesso!')
permission_required = ('protocoloadm.action_anular_protocolo', )
def get_success_url(self):
return reverse('sapl.protocoloadm:protocolo')
def form_valid(self, form):
materia = MateriaLegislativa.objects.get(numero=form.cleaned_data['numero'],
ano=form.cleaned_data['ano'],
tipo=form.cleaned_data['tipo'])
materia.numero_protocolo = None
materia.save()
return redirect(self.get_success_url())

3
sapl/relatorios/views.py

@ -795,7 +795,8 @@ def relatorio_sessao_plenaria(request, pk):
for idx in range(len(lst_expedientes)): for idx in range(len(lst_expedientes)):
txt_expedientes = lst_expedientes[idx]['txt_expediente'] txt_expedientes = lst_expedientes[idx]['txt_expediente']
txt_expedientes = TrocaTag(txt_expedientes, '<table', 'table>', 6, 6, 'expedientes') txt_expedientes = TrocaTag(txt_expedientes, '<table', 'table>', 6, 6,
'expedientes', '</para><blockTable style = "', 'blockTable><para>')
lst_expedientes[idx]['txt_expediente'] = txt_expedientes lst_expedientes[idx]['txt_expediente'] = txt_expedientes
pdf = pdf_sessao_plenaria_gerar.principal( pdf = pdf_sessao_plenaria_gerar.principal(

42
sapl/sessao/forms.py

@ -124,7 +124,7 @@ class BancadaForm(ModelForm):
bancada = super(BancadaForm, self).save(commit) bancada = super(BancadaForm, self).save(commit)
content_type = ContentType.objects.get_for_model(Bancada) content_type = ContentType.objects.get_for_model(Bancada)
object_id = bancada.pk object_id = bancada.pk
tipo = TipoAutor.objects.get(descricao='Bancada Parlamentar') tipo = TipoAutor.objects.get(descricao__icontains='Bancada')
Autor.objects.create( Autor.objects.create(
content_type=content_type, content_type=content_type,
object_id=object_id, object_id=object_id,
@ -159,7 +159,7 @@ class BlocoForm(ModelForm):
bloco = super(BlocoForm, self).save(commit) bloco = super(BlocoForm, self).save(commit)
content_type = ContentType.objects.get_for_model(Bloco) content_type = ContentType.objects.get_for_model(Bloco)
object_id = bloco.pk object_id = bloco.pk
tipo = TipoAutor.objects.get(descricao='Bloco Parlamentar') tipo = TipoAutor.objects.get(descricao__icontains='Bloco')
Autor.objects.create( Autor.objects.create(
content_type=content_type, content_type=content_type,
object_id=object_id, object_id=object_id,
@ -179,15 +179,17 @@ class ExpedienteMateriaForm(ModelForm):
required=True, required=True,
queryset=TipoMateriaLegislativa.objects.all(), queryset=TipoMateriaLegislativa.objects.all(),
empty_label='Selecione', empty_label='Selecione',
) widget=forms.Select(attrs={'autocomplete': 'off'}))
numero_materia = forms.CharField( numero_materia = forms.CharField(
label='Número Matéria', required=True) label='Número Matéria', required=True,
widget=forms.TextInput(attrs={'autocomplete': 'off'}))
ano_materia = forms.CharField( ano_materia = forms.CharField(
label='Ano Matéria', label='Ano Matéria',
initial=int(data_atual.year), initial=int(data_atual.year),
required=True) required=True,
widget=forms.TextInput(attrs={'autocomplete': 'off'}))
data_ordem = forms.CharField( data_ordem = forms.CharField(
label='Data Sessão', label='Data Sessão',
@ -452,7 +454,7 @@ class OradorForm(ModelForm):
sessao_plenaria_id=id_sessao)] sessao_plenaria_id=id_sessao)]
self.fields['parlamentar'].queryset = Parlamentar.objects.filter( self.fields['parlamentar'].queryset = Parlamentar.objects.filter(
id__in=ids).order_by('nome_completo') id__in=ids).order_by('nome_parlamentar')
class Meta: class Meta:
model = Orador model = Orador
@ -464,14 +466,32 @@ class OradorExpedienteForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(OradorExpedienteForm, self).__init__(*args, **kwargs) super(OradorExpedienteForm, self).__init__(*args, **kwargs)
legislatura_atual = [l for l in Legislatura.objects.all() if l.atual] legislatura_vigente = SessaoPlenaria.objects.get(pk=kwargs['initial']['id_sessao']).legislatura
if legislatura_atual: if legislatura_vigente:
legislatura_atual = legislatura_atual[0]
self.fields['parlamentar'].queryset = \ self.fields['parlamentar'].queryset = \
Parlamentar.objects.filter(ativo=True, Parlamentar.objects.filter(ativo=True,
mandato__legislatura=legislatura_atual mandato__legislatura=legislatura_vigente
).order_by('nome_parlamentar') ).order_by('nome_parlamentar')
def clean(self):
super(OradorExpedienteForm, self).clean()
cleaned_data = self.cleaned_data
if not self.is_valid():
return self.cleaned_data
sessao_id = self.initial['id_sessao']
ordem = OradorExpediente.objects.filter(
sessao_plenaria_id=sessao_id,
numero_ordem=cleaned_data['numero_ordem']
).exists()
if ordem:
raise ValidationError(_(
'Já existe orador nesta posição da ordem de pronunciamento'))
return self.cleaned_data
def clean(self): def clean(self):
super(OradorExpedienteForm, self).clean() super(OradorExpedienteForm, self).clean()

147
sapl/sessao/views.py

@ -4,7 +4,7 @@ from operator import itemgetter
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import permission_required from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db.models import Max, Q from django.db.models import Max, Q
from django.forms.utils import ErrorList from django.forms.utils import ErrorList
@ -16,7 +16,7 @@ from django.utils.decorators import method_decorator
from django.utils.html import strip_tags from django.utils.html import strip_tags
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.generic import FormView, ListView, TemplateView from django.views.generic import FormView, ListView, TemplateView, CreateView, UpdateView
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
@ -30,12 +30,11 @@ from sapl.materia.forms import filtra_tramitacao_status
from sapl.materia.models import (Autoria, DocumentoAcessorio, from sapl.materia.models import (Autoria, DocumentoAcessorio,
TipoMateriaLegislativa, Tramitacao) TipoMateriaLegislativa, Tramitacao)
from sapl.materia.views import MateriaLegislativaPesquisaView from sapl.materia.views import MateriaLegislativaPesquisaView
from sapl.norma.models import NormaJuridica
from sapl.parlamentares.models import (Filiacao, Legislatura, Mandato, from sapl.parlamentares.models import (Filiacao, Legislatura, Mandato,
Parlamentar, SessaoLegislativa) Parlamentar, SessaoLegislativa)
from sapl.sessao.apps import AppConfig from sapl.sessao.apps import AppConfig
from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm
from sapl.utils import show_results_filter_set from sapl.utils import show_results_filter_set, remover_acentos
from .forms import (AdicionarVariasMateriasFilterSet, BancadaForm, BlocoForm, from .forms import (AdicionarVariasMateriasFilterSet, BancadaForm, BlocoForm,
ExpedienteForm, ListMateriaForm, MesaForm, ExpedienteForm, ListMateriaForm, MesaForm,
@ -278,8 +277,8 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
else: else:
resultado = '''Não há resultado''' resultado = '''Não há resultado'''
else: else:
resultado = obj.registrovotacao_set.get( resultado = obj.registrovotacao_set.filter(
materia_id=obj.materia_id) materia_id=obj.materia_id).last()
resultado_descricao = resultado.tipo_resultado_votacao.nome resultado_descricao = resultado.tipo_resultado_votacao.nome
resultado_observacao = resultado.observacao resultado_observacao = resultado.observacao
@ -386,13 +385,15 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
context['rows'][i][3] = (resultado, None) context['rows'][i][3] = (resultado, None)
return context return context
def get_presencas_generic(model, sessao, legislatura): def get_presencas_generic(model, sessao, legislatura):
presencas = model.objects.filter( presencas = model.objects.filter(
sessao_plenaria=sessao) sessao_plenaria=sessao)
presentes = [p.parlamentar for p in presencas] presentes = [p.parlamentar for p in presencas]
presentes = sorted(presentes, key=lambda x: remover_acentos(x.nome_parlamentar))
mandato = Mandato.objects.filter( mandato = Mandato.objects.filter(
legislatura=legislatura).order_by('parlamentar__nome_parlamentar') legislatura=legislatura).order_by('parlamentar__nome_parlamentar')
@ -434,10 +435,11 @@ class MateriaOrdemDiaCrud(MasterDetailCrud):
form_class = OrdemDiaForm form_class = OrdemDiaForm
def get_initial(self): def get_initial(self):
self.initial['tipo_materia'] = self.object.materia.tipo.id initial = super(UpdateView, self).get_initial()
self.initial['numero_materia'] = self.object.materia.numero initial['tipo_materia'] = self.object.materia.tipo.id
self.initial['ano_materia'] = self.object.materia.ano initial['numero_materia'] = self.object.materia.numero
return self.initial initial['ano_materia'] = self.object.materia.ano
return initial
class DetailView(MasterDetailCrud.DetailView): class DetailView(MasterDetailCrud.DetailView):
@ -491,14 +493,15 @@ class ExpedienteMateriaCrud(MasterDetailCrud):
form_class = ExpedienteMateriaForm form_class = ExpedienteMateriaForm
def get_initial(self): def get_initial(self):
self.initial['data_ordem'] = SessaoPlenaria.objects.get( initial = super(CreateView, self).get_initial()
initial['data_ordem'] = SessaoPlenaria.objects.get(
pk=self.kwargs['pk']).data_inicio.strftime('%d/%m/%Y') pk=self.kwargs['pk']).data_inicio.strftime('%d/%m/%Y')
max_numero_ordem = ExpedienteMateria.objects.filter( max_numero_ordem = ExpedienteMateria.objects.filter(
sessao_plenaria=self.kwargs['pk']).aggregate( sessao_plenaria=self.kwargs['pk']).aggregate(
Max('numero_ordem'))['numero_ordem__max'] Max('numero_ordem'))['numero_ordem__max']
self.initial['numero_ordem'] = ( initial['numero_ordem'] = (
max_numero_ordem if max_numero_ordem else 0) + 1 max_numero_ordem if max_numero_ordem else 0) + 1
return self.initial return initial
def get_success_url(self): def get_success_url(self):
return reverse('sapl.sessao:expedientemateria_list', return reverse('sapl.sessao:expedientemateria_list',
@ -508,10 +511,11 @@ class ExpedienteMateriaCrud(MasterDetailCrud):
form_class = ExpedienteMateriaForm form_class = ExpedienteMateriaForm
def get_initial(self): def get_initial(self):
self.initial['tipo_materia'] = self.object.materia.tipo.id initial = super(UpdateView, self).get_initial()
self.initial['numero_materia'] = self.object.materia.numero initial['tipo_materia'] = self.object.materia.tipo.id
self.initial['ano_materia'] = self.object.materia.ano initial['numero_materia'] = self.object.materia.numero
return self.initial initial['ano_materia'] = self.object.materia.ano
return initial
class DetailView(MasterDetailCrud.DetailView): class DetailView(MasterDetailCrud.DetailView):
@ -760,6 +764,7 @@ class PainelView(PermissionRequiredForAppCrudMixin, TemplateView):
request.session['discurso'] = 'stop' request.session['discurso'] = 'stop'
request.session['aparte'] = 'stop' request.session['aparte'] = 'stop'
request.session['ordem'] = 'stop' request.session['ordem'] = 'stop'
request.session['consideracoes'] = 'stop'
return TemplateView.get(self, request, *args, **kwargs) return TemplateView.get(self, request, *args, **kwargs)
@ -767,9 +772,10 @@ class PainelView(PermissionRequiredForAppCrudMixin, TemplateView):
cronometro_discurso = AppsAppConfig.attr('cronometro_discurso') cronometro_discurso = AppsAppConfig.attr('cronometro_discurso')
cronometro_aparte = AppsAppConfig.attr('cronometro_aparte') cronometro_aparte = AppsAppConfig.attr('cronometro_aparte')
cronometro_ordem = AppsAppConfig.attr('cronometro_ordem') cronometro_ordem = AppsAppConfig.attr('cronometro_ordem')
cronometro_consideracoes = AppsAppConfig.attr('cronometro_consideracoes')
if (not cronometro_discurso or not cronometro_aparte if (not cronometro_discurso or not cronometro_aparte
or not cronometro_ordem): or not cronometro_ordem or not cronometro_consideracoes):
msg = _( msg = _(
'Você precisa primeiro configurar os cronômetros \ 'Você precisa primeiro configurar os cronômetros \
nas Configurações da Aplicação') nas Configurações da Aplicação')
@ -785,6 +791,9 @@ class PainelView(PermissionRequiredForAppCrudMixin, TemplateView):
m, s, x = cronometro_ordem.isoformat().split(':') m, s, x = cronometro_ordem.isoformat().split(':')
cronometro_ordem = int(m) * 60 + int(s) cronometro_ordem = int(m) * 60 + int(s)
m, s, x = cronometro_consideracoes.isoformat().split(':')
cronometro_consideracoes = int(m) * 60 + int(s)
context = TemplateView.get_context_data(self, **kwargs) context = TemplateView.get_context_data(self, **kwargs)
context.update({ context.update({
'head_title': str(_('Painel Plenário')), 'head_title': str(_('Painel Plenário')),
@ -793,7 +802,8 @@ class PainelView(PermissionRequiredForAppCrudMixin, TemplateView):
'sessaoplenaria': SessaoPlenaria.objects.get(pk=kwargs['pk']), 'sessaoplenaria': SessaoPlenaria.objects.get(pk=kwargs['pk']),
'cronometro_discurso': cronometro_discurso, 'cronometro_discurso': cronometro_discurso,
'cronometro_aparte': cronometro_aparte, 'cronometro_aparte': cronometro_aparte,
'cronometro_ordem': cronometro_ordem}) 'cronometro_ordem': cronometro_ordem,
'cronometro_consideracoes': cronometro_consideracoes})
return context return context
@ -978,20 +988,19 @@ class MesaView(FormMixin, DetailView):
return self.render_to_response(context) return self.render_to_response(context)
mesa = sessao.integrantemesa_set.all() if sessao else [] mesa = sessao.integrantemesa_set.all() if sessao else []
cargos_ocupados = [m.cargo for m in mesa] cargos_ocupados = [m.cargo for m in mesa]
cargos = CargoMesa.objects.all() cargos = CargoMesa.objects.all()
cargos_vagos = list(set(cargos) - set(cargos_ocupados)) cargos_vagos = list(set(cargos) - set(cargos_ocupados))
# FIX-ME: tem formas melhores de fazer isso, poupando linhas. # FIX-ME: tem formas melhores de fazer isso, poupando linhas.
parlamentares = Legislatura.objects.first().mandato_set.all() parlamentares = Legislatura.objects.get(id=sessao.legislatura_id).mandato_set.all()
parlamentares_ocupados = [m.parlamentar for m in mesa] parlamentares_ocupados = [m.parlamentar for m in mesa]
parlamentares_vagos = list( parlamentares_vagos = list(
set( set(
[p.parlamentar for p in parlamentares]) - set( [p.parlamentar for p in parlamentares]) - set(
parlamentares_ocupados)) parlamentares_ocupados))
org_parlamentares_vagos = parlamentares_vagos
org_parlamentares_vagos = sorted(parlamentares_vagos, key=lambda x: x.nome_parlamentar) org_parlamentares_vagos.sort(key=lambda x: remover_acentos(x.nome_parlamentar))
org_parlamentares_vagos = [p for p in org_parlamentares_vagos if p.ativo] org_parlamentares_vagos = [p for p in org_parlamentares_vagos if p.ativo]
# Se todos os cargos estiverem ocupados, a listagem de parlamentares # Se todos os cargos estiverem ocupados, a listagem de parlamentares
# deve ser renderizada vazia # deve ser renderizada vazia
@ -1046,8 +1055,10 @@ def atualizar_mesa(request):
lista_composicao = [(c.id, c.parlamentar.__str__(), lista_composicao = [(c.id, c.parlamentar.__str__(),
c.cargo.__str__()) for c in composicao_mesa] c.cargo.__str__()) for c in composicao_mesa]
lista_parlamentares = [( lista_parlamentares = [(
p.id, p.__str__()) for p in parlamentares_vagos] p.id, p.nome_parlamentar)
for p in parlamentares_vagos if p.ativo]
lista_cargos = [(c.id, c.__str__()) for c in cargos_vagos] lista_cargos = [(c.id, c.__str__()) for c in cargos_vagos]
lista_parlamentares.sort(key=lambda x: remover_acentos(x[1]))
return JsonResponse( return JsonResponse(
{'lista_composicao': lista_composicao, {'lista_composicao': lista_composicao,
@ -1113,15 +1124,13 @@ def remove_parlamentar_composicao(request):
if 'composicao_mesa' in request.POST: if 'composicao_mesa' in request.POST:
try: try:
composicao = IntegranteMesa.objects.get( IntegranteMesa.objects.get(
id=int(request.POST['composicao_mesa'])) id=int(request.POST['composicao_mesa'])).delete()
except ObjectDoesNotExist: except ObjectDoesNotExist:
return JsonResponse( return JsonResponse(
{'msg': ( {'msg': (
'Composição da Mesa não pôde ser removida!', 0)}) 'Composição da Mesa não pôde ser removida!', 0)})
composicao.delete()
return JsonResponse( return JsonResponse(
{'msg': ( {'msg': (
'Parlamentar excluido com sucesso!', 1)}) 'Parlamentar excluido com sucesso!', 1)})
@ -1140,9 +1149,10 @@ class ResumoOrdenacaoView(PermissionRequiredMixin, FormView):
return reverse('sapl.base:sistema') return reverse('sapl.base:sistema')
def get_initial(self): def get_initial(self):
initial = super(ResumoOrdenacaoView, self).get_initial()
ordenacao = ResumoOrdenacao.objects.first() ordenacao = ResumoOrdenacao.objects.first()
if ordenacao: if ordenacao:
return {'primeiro': ordenacao.primeiro, initial.update({'primeiro': ordenacao.primeiro,
'segundo': ordenacao.segundo, 'segundo': ordenacao.segundo,
'terceiro': ordenacao.terceiro, 'terceiro': ordenacao.terceiro,
'quarto': ordenacao.quarto, 'quarto': ordenacao.quarto,
@ -1151,8 +1161,8 @@ class ResumoOrdenacaoView(PermissionRequiredMixin, FormView):
'setimo': ordenacao.setimo, 'setimo': ordenacao.setimo,
'oitavo': ordenacao.oitavo, 'oitavo': ordenacao.oitavo,
'nono': ordenacao.nono, 'nono': ordenacao.nono,
'decimo': ordenacao.decimo} 'decimo': ordenacao.decimo})
return self.initial.copy() return initial
def form_valid(self, form): def form_valid(self, form):
ordenacao = ResumoOrdenacao.objects.get_or_create()[0] ordenacao = ResumoOrdenacao.objects.get_or_create()[0]
@ -1450,6 +1460,11 @@ class ExpedienteView(FormMixin, DetailView):
self.object = self.get_object() self.object = self.get_object()
form = ExpedienteForm(request.POST) form = ExpedienteForm(request.POST)
if 'apagar-expediente' in request.POST:
ExpedienteSessao.objects.filter(
sessao_plenaria_id=self.object.id).delete()
return self.form_valid(form)
if form.is_valid(): if form.is_valid():
list_tipo = request.POST.getlist('tipo') list_tipo = request.POST.getlist('tipo')
list_conteudo = request.POST.getlist('conteudo') list_conteudo = request.POST.getlist('conteudo')
@ -1525,8 +1540,7 @@ class VotacaoEditView(SessaoPermissionMixin):
ordem_id = kwargs['oid'] ordem_id = kwargs['oid']
if(int(request.POST['anular_votacao']) == 1): if(int(request.POST['anular_votacao']) == 1):
for r in RegistroVotacao.objects.filter(ordem_id=ordem_id): RegistroVotacao.objects.filter(ordem_id=ordem_id).delete()
r.delete()
ordem = OrdemDia.objects.get( ordem = OrdemDia.objects.get(
sessao_plenaria_id=self.object.id, sessao_plenaria_id=self.object.id,
@ -1557,9 +1571,8 @@ class VotacaoEditView(SessaoPermissionMixin):
materia = {'materia': ordem.materia, 'ementa': ordem.materia.ementa} materia = {'materia': ordem.materia, 'ementa': ordem.materia.ementa}
context.update({'materia': materia}) context.update({'materia': materia})
votacao = RegistroVotacao.objects.filter( votacao = RegistroVotacao.objects.filter(materia_id=materia_id,
materia_id=materia_id, ordem_id=ordem_id).last()
ordem_id=ordem_id).last()
votacao_existente = {'observacao': sub( votacao_existente = {'observacao': sub(
'&nbsp;', ' ', strip_tags(votacao.observacao)), '&nbsp;', ' ', strip_tags(votacao.observacao)),
'resultado': votacao.tipo_resultado_votacao.nome, 'resultado': votacao.tipo_resultado_votacao.nome,
@ -1706,8 +1719,7 @@ def fechar_votacao_materia(materia):
VotoParlamentar.objects.filter(ordem=materia).delete() VotoParlamentar.objects.filter(ordem=materia).delete()
elif type(materia) == ExpedienteMateria: elif type(materia) == ExpedienteMateria:
RegistroVotacao.objects.filter( RegistroVotacao.objects.filter(expediente=materia).delete()
expediente=materia).delete()
VotoParlamentar.objects.filter(expediente=materia).delete() VotoParlamentar.objects.filter(expediente=materia).delete()
if materia.resultado: if materia.resultado:
@ -1755,7 +1767,7 @@ class VotacaoNominalAbstract(SessaoPermissionMixin):
elif self.expediente: elif self.expediente:
expediente_id = kwargs['oid'] expediente_id = kwargs['oid']
if (RegistroVotacao.objects.filter( if (RegistroVotacao.objects.filter(
expediente_id=expediente_id).exists()): expediente_id=expediente_id).exists()):
msg = _('Esta matéria já foi votada!') msg = _('Esta matéria já foi votada!')
messages.add_message(request, messages.ERROR, msg) messages.add_message(request, messages.ERROR, msg)
return HttpResponseRedirect(reverse( return HttpResponseRedirect(reverse(
@ -1842,8 +1854,7 @@ class VotacaoNominalAbstract(SessaoPermissionMixin):
return self.form_invalid(form) return self.form_invalid(form)
# Remove todas as votação desta matéria, caso existam # Remove todas as votação desta matéria, caso existam
if self.ordem: if self.ordem:
RegistroVotacao.objects.filter( RegistroVotacao.objects.filter(ordem_id=ordem_id).delete()
ordem_id=ordem_id).delete()
elif self.expediente: elif self.expediente:
RegistroVotacao.objects.filter( RegistroVotacao.objects.filter(
expediente_id=expediente_id).delete() expediente_id=expediente_id).delete()
@ -1971,11 +1982,10 @@ class VotacaoNominalEditAbstract(SessaoPermissionMixin):
if self.ordem: if self.ordem:
ordem_id = kwargs['oid'] ordem_id = kwargs['oid']
try: ordem = OrdemDia.objects.filter(id=ordem_id).last()
ordem = OrdemDia.objects.get(id=ordem_id) votacao = RegistroVotacao.objects.filter(ordem_id=ordem_id).last()
votacao = RegistroVotacao.objects.get(
ordem_id=ordem_id) if not ordem or not votacao:
except ObjectDoesNotExist:
raise Http404() raise Http404()
materia = ordem.materia materia = ordem.materia
@ -1984,11 +1994,10 @@ class VotacaoNominalEditAbstract(SessaoPermissionMixin):
elif self.expediente: elif self.expediente:
expediente_id = kwargs['oid'] expediente_id = kwargs['oid']
try: expediente = ExpedienteMateria.objects.filter(id=expediente_id).last()
expediente = ExpedienteMateria.objects.get(id=expediente_id) votacao = RegistroVotacao.objects.filter(expediente_id=expediente_id).last()
votacao = RegistroVotacao.objects.get(
expediente_id=expediente_id) if not expediente or not votacao:
except ObjectDoesNotExist:
raise Http404() raise Http404()
materia = expediente.materia materia = expediente.materia
@ -2105,9 +2114,9 @@ class VotacaoNominalTransparenciaDetailView(TemplateView):
materia_votacao = self.request.GET.get('materia', None) materia_votacao = self.request.GET.get('materia', None)
if materia_votacao == 'ordem': if materia_votacao == 'ordem':
votacao = RegistroVotacao.objects.get(ordem=self.kwargs['oid']) votacao = RegistroVotacao.objects.filter(ordem=self.kwargs['oid']).last()
elif materia_votacao == 'expediente': elif materia_votacao == 'expediente':
votacao = RegistroVotacao.objects.get(expediente=self.kwargs['oid']) votacao = RegistroVotacao.objects.filter(expediente=self.kwargs['oid']).last()
else: else:
raise Http404() raise Http404()
@ -2141,10 +2150,9 @@ class VotacaoNominalExpedienteDetailView(DetailView):
materia_id = kwargs['mid'] materia_id = kwargs['mid']
expediente_id = kwargs['oid'] expediente_id = kwargs['oid']
votacao = RegistroVotacao.objects.get( votacao = RegistroVotacao.objects.filter(materia_id=materia_id,
materia_id=materia_id, expediente_id=expediente_id).last()
expediente_id=expediente_id) expediente = ExpedienteMateria.objects.filter(id=expediente_id).last()
expediente = ExpedienteMateria.objects.get(id=expediente_id)
votos = VotoParlamentar.objects.filter(votacao_id=votacao.id) votos = VotoParlamentar.objects.filter(votacao_id=votacao.id)
list_votos = [] list_votos = []
@ -2189,9 +2197,9 @@ class VotacaoSimbolicaTransparenciaDetailView(TemplateView):
materia_votacao = self.request.GET.get('materia', None) materia_votacao = self.request.GET.get('materia', None)
if materia_votacao == 'ordem': if materia_votacao == 'ordem':
votacao = RegistroVotacao.objects.get(ordem=self.kwargs['oid']) votacao = RegistroVotacao.objects.filter(ordem=self.kwargs['oid']).last()
elif materia_votacao == 'expediente': elif materia_votacao == 'expediente':
votacao = RegistroVotacao.objects.get(expediente=self.kwargs['oid']) votacao = RegistroVotacao.objects.filter(expediente=self.kwargs['oid']).last()
else: else:
raise Http404() raise Http404()
@ -2377,14 +2385,9 @@ class VotacaoExpedienteEditView(SessaoPermissionMixin):
'ementa': expediente.materia.ementa} 'ementa': expediente.materia.ementa}
context.update({'materia': materia}) context.update({'materia': materia})
try: votacao = RegistroVotacao.objects.filter(materia_id=materia_id,
votacao = RegistroVotacao.objects.get( expediente_id=expediente_id
materia_id=materia_id, ).last()
expediente_id=expediente_id)
except MultipleObjectsReturned:
votacao = RegistroVotacao.objects.filter(
materia_id=materia_id,
expediente_id=expediente_id).last()
votacao_existente = {'observacao': sub( votacao_existente = {'observacao': sub(
'&nbsp;', ' ', strip_tags(votacao.observacao)), '&nbsp;', ' ', strip_tags(votacao.observacao)),
'resultado': votacao.tipo_resultado_votacao.nome, 'resultado': votacao.tipo_resultado_votacao.nome,
@ -2403,10 +2406,8 @@ class VotacaoExpedienteEditView(SessaoPermissionMixin):
materia_id = kwargs['mid'] materia_id = kwargs['mid']
expediente_id = kwargs['oid'] expediente_id = kwargs['oid']
if(int(request.POST['anular_votacao']) == 1): if int(request.POST['anular_votacao']) == 1:
for r in RegistroVotacao.objects.filter( RegistroVotacao.objects.filter(expediente_id=expediente_id).delete()
expediente_id=expediente_id):
r.delete()
expediente = ExpedienteMateria.objects.get( expediente = ExpedienteMateria.objects.get(
sessao_plenaria_id=self.object.id, sessao_plenaria_id=self.object.id,
@ -2596,7 +2597,7 @@ class PesquisarSessaoPlenariaView(FilterView):
'tipo', 'sessao_legislativa', 'legislatura') 'tipo', 'sessao_legislativa', 'legislatura')
qs = qs.distinct().order_by( qs = qs.distinct().order_by(
'-legislatura__numero', '-data_inicio', '-numero') '-legislatura__numero', '-data_inicio', '-hora_inicio')
kwargs.update({ kwargs.update({
'queryset': qs, 'queryset': qs,

2
sapl/settings.py

@ -127,7 +127,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'speedinfo.middleware.ProfilerMiddleware', # 'speedinfo.middleware.ProfilerMiddleware', # Bug na versão 1.9
) )
CACHES = { CACHES = {

11
sapl/static/XSLT/HTML/.objects

@ -1,11 +0,0 @@
estilo.css:DTML Method
indicacao.xsl:File
mocao.xsl:File
mocao2.xsl:File
parecer.xsl:File
pedido.xsl:File
pedido2.xsl:File
pl.xsl:File
pl2.xsl:File
requerimento.xsl:File
requerimento2.xsl:File

95
sapl/static/XSLT/HTML/estilo.css

@ -1,95 +0,0 @@
body {
font-family: Times;
text-align: justify;
font-size: 12 pt;
margin: 5px 1cm 20px 2cm;
}
p,
.p{
font-family: Times;
text-align: justify;
font-size: 12pt;
text-indent: 1.5cm;
margin: 40px 0 20px 0;
}
.pequeno {
font-family: Times;
text-align: left;
font-size: 13pt;
margin: 0px 0 0px 0;
}
.cabecalho {
font-family: Times;
font-weight:bold;
text-align: left;
font-size: 15pt;
margin: 10px 0 0px 0;
}
.texto {
font-family: Times;
text-align: justify;
font-size: 12pt;
margin: 0px 0px 0px 0px;
}
.data {
text-align: right;
}
.autor {
text-align: center;
}
.center {
text-align: center;
}
.semrecuo {
text-indent: 0;
}
.ementa {
text-align: justify;
margin-left: 50%;
text-indent: 0;
}
.titulos1 {
text-align: center;
margin: 10px 0 0px 0;
}
.titulos2 {
text-align: center;
margin: 0px 0 0px 0;
}
p.artigo {
text-align: justify;
text-indent: 1cm;
margin: 10px 0 0px 0;
}
#imagem {
float:left;
}
#autores
{
-moz-column-count:3; /* Firefox */
-webkit-column-count:3; /* Safari and Chrome */
width:50px;
}
#col1 { width: 33%; float: left; center: 10px; }
#col2 { width: 33%; float: left; center: 10px; }
#col3 { width: 33%; float: left; center: 10px; }

51
sapl/static/XSLT/HTML/indicacao.xsl

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ind="/XSD/Indicacao">
<xsl:template match="/">
<html>
<head>
<link href="/XSLT/HTML/estilo.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="ind:ementa_text">
<div>
<div id="imagem">
<img border="0" src="http://sapl.agudo.rs.leg.br/generico/sapl_documentos/props_sapl/logo_casa"/><br></br>
</div><br></br>
<p class ="cabecalho">Câmara Municipal de Agudo</p>
<p class ="pequeno"> Estado do Rio Grande do Sul <br></br><br></br><br></br></p>
</div>
<p class="autor"><strong><xsl:value-of select="text()" /></strong></p>
</xsl:template>
<xsl:template match="ind:autoria_text">
<p class="semrecuo"><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:destinatario_text">
<p class="semrecuo"><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:indicacao_text">
<p><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:data_text">
<p><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:autor_text">
<p class="autor"><xsl:value-of select="text()" /></p>
</xsl:template>
</xsl:stylesheet>

41
sapl/static/XSLT/HTML/mocao.xsl

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:moc="/XSD/Mocao">
<xsl:template match="/">
<html>
<body>
<table>
<xsl:apply-templates />
</table>
</body>
</html>
</xsl:template>
<xsl:template match="moc:ementa_text">
<tr>
<td width="50%"></td>
<td width="50%" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="moc:mocao_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="moc:data_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="moc:autor_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
</xsl:stylesheet>

45
sapl/static/XSLT/HTML/mocao2.xsl

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ind="/XSD/Mocao">
<xsl:template match="/">
<html>
<head>
<link href="/sapl/XSLT/HTML/estilo.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div>
<div id="imagem">
<img border="0" src="/sapl/sapl_documentos/props_sapl/logo_casa"/>
</div><br></br>
<p class ="cabecalho">Câmara Municipal de Agudo</p>
<p class ="pequeno">Estado do Rio Grande do Sul<br></br><br></br><br></br> </p>
</div>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="ind:ementa_text">
<p class ="autor"><strong><xsl:value-of select="text()" /></strong></p>
</xsl:template>
<xsl:template match="ind:mocao_text">
<p class ="texto"><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:justificativa_text">
<p class ="texto"><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:data_text">
<p><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:autor_text">
<p class="autor"><xsl:value-of select="text()" /></p>
</xsl:template>
</xsl:stylesheet>

41
sapl/static/XSLT/HTML/parecer.xsl

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:par="/XSD/Parecer">
<xsl:template match="/">
<html>
<body>
<table>
<xsl:apply-templates />
</table>
</body>
</html>
</xsl:template>
<xsl:template match="par:ementa_text">
<tr>
<td width="50%"></td>
<td width="50%" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="par:parecer_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="par:data_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="par:autor_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
</xsl:stylesheet>

47
sapl/static/XSLT/HTML/pedido.xsl

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:req="/XSD/Requerimento">
<xsl:template match="/">
<html>
<body>
<table>
<xsl:apply-templates />
</table>
</body>
</html>
</xsl:template>
<xsl:template match="req:ementa_text">
<tr>
<td width="50%"></td>
<td width="50%" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="req:requisicao_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="req:justificativa_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="req:data_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="req:autor_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
</xsl:stylesheet>

53
sapl/static/XSLT/HTML/pedido2.xsl

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ind="/XSD/Pedido">
<xsl:template match="/">
<html>
<head>
<link href="/sapl/XSLT/HTML/estilo.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div>
<div id="imagem">
<img border="0" src="/sapl/sapl_documentos/props_sapl/logo_casa"/>
</div><br></br>
<p class ="cabecalho">Câmara Municipal de Agudo</p>
<p class ="pequeno">Estado do Rio Grande do Sul<br></br><br></br><br></br> </p>
</div>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="ind:ementa_text">
<p class ="autor"><strong><xsl:value-of select="text()" /></strong></p>
</xsl:template>
<xsl:template match="ind:autoria_text">
<p class="semrecuo"><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:destinatario_text">
<p class="semrecuo"><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:solicitacao_text">
<p class="texto"><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:alinea_text">
<p class="texto"><xsl:value-of select="concat(../@Rotulo,' ',text())"/></p>
</xsl:template>
<xsl:template match="ind:data_text">
<p><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:autor_text">
<p class="autor"><xsl:value-of select="text()" /></p>
</xsl:template>
</xsl:stylesheet>

105
sapl/static/XSLT/HTML/pl.xsl

@ -1,105 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:pl="/XSD/ProjLei">
<xsl:output encoding="ISO-8859-1"/>
<xsl:template match="/pl:pl">
<html>
<head>
<title>
<xsl:value-of select="@id"/>
</title>
<style type="text/css">
body {margin-left: 2cm; margin-right: 1cm;}
p {font-family: Times; font-size: 12pt;}
p.epigrafe {text-align: center; text-transform: uppercase;}
p.ementa {text-align: justify; margin-left: 50%;}
p.preambulo {text-transform: uppercase; text-indent: 1cm;}
p.artigo {text-align: justify; text-indent: 1cm;}
p.paragrafo {text-align: justify; text-indent: 1cm;}
p.inciso {text-align: justify; text-indent: 1cm;}
p.alinea {text-align: justify; text-indent: 1cm;}
p.item {text-align: justify; text-indent: 1cm;}
p.justificativa {text-align: justify; text-indent: 1cm;}
p.mensagem {text-align: justify;}
p.data_apresentacao {text-align: justify; text-indent: 1cm;}
p.autor {text-align: center; text-transform: uppercase;}
h3.cab_secao {text-align: center; font-size: 12pt;}
</style>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="pl:proposicao">
<hr/>
<h3 class="cab_secao">PROPOSIÇÃO</h3>
<hr/>
<xsl:apply-templates select="./*"/>
</xsl:template>
<xsl:template match="pl:epigrafe">
<p class="epigrafe">
<xsl:value-of select="pl:epigrafe_text"/>
</p>
</xsl:template>
<xsl:template match="pl:ementa">
<p class="ementa">
<xsl:value-of select="pl:ementa_text"/>
</p>
</xsl:template>
<xsl:template match="pl:preambulo">
<p class="preambulo">
<xsl:value-of select="pl:preambulo_text"/>
</p>
</xsl:template>
<xsl:template match="pl:artigo_text">
<p class="artigo">
<xsl:value-of select="concat(../@Rotulo,' ',text())"/>
</p>
</xsl:template>
<xsl:template match="pl:paragrafo_text">
<p class="paragrafo">
<xsl:value-of select="concat(../@Rotulo,' ',text())"/>
</p>
</xsl:template>
<xsl:template match="pl:inciso_text">
<p class="inciso">
<xsl:value-of select="concat(../@Rotulo,' - ',text())"/>
</p>
</xsl:template>
<xsl:template match="pl:alinea_text">
<p class="alinea">
<xsl:value-of select="concat(../@Rotulo,' ',text())"/>
</p>
</xsl:template>
<xsl:template match="pl:item_text">
<p class="item">
<xsl:value-of select="concat(../@Rotulo,' ',text())"/>
</p>
</xsl:template>
<xsl:template match="pl:data_apresentacao_text">
<p class="data_apresentacao">
<xsl:value-of select="text()"/>
</p>
</xsl:template>
<xsl:template match="pl:autor_text">
<p class="autor">
<xsl:value-of select="text()"/>
</p>
</xsl:template>
<xsl:template match="pl:justificativa">
<hr/>
<h3 class="cab_secao">JUSTIFICATIVA</h3>
<hr/>
<p class="justificativa">
<xsl:value-of select="pl:justificativa_text"/>
</p>
</xsl:template>
<xsl:template match="pl:mensagem">
<hr/>
<h3 class="cab_secao">MENSAGEM</h3>
<hr/>
<p class="mensagem">
<xsl:value-of select="pl:mensagem_text"/>
</p>
</xsl:template>
</xsl:stylesheet>

100
sapl/static/XSLT/HTML/pl2.xsl

@ -1,100 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ind="/XSD/ProjLei">
<xsl:template match="/">
<html>
<head>
<link href="/sapl/XSLT/HTML/estilo.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div>
<div id="imagem">
<img border="0" src="/sapl/sapl_documentos/props_sapl/logo_casa"/>
</div><br></br>
<p class ="cabecalho">Câmara Municipal de Agudo</p>
<p class ="pequeno">Estado do Rio Grande do Sul<br></br><br></br><br></br> </p>
</div>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="ind:epigrafe_text">
<p class ="autor"><strong><xsl:value-of select="text()" /></strong></p>
</xsl:template>
<xsl:template match="ind:ementa_text">
<p class ="ementa"><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:preambulo_text">
<p class ="artigo"><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:parte_text">
<p class ="titulos1"><xsl:value-of select="concat(../@Rotulo,' ')"/></p>
<p class ="titulos2"><xsl:value-of select="text()"/></p>
</xsl:template>
<xsl:template match="ind:livro_text">
<p class ="titulos1"><xsl:value-of select="concat(../@Rotulo,' ')"/></p>
<p class ="titulos2"><xsl:value-of select="text()"/></p>
</xsl:template>
<xsl:template match="ind:titulo_text">
<p class ="titulos1"><xsl:value-of select="concat(../@Rotulo,' ')"/></p>
<p class ="titulos2"><xsl:value-of select="text()"/></p>
</xsl:template>
<xsl:template match="ind:capitulo_text">
<p class ="titulos1"><xsl:value-of select="concat(../@Rotulo,' ')"/></p>
<p class ="titulos2"><xsl:value-of select="text()"/></p>
</xsl:template>
<xsl:template match="ind:secao_text">
<p class ="titulos1"><xsl:value-of select="concat(../@Rotulo,' ')"/></p>
<p class ="titulos2"><xsl:value-of select="text()"/></p>
</xsl:template>
<xsl:template match="ind:subsecao_text">
<p class ="titulos1"><xsl:value-of select="concat(../@Rotulo,' ')"/></p>
<p class ="titulos2"><xsl:value-of select="text()"/></p>
</xsl:template>
<xsl:template match="ind:artigo_text">
<p class="artigo"><xsl:value-of select="concat(../@Rotulo,' ',text())"/></p>
</xsl:template>
<xsl:template match="ind:paragrafo_text">
<p class="artigo"><xsl:value-of select="concat(../@Rotulo,' ',text())"/></p>
</xsl:template>
<xsl:template match="ind:inciso_text">
<p class="artigo"><xsl:value-of select="concat(../@Rotulo,' ',text())"/></p>
</xsl:template>
<xsl:template match="ind:alinea_text">
<p class="artigo"><xsl:value-of select="concat(../@Rotulo,' ',text())"/></p>
</xsl:template>
<xsl:template match="ind:item_text">
<p class="artigo"><xsl:value-of select="concat(../@Rotulo,' ',text())"/></p>
</xsl:template>
<xsl:template match="ind:data_apresentacao_text">
<p class="artigo"><xsl:value-of select="concat(../@Rotulo,' ',text())"/></p>
</xsl:template>
<xsl:template match="ind:autor_text">
<p class="artigo"><xsl:value-of select="concat(../@Rotulo,' ',text())"/></p>
</xsl:template>
<xsl:template match="ind:justificativa_text">
<p class="artigo"><xsl:value-of select="text()" /></p>
</xsl:template>
</xsl:stylesheet>

52
sapl/static/XSLT/HTML/requerimento.xsl

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:req="/XSD/Requerimento">
<xsl:template match="/">
<html>
<body>
<table>
<xsl:apply-templates />
</table>
</body>
</html>
</xsl:template>
<xsl:template match="req:destinatario_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="req:ementa_text">
<tr>
<td width="50%"></td>
<td width="50%" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="req:requisicao_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="req:justificativa_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="req:data_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
<xsl:template match="req:autor_text">
<tr>
<td colspan="2" align="left"><xsl:value-of select="text()" /></td>
</tr>
</xsl:template>
</xsl:stylesheet>

57
sapl/static/XSLT/HTML/requerimento2.xsl

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ind="/XSD/Requerimento">
<xsl:template match="/">
<html>
<head>
<link href="/sapl/XSLT/HTML/estilo.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div>
<div id="imagem">
<img border="0" src="/sapl/sapl_documentos/props_sapl/logo_casa"/>
</div><br></br>
<p class ="cabecalho">Câmara Municipal de Agudo</p>
<p class ="pequeno">Estado do Rio Grande do Sul<br></br><br></br><br></br> </p>
</div>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="ind:ementa_text">
<p class="autor"><strong><xsl:value-of select="text()" /></strong></p>
</xsl:template>
<xsl:template match="ind:formatratamento_text">
<p class="semrecuo"><xsl:value-of select="text()"/> </p>
</xsl:template>
<xsl:template match="ind:parlamentar_text">
<p class="semrecuo"><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:cargofuncao_text">
<p class="semrecuo"><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:requisicao_text">
<p class="texto"><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:justificativa_text">
<p class="texto"><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:data_text">
<p><xsl:value-of select="text()" /></p>
</xsl:template>
<xsl:template match="ind:autor_text">
<p class="autor"><xsl:value-of select="text()" /></p>
</xsl:template>
</xsl:stylesheet>

2
sapl/templates/base/layouts.yaml

@ -21,7 +21,7 @@ AppConfig:
- texto_articulado_proposicao texto_articulado_materia texto_articulado_norma - texto_articulado_proposicao texto_articulado_materia texto_articulado_norma
{% trans 'Cronômetros do Painel' %}: {% trans 'Cronômetros do Painel' %}:
- cronometro_discurso cronometro_aparte cronometro_ordem - cronometro_discurso cronometro_aparte cronometro_ordem cronometro_consideracoes
{% trans 'Configurações do Painel' %}: {% trans 'Configurações do Painel' %}:
- mostrar_brasao_painel - mostrar_brasao_painel

2
sapl/templates/materia/layouts.yaml

@ -23,7 +23,7 @@ MateriaLegislativa:
{% trans 'Identificação Básica' %}: {% trans 'Identificação Básica' %}:
- tipo ano numero - tipo ano numero
- data_apresentacao numero_protocolo tipo_apresentacao - data_apresentacao numero_protocolo tipo_apresentacao
- autor - tipo_autor autor
- texto_original - texto_original
{% trans 'Outras Informações' %}: {% trans 'Outras Informações' %}:
- apelido dias_prazo polemica - apelido dias_prazo polemica

38
sapl/templates/materia/materialegislativa_form.html

@ -22,6 +22,44 @@
} }
} }
$("#id_tipo, #id_ano").change(recuperar_numero_ano); $("#id_tipo, #id_ano").change(recuperar_numero_ano);
function compare(a, b) {
if (a.text < b.text)
return -1;
if (a.text > b.text)
return 1;
return 0;
}
$(document).ready(function() {
$("#id_tipo_autor").change(function() {
var tipo_selecionado = $("#id_tipo_autor").val();
var autor_selecionado = $("#id_autor").val();
$("#id_autor option").remove()
if (tipo_selecionado !== undefined && tipo_selecionado !== null) {
var json_data = {
tipo : tipo_selecionado,
data_relativa : $("#id_data_apresentacao").val()
}
$.getJSON("/api/autor/possiveis", json_data, function(data){
if (data) {
var results = data.sort(compare);
if (results.length > 1) {
$("#id_autor").append("<option>-----</option>");
}
$.each(results, function(idx, obj) {
$("#id_autor")
.append($("<option></option>")
.attr("value", obj.value)
.text(obj.text));
});
$("#id_autor").val(autor_selecionado);
}
});
}
});
$("#id_tipo_autor").trigger('change');
});
</script> </script>
{% endblock %} {% endblock %}

2
sapl/templates/norma/normajuridica_detail.html

@ -35,7 +35,7 @@
<hr /> <hr />
<div class="row-fluid"> <div class="row-fluid">
<div class="col-sm-12"> <div class="col-sm-12">
<p class="control-label">Relacionamentos</p> <p class="control-label">Normas Relacionadas</p>
{% if object.get_normas_relacionadas.0|length > 0 %} {% if object.get_normas_relacionadas.0|length > 0 %}
{% for p in object.get_normas_relacionadas.0 %} {% for p in object.get_normas_relacionadas.0 %}

3
sapl/templates/norma/subnav.yaml

@ -2,8 +2,9 @@
- title: {% trans 'Início' %} - title: {% trans 'Início' %}
url: normajuridica_detail url: normajuridica_detail
- title: {% trans 'Normas Relacionadas' %} - title: {% trans 'Alterações em Outras Normas' %}
url: normarelacionada_list url: normarelacionada_list
check_permission: norma.list_normarelacionada
# Opção adicionada para chamar o TextoArticulado da norma. # Opção adicionada para chamar o TextoArticulado da norma.
# para integração foram necessárias apenas criar a url norma_ta em urls.py # para integração foram necessárias apenas criar a url norma_ta em urls.py

30
sapl/templates/painel/index.html

@ -28,7 +28,7 @@
ul, li { ul, li {
list-style-type: none; list-style-type: none;
} }
#date, #sessao_plenaria, #sessao_plenaria_data, #sessao_plenaria_hora_inicio, #message, #cronometro_discurso, #cronometro_aparte, #cronometro_ordem, #relogio, #parlamentares, #votacao, #materia_legislativa_texto, #observacao_materia, #resultado_votacao, #orador { #date, #sessao_plenaria, #sessao_plenaria_data, #sessao_plenaria_hora_inicio, #message, #cronometro_discurso, #cronometro_aparte, #cronometro_ordem, #cronometro_consideracoes, #relogio, #parlamentares, #votacao, #materia_legislativa_texto, #observacao_materia, #resultado_votacao, #orador {
font-family: Verdana; font-family: Verdana;
} }
} }
@ -108,6 +108,9 @@
<tr> <tr>
<td style="font-family:Verdana; text-align:center;"><font size="5" color="white">Questão de Ordem: <span id="cronometro_ordem"></span></font></td> <td style="font-family:Verdana; text-align:center;"><font size="5" color="white">Questão de Ordem: <span id="cronometro_ordem"></span></font></td>
</tr> </tr>
<tr>
<td style="font-family:Verdana; text-align:center;"><font size="5" color="white">Considerações Finais: <span id="cronometro_consideracoes"></span></font></td>
</tr>
</table> </table>
</div> </div>
@ -195,9 +198,20 @@
audioAlertFinish.play(); audioAlertFinish.play();
}); });
$('#cronometro_consideracoes').runner({
autostart: false,
countdown: true,
startAt: {{ 'consideracoes'|cronometro_to_seconds }} * 1000,
stopAt: 0,
milliseconds: false
}).on('runnerFinish', function(eventObject, info){
audioAlertFinish.play();
});
var discurso_previous; var discurso_previous;
var ordem_previous; var ordem_previous;
var aparte_previous; var aparte_previous;
var consideracoes_previous;
var counter = 1; var counter = 1;
(function poll() { (function poll() {
@ -325,6 +339,16 @@
ordem_previous = ordem_current; ordem_previous = ordem_current;
} }
var consideracoes_current = data["cronometro_consideracoes"];
if (!consideracoes_previous){
consideracoes_previous = ''
}
if (consideracoes_current != consideracoes_previous) {
$('#cronometro_consideracoes').runner(consideracoes_current);
consideracoes_previous = consideracoes_current;
}
if($('#cronometro_discurso').runner('info').formattedTime == 30) { if($('#cronometro_discurso').runner('info').formattedTime == 30) {
audioAlertFinish.play(); audioAlertFinish.play();
} }
@ -337,6 +361,10 @@
audioAlertFinish.play(); audioAlertFinish.play();
} }
if($('#cronometro_consideracoes').runner('info').formattedTime == 30) {
audioAlertFinish.play();
}
if (data['materia_legislativa_texto']){ if (data['materia_legislativa_texto']){
$("#materia_legislativa_texto").text(data["materia_legislativa_texto"]); $("#materia_legislativa_texto").text(data["materia_legislativa_texto"]);
} }

12
sapl/templates/parlamentares/layouts.yaml

@ -37,10 +37,10 @@ Parlamentar:
- situacao_militar profissao - situacao_militar profissao
- endereco_web - endereco_web
- email - email
- numero_gab_parlamentar telefone fax - numero_gab_parlamentar telefone
- endereco_residencia cep_residencia - endereco_residencia cep_residencia
- municipio_residencia uf_residencia - municipio_residencia uf_residencia
- telefone_residencia fax_residencia - telefone_residencia
- locais_atuacao - locais_atuacao
- fotografia:5 - fotografia:5
- biografia - biografia
@ -54,10 +54,10 @@ ParlamentarUpdate:
- situacao_militar profissao - situacao_militar profissao
- endereco_web - endereco_web
- email - email
- numero_gab_parlamentar telefone fax - numero_gab_parlamentar telefone
- endereco_residencia cep_residencia - endereco_residencia cep_residencia
- municipio_residencia uf_residencia - municipio_residencia uf_residencia
- telefone_residencia fax_residencia - telefone_residencia
- locais_atuacao - locais_atuacao
- fotografia cropping - fotografia cropping
- biografia - biografia
@ -73,10 +73,10 @@ ParlamentarCreate:
- situacao_militar profissao - situacao_militar profissao
- endereco_web - endereco_web
- email - email
- numero_gab_parlamentar telefone fax - numero_gab_parlamentar telefone
- endereco_residencia cep_residencia - endereco_residencia cep_residencia
- municipio_residencia - municipio_residencia
- telefone_residencia fax_residencia - telefone_residencia
- locais_atuacao - locais_atuacao
- fotografia - fotografia
- biografia - biografia

10
sapl/templates/protocoloadm/comprovante.html

@ -61,14 +61,14 @@
<th>Data / Horário</th> <th>Data / Horário</th>
<td>{{ protocolo.data|date:"d/m/Y" }} - {{ protocolo.timestamp|date:"H:i:s" }}</td> <td>{{ protocolo.data|date:"d/m/Y" }} - {{ protocolo.timestamp|date:"H:i:s" }}</td>
</tr> </tr>
{% if protocolo.tipo_processo == 0 %} {% if protocolo.tipo_processo == 1 %}
<tr> <tr>
<th>Ementa</th> <th>Ementa</th>
<td>{{ protocolo.assunto_ementa }}</td> <td>{{ protocolo.assunto_ementa }}</td>
</tr> </tr>
<tr> <tr>
<th>Interessado</th> <th>Autor</th>
<td>{{ protocolo.interessado }}</td> <td>{{ protocolo.autor }}</td>
</tr> </tr>
{% endif %} {% endif %}
<tr> <tr>
@ -85,5 +85,9 @@
<th>Número Páginas</th> <th>Número Páginas</th>
<td>{{ protocolo.numero_paginas }}</td> <td>{{ protocolo.numero_paginas }}</td>
</tr> </tr>
<tr>
<th>Comprovante emitido por</th>
<td>{{ request.user.username }}</td>
</tr>
</table> </table>
{% endblock detail_content %} {% endblock detail_content %}

1
sapl/templates/protocoloadm/layouts.yaml

@ -11,6 +11,7 @@ DocumentoAdministrativo:
- interessado tramitacao - interessado tramitacao
- texto_integral - texto_integral
{% trans 'Outras Informações' %}: {% trans 'Outras Informações' %}:
- numero_externo
- dias_prazo data_fim_prazo - dias_prazo data_fim_prazo
- observacao - observacao

2
sapl/templates/protocoloadm/protocoloadm_detail.html

@ -5,5 +5,7 @@
<a href="{% url 'sapl.protocoloadm:protocolar_mat' %}" class="btn btn-default">{% trans 'Protocolar Matéria' %}</a> <a href="{% url 'sapl.protocoloadm:protocolar_mat' %}" class="btn btn-default">{% trans 'Protocolar Matéria' %}</a>
<a href="{% url 'sapl.protocoloadm:protocolar_doc' %}" class="btn btn-default">{% trans 'Protocolar Documento' %}</a> <a href="{% url 'sapl.protocoloadm:protocolar_doc' %}" class="btn btn-default">{% trans 'Protocolar Documento' %}</a>
<a href="{% url 'sapl.protocoloadm:anular_protocolo' %}" class="btn btn-default btn-excluir">{% trans 'Anular Protocolo' %}</a> <a href="{% url 'sapl.protocoloadm:anular_protocolo' %}" class="btn btn-default btn-excluir">{% trans 'Anular Protocolo' %}</a>
<a href="{% url 'sapl.protocoloadm:desvincular_documento' %}" class="btn btn-default btn-excluir">{% trans 'Desvincular Documentos' %}</a>
<a href="{% url 'sapl.protocoloadm:desvincular_materia' %}" class="btn btn-default btn-excluir">{% trans 'Desvincular Matérias' %}</a>
</div> </div>
{% endblock editions %} {% endblock editions %}

6
sapl/templates/sessao/blocos_resumo/conteudo_multimidia.html

@ -1,8 +1,8 @@
<fieldset> <fieldset>
<legend>Conteúdo Multimídia</legend> <legend>Conteúdo Multimídia</legend>
<div class="row"> <div class="row">
<div class="col-md-6">{{multimidia_audio}}</div> <div class="col-md-6">Audio: <a href={{multimidia_audio|slice:"6:"}}>{{multimidia_audio|slice:"6:"}}</a></div>
<div class="col-md-6">{{multimidia_video}}</div> <div class="col-md-6">Video: <a href={{multimidia_video|slice:"6:"}}>{{multimidia_video|slice:"6:"}}</a></div>
</div> </div>
</fieldset> </fieldset>
<br /><br /><br /> <br /><br /><br />

2
sapl/templates/sessao/blocos_resumo/expedientes.html

@ -6,7 +6,7 @@
<tr> <tr>
<td> <td>
<b>{{e.tipo}}: </b> <br /><br /> <b>{{e.tipo}}: </b> <br /><br />
<div contenteditable="false" style="border:0.5px solid #BAB4B1; border-radius: 10px; background-color: rgba(225, 225, 225, .8);"> <div style="border:0.5px solid #BAB4B1; border-radius: 10px; background-color: rgba(225, 225, 225, .8);">
<p>{{e.conteudo|safe}}</p> <p>{{e.conteudo|safe}}</p>
</div> </div>
</td> </td>

1
sapl/templates/sessao/expediente.html

@ -28,6 +28,7 @@
<br /> <br />
<input type="submit" value="Salvar" class="btn btn-primary"/> <input type="submit" value="Salvar" class="btn btn-primary"/>
<input type="submit" id="apagar-expediente" name="apagar-expediente" value="Apagar" class="btn btn-danger" />
</form> </form>
{% endif %} {% endif %}

2
sapl/templates/sessao/mesa.html

@ -38,7 +38,7 @@
<label id='parlamentar-cargo-title' style="display: none">Parlamentar | Cargo</label> <label id='parlamentar-cargo-title' style="display: none">Parlamentar | Cargo</label>
<select class="form-control" name="parlamentar" id="id_parlamentar" style="display: none"> <select class="form-control" name="parlamentar" id="id_parlamentar" style="display: none">
{% for p in parlamentares %} {% for p in parlamentares %}
<option value="{{p.id}}">{{p.nome_completo}}</option> <option value="{{p.id}}">{{p.nome_parlamentar}}</option>
{% endfor %} {% endfor %}
</select> </select>
<br /> <br />

92
sapl/templates/sessao/painel.html

@ -69,6 +69,20 @@
</div> </div>
<br/> <br/>
<br/> <br/>
<div class="row">
<div class="col-md-12"><h3>Cronômetro de Considerações Finais</h3></div>
</div>
<div class="row">
<div class="col-xs-2"><input size="2" id="consideracoes" name="consideracoes" value="" readyonly="true" class="form-control"></div>
</div>
<br />
<div class="row">
<div class="col-md-6"><button type="button" id="consideracoesStart" class="btn btn-success">Iniciar</button></div>
<div class="col-md-6"><button type="button" id="consideracoesReset" class="btn btn-success">Reiniciar</button></div>
</div>
<br /><br >
<div class="row"> <div class="row">
<div class="col-md-6"><button type="button" id="sinalSonoro" class="btn btn-success" onclick="document.getElementById('audio').play();">Sinal Sonoro</button></div> <div class="col-md-6"><button type="button" id="sinalSonoro" class="btn btn-success" onclick="document.getElementById('audio').play();">Sinal Sonoro</button></div>
</div> </div>
@ -102,6 +116,7 @@ $(function() {
$('#discurso').prop('disabled', true); $('#discurso').prop('disabled', true);
$('#aparte').prop('disabled', true); $('#aparte').prop('disabled', true);
$('#ordem').prop('disabled', true); $('#ordem').prop('disabled', true);
$('#consideracoes').prop('disabled', true);
$('#discurso').runner({ $('#discurso').runner({
autostart: false, autostart: false,
@ -119,6 +134,8 @@ $(function() {
$('#aparteReset').prop('disabled', false); $('#aparteReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false); $('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false); $('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
}); });
@ -135,6 +152,8 @@ $(function() {
$('#aparteReset').prop('disabled', false); $('#aparteReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false); $('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false); $('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} else { } else {
@ -147,6 +166,8 @@ $(function() {
$('#aparteReset').prop('disabled', false); $('#aparteReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false); $('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false); $('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} }
}); });
@ -174,6 +195,8 @@ $(function() {
$('#discursoReset').prop('disabled', false); $('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false); $('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false); $('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
}); });
@ -189,6 +212,8 @@ $(function() {
$('#discursoReset').prop('disabled', false); $('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false); $('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false); $('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} else { } else {
$.get('/painel/cronometro', { tipo: 'aparte', action: 'stop' } ); $.get('/painel/cronometro', { tipo: 'aparte', action: 'stop' } );
@ -200,6 +225,8 @@ $(function() {
$('#discursoReset').prop('disabled', false); $('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false); $('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false); $('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} }
}); });
@ -227,6 +254,8 @@ $(function() {
$('#discursoReset').prop('disabled', false); $('#discursoReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false); $('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false); $('#aparteReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
}); });
$('#ordemStart').click(function() { $('#ordemStart').click(function() {
@ -241,6 +270,8 @@ $(function() {
$('#discursoReset').prop('disabled', false); $('#discursoReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false); $('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false); $('#aparteReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} else { } else {
@ -253,6 +284,8 @@ $(function() {
$('#discursoReset').prop('disabled', false); $('#discursoReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false); $('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false); $('#aparteReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} }
}); });
@ -264,6 +297,65 @@ $(function() {
$('#ordem').runner('reset'); $('#ordem').runner('reset');
}); });
$('#consideracoes').runner({
autostart: false,
countdown: true,
startAt: {{cronometro_consideracoes}} * 1000,
stopAt: 0,
milliseconds: false
}).on('runnerFinish', function(eventObject, info){
$.get('/painel/cronometro', { tipo: 'consideracoes', action: 'stop' } );
$('#consideracoesReset').show();
$('#consideracoes').runner('stop');
$('#consideracoesStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
});
$('#consideracoesStart').click(function(){
if ($('#consideracoesStart').text() == 'Iniciar') {
$.get('/painel/cronometro', { tipo: 'consideracoes', action: 'start' } );
$('#consideracoesReset').hide();
$('#consideracoes').runner('start');
$('#consideracoesStart').text('Parar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
} else {
$.get('/painel/cronometro', { tipo: 'consideracoes', action: 'stop' } );
$('#consideracoesReset').show();
$('#consideracoes').runner('stop');
$('#consideracoesStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
}
});
$('#consideracoesReset').click(function() {
$.get('/painel/cronometro', { tipo: 'consideracoes', action: 'reset' } );
$('#consideracoes').runner('stop');
$('#consideracoes').runner('reset');
});
}); });
function switch_painel(aberto) { function switch_painel(aberto) {

4
sapl/test_urls.py

@ -164,7 +164,9 @@ apps_url_patterns_prefixs_and_users = {
'/logout', '/logout',
'/ajuda', '/ajuda',
'/email', '/email',
'/recuperar-senha' '/recuperar-senha',
'/sapl',
'/XSLT',
]}, ]},
'comissoes': { 'comissoes': {
'users': {'operador_geral': ['/sistema', '/comissao'], 'users': {'operador_geral': ['/sistema', '/comissao'],

5
sapl/urls.py

@ -21,6 +21,7 @@ from django.views.generic.base import RedirectView, TemplateView
from django.views.static import serve as view_static_server from django.views.static import serve as view_static_server
import sapl.api.urls import sapl.api.urls
import sapl.audiencia.urls
import sapl.base.urls import sapl.base.urls
import sapl.comissoes.urls import sapl.comissoes.urls
import sapl.compilacao.urls import sapl.compilacao.urls
@ -33,7 +34,6 @@ import sapl.protocoloadm.urls
import sapl.redireciona_urls.urls import sapl.redireciona_urls.urls
import sapl.relatorios.urls import sapl.relatorios.urls
import sapl.sessao.urls import sapl.sessao.urls
import sapl.audiencia.urls
urlpatterns = [ urlpatterns = [
url(r'^$', TemplateView.as_view(template_name='index.html'), url(r'^$', TemplateView.as_view(template_name='index.html'),
@ -62,9 +62,6 @@ urlpatterns = [
url(r'^favicon\.ico$', RedirectView.as_view( url(r'^favicon\.ico$', RedirectView.as_view(
url='/static/img/favicon.ico', permanent=True)), url='/static/img/favicon.ico', permanent=True)),
# Folhas XSLT e extras referenciadas por documentos migrados do sapl 2.5
url(r'^XSLT/HTML/(?P<path>.*)$', RedirectView.as_view(
url='/static/XSLT/HTML/%(path)s', permanent=False)),
url(r'', include(sapl.redireciona_urls.urls)), url(r'', include(sapl.redireciona_urls.urls)),
] ]

13
sapl/utils.py

@ -2,6 +2,7 @@ import hashlib
import logging import logging
import os import os
import re import re
import unicodedata
from functools import wraps from functools import wraps
from operator import itemgetter from operator import itemgetter
from unicodedata import normalize as unicodedata_normalize from unicodedata import normalize as unicodedata_normalize
@ -687,7 +688,7 @@ def ExtraiTag(texto, posicao):
return i + 1 return i + 1
def TrocaTag(texto, startTag, endTag, sizeStart, sizeEnd, styleName): def TrocaTag(texto, startTag, endTag, sizeStart, sizeEnd, styleName, subinitiTag, subendTag):
textoSaida = '' textoSaida = ''
insideTag = 0 insideTag = 0
i = 0 i = 0
@ -696,16 +697,19 @@ def TrocaTag(texto, startTag, endTag, sizeStart, sizeEnd, styleName):
if '<tbody>' in texto: if '<tbody>' in texto:
texto = texto.replace('<tbody>', '') texto = texto.replace('<tbody>', '')
texto = texto.replace('</tbody>', '') texto = texto.replace('</tbody>', '')
if '<p>' in texto:
texto = texto.replace('<p>', '')
texto = texto.replace('</p>', '')
while (i < len(texto)): while (i < len(texto)):
shard = texto[i:i + sizeStart] shard = texto[i:i + sizeStart]
if (shard == startTag): if (shard == startTag):
i = ExtraiTag(texto, i) i = ExtraiTag(texto, i)
textoSaida += '</para><blockTable style = "' + styleName + '">' textoSaida += subinitiTag + styleName + '">'
insideTag = 1 insideTag = 1
else: else:
if (insideTag == 1): if (insideTag == 1):
if (texto[i:i + sizeEnd] == endTag): if (texto[i:i + sizeEnd] == endTag):
textoSaida += 'blockTable><para>' textoSaida += subendTag
insideTag = 0 insideTag = 0
i += sizeEnd i += sizeEnd
else: else:
@ -732,3 +736,6 @@ def RemoveTag(texto):
i += 1 i += 1
return textoSaida return textoSaida
def remover_acentos(string):
return unicodedata.normalize('NFKD', string).encode('ASCII', 'ignore').decode()

10
check_migrations.sh → scripts/django/check_migrations.sh

@ -11,13 +11,15 @@
# A chamada do django 1.10 INVERTE ISSO. # A chamada do django 1.10 INVERTE ISSO.
# #
# https://docs.djangoproject.com/en/1.10/ref/django-admin/#cmdoption-makemigrations-check # https://docs.djangoproject.com/en/1.10/ref/django-admin/#cmdoption-makemigrations-check
if python manage.py makemigrations --dry-run --exit > /dev/null; then
git_project_root=$(git rev-parse --show-toplevel)
if python ${git_project_root}/manage.py makemigrations --dry-run --exit > /dev/null; then
NC='\033[0m' NC='\033[0m'
RED='\033[0;31m' RED='\033[0;31m'
echo echo
echo -e "${RED}ALGUMAS ALTERAÇÕES EXIGEM MIGRAÇÃO.${NC}" echo -e "${RED}ALGUMAS ALTERAÇÕES EXIGEM MIGRAÇÃO.${NC}"
echo -e "${RED}RODE 'python manage.py makemigrations' ANTES DE SUBMETER SEU CÓDIGO...${NC}" echo -e "${RED}Execute o comando 'python manage.py makemigrations' ANTES DE SUBMETER SEU CÓDIGO...${NC}"
echo -e "${RED}lembre de adicionar os arquivos criados ao git com 'git add .' ou semelhante.${NC}" echo -e "${RED}Lembre de adicionar os arquivos criados ao git com 'git add <arquivo>' ou semelhante.${NC}"
echo echo
exit 1 exit 1
fi fi

4
check_qa.sh → scripts/django/check_qa.sh

@ -2,6 +2,10 @@
# Verifica se um breakpoint foi esquecido no código # Verifica se um breakpoint foi esquecido no código
me=`basename "$0"` me=`basename "$0"`
git_project_root=$(git rev-parse --show-toplevel)
cd ${git_project_root}
busca=`grep --color=auto --exclude=$me --exclude=ipython_log.py* -r -l "pdb.set_trace()" .` busca=`grep --color=auto --exclude=$me --exclude=ipython_log.py* -r -l "pdb.set_trace()" .`
if [ ! -z "$busca" ] if [ ! -z "$busca" ]

2
fix_qa.sh → scripts/django/fix_qa.sh

@ -8,5 +8,7 @@
# Uma forma simples de fazer isso é adicionando antes suas mudanças à # Uma forma simples de fazer isso é adicionando antes suas mudanças à
# "staging area" do git, com `git add .` e após usar o script `git diff`. # "staging area" do git, com `git add .` e após usar o script `git diff`.
git_project_root=$(git rev-parse --show-toplevel)
cd ${git_project_root}
isort --recursive --skip='migrations' --skip='templates' --skip='ipython_log.py*' . isort --recursive --skip='migrations' --skip='templates' --skip='ipython_log.py*' .
autopep8 --in-place --recursive . --exclude='migrations,ipython_log.py*' autopep8 --in-place --recursive . --exclude='migrations,ipython_log.py*'

3
scripts/gerar_grafico_apps.sh → scripts/django/gerar_grafico_apps.sh

@ -1,3 +1,6 @@
#!/bin/bash #!/bin/bash
git_project_root=$(git rev-parse --show-toplevel)
cd ${git_project_root}
python -c "from sapl.settings import SAPL_APPS; print(*[s.split('.')[-1] for s in SAPL_APPS])" | xargs -t ./manage.py graph_models -d -g -o zzz.png -l fdp python -c "from sapl.settings import SAPL_APPS; print(*[s.split('.')[-1] for s in SAPL_APPS])" | xargs -t ./manage.py graph_models -d -g -o zzz.png -l fdp

5
scripts/reset_all_migrations.sh → scripts/django/reset_all_migrations.sh

@ -3,6 +3,11 @@
# Sends all django migrations to the trash bin # Sends all django migrations to the trash bin
# Requires trash-cli. To install: # Requires trash-cli. To install:
# sudo apt-get install trash-cli # sudo apt-get install trash-cli
hash trash-put 2>/dev/null || { echo >&2 "I require trash-put but it's not installed. Aborting."; exit 1; }
git_project_root=$(git rev-parse --show-toplevel)
cd ${git_project_root}
find -name 00*.py | grep /migrations/ | xargs trash-put find -name 00*.py | grep /migrations/ | xargs trash-put
# Make all migrations from scratch # Make all migrations from scratch

5
test_and_check_qa.sh → scripts/django/test_and_check_qa.sh

@ -2,6 +2,9 @@
# QA checks: run this before every commit # QA checks: run this before every commit
git_project_root=$(git rev-parse --show-toplevel)
cd ${git_project_root}
py.test py.test
py.test --ds=sapl.crud.tests.settings sapl/crud/tests py.test --ds=sapl.crud.tests.settings sapl/crud/tests
./check_qa.sh ./scripts/django/check_qa.sh

2
scripts/hooks/pre-commit

@ -4,5 +4,5 @@
if git diff --cached --name-status | grep -q '^M.*models\.py$'; then if git diff --cached --name-status | grep -q '^M.*models\.py$'; then
# se a checagem de migrations falhar impedimos o commit # se a checagem de migrations falhar impedimos o commit
set -e set -e
./check_migrations.sh ./scripts/django/check_migrations.sh
fi fi

14
scripts/redbaron.py

@ -1,10 +1,14 @@
import os import os
import re import re
import subprocess
from redbaron import RedBaron from redbaron import RedBaron
from redbaron.nodes import EndlNode, ReturnNode, StringNode from redbaron.nodes import EndlNode, ReturnNode, StringNode
root = '/home/mazza/work/sapl' git_project_root = subprocess.Popen(
["git", "rev-parse", "--show-toplevel"],
stdout=subprocess.PIPE
).communicate()[0].decode('utf-8').replace('\n', '')
def ignorado(path, name): def ignorado(path, name):
@ -13,13 +17,13 @@ def ignorado(path, name):
'relatorios/templates.*', 'relatorios/templates.*',
'.*/migrations', '.*/migrations',
]: ]:
if re.match(os.path.join(root, pattern), path): if re.match(os.path.join(git_project_root, pattern), path):
return True return True
return name.startswith('ipython_log.py') or name == 'manage.py' return name.startswith('ipython_log.py') or name == 'manage.py'
filenames = [os.path.join(path, name) filenames = [os.path.join(path, name)
for path, subdirs, files in os.walk(root) for path, subdirs, files in os.walk(git_project_root)
for name in files for name in files
if name.endswith('.py') and not ignorado(path, name)] if name.endswith('.py') and not ignorado(path, name)]
@ -37,7 +41,7 @@ def build_red(filename):
def write(node): def write(node):
red = node.root red = node.git_project_root
with open(red.__filename__, "w") as source_code: with open(red.__filename__, "w") as source_code:
source_code.write(red.dumps()) source_code.write(red.dumps())
@ -82,7 +86,7 @@ def fix(n):
def local(node): def local(node):
res = '%s:%s' % (node.root.__filename__, res = '%s:%s' % (node.git_project_root.__filename__,
node.absolute_bounding_box.top_left.line) node.absolute_bounding_box.top_left.line)
os.system("echo '%s' | xclip -selection c" % res) os.system("echo '%s' | xclip -selection c" % res)
return res return res

7
scripts_docker/remove-all-containers.sh

@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
sudo docker stop $(docker ps -a -q) # Parar containers sudo docker stop $(docker ps -a -q) # Para containers
sudo docker rm $(sudo docker ps -a -q) # Remover containers sudo docker rm $(sudo docker ps -a -q) # Remove containers
sudo docker rmi -f $( sudo docker images -q ) # Remover imagens sudo docker rmi -f $( sudo docker images -q ) # Remove imagens
sudo docker volume rm $(sudo docker volume ls -q -f dangling=true) # Remove volumes

2
setup.py

@ -49,7 +49,7 @@ install_requires = [
] ]
setup( setup(
name='interlegis-sapl', name='interlegis-sapl',
version='3.1.72', version='3.1.84',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007', license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007',

Loading…
Cancel
Save