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

2
docker-compose.yml

@ -11,7 +11,7 @@ sapldb:
ports:
- "5432:5432"
sapl:
image: interlegis/sapl:3.1.72
image: interlegis/sapl:3.1.84
restart: always
environment:
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 \
software-properties-common build-essential libxml2-dev libjpeg-dev \
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
curl -sL https://deb.nodesource.com/setup_6.x | bash -
curl -sL https://deb.nodesource.com/setup_8.x | bash -
exit
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``::
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
* 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 (*)
USER=`whoami` # the user 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
MAX_REQUESTS=100 # number of requests before restarting worker
DJANGO_SETTINGS_MODULE=sapl.settings # which settings file should Django use (*)
DJANGO_WSGI_MODULE=sapl.wsgi # WSGI module name (*)
@ -41,6 +42,7 @@ test -d $RUNDIR || mkdir -p $RUNDIR
exec gunicorn ${DJANGO_WSGI_MODULE}:application \
--name $NAME \
--workers $NUM_WORKERS \
--max-requests $MAX_REQUESTS \
--user $USER \
--access-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
GitPython
mysqlclient==1.3.12
pyaml

2
requirements/requirements.txt

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

20
sapl/audiencia/views.py

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

12
sapl/base/forms.py

@ -525,6 +525,11 @@ class RelatorioAtasFilterSet(django_filters.FilterSet):
model = SessaoPlenaria
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):
super(RelatorioAtasFilterSet, self).__init__(
*args, **kwargs)
@ -588,7 +593,7 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
@property
def qs(self):
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:
model = MateriaLegislativa
@ -628,7 +633,7 @@ class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet):
@property
def qs(self):
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:
model = MateriaLegislativa
@ -829,6 +834,7 @@ class ConfiguracoesAppForm(ModelForm):
'cronometro_discurso',
'cronometro_aparte',
'cronometro_ordem',
'cronometro_consideracoes',
'mostrar_brasao_painel',
'receber_recibo_proposicao']
@ -837,6 +843,8 @@ class ConfiguracoesAppForm(ModelForm):
self.fields['cronometro_discurso'].widget.attrs['class'] = 'cronometro'
self.fields['cronometro_aparte'].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):
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,
null=True)
cronometro_consideracoes = models.TimeField(
verbose_name=_('Cronômetro de Considerações Finais'),
blank=True,
null=True)
mostrar_brasao_painel = models.BooleanField(
default=False,
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.contrib.auth import views
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.views import (password_reset, password_reset_complete,
password_reset_confirm,
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.settings import EMAIL_SEND_USER
from sapl.settings import EMAIL_SEND_USER, MEDIA_URL
from .apps import AppConfig
from .forms import LoginForm, NovaSenhaForm, RecuperarSenhaForm
from .views import (AlterarSenha, AppConfigCrud, CasaLegislativaCrud,
CreateUsuarioView, DeleteUsuarioView, EditUsuarioView,
HelpTopicView, ListarUsuarioView, RelatorioAtasView,
RelatorioDataFimPrazoTramitacaoView,
HelpTopicView, ListarUsuarioView, LogotipoView,
RelatorioAtasView, RelatorioDataFimPrazoTramitacaoView,
RelatorioHistoricoTramitacaoView,
RelatorioMateriasPorAnoAutorTipoView,
RelatorioMateriasPorAutorView,
@ -120,4 +122,13 @@ urlpatterns = [
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

25
sapl/base/views.py

@ -1,5 +1,6 @@
from django.conf import settings
from django.contrib.auth import get_user_model, update_session_auth_hash
import os
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.auth.models import Group
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.utils.encoding import force_bytes
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.views.generic import (CreateView, DeleteView, DetailView, FormView,
ListView, UpdateView)
from django.views.generic.base import TemplateView
from django.utils.translation import string_concat
from django.views.generic import (CreateView, DeleteView, FormView, ListView,
UpdateView)
from django.views.generic.base import RedirectView, TemplateView
from django_filters.views import FilterView
from haystack.views import SearchView
from sapl import settings
from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm
from sapl.base.models import Autor, TipoAutor
from sapl.crud.base import CrudAux, make_pagination
@ -759,3 +761,14 @@ class AlterarSenha(FormView):
user.save()
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():
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'] <
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:
verbose_name = _('Período 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):
if self.data_inicio and self.data_fim:
@ -140,6 +141,7 @@ class Composicao(models.Model): # IGNORE
class Meta:
verbose_name = _('Composição de Comissão')
verbose_name_plural = _('Composições de Comissão')
ordering = ['periodo']
def __str__(self):
return '%s: %s' % (self.comissao.sigla, self.periodo)

7
sapl/comissoes/views.py

@ -51,6 +51,9 @@ class PeriodoComposicaoCrud(CrudAux):
class UpdateView(CrudAux.UpdateView):
form_class = PeriodoForm
# class ListView(CrudAux.ListView):
class ParticipacaoCrud(MasterDetailCrud):
model = Participacao
parent_field = 'composicao__comissao'
@ -112,7 +115,9 @@ class ComposicaoCrud(MasterDetailCrud):
composicao_pk = self.take_composicao_pk()
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:
context['composicao_pk'] = ultima_composicao.pk
else:

3
sapl/hashers.py

@ -46,11 +46,12 @@ ZOPE_SHA1_PREFIX = '{SSHA}'
def zope_encoded_password_to_django(encoded):
"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):]
salt = get_salt_from_zope_sha1(data)
hasher = ZopeSHA1PasswordHasher()
return super(ZopeSHA1PasswordHasher, hasher).encode(data, salt)
else:
# assume it's a plain password and use the default hashing
# a None password blocks login, forcing a password reset
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.management.base import BaseCommand
from sapl.legacy.migracao import migrar, migrar_dados
from sapl.legacy.migracao import migrar
class Command(BaseCommand):
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):
management.call_command('migrate')
somente_dados, interativo = options['dados'], not options['force']
if somente_dados:
migrar_dados(interativo=interativo)
else:
migrar(interativo=interativo)
migrar(interativo=False)

88
sapl/legacy/migracao.py

@ -1,42 +1,76 @@
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_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):
migrar_dados(interativo=interativo)
migrar_usuarios()
migrar_documentos()
def adornar_msg(msg):
return '\n{1}\n{0}\n{1}'.format(msg, '#' * len(msg))
# fonte: https://stackoverflow.com/a/17081026/1877490
def make_tarfile(output_filename, source_dir):
with tarfile.open(output_filename, "w:gz") as tar:
tar.add(source_dir, arcname=os.path.basename(source_dir))
def migrar(interativo=False):
if TAG_MARCO in REPO.tags:
info('A migração já está feita.')
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
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')
def compactar_media():
# tar de media/sapl
print('Criando tar de media... ', end='', flush=True)
tar_media = settings.MEDIA_ROOT.child('{}.media.tgz'.format(banco))
dir_media = settings.MEDIA_ROOT.child('sapl')
with tarfile.open(tar_media, "w:gz") as tar:
tar.add(dir_media, arcname=dir_media.name)
arq_tar = DIR_REPO.child('{}.media.tar'.format(NOME_BANCO_LEGADO))
arq_tar.remove()
subprocess.check_output(['tar', 'cfh', arq_tar, '-C', DIR_REPO, 'sapl'])
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 subprocess
import traceback
from collections import OrderedDict, defaultdict, namedtuple
from datetime import date
@ -7,10 +10,13 @@ from itertools import groupby
from operator import xor
from subprocess import PIPE, call
import git
import pkg_resources
import pyaml
import pytz
import reversion
import yaml
from bs4 import BeautifulSoup
from django.apps import apps
from django.contrib.auth import get_user_model
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.db import connections, transaction
from django.db.models import Max, Q
from pyaml import UnsafePrettyYAMLDumper
from unipath import Path
from sapl.base.models import AppConfig as AppConf
from sapl.base.models import Autor, TipoAutor, cria_models_tipo_autor
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 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,
Proposicao, StatusTramitacao, TipoDocumento,
TipoMateriaLegislativa, TipoProposicao,
@ -35,11 +46,11 @@ from sapl.parlamentares.models import (Legislatura, Mandato, Parlamentar,
Partido, TipoAfastamento)
from sapl.protocoloadm.models import (DocumentoAdministrativo, Protocolo,
StatusTramitacaoAdministrativo)
from sapl.sessao.models import (ExpedienteMateria, OrdemDia, RegistroVotacao,
TipoResultadoVotacao)
from sapl.settings import DATABASES, PROJECT_DIR
from sapl.sessao.models import (ExpedienteMateria, ExpedienteSessao, OrdemDia,
RegistroVotacao, TipoResultadoVotacao)
from sapl.utils import normalize
from .scripts.normaliza_dump_mysql import normaliza_dump_mysql
from .timezonesbrasil import get_timezone
# BASE ######################################################################
@ -136,6 +147,7 @@ for nome_novo, nome_antigo in (('comissao', 'cod_comissao'),
class CampoVirtual(namedtuple('CampoVirtual', 'model related_model')):
null = True
CAMPOS_VIRTUAIS_PROPOSICAO = {
TipoMateriaLegislativa: CampoVirtual(Proposicao, MateriaLegislativa),
TipoDocumento: CampoVirtual(Proposicao, DocumentoAdministrativo)
@ -143,6 +155,15 @@ CAMPOS_VIRTUAIS_PROPOSICAO = {
for campo_virtual in CAMPOS_VIRTUAIS_PROPOSICAO.values():
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_AUTOR = {related: CampoVirtual(Autor, related)
for related in (Parlamentar, Comissao, Partido)}
@ -159,6 +180,7 @@ for related, campo_antigo in [(Parlamentar, 'cod_parlamentar'),
def info(msg):
print('INFO: ' + msg)
ocorrencias = defaultdict(list)
@ -201,7 +223,7 @@ class ForeignKeyFaltando(ObjectDoesNotExist):
campo = campos_novos_para_antigos[self.field]
_, tabela, campos_pk = get_estrutura_legado(self.field.model)
pk = {c: getattr(self.old, c) for c in campos_pk}
sql = 'select * from {} where {}'.format(
sql = 'select * from {} where {};'.format(
tabela,
' and '.join(['{} = {}'.format(k, v) for k, v in pk.items()]))
return OrderedDict((('campo', campo),
@ -494,6 +516,8 @@ PROPAGACOES_DE_EXCLUSAO = [
('parlamentar', 'dependente', 'cod_parlamentar'),
('parlamentar', 'filiacao', 'cod_parlamentar'),
('parlamentar', 'mandato', 'cod_parlamentar'),
('parlamentar', 'composicao_mesa', 'cod_parlamentar'),
('parlamentar', 'composicao_comissao', 'cod_parlamentar'),
# comissao
('comissao', 'composicao_comissao', 'cod_comissao'),
@ -518,6 +542,11 @@ PROPAGACOES_DE_EXCLUSAO = [
('materia_legislativa', 'anexada', 'cod_materia_principal'),
('materia_legislativa', 'anexada', 'cod_materia_anexada'),
('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', 'tramitacao_administrativo', 'cod_documento'),
@ -548,6 +577,9 @@ def uniformiza_banco():
garante_coluna_no_legado('tipo_materia_legislativa',
'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)
# e dat_sessao em sessao_plenaria_presenca
if not existe_coluna_no_legado('sessao_plenaria_presenca',
@ -695,31 +727,26 @@ def fill_dados_basicos():
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):
sequence_name = '%s_id_seq' % model._meta.db_table
exec_sql('ALTER SEQUENCE %s RESTART WITH %s MINVALUE -1;' % (
sequence_name, id))
DIR_DADOS_MIGRACAO = Path('~/migracao_sapl/').expand()
PATH_TABELA_TIMEZONES = DIR_DADOS_MIGRACAO.child('tabela_timezones.yaml')
DIR_RESULTADOS = DIR_DADOS_MIGRACAO.child('resultados')
REPO = git.Repo.init(DIR_REPO)
def dict_representer(dumper, data):
return dumper.represent_dict(data.items())
yaml.add_representer(OrderedDict, dict_representer)
# 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)
PATH_TABELA_TIMEZONES = DIR_DADOS_MIGRACAO.child('tabela_timezones.yaml')
with open(PATH_TABELA_TIMEZONES, 'r') as arq:
tabela_timezones = yaml.load(arq)
municipio, uf, nome_timezone = tabela_timezones[sigla_casa]
@ -762,7 +789,27 @@ def populate_renamed_fields(new, old):
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):
# 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()
# excluindo database antigo.
@ -789,19 +836,16 @@ def migrar_dados(interativo=True):
info('Começando migração: ...')
try:
ocorrencias.clear()
dir_ocorrencias = DIR_RESULTADOS.child(date.today().isoformat())
dir_ocorrencias.mkdir(parents=True)
migrar_todos_os_models()
except Exception as e:
ocorrencias['traceback'] = str(traceback.format_exc())
raise e
finally:
# grava ocorrências
arq_ocorrencias = dir_ocorrencias.child(
nome_banco_legado + '.yaml')
arq_ocorrencias = Path(REPO.working_dir, 'ocorrencias.yaml')
with open(arq_ocorrencias, 'w') as arq:
dump = yaml.dump(dict(ocorrencias), allow_unicode=True)
arq.write(dump.replace('\n- ', '\n\n- '))
pyaml.dump(ocorrencias, arq, vspacing=1)
REPO.git.add([arq_ocorrencias.name])
info('Ocorrências salvas em\n {}'.format(arq_ocorrencias))
# 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
def migrar_todos_os_models():
def get_models_a_migrar():
models = [model for app in appconfs for model in app.models.values()
if model in field_renames]
# Devido à referência TipoProposicao.tipo_conteudo_related
@ -829,7 +873,11 @@ def migrar_todos_os_models():
move_para_depois_de(models, Proposicao,
[MateriaLegislativa, DocumentoAdministrativo])
for model in models:
return models
def migrar_todos_os_models():
for model in get_models_a_migrar():
migrar_model(model)
@ -852,10 +900,14 @@ def migrar_model(model):
def get_id_do_legado(old):
return getattr(old, nome_pk)
ultima_pk_legado = model_legado.objects.all().aggregate(
Max('pk'))['pk__max'] or 0
else:
# a pk no legado tem mais de um campo
old_records = iter_sql_records(tabela_legado)
get_id_do_legado = None
ultima_pk_legado = model_legado.objects.count()
ajuste_antes_salvar = AJUSTE_ANTES_SALVAR.get(model)
ajuste_depois_salvar = AJUSTE_DEPOIS_SALVAR.get(model)
@ -898,10 +950,13 @@ def migrar_model(model):
if 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:
last_pk = get_last_pk(model)
reinicia_sequence(model, last_pk + 1)
reinicia_sequence(model, ultima_pk_legado + 1)
# apaga registros migrados do legado
if sql_delete_legado:
@ -1061,22 +1116,16 @@ def adjust_tipoafastamento(new, old):
new.indicador = 'F'
TIPO_MATERIA_OU_TIPO_DOCUMENTO = {'M': TipoMateriaLegislativa,
'D': TipoDocumento}
def set_generic_fk(new, campo_virtual, old):
new.content_type = content_types[campo_virtual.related_model]
new.object_id = get_fk_related(campo_virtual, old)
def adjust_tipoproposicao(new, old):
"Aponta para o tipo relacionado de matéria ou documento"
value = old.tip_mat_ou_doc
model_tipo = TIPO_MATERIA_OU_TIPO_DOCUMENTO[old.ind_mat_ou_doc]
tipo = model_tipo.objects.filter(pk=value)
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})
if old.tip_mat_ou_doc:
campo_virtual = CAMPOS_VIRTUAIS_TIPO_PROPOSICAO[old.ind_mat_ou_doc]
set_generic_fk(new, campo_virtual, 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:
tipo_mat_ou_doc = type(new.tipo.tipo_conteudo_related)
campo_virtual = CAMPOS_VIRTUAIS_PROPOSICAO[tipo_mat_ou_doc]
new.content_type = content_types[campo_virtual.related_model]
new.object_id = get_fk_related(campo_virtual, old)
set_generic_fk(new, campo_virtual, old)
def adjust_statustramitacao(new, old):
@ -1162,10 +1210,9 @@ def adjust_autor(new, old):
break
if old.col_username:
user_model = get_user_model()
if not user_model.objects.filter(username=old.col_username).exists():
# cria um novo ususaŕio para o autor
user = user_model(username=old.col_username)
user, created = get_user_model().objects.get_or_create(
username=old.col_username)
if created:
# gera uma senha inutilizável, que precisará ser trocada
user.set_password(None)
with reversion.create_revision():
@ -1173,8 +1220,9 @@ def adjust_autor(new, old):
reversion.set_comment(
'Usuário criado pela migração para o autor {}'.format(
old.cod_autor))
grupo_autor = Group.objects.get(name="Autor")
user.groups.add(grupo_autor)
grupo_autor = Group.objects.get(name="Autor")
user.groups.add(grupo_autor)
new.user = user
def adjust_comissao(new, old):
@ -1199,6 +1247,21 @@ def adjust_tiporesultadovotacao(new, old):
{'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 = {
Autor: adjust_autor,
TipoAutor: adjust_tipo_autor,
@ -1220,10 +1283,71 @@ AJUSTE_ANTES_SALVAR = {
StatusTramitacaoAdministrativo: adjust_statustramitacaoadm,
Tramitacao: adjust_tramitacao,
TipoResultadoVotacao: adjust_tiporesultadovotacao,
ExpedienteSessao: adjust_expediente_sessao,
}
AJUSTE_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 re
from glob import glob
from os.path import join
import yaml
from django.db import transaction
from image_cropping.fields import ImageCropField
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,
Proposicao)
from sapl.norma.models import NormaJuridica
@ -14,107 +16,56 @@ from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import (DocumentoAcessorioAdministrativo,
DocumentoAdministrativo)
from sapl.sessao.models import SessaoPlenaria
from sapl.settings import MEDIA_ROOT
# MIGRAÇÃO DE DOCUMENTOS ###################################################
def get_ano(obj):
return [obj.ano]
def ___(obj):
return []
DOCS = {
CasaLegislativa: [
('logotipo',
'props_sapl/{}.*',
'public/casa/logotipo/',
___)
],
Parlamentar: [
('fotografia',
'parlamentar/fotos/{}_foto_parlamentar',
'public/parlamentar/{0}/',
___)
],
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}/',
___)
],
CasaLegislativa: [('logotipo', 'props_sapl/{}.*')],
Parlamentar: [('fotografia', 'parlamentar/fotos/{}_foto_parlamentar')],
MateriaLegislativa: [('texto_original', 'materia/{}_texto_integral')],
DocumentoAcessorio: [('arquivo', 'materia/{}')],
NormaJuridica: [('texto_integral', 'norma_juridica/{}_texto_integral')],
SessaoPlenaria: [('upload_pauta', 'pauta_sessao/{}_pauta_sessao'),
('upload_ata', 'ata_sessao/{}_ata_sessao'),
('upload_anexo', 'anexo_sessao/{}_texto_anexado')],
Proposicao: [('texto_original', 'proposicao/{}')],
DocumentoAdministrativo: [('texto_integral',
'administrativo/{}_texto_integral')],
DocumentoAcessorioAdministrativo: [('arquivo', 'administrativo/{}')],
}
DOCS = {model: [(campo,
os.path.join('sapl_documentos', origem),
os.path.join('sapl', destino),
get_extra_args)
for campo, origem, destino, get_extra_args in campos]
DOCS = {model: [(campo, join('sapl_documentos', origem))
for campo, origem, in campos]
for model, campos in DOCS.items()}
def em_media(caminho):
return os.path.join(MEDIA_ROOT, caminho)
def mover_documento(origem, destino):
origem, destino = [em_media(c) if not os.path.isabs(c) else c
def mover_documento(repo, origem, destino):
origem, destino = [join(repo.working_dir, c) if not os.path.isabs(c) else c
for c in (origem, destino)]
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 ####')
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:
propriedades = yaml.safe_load(arquivo)
casa = CasaLegislativa.objects.first()
@ -134,67 +85,73 @@ def migrar_propriedades_da_casa():
for campo, prop in campos_para_propriedades:
setattr(casa, campo, propriedades[prop])
# Localidade
# localidade
sql_localidade = '''
select nom_localidade, sgl_uf from localidade
where cod_localidade = {}'''.format(propriedades['cod_localidade'])
[(casa.municipio, casa.uf)] = exec_legado(sql_localidade)
print('.... Migrando logotipo da casa ....')
[(_, origem, destino, __)] = DOCS[CasaLegislativa]
# 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
# logotipo
migrar_logotipo(repo, casa, propriedades)
casa.save()
os.remove(caminho)
repo.git.rm(caminho)
def migrar_docs_por_ids(model):
for campo, base_origem, base_destino, get_extra_args in DOCS[model]:
def migrar_docs_por_ids(repo, model):
for campo, base_origem in DOCS[model]:
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+)')
pat = re.compile('^{}\.\w+$'.format(nome_origem))
if not os.path.isdir(dir_origem):
print(' >>> O diretório {} não existe! Abortado.'.format(
dir_origem))
continue
for arq in os.listdir(dir_origem):
match = pat.match(arq)
if match:
matches = [pat.match(arq) for arq in os.listdir(dir_origem)]
ids_origens = [(int(m.group(1)),
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
origem = os.path.join(dir_origem, match.group(0))
id = match.group(1)
try:
obj = model.objects.get(pk=id)
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)
obj = objetos.get(id)
if obj:
destino = upload_to(obj, os.path.basename(origem))
mover_documento(repo, origem, 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()
else:
msg = ' {} (pk={}) não encontrado para documento em [{}]'
print(msg.format(model.__name__, id, origem))
def migrar_documentos():
# aqui supomos que uma pasta chamada sapl_documentos está em MEDIA_ROOT
# com o conteúdo da pasta de mesmo nome do zope
# Os arquivos da pasta serão MOVIDOS para a nova estrutura!
# A pasta, após conferência do que não foi migrado, deve ser apagada.
def migrar_documentos(repo):
# aqui supomos que as pastas XSLT e sapl_documentos estão em
# <repo.working_dir> com o conteúdo exportado do zope
# Os arquivos das pastas serão (git) MOVIDOS para a nova estrutura!
#
# 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 [
Parlamentar,
@ -206,14 +163,13 @@ def migrar_documentos():
DocumentoAdministrativo,
DocumentoAcessorioAdministrativo,
]:
migrar_docs_por_ids(model)
migrar_docs_por_ids(repo, model)
sobrando = [os.path.join(dir, file)
for (dir, _, files) in os.walk(em_media('sapl_documentos'))
sobrando = [join(dir, file)
for (dir, _, files) in os.walk(join(repo.working_dir,
'sapl_documentos'))
for file in files]
if sobrando:
print('\n#### Encerrado ####\n\n'
'{} documentos sobraram sem ser migrados!!!'.format(
len(sobrando)))
for doc in sobrando:
print(' {}'. format(doc))

8
sapl/legacy/migracao_usuarios.py

@ -1,8 +1,8 @@
import yaml
from django.contrib.auth.models import Group, User
from unipath import Path
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)
for legado, novo in [
@ -44,9 +44,9 @@ def decode_nome(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.
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
"""
ARQUIVO_USUARIOS = MEDIA_ROOT.child('usuarios.yaml')
ARQUIVO_USUARIOS = Path(dir_repo).child('usuarios.yaml')
with open(ARQUIVO_USUARIOS, 'r') as f:
usuarios = yaml.load(f)
# 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()
dat_publicacao = models.DateField(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)
dat_origem_externa = models.DateField(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 None
def allow_migrate(self, db, model):
if model._meta.app_label == 'legacy':
def allow_migrate(self, db, app_label, model_name=None, **hints):
if app_label == 'legacy':
return False
return None

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

@ -5,6 +5,8 @@
# Esse script precisa rodar em python 2
# e depende apenas do descrito no arquivo requiments.txt
import cStringIO
import hashlib
import mimetypes
import os
import sys
@ -12,21 +14,32 @@ from collections import defaultdict
from contextlib import contextmanager
from functools import partial
import git
import magic
import yaml
import ZODB.DB
import ZODB.FileStorage
from unipath import Path
from ZODB.broken import Broken
from variaveis_comuns import DIR_DADOS_MIGRACAO, TAG_ZOPE
EXTENSOES = {
'application/msword': '.doc',
'application/pdf': '.pdf',
'application/vnd.oasis.opendocument.text': '.odt',
'application/vnd.ms-excel': '.xls',
'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',
'text/xml': '.xml',
'application/zip': '.zip',
'application/x-rar': '.rar',
'image/jpeg': '.jpeg',
'image/png': '.png',
'image/gif': '.gif',
@ -38,6 +51,11 @@ EXTENSOES = {
'image/tiff': '.tiff',
'application/tiff': '.tiff',
'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...
'text/richtext': '.rtf',
@ -45,7 +63,9 @@ EXTENSOES = {
# sem extensao
'application/octet-stream': '', # binário
'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
def guess_extension(caminho):
mime = magic.from_file(caminho, mime=True)
try:
return EXTENSOES[mime]
except KeyError as e:
msg = '\n'.join([
'Extensão não conhecida para o arquivo:',
caminho,
'E mimetype:',
mime,
' Algumas possibilidades são:', ] +
def guess_extension(fullname, buffer):
mime = magic.from_buffer(buffer, mime=True)
extensao = EXTENSOES.get(mime)
if extensao is not None:
return extensao
else:
possibilidades = '\n'.join(
[" '{}': '{}',".format(mime, ext)
for ext in mimetypes.guess_all_extensions(mime)] +
['Atualize o código do dicionário EXTENSOES!']
)
print(msg)
raise Exception(msg, e)
def dump_file(doc, path):
name = doc['__name__']
fullname = os.path.join(path, name)
for ext in mimetypes.guess_all_extensions(mime)])
print('''Extensão não conhecida para o arquivo: {}
e mimetype: {}
Algumas possibilidades são:
{}
Atualize o código do dicionário EXTENSOES!
'''.format(fullname, mime, possibilidades)
)
return '.DESCONHECIDO.{}'.format(mime.replace('/', '__'))
def get_conteudo_file(doc):
# A partir daqui usamos dict.pop('...') nos __Broken_state__
# para contornar um "vazamento" de memória que ocorre
# ao percorrer a árvore de objetos
@ -95,25 +112,28 @@ def dump_file(doc, path):
doc['data'] = pdata
pdata = doc
with open(fullname, 'w') as arq:
while pdata:
arq.write(pdata.pop('data'))
pdata = br(pdata.pop('next', None))
output = cStringIO.StringIO()
while pdata:
output.write(pdata.pop('data'))
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
def get_conteudo_dtml_method(doc):
return doc['raw']
def enumerate_by_key_list(folder, key_list, type_key):
for entry in folder.get(key_list, []):
id, meta_type = entry['id'], entry[type_key]
@ -131,11 +151,16 @@ enumerate_properties = partial(enumerate_by_key_list,
def enumerate_btree(folder):
contagem_esperada = folder['_count'].value
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):
obj, meta_type = br(obj), type(obj).__name__
yield id, obj, meta_type
# 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)
@ -148,14 +173,14 @@ def logando_nao_identificados():
if nao_identificados:
print('#' * 80)
print('#' * 80)
print(u'FORAM ENCONTRADOS ARQUIVOS DE FORMATO NÃO IDENTIFICADO!!!')
print(u'REFAÇA A EXPORTAÇÃO\n')
print('FORAM ENCONTRADOS ARQUIVOS DE FORMATO NÃO IDENTIFICADO!!!')
print('REFAÇA A EXPORTAÇÃO\n')
print(nao_identificados)
print('#' * 80)
print('#' * 80)
def dump_folder(folder, path='', enum=enumerate_folder):
def dump_folder(folder, path, salvar, enum=enumerate_folder):
name = folder['id']
path = os.path.join(path, name)
if not os.path.exists(path):
@ -165,7 +190,7 @@ def dump_folder(folder, path='', enum=enumerate_folder):
if dump == '?':
nao_identificados[meta_type].append(path + '/' + id)
elif dump:
id_interno = dump(obj, path)
id_interno = dump(obj, path, salvar)
assert id == id_interno
return name
@ -201,24 +226,24 @@ def read_sde(element):
return data
def save_as_yaml(path, name, obj):
def save_as_yaml(path, name, obj, salvar):
fullname = os.path.join(path, name)
with open(fullname, 'w') as arquivo:
yaml.safe_dump(obj, arquivo, allow_unicode=True)
print(fullname)
return fullname
conteudo = yaml.safe_dump(obj, allow_unicode=True)
salvar(fullname, conteudo)
def dump_sde(strdoc, path, tipo):
def dump_sde(strdoc, path, salvar, tipo):
id = strdoc['id']
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
DUMP_FUNCTIONS = {
'File': dump_file,
'Image': dump_file,
'DTML Method': partial(dump_file,
get_conteudo=get_conteudo_dtml_method),
'Folder': partial(dump_folder, enum=enumerate_folder),
'BTreeFolder2': partial(dump_folder, enum=enumerate_btree),
'SDE-Document': partial(dump_sde, tipo='sde.document'),
@ -233,7 +258,7 @@ DUMP_FUNCTIONS = {
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)
connection = db.open()
root = connection.root()
@ -255,42 +280,115 @@ def find_sapl(app):
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'])
ids = [p['id'] for p in props_sapl['_properties']]
props = {id: props_sapl[id] for id in ids}
props = {id: p.decode(encoding) if isinstance(p, str) else p
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 = {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)
try:
sapl = find_sapl(app)
# extrai folhas XSLT
dump_folder(br(sapl['XSLT']), destino)
dump_folder(br(sapl['XSLT']), destino, salvar)
# extrai usuários com suas senhas e perfis
dump_usuarios(sapl, destino)
dump_usuarios(sapl, destino, salvar)
# extrai documentos
docs = br(sapl['sapl_documentos'])
with logando_nao_identificados():
dump_folder(docs, destino)
dump_propriedades(docs, destino)
dump_folder(docs, destino, salvar)
dump_propriedades(docs, destino, salvar)
finally:
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 len(sys.argv) == 2:
data_fs_path = sys.argv[1]
dump_sapl(data_fs_path)
sigla = sys.argv[1]
dump_sapl(sigla)
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
PyYAML==3.12
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
# rodar esse script na raiz do projeto
if [ $# -ge 2 ]; then
# proteje pasta com dumps de alterações acidentais
# chmod -R -w ~/migracao_sapl/sapl_dumps
if [ $# -eq 1 ]; then
DIR_MIGRACAO=~/migracao_sapl
@ -20,28 +17,11 @@ if [ $# -ge 2 ]; then
echo "########################################" | tee -a $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 >> $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
else
echo "USO:"
echo " $0 <nome_database> <usuário mysql> [senha mysql]"
echo " $0 <nome_database>"
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"
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;"
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 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
config = Config(RepositoryEnv(BASE_DIR.child('legacy', '.env')))
@ -33,3 +36,10 @@ DEBUG = True
# delisga indexação fulltext em tempo real
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,
TipoNormaJuridica)
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.utils import (RANGE_ANOS, YES_NO_CHOICES,
ChoiceWithoutValidationField,
@ -162,6 +162,11 @@ class MateriaSimplificadaForm(ModelForm):
class MateriaLegislativaForm(ModelForm):
tipo_autor = ModelChoiceField(label=_('Tipo Autor'),
required=False,
queryset=TipoAutor.objects.all(),
empty_label=_('------'), )
autor = forms.ModelChoiceField(required=False,
empty_label='------',
queryset=Autor.objects.all()
@ -172,6 +177,15 @@ class MateriaLegislativaForm(ModelForm):
exclude = ['texto_articulado', 'autores', 'proposicao',
'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):
super(MateriaLegislativaForm, self).clean()
@ -182,29 +196,54 @@ class MateriaLegislativaForm(ModelForm):
data_apresentacao = cleaned_data['data_apresentacao']
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:
raise ValidationError("O ano da matéria não pode ser "
"diferente do ano na data de apresentação")
raise ValidationError(_("O ano da matéria não pode ser "
"diferente do ano na data de apresentação"))
ano_origem_externa = cleaned_data['ano_origem_externa']
data_origem_externa = cleaned_data['data_origem_externa']
if ano_origem_externa and data_origem_externa and \
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 "
"origem externa")
"origem externa"))
return cleaned_data
def save(self, commit=False):
if not self.instance.pk:
primeiro_autor = True
else:
primeiro_autor = False
materia = super(MateriaLegislativaForm, self).save(commit)
materia.save()
if self.cleaned_data['autor']:
autoria = Autoria()
autoria.primeiro_autor = True
autoria.primeiro_autor = primeiro_autor
autoria.materia = materia
autoria.autor = self.cleaned_data['autor']
autoria.save()
@ -582,8 +621,18 @@ class AnexadaForm(ModelForm):
msg = _('A matéria a ser anexada não existe no cadastro'
' de matérias legislativas.')
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

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,
verbose_name=_('Tipo'))
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(
blank=True, null=True, verbose_name=_('Ano'), choices=RANGE_ANOS)
data_origem_externa = models.DateField(
@ -325,9 +325,17 @@ class AcompanhamentoMateria(models.Model):
verbose_name_plural = _('Acompanhamentos de Matéria')
def __str__(self):
# FIXME str should be human readable, using hash is very strange
return _('%(materia)s - #%(hash)s') % {
'materia': self.materia, 'hash': self.hash}
if self.data_cadastro is None:
return _('%(materia)s - %(email)s') % {
'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()

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

13
sapl/norma/forms.py

@ -83,16 +83,19 @@ class NormaJuridicaForm(ModelForm):
label='Matéria',
required=False,
queryset=TipoMateriaLegislativa.objects.all(),
empty_label='Selecione'
empty_label='Selecione',
widget=forms.Select(attrs={'autocomplete': 'off'})
)
numero_materia = forms.CharField(
label='Número Matéria',
required=False
required=False,
widget=forms.TextInput(attrs={'autocomplete': 'off'})
)
ano_materia = forms.ChoiceField(
label='Ano Matéria',
required=False,
choices=ANO_CHOICES,
widget=forms.Select(attrs={'autocomplete': 'off'})
)
class Meta:
@ -122,7 +125,11 @@ class NormaJuridicaForm(ModelForm):
if not self.is_valid():
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
cleaned_data['numero_materia'] and
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.utils import timezone
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.edit import FormView
from django_filters.views import FilterView
@ -40,10 +40,9 @@ class NormaRelacionadaCrud(MasterDetailCrud):
model = NormaRelacionada
parent_field = 'norma_principal'
help_topic = 'norma_juridica'
public = [RP_LIST, RP_DETAIL]
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['norma_relacionada']
list_field_names = ['norma_relacionada', 'tipo_vinculo']
class CreateView(MasterDetailCrud.CreateView):
form_class = NormaRelacionadaForm
@ -52,11 +51,12 @@ class NormaRelacionadaCrud(MasterDetailCrud):
form_class = NormaRelacionadaForm
def get_initial(self):
self.initial['tipo'] = self.object.norma_relacionada.tipo.id
self.initial['numero'] = self.object.norma_relacionada.numero
self.initial['ano'] = self.object.norma_relacionada.ano
self.initial['ementa'] = self.object.norma_relacionada.ementa
return self.initial
initial = super(UpdateView, self).get_initial()
initial['tipo'] = self.object.norma_relacionada.tipo.id
initial['numero'] = self.object.norma_relacionada.numero
initial['ano'] = self.object.norma_relacionada.ano
initial['ementa'] = self.object.norma_relacionada.ementa
return initial
class DetailView(MasterDetailCrud.DetailView):
@ -172,12 +172,13 @@ class NormaCrud(Crud):
layout_key = 'NormaJuridicaCreate'
def get_initial(self):
initial = super(UpdateView, self).get_initial()
norma = NormaJuridica.objects.get(id=self.kwargs['pk'])
if norma.materia:
self.initial['tipo_materia'] = norma.materia.tipo
self.initial['ano_materia'] = norma.materia.ano
self.initial['numero_materia'] = norma.materia.numero
return self.initial.copy()
initial['tipo_materia'] = norma.materia.tipo
initial['ano_materia'] = norma.materia.ano
initial['numero_materia'] = norma.materia.numero
return initial
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 = (
('A', _('Aparte')),
('D', _('Discurso')),
('O', _('Ordem do dia'))
('O', _('Ordem do dia')),
('C', _('Considerações finais'))
)
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_discurso': get_cronometro_status(request, 'discurso'),
'cronometro_ordem': get_cronometro_status(request, 'ordem'),
'cronometro_consideracoes': get_cronometro_status(request, 'consideracoes'),
'status_painel': sessao.painel_aberto,
'brasao': brasao
}

4
sapl/parlamentares/forms.py

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

2
sapl/parlamentares/views.py

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

165
sapl/protocoloadm/forms.py

@ -155,6 +155,7 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
fields = ['tipo',
'numero',
'protocolo__numero',
'numero_externo',
'data',
'tramitacaoadministrativo__unidade_tramitacao_destino',
'tramitacaoadministrativo__status']
@ -173,7 +174,8 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
row2 = to_row(
[('ano', 4),
('protocolo__numero', 4),
('protocolo__numero', 2),
('numero_externo', 2),
('data', 4)])
row3 = to_row(
@ -645,6 +647,7 @@ class DocumentoAdministrativoForm(ModelForm):
'tramitacao',
'dias_prazo',
'data_fim_prazo',
'numero_externo',
'observacao',
'texto_integral',
'protocolo',
@ -655,9 +658,6 @@ class DocumentoAdministrativoForm(ModelForm):
def clean(self):
super(DocumentoAdministrativoForm, self).clean()
if not self.is_valid():
return self.cleaned_data
cleaned_data = self.cleaned_data
if not self.is_valid():
@ -665,14 +665,22 @@ class DocumentoAdministrativoForm(ModelForm):
numero_protocolo = self.data['numero_protocolo']
ano_protocolo = self.data['ano_protocolo']
numero_documento = self.cleaned_data['numero']
tipo_documento = self.data['tipo']
documento = DocumentoAdministrativo.objects.filter(numero=numero_documento,
tipo=tipo_documento, ano=ano_protocolo)
if documento:
raise ValidationError('Documento já existente')
numero_documento = int(self.cleaned_data['numero'])
tipo_documento = int(self.data['tipo'])
ano_documento = int(self.data['ano'])
# não permite atualizar para numero/ano/tipo existente
if self.instance.pk:
mudanca_doc = numero_documento != self.instance.numero \
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
if numero_protocolo and ano_protocolo:
@ -690,6 +698,22 @@ class DocumentoAdministrativoForm(ModelForm):
numero_protocolo, ano_protocolo))
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
def save(self, commit=True):
@ -720,7 +744,7 @@ class DocumentoAdministrativoForm(ModelForm):
[('texto_integral', 12)])
row6 = to_row(
[('dias_prazo', 6), ('data_fim_prazo', 6)])
[('numero_externo', 4), ('dias_prazo', 6), ('data_fim_prazo', 2)])
row7 = to_row(
[('observacao', 12)])
@ -733,3 +757,118 @@ class DocumentoAdministrativoForm(ModelForm):
row6, row7))
super(DocumentoAdministrativoForm, self).__init__(
*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?'),
choices=YES_NO_CHOICES)
assunto = models.TextField(verbose_name=_('Assunto'))
numero_externo = models.PositiveIntegerField(
blank=True,
null=True,
verbose_name=_('Número Externo'))
observacao = models.TextField(
blank=True, verbose_name=_('Observação'))
texto_integral = models.FileField(

8
sapl/protocoloadm/urls.py

@ -15,7 +15,9 @@ from sapl.protocoloadm.views import (AnularProtocoloAdmView,
TipoDocumentoAdministrativoCrud,
TramitacaoAdmCrud,
atualizar_numero_documento,
doc_texto_integral)
doc_texto_integral,
DesvincularDocumentoView,
DesvincularMateriaView)
from .apps import AppConfig
@ -61,6 +63,10 @@ urlpatterns_protocolo = [
url(r'^protocoloadm/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',
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.utils import timezone
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.edit import FormView
from django_filters.views import FilterView
import sapl
@ -30,7 +31,7 @@ from .forms import (AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm,
DocumentoAdministrativoFilterSet,
DocumentoAdministrativoForm, ProtocoloDocumentForm,
ProtocoloFilterSet, ProtocoloMateriaForm,
TramitacaoAdmEditForm, TramitacaoAdmForm)
TramitacaoAdmEditForm, TramitacaoAdmForm, DesvincularDocumentoForm, DesvincularMateriaForm)
from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo,
StatusTramitacaoAdministrativo,
TipoDocumentoAdministrativo, TramitacaoAdministrativo)
@ -451,10 +452,11 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
if not protocolo.numero:
protocolo.numero = (numero['numero__max'] + 1) if numero['numero__max'] else 1
if protocolo.numero < (numero['numero__max'] + 1):
msg = _('Número de protocolo deve ser maior que {}').format(numero['numero__max'])
messages.add_message(self.request, messages.ERROR, msg)
return self.render_to_response(self.get_context_data())
if numero['numero__max']:
if protocolo.numero < (numero['numero__max'] + 1):
msg = _('Número de protocolo deve ser maior que {}').format(numero['numero__max'])
messages.add_message(self.request, messages.ERROR, msg)
return self.render_to_response(self.get_context_data())
protocolo.ano = timezone.now().year
protocolo.data = timezone.now().date()
protocolo.hora = timezone.now().time()
@ -608,18 +610,19 @@ class TramitacaoAdmCrud(MasterDetailCrud):
form_class = TramitacaoAdmForm
def get_initial(self):
initial = super(CreateView, self).get_initial()
local = DocumentoAdministrativo.objects.get(
pk=self.kwargs['pk']).tramitacaoadministrativo_set.order_by(
'-data_tramitacao',
'-id').first()
if local:
self.initial['unidade_tramitacao_local'
initial['unidade_tramitacao_local'
] = local.unidade_tramitacao_destino.pk
else:
self.initial['unidade_tramitacao_local'] = ''
self.initial['data_tramitacao'] = timezone.now().date()
return self.initial
initial['unidade_tramitacao_local'] = ''
initial['data_tramitacao'] = timezone.now().date()
return initial
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -720,3 +723,39 @@ def atualizar_numero_documento(request):
{'numero': 1, 'ano': ano})
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)):
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
pdf = pdf_sessao_plenaria_gerar.principal(

42
sapl/sessao/forms.py

@ -124,7 +124,7 @@ class BancadaForm(ModelForm):
bancada = super(BancadaForm, self).save(commit)
content_type = ContentType.objects.get_for_model(Bancada)
object_id = bancada.pk
tipo = TipoAutor.objects.get(descricao='Bancada Parlamentar')
tipo = TipoAutor.objects.get(descricao__icontains='Bancada')
Autor.objects.create(
content_type=content_type,
object_id=object_id,
@ -159,7 +159,7 @@ class BlocoForm(ModelForm):
bloco = super(BlocoForm, self).save(commit)
content_type = ContentType.objects.get_for_model(Bloco)
object_id = bloco.pk
tipo = TipoAutor.objects.get(descricao='Bloco Parlamentar')
tipo = TipoAutor.objects.get(descricao__icontains='Bloco')
Autor.objects.create(
content_type=content_type,
object_id=object_id,
@ -179,15 +179,17 @@ class ExpedienteMateriaForm(ModelForm):
required=True,
queryset=TipoMateriaLegislativa.objects.all(),
empty_label='Selecione',
)
widget=forms.Select(attrs={'autocomplete': 'off'}))
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(
label='Ano Matéria',
initial=int(data_atual.year),
required=True)
required=True,
widget=forms.TextInput(attrs={'autocomplete': 'off'}))
data_ordem = forms.CharField(
label='Data Sessão',
@ -452,7 +454,7 @@ class OradorForm(ModelForm):
sessao_plenaria_id=id_sessao)]
self.fields['parlamentar'].queryset = Parlamentar.objects.filter(
id__in=ids).order_by('nome_completo')
id__in=ids).order_by('nome_parlamentar')
class Meta:
model = Orador
@ -464,14 +466,32 @@ class OradorExpedienteForm(ModelForm):
def __init__(self, *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:
legislatura_atual = legislatura_atual[0]
if legislatura_vigente:
self.fields['parlamentar'].queryset = \
Parlamentar.objects.filter(ativo=True,
mandato__legislatura=legislatura_atual
).order_by('nome_parlamentar')
mandato__legislatura=legislatura_vigente
).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):
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.auth.decorators import permission_required
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.db.models import Max, Q
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.translation import ugettext_lazy as _
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.detail import DetailView
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,
TipoMateriaLegislativa, Tramitacao)
from sapl.materia.views import MateriaLegislativaPesquisaView
from sapl.norma.models import NormaJuridica
from sapl.parlamentares.models import (Filiacao, Legislatura, Mandato,
Parlamentar, SessaoLegislativa)
from sapl.sessao.apps import AppConfig
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,
ExpedienteForm, ListMateriaForm, MesaForm,
@ -278,8 +277,8 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
else:
resultado = '''Não há resultado'''
else:
resultado = obj.registrovotacao_set.get(
materia_id=obj.materia_id)
resultado = obj.registrovotacao_set.filter(
materia_id=obj.materia_id).last()
resultado_descricao = resultado.tipo_resultado_votacao.nome
resultado_observacao = resultado.observacao
@ -386,13 +385,15 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
context['rows'][i][3] = (resultado, None)
return context
def get_presencas_generic(model, sessao, legislatura):
presencas = model.objects.filter(
sessao_plenaria=sessao)
presentes = [p.parlamentar for p in presencas]
presentes = sorted(presentes, key=lambda x: remover_acentos(x.nome_parlamentar))
mandato = Mandato.objects.filter(
legislatura=legislatura).order_by('parlamentar__nome_parlamentar')
@ -434,10 +435,11 @@ class MateriaOrdemDiaCrud(MasterDetailCrud):
form_class = OrdemDiaForm
def get_initial(self):
self.initial['tipo_materia'] = self.object.materia.tipo.id
self.initial['numero_materia'] = self.object.materia.numero
self.initial['ano_materia'] = self.object.materia.ano
return self.initial
initial = super(UpdateView, self).get_initial()
initial['tipo_materia'] = self.object.materia.tipo.id
initial['numero_materia'] = self.object.materia.numero
initial['ano_materia'] = self.object.materia.ano
return initial
class DetailView(MasterDetailCrud.DetailView):
@ -491,14 +493,15 @@ class ExpedienteMateriaCrud(MasterDetailCrud):
form_class = ExpedienteMateriaForm
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')
max_numero_ordem = ExpedienteMateria.objects.filter(
sessao_plenaria=self.kwargs['pk']).aggregate(
Max('numero_ordem'))['numero_ordem__max']
self.initial['numero_ordem'] = (
initial['numero_ordem'] = (
max_numero_ordem if max_numero_ordem else 0) + 1
return self.initial
return initial
def get_success_url(self):
return reverse('sapl.sessao:expedientemateria_list',
@ -508,10 +511,11 @@ class ExpedienteMateriaCrud(MasterDetailCrud):
form_class = ExpedienteMateriaForm
def get_initial(self):
self.initial['tipo_materia'] = self.object.materia.tipo.id
self.initial['numero_materia'] = self.object.materia.numero
self.initial['ano_materia'] = self.object.materia.ano
return self.initial
initial = super(UpdateView, self).get_initial()
initial['tipo_materia'] = self.object.materia.tipo.id
initial['numero_materia'] = self.object.materia.numero
initial['ano_materia'] = self.object.materia.ano
return initial
class DetailView(MasterDetailCrud.DetailView):
@ -760,6 +764,7 @@ class PainelView(PermissionRequiredForAppCrudMixin, TemplateView):
request.session['discurso'] = 'stop'
request.session['aparte'] = 'stop'
request.session['ordem'] = 'stop'
request.session['consideracoes'] = 'stop'
return TemplateView.get(self, request, *args, **kwargs)
@ -767,9 +772,10 @@ class PainelView(PermissionRequiredForAppCrudMixin, TemplateView):
cronometro_discurso = AppsAppConfig.attr('cronometro_discurso')
cronometro_aparte = AppsAppConfig.attr('cronometro_aparte')
cronometro_ordem = AppsAppConfig.attr('cronometro_ordem')
cronometro_consideracoes = AppsAppConfig.attr('cronometro_consideracoes')
if (not cronometro_discurso or not cronometro_aparte
or not cronometro_ordem):
or not cronometro_ordem or not cronometro_consideracoes):
msg = _(
'Você precisa primeiro configurar os cronômetros \
nas Configurações da Aplicação')
@ -785,6 +791,9 @@ class PainelView(PermissionRequiredForAppCrudMixin, TemplateView):
m, s, x = cronometro_ordem.isoformat().split(':')
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.update({
'head_title': str(_('Painel Plenário')),
@ -793,7 +802,8 @@ class PainelView(PermissionRequiredForAppCrudMixin, TemplateView):
'sessaoplenaria': SessaoPlenaria.objects.get(pk=kwargs['pk']),
'cronometro_discurso': cronometro_discurso,
'cronometro_aparte': cronometro_aparte,
'cronometro_ordem': cronometro_ordem})
'cronometro_ordem': cronometro_ordem,
'cronometro_consideracoes': cronometro_consideracoes})
return context
@ -978,20 +988,19 @@ class MesaView(FormMixin, DetailView):
return self.render_to_response(context)
mesa = sessao.integrantemesa_set.all() if sessao else []
cargos_ocupados = [m.cargo for m in mesa]
cargos = CargoMesa.objects.all()
cargos_vagos = list(set(cargos) - set(cargos_ocupados))
# 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_vagos = list(
set(
[p.parlamentar for p in parlamentares]) - set(
parlamentares_ocupados))
org_parlamentares_vagos = sorted(parlamentares_vagos, key=lambda x: x.nome_parlamentar)
org_parlamentares_vagos = parlamentares_vagos
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]
# Se todos os cargos estiverem ocupados, a listagem de parlamentares
# deve ser renderizada vazia
@ -1046,8 +1055,10 @@ def atualizar_mesa(request):
lista_composicao = [(c.id, c.parlamentar.__str__(),
c.cargo.__str__()) for c in composicao_mesa]
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_parlamentares.sort(key=lambda x: remover_acentos(x[1]))
return JsonResponse(
{'lista_composicao': lista_composicao,
@ -1113,15 +1124,13 @@ def remove_parlamentar_composicao(request):
if 'composicao_mesa' in request.POST:
try:
composicao = IntegranteMesa.objects.get(
id=int(request.POST['composicao_mesa']))
IntegranteMesa.objects.get(
id=int(request.POST['composicao_mesa'])).delete()
except ObjectDoesNotExist:
return JsonResponse(
{'msg': (
'Composição da Mesa não pôde ser removida!', 0)})
composicao.delete()
return JsonResponse(
{'msg': (
'Parlamentar excluido com sucesso!', 1)})
@ -1140,9 +1149,10 @@ class ResumoOrdenacaoView(PermissionRequiredMixin, FormView):
return reverse('sapl.base:sistema')
def get_initial(self):
initial = super(ResumoOrdenacaoView, self).get_initial()
ordenacao = ResumoOrdenacao.objects.first()
if ordenacao:
return {'primeiro': ordenacao.primeiro,
initial.update({'primeiro': ordenacao.primeiro,
'segundo': ordenacao.segundo,
'terceiro': ordenacao.terceiro,
'quarto': ordenacao.quarto,
@ -1151,8 +1161,8 @@ class ResumoOrdenacaoView(PermissionRequiredMixin, FormView):
'setimo': ordenacao.setimo,
'oitavo': ordenacao.oitavo,
'nono': ordenacao.nono,
'decimo': ordenacao.decimo}
return self.initial.copy()
'decimo': ordenacao.decimo})
return initial
def form_valid(self, form):
ordenacao = ResumoOrdenacao.objects.get_or_create()[0]
@ -1450,6 +1460,11 @@ class ExpedienteView(FormMixin, DetailView):
self.object = self.get_object()
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():
list_tipo = request.POST.getlist('tipo')
list_conteudo = request.POST.getlist('conteudo')
@ -1525,8 +1540,7 @@ class VotacaoEditView(SessaoPermissionMixin):
ordem_id = kwargs['oid']
if(int(request.POST['anular_votacao']) == 1):
for r in RegistroVotacao.objects.filter(ordem_id=ordem_id):
r.delete()
RegistroVotacao.objects.filter(ordem_id=ordem_id).delete()
ordem = OrdemDia.objects.get(
sessao_plenaria_id=self.object.id,
@ -1557,9 +1571,8 @@ class VotacaoEditView(SessaoPermissionMixin):
materia = {'materia': ordem.materia, 'ementa': ordem.materia.ementa}
context.update({'materia': materia})
votacao = RegistroVotacao.objects.filter(
materia_id=materia_id,
ordem_id=ordem_id).last()
votacao = RegistroVotacao.objects.filter(materia_id=materia_id,
ordem_id=ordem_id).last()
votacao_existente = {'observacao': sub(
'&nbsp;', ' ', strip_tags(votacao.observacao)),
'resultado': votacao.tipo_resultado_votacao.nome,
@ -1706,8 +1719,7 @@ def fechar_votacao_materia(materia):
VotoParlamentar.objects.filter(ordem=materia).delete()
elif type(materia) == ExpedienteMateria:
RegistroVotacao.objects.filter(
expediente=materia).delete()
RegistroVotacao.objects.filter(expediente=materia).delete()
VotoParlamentar.objects.filter(expediente=materia).delete()
if materia.resultado:
@ -1755,7 +1767,7 @@ class VotacaoNominalAbstract(SessaoPermissionMixin):
elif self.expediente:
expediente_id = kwargs['oid']
if (RegistroVotacao.objects.filter(
expediente_id=expediente_id).exists()):
expediente_id=expediente_id).exists()):
msg = _('Esta matéria já foi votada!')
messages.add_message(request, messages.ERROR, msg)
return HttpResponseRedirect(reverse(
@ -1842,8 +1854,7 @@ class VotacaoNominalAbstract(SessaoPermissionMixin):
return self.form_invalid(form)
# Remove todas as votação desta matéria, caso existam
if self.ordem:
RegistroVotacao.objects.filter(
ordem_id=ordem_id).delete()
RegistroVotacao.objects.filter(ordem_id=ordem_id).delete()
elif self.expediente:
RegistroVotacao.objects.filter(
expediente_id=expediente_id).delete()
@ -1971,11 +1982,10 @@ class VotacaoNominalEditAbstract(SessaoPermissionMixin):
if self.ordem:
ordem_id = kwargs['oid']
try:
ordem = OrdemDia.objects.get(id=ordem_id)
votacao = RegistroVotacao.objects.get(
ordem_id=ordem_id)
except ObjectDoesNotExist:
ordem = OrdemDia.objects.filter(id=ordem_id).last()
votacao = RegistroVotacao.objects.filter(ordem_id=ordem_id).last()
if not ordem or not votacao:
raise Http404()
materia = ordem.materia
@ -1984,11 +1994,10 @@ class VotacaoNominalEditAbstract(SessaoPermissionMixin):
elif self.expediente:
expediente_id = kwargs['oid']
try:
expediente = ExpedienteMateria.objects.get(id=expediente_id)
votacao = RegistroVotacao.objects.get(
expediente_id=expediente_id)
except ObjectDoesNotExist:
expediente = ExpedienteMateria.objects.filter(id=expediente_id).last()
votacao = RegistroVotacao.objects.filter(expediente_id=expediente_id).last()
if not expediente or not votacao:
raise Http404()
materia = expediente.materia
@ -2105,9 +2114,9 @@ class VotacaoNominalTransparenciaDetailView(TemplateView):
materia_votacao = self.request.GET.get('materia', None)
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':
votacao = RegistroVotacao.objects.get(expediente=self.kwargs['oid'])
votacao = RegistroVotacao.objects.filter(expediente=self.kwargs['oid']).last()
else:
raise Http404()
@ -2141,10 +2150,9 @@ class VotacaoNominalExpedienteDetailView(DetailView):
materia_id = kwargs['mid']
expediente_id = kwargs['oid']
votacao = RegistroVotacao.objects.get(
materia_id=materia_id,
expediente_id=expediente_id)
expediente = ExpedienteMateria.objects.get(id=expediente_id)
votacao = RegistroVotacao.objects.filter(materia_id=materia_id,
expediente_id=expediente_id).last()
expediente = ExpedienteMateria.objects.filter(id=expediente_id).last()
votos = VotoParlamentar.objects.filter(votacao_id=votacao.id)
list_votos = []
@ -2189,9 +2197,9 @@ class VotacaoSimbolicaTransparenciaDetailView(TemplateView):
materia_votacao = self.request.GET.get('materia', None)
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':
votacao = RegistroVotacao.objects.get(expediente=self.kwargs['oid'])
votacao = RegistroVotacao.objects.filter(expediente=self.kwargs['oid']).last()
else:
raise Http404()
@ -2377,14 +2385,9 @@ class VotacaoExpedienteEditView(SessaoPermissionMixin):
'ementa': expediente.materia.ementa}
context.update({'materia': materia})
try:
votacao = RegistroVotacao.objects.get(
materia_id=materia_id,
expediente_id=expediente_id)
except MultipleObjectsReturned:
votacao = RegistroVotacao.objects.filter(
materia_id=materia_id,
expediente_id=expediente_id).last()
votacao = RegistroVotacao.objects.filter(materia_id=materia_id,
expediente_id=expediente_id
).last()
votacao_existente = {'observacao': sub(
'&nbsp;', ' ', strip_tags(votacao.observacao)),
'resultado': votacao.tipo_resultado_votacao.nome,
@ -2403,10 +2406,8 @@ class VotacaoExpedienteEditView(SessaoPermissionMixin):
materia_id = kwargs['mid']
expediente_id = kwargs['oid']
if(int(request.POST['anular_votacao']) == 1):
for r in RegistroVotacao.objects.filter(
expediente_id=expediente_id):
r.delete()
if int(request.POST['anular_votacao']) == 1:
RegistroVotacao.objects.filter(expediente_id=expediente_id).delete()
expediente = ExpedienteMateria.objects.get(
sessao_plenaria_id=self.object.id,
@ -2596,7 +2597,7 @@ class PesquisarSessaoPlenariaView(FilterView):
'tipo', 'sessao_legislativa', 'legislatura')
qs = qs.distinct().order_by(
'-legislatura__numero', '-data_inicio', '-numero')
'-legislatura__numero', '-data_inicio', '-hora_inicio')
kwargs.update({
'queryset': qs,

2
sapl/settings.py

@ -127,7 +127,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
'speedinfo.middleware.ProfilerMiddleware',
# 'speedinfo.middleware.ProfilerMiddleware', # Bug na versão 1.9
)
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
{% trans 'Cronômetros do Painel' %}:
- cronometro_discurso cronometro_aparte cronometro_ordem
- cronometro_discurso cronometro_aparte cronometro_ordem cronometro_consideracoes
{% trans 'Configurações do Painel' %}:
- mostrar_brasao_painel

2
sapl/templates/materia/layouts.yaml

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

38
sapl/templates/materia/materialegislativa_form.html

@ -22,6 +22,44 @@
}
}
$("#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>
{% endblock %}

2
sapl/templates/norma/normajuridica_detail.html

@ -35,7 +35,7 @@
<hr />
<div class="row-fluid">
<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 %}
{% for p in object.get_normas_relacionadas.0 %}

3
sapl/templates/norma/subnav.yaml

@ -2,8 +2,9 @@
- title: {% trans 'Início' %}
url: normajuridica_detail
- title: {% trans 'Normas Relacionadas' %}
- title: {% trans 'Alterações em Outras Normas' %}
url: normarelacionada_list
check_permission: norma.list_normarelacionada
# Opção adicionada para chamar o TextoArticulado da norma.
# 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 {
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;
}
}
@ -108,6 +108,9 @@
<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>
</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>
</div>
@ -195,9 +198,20 @@
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 ordem_previous;
var aparte_previous;
var consideracoes_previous;
var counter = 1;
(function poll() {
@ -325,6 +339,16 @@
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) {
audioAlertFinish.play();
}
@ -337,6 +361,10 @@
audioAlertFinish.play();
}
if($('#cronometro_consideracoes').runner('info').formattedTime == 30) {
audioAlertFinish.play();
}
if (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
- endereco_web
- email
- numero_gab_parlamentar telefone fax
- numero_gab_parlamentar telefone
- endereco_residencia cep_residencia
- municipio_residencia uf_residencia
- telefone_residencia fax_residencia
- telefone_residencia
- locais_atuacao
- fotografia:5
- biografia
@ -54,10 +54,10 @@ ParlamentarUpdate:
- situacao_militar profissao
- endereco_web
- email
- numero_gab_parlamentar telefone fax
- numero_gab_parlamentar telefone
- endereco_residencia cep_residencia
- municipio_residencia uf_residencia
- telefone_residencia fax_residencia
- telefone_residencia
- locais_atuacao
- fotografia cropping
- biografia
@ -73,10 +73,10 @@ ParlamentarCreate:
- situacao_militar profissao
- endereco_web
- email
- numero_gab_parlamentar telefone fax
- numero_gab_parlamentar telefone
- endereco_residencia cep_residencia
- municipio_residencia
- telefone_residencia fax_residencia
- telefone_residencia
- locais_atuacao
- fotografia
- biografia

10
sapl/templates/protocoloadm/comprovante.html

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

1
sapl/templates/protocoloadm/layouts.yaml

@ -11,6 +11,7 @@ DocumentoAdministrativo:
- interessado tramitacao
- texto_integral
{% trans 'Outras Informações' %}:
- numero_externo
- dias_prazo data_fim_prazo
- 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_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: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>
{% endblock editions %}

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

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

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

@ -6,7 +6,7 @@
<tr>
<td>
<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>
</div>
</td>

1
sapl/templates/sessao/expediente.html

@ -28,6 +28,7 @@
<br />
<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>
{% endif %}

2
sapl/templates/sessao/mesa.html

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

92
sapl/templates/sessao/painel.html

@ -69,6 +69,20 @@
</div>
<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="col-md-6"><button type="button" id="sinalSonoro" class="btn btn-success" onclick="document.getElementById('audio').play();">Sinal Sonoro</button></div>
</div>
@ -102,6 +116,7 @@ $(function() {
$('#discurso').prop('disabled', true);
$('#aparte').prop('disabled', true);
$('#ordem').prop('disabled', true);
$('#consideracoes').prop('disabled', true);
$('#discurso').runner({
autostart: false,
@ -119,6 +134,8 @@ $(function() {
$('#aparteReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
});
@ -135,6 +152,8 @@ $(function() {
$('#aparteReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} else {
@ -147,6 +166,8 @@ $(function() {
$('#aparteReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
}
});
@ -174,6 +195,8 @@ $(function() {
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
});
@ -189,6 +212,8 @@ $(function() {
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} else {
$.get('/painel/cronometro', { tipo: 'aparte', action: 'stop' } );
@ -200,6 +225,8 @@ $(function() {
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
}
});
@ -227,6 +254,8 @@ $(function() {
$('#discursoReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
});
$('#ordemStart').click(function() {
@ -241,6 +270,8 @@ $(function() {
$('#discursoReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} else {
@ -253,6 +284,8 @@ $(function() {
$('#discursoReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
}
});
@ -264,6 +297,65 @@ $(function() {
$('#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) {

4
sapl/test_urls.py

@ -164,7 +164,9 @@ apps_url_patterns_prefixs_and_users = {
'/logout',
'/ajuda',
'/email',
'/recuperar-senha'
'/recuperar-senha',
'/sapl',
'/XSLT',
]},
'comissoes': {
'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
import sapl.api.urls
import sapl.audiencia.urls
import sapl.base.urls
import sapl.comissoes.urls
import sapl.compilacao.urls
@ -33,7 +34,6 @@ import sapl.protocoloadm.urls
import sapl.redireciona_urls.urls
import sapl.relatorios.urls
import sapl.sessao.urls
import sapl.audiencia.urls
urlpatterns = [
url(r'^$', TemplateView.as_view(template_name='index.html'),
@ -62,9 +62,6 @@ urlpatterns = [
url(r'^favicon\.ico$', RedirectView.as_view(
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)),
]

13
sapl/utils.py

@ -2,6 +2,7 @@ import hashlib
import logging
import os
import re
import unicodedata
from functools import wraps
from operator import itemgetter
from unicodedata import normalize as unicodedata_normalize
@ -687,7 +688,7 @@ def ExtraiTag(texto, posicao):
return i + 1
def TrocaTag(texto, startTag, endTag, sizeStart, sizeEnd, styleName):
def TrocaTag(texto, startTag, endTag, sizeStart, sizeEnd, styleName, subinitiTag, subendTag):
textoSaida = ''
insideTag = 0
i = 0
@ -696,16 +697,19 @@ def TrocaTag(texto, startTag, endTag, sizeStart, sizeEnd, styleName):
if '<tbody>' in texto:
texto = texto.replace('<tbody>', '')
texto = texto.replace('</tbody>', '')
if '<p>' in texto:
texto = texto.replace('<p>', '')
texto = texto.replace('</p>', '')
while (i < len(texto)):
shard = texto[i:i + sizeStart]
if (shard == startTag):
i = ExtraiTag(texto, i)
textoSaida += '</para><blockTable style = "' + styleName + '">'
textoSaida += subinitiTag + styleName + '">'
insideTag = 1
else:
if (insideTag == 1):
if (texto[i:i + sizeEnd] == endTag):
textoSaida += 'blockTable><para>'
textoSaida += subendTag
insideTag = 0
i += sizeEnd
else:
@ -732,3 +736,6 @@ def RemoveTag(texto):
i += 1
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.
#
# 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'
RED='\033[0;31m'
echo
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}lembre de adicionar os arquivos criados ao git com 'git add .' ou semelhante.${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 <arquivo>' ou semelhante.${NC}"
echo
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
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()" .`
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 à
# "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*' .
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
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

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

@ -3,6 +3,11 @@
# Sends all django migrations to the trash bin
# Requires trash-cli. To install:
# 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
# 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
git_project_root=$(git rev-parse --show-toplevel)
cd ${git_project_root}
py.test
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
# se a checagem de migrations falhar impedimos o commit
set -e
./check_migrations.sh
./scripts/django/check_migrations.sh
fi

14
scripts/redbaron.py

@ -1,10 +1,14 @@
import os
import re
import subprocess
from redbaron import RedBaron
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):
@ -13,13 +17,13 @@ def ignorado(path, name):
'relatorios/templates.*',
'.*/migrations',
]:
if re.match(os.path.join(root, pattern), path):
if re.match(os.path.join(git_project_root, pattern), path):
return True
return name.startswith('ipython_log.py') or name == 'manage.py'
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
if name.endswith('.py') and not ignorado(path, name)]
@ -37,7 +41,7 @@ def build_red(filename):
def write(node):
red = node.root
red = node.git_project_root
with open(red.__filename__, "w") as source_code:
source_code.write(red.dumps())
@ -82,7 +86,7 @@ def fix(n):
def local(node):
res = '%s:%s' % (node.root.__filename__,
res = '%s:%s' % (node.git_project_root.__filename__,
node.absolute_bounding_box.top_left.line)
os.system("echo '%s' | xclip -selection c" % res)
return res

7
scripts_docker/remove-all-containers.sh

@ -1,4 +1,5 @@
#!/bin/bash
sudo docker stop $(docker ps -a -q) # Parar containers
sudo docker rm $(sudo docker ps -a -q) # Remover containers
sudo docker rmi -f $( sudo docker images -q ) # Remover imagens
sudo docker stop $(docker ps -a -q) # Para containers
sudo docker rm $(sudo docker ps -a -q) # Remove containers
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(
name='interlegis-sapl',
version='3.1.72',
version='3.1.84',
packages=find_packages(),
include_package_data=True,
license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007',

Loading…
Cancel
Save