Browse Source

Merge branch '3.1.x'

pull/1828/merge
Edward Ribeiro 7 years ago
parent
commit
f07c2eabb5
  1. 2
      docker-compose.yml
  2. 51
      sapl/audiencia/forms.py
  3. 20
      sapl/audiencia/migrations/0005_auto_20180806_1236.py
  4. 20
      sapl/audiencia/migrations/0006_auto_20180808_0856.py
  5. 2
      sapl/audiencia/models.py
  6. 26
      sapl/audiencia/tests/test_audiencia.py
  7. 4
      sapl/audiencia/views.py
  8. 11
      sapl/base/forms.py
  9. 20
      sapl/base/migrations/0018_auto_20180801_1652.py
  10. 21
      sapl/base/migrations/0019_auto_20180815_1025.py
  11. 24
      sapl/base/models.py
  12. 19
      sapl/base/templatetags/common_tags.py
  13. 2
      sapl/base/views.py
  14. 2
      sapl/comissoes/forms.py
  15. 39
      sapl/compilacao/models.py
  16. 29
      sapl/compilacao/views.py
  17. 4
      sapl/crud/base.py
  18. 11
      sapl/legacy/management/commands/migracao_25_31.py
  19. 4
      sapl/legacy/migracao.py
  20. 52
      sapl/legacy/migracao_dados.py
  21. 1
      sapl/legacy/migracao_usuarios.py
  22. 59
      sapl/legacy/scripts/ressucita_dependencias.py
  23. 6
      sapl/materia/forms.py
  24. 10
      sapl/materia/views.py
  25. 37
      sapl/norma/forms.py
  26. 31
      sapl/norma/migrations/0012_anexonormajuridica.py
  27. 34
      sapl/norma/models.py
  28. 6
      sapl/norma/urls.py
  29. 48
      sapl/norma/views.py
  30. 178
      sapl/painel/views.py
  31. 27
      sapl/parlamentares/forms.py
  32. 20
      sapl/parlamentares/migrations/0024_auto_20180814_1237.py
  33. 2
      sapl/parlamentares/models.py
  34. 10
      sapl/parlamentares/views.py
  35. 16
      sapl/protocoloadm/views.py
  36. 17
      sapl/redireciona_urls/urls.py
  37. 48
      sapl/redireciona_urls/views.py
  38. 4
      sapl/rules/apps.py
  39. 1
      sapl/rules/map_rules.py
  40. 18
      sapl/sessao/forms.py
  41. 18
      sapl/sessao/tests/test_sessao.py
  42. 7
      sapl/settings.py
  43. 2
      sapl/templates/base.html
  44. 37
      sapl/templates/base/RelatorioMateriasPorAutor_filter.html
  45. 2
      sapl/templates/compilacao/dispositivo_form.html
  46. 10
      sapl/templates/materia/materialegislativa_detail.html
  47. 9
      sapl/templates/materia/prop_pendentes_list.html
  48. 5
      sapl/templates/norma/layouts.yaml
  49. 16
      sapl/templates/norma/normajuridica_detail.html
  50. 2
      sapl/templates/norma/subnav.yaml
  51. 4
      sapl/templates/painel/index.html
  52. 2
      sapl/templates/painel/voto_nominal.html
  53. 8
      sapl/templates/protocoloadm/documentoadministrativo_filter.html
  54. 10
      sapl/templates/sessao/adicionar_varias_materias_expediente.html
  55. 5
      sapl/test_urls.py
  56. 2
      setup.py

2
docker-compose.yml

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

51
sapl/audiencia/forms.py

@ -16,18 +16,21 @@ class AudienciaForm(forms.ModelForm):
tipo_materia = forms.ModelChoiceField(
label=_('Tipo Matéria'),
required=True,
required=False,
queryset=TipoMateriaLegislativa.objects.all(),
empty_label='Selecione',
)
numero_materia = forms.CharField(
label='Número Matéria', required=True)
label='Número Matéria', required=False)
ano_materia = forms.CharField(
label='Ano Matéria',
initial=int(data_atual.year),
required=True)
required=False)
materia = forms.ModelChoiceField(required=False,
widget=forms.HiddenInput(),
queryset=MateriaLegislativa.objects.all())
class Meta:
model = AudienciaPublica
@ -36,7 +39,7 @@ class AudienciaForm(forms.ModelForm):
'observacao', 'audiencia_cancelada', 'url_audio',
'url_video', 'upload_pauta', 'upload_ata',
'upload_anexo', 'tipo_materia', 'numero_materia',
'ano_materia']
'ano_materia', 'materia']
def __init__(self, **kwargs):
@ -59,27 +62,43 @@ class AudienciaForm(forms.ModelForm):
if not self.is_valid():
return cleaned_data
materia = cleaned_data['numero_materia']
ano_materia = cleaned_data['ano_materia']
tipo_materia = cleaned_data['tipo_materia']
if materia and ano_materia and tipo_materia:
try:
materia = MateriaLegislativa.objects.get(
numero=self.cleaned_data['numero_materia'],
ano=self.cleaned_data['ano_materia'],
tipo=self.cleaned_data['tipo_materia'])
numero=materia,
ano=ano_materia,
tipo=tipo_materia)
except ObjectDoesNotExist:
msg = _('A matéria a ser inclusa não existe no cadastro'
' de matérias legislativas.')
msg = _('A matéria %s%s/%s não existe no cadastro'
' de matérias legislativas.' % (tipo_materia, materia, ano_materia))
raise ValidationError(msg)
else:
cleaned_data['materia'] = materia
else:
campos = [materia, tipo_materia, ano_materia]
if campos.count(None) + campos.count('') < len(campos):
msg = _('Preencha todos os campos relacionados à Matéria Legislativa')
raise ValidationError(msg)
if not cleaned_data['numero']:
ultima_audiencia = AudienciaPublica.objects.all().order_by('numero').last()
if ultima_audiencia:
cleaned_data['numero'] = ultima_audiencia.numero + 1
else:
cleaned_data['numero'] = 1
if self.cleaned_data['hora_inicio'] and self.cleaned_data['hora_fim']:
if (self.cleaned_data['hora_fim'] <
self.cleaned_data['hora_inicio']):
msg = _('A hora de fim não pode ser anterior a hora de ínicio')
raise ValidationError(msg)
return self.cleaned_data
@transaction.atomic()
def save(self, commit=True):
audiencia = super(AudienciaForm, self).save(commit)
return audiencia
return cleaned_data

20
sapl/audiencia/migrations/0005_auto_20180806_1236.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-08-06 15:36
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('audiencia', '0004_auto_20180305_1006'),
]
operations = [
migrations.AlterField(
model_name='audienciapublica',
name='hora_fim',
field=models.CharField(blank=True, max_length=5, null=True, verbose_name='Horário Fim(hh:mm)'),
),
]

20
sapl/audiencia/migrations/0006_auto_20180808_0856.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-08-08 11:56
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('audiencia', '0005_auto_20180806_1236'),
]
operations = [
migrations.AlterField(
model_name='audienciapublica',
name='hora_fim',
field=models.CharField(blank=True, max_length=5, verbose_name='Horário Fim(hh:mm)'),
),
]

2
sapl/audiencia/models.py

@ -71,7 +71,7 @@ class AudienciaPublica(models.Model):
hora_inicio = models.CharField(
max_length=5, verbose_name=_('Horário Início(hh:mm)'))
hora_fim = models.CharField(
max_length=5, verbose_name=_('Horário Fim(hh:mm)'))
max_length=5, blank=True, verbose_name=_('Horário Fim(hh:mm)'))
observacao = models.TextField(
max_length=500, blank=True, verbose_name=_('Observação'))
audiencia_cancelada = models.BooleanField(

26
sapl/audiencia/tests/test_audiencia.py

@ -3,6 +3,8 @@ from django.utils.translation import ugettext as _
from model_mommy import mommy
from sapl.audiencia import forms
from sapl.audiencia.models import TipoAudienciaPublica
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
@pytest.mark.django_db(transaction=False)
def test_valida_campos_obrigatorios_audiencia_form():
@ -15,11 +17,25 @@ def test_valida_campos_obrigatorios_audiencia_form():
assert errors['nome'] == [_('Este campo é obrigatório.')]
assert errors['tema'] == [_('Este campo é obrigatório.')]
assert errors['tipo'] == [_('Este campo é obrigatório.')]
assert errors['tipo_materia'] == [_('Este campo é obrigatório.')]
assert errors['numero_materia'] == [_('Este campo é obrigatório.')]
assert errors['ano_materia'] == [_('Este campo é obrigatório.')]
assert errors['data'] == [_('Este campo é obrigatório.')]
assert errors['hora_inicio'] == [_('Este campo é obrigatório.')]
assert errors['hora_fim'] == [_('Este campo é obrigatório.')]
assert len(errors) == 9
assert len(errors) == 5
@pytest.mark.django_db(transaction=False)
def test_audiencia_form_hora_invalida():
tipo_materia = mommy.make(TipoMateriaLegislativa)
tipo = mommy.make(TipoAudienciaPublica)
form = forms.AudienciaForm(data={'nome': 'Nome da Audiencia',
'tema': 'Tema da Audiencia',
'tipo': tipo,
'data': '2016-10-01',
'hora_inicio': '10:00',
'hora_fim': '9:00',
})
assert not form.is_valid()

4
sapl/audiencia/views.py

@ -6,15 +6,17 @@ from sapl.crud.base import RP_DETAIL, RP_LIST, Crud
from .forms import AudienciaForm
from .models import AudienciaPublica
def index(request):
return HttpResponse("Audiência Pública")
class AudienciaCrud(Crud):
model = AudienciaPublica
public = [RP_LIST, RP_DETAIL, ]
class BaseMixin(Crud.BaseMixin):
list_field_names = ['materia', 'tipo', 'numero', 'nome',
list_field_names = ['numero', 'nome', 'tipo', 'materia',
'data']
ordering = 'nome', 'numero', 'tipo', 'data'

11
sapl/base/forms.py

@ -45,7 +45,7 @@ STATUS_USER_CHOICE = [
def get_roles():
roles = [(g.id, g.name) for g in Group.objects.all().order_by('name')
if g.name != 'Votante' and g.name != 'Autor']
if g.name != 'Votante']
return roles
@ -62,8 +62,6 @@ class UsuarioCreateForm(ModelForm):
user_active = forms.ChoiceField(required=False, choices=YES_NO_CHOICES,
label="Usuário ativo?", initial='True')
#ROLES = [(g.id, g.name) for g in Group.objects.all().order_by('name')]
roles = forms.MultipleChoiceField(
required=True, widget=forms.CheckboxSelectMultiple(), choices=get_roles)
@ -673,6 +671,11 @@ class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
label='Ano da Matéria',
choices=RANGE_ANOS)
@property
def qs(self):
parent = super(RelatorioMateriasTramitacaoilterSet, self).qs
return parent.distinct().order_by('-ano', 'tipo', '-numero')
class Meta:
model = MateriaLegislativa
fields = ['ano', 'tipo', 'tramitacao__unidade_tramitacao_local',
@ -738,7 +741,7 @@ class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet):
@property
def qs(self):
parent = super(RelatorioMateriasPorAutorFilterSet, self).qs
return parent.distinct().order_by('-ano', '-numero')
return parent.distinct().filter(autoria__primeiro_autor=True).order_by('autoria__autor', '-autoria__primeiro_autor', 'tipo', '-ano', '-numero')
class Meta:
model = MateriaLegislativa

20
sapl/base/migrations/0018_auto_20180801_1652.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-08-01 19:52
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0017_appconfig_cronometro_consideracoes'),
]
operations = [
migrations.AlterField(
model_name='autor',
name='nome',
field=models.CharField(blank=True, max_length=120, verbose_name='Nome do Autor'),
),
]

21
sapl/base/migrations/0019_auto_20180815_1025.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-08-15 13:25
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('base', '0018_auto_20180801_1652'),
]
operations = [
migrations.AlterField(
model_name='autor',
name='tipo',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='base.TipoAutor', verbose_name='Tipo do Autor'),
),
]

24
sapl/base/models.py

@ -5,6 +5,7 @@ from django.db import models
from django.db.models.signals import post_migrate
from django.db.utils import DEFAULT_DB_ALIAS
from django.utils.translation import ugettext_lazy as _
from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES,
get_settings_auth_user_model, models_with_gr_for_model)
@ -178,7 +179,8 @@ class Autor(models.Model):
on_delete=models.SET_NULL,
null=True)
tipo = models.ForeignKey(TipoAutor, verbose_name=_('Tipo do Autor'))
tipo = models.ForeignKey(TipoAutor, verbose_name=_('Tipo do Autor'),
on_delete=models.PROTECT)
content_type = models.ForeignKey(
ContentType,
@ -188,7 +190,7 @@ class Autor(models.Model):
autor_related = GenericForeignKey('content_type', 'object_id')
nome = models.CharField(
max_length=60, blank=True, verbose_name=_('Nome do Autor'))
max_length=120, blank=True, verbose_name=_('Nome do Autor'))
cargo = models.CharField(max_length=50, blank=True)
@ -199,23 +201,17 @@ class Autor(models.Model):
ordering = ('nome',)
def __str__(self):
if self.autor_related:
return str(self.autor_related)
else:
if str(self.cargo):
return _('%(nome)s - %(cargo)s') % {
'nome': self.nome, 'cargo': self.cargo}
if self.nome:
if self.cargo:
return '{} - {}'.format(self.nome, self.cargo)
else:
return str(self.nome)
"""if str(self.tipo) == 'Parlamentar' and self.parlamentar:
return self.parlamentar.nome_parlamentar
elif str(self.tipo) == 'Comissao' and self.comissao:
return str(self.comissao)
elif str(self.tipo) == 'Partido' and self.partido:
return str(self.partido)
else:
"""
if self.user:
return str(self.user.username)
return '?'
def cria_models_tipo_autor(app_config=None, verbosity=2, interactive=True,

19
sapl/base/templatetags/common_tags.py

@ -44,6 +44,15 @@ def split(value, arg):
return value.split(arg)
@register.filter
def to_str(arg):
return str(arg)
@register.filter
def get_last_item_from_list(list,arg):
return list[arg]
@register.filter
def sort_by_keys(value, key):
transformed = []
@ -63,6 +72,16 @@ def sort_by_keys(value, key):
return transformed
@register.filter
def paginacao_limite_inferior(pagina):
return (int(pagina) - 1) * 10
@register.filter
def paginacao_limite_superior(pagina):
return int(pagina) * 10
@register.filter
def lookup(d, key):
return d[key] if key in d else []

2
sapl/base/views.py

@ -393,7 +393,7 @@ class RelatorioMateriasTramitacaoView(FilterView):
context = super(RelatorioMateriasTramitacaoView,
self).get_context_data(**kwargs)
context['title'] = _('Matérias por Ano, Autor e Tipo')
context['title'] = _('Matérias em Tramitação')
qs = context['object_list']
qs = qs.filter(em_tramitacao=True)

2
sapl/comissoes/forms.py

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

39
sapl/compilacao/models.py

@ -69,8 +69,15 @@ class BaseModel(models.Model):
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None, clean=True):
if clean:
# método clean não pode ser chamado no caso do save que está sendo
# executado é o save de revision_pre_delete_signal
import inspect
funcs = list(filter(lambda x: x == 'revision_pre_delete_signal',
map(lambda x: x[3], inspect.stack())))
if clean and not funcs:
self.clean()
return models.Model.save(
self,
force_insert=force_insert,
@ -1458,7 +1465,7 @@ class Dispositivo(BaseModel, TimestampedMixin):
tipo_dispositivo_id=self.tipo_dispositivo.pk))
else: # contagem continua restrita a articulacao
proxima_articulacao = self.get_proximo_nivel_zero()
proxima_articulacao = self.select_next_root()
if proxima_articulacao is None:
irmaos = list(Dispositivo.objects.filter(
@ -1550,25 +1557,15 @@ class Dispositivo(BaseModel, TimestampedMixin):
irmao.clean()
irmao.save()
def get_proximo_nivel_zero(self):
proxima_articulacao = Dispositivo.objects.order_by('ordem').filter(
ordem__gt=self.ordem,
nivel=0,
ta_id=self.ta_id).first()
return proxima_articulacao
def get_nivel_zero_anterior(self):
anterior_articulacao = Dispositivo.objects.order_by('ordem').filter(
ordem__lt=self.ordem,
nivel=0,
ta_id=self.ta_id).last()
return anterior_articulacao
def get_niveis_zero(self):
niveis_zero = Dispositivo.objects.order_by('ordem').filter(
nivel=0,
ta_id=self.ta_id)
return niveis_zero
def select_roots(self):
return Dispositivo.objects.order_by(
'ordem').filter(nivel=0, ta_id=self.ta_id)
def select_next_root(self):
return self.select_roots().filter(ordem__gt=self.ordem).first()
def select_prev_root(self):
return self.select_roots().filter(ordem__lt=self.ordem).last()
# metodo obsoleto, foi acrescentado o campo auto_inserido no modelo
def is_relative_auto_insert__obsoleto(self, perfil_pk=None):

29
sapl/compilacao/views.py

@ -15,7 +15,7 @@ from django.db import connection, transaction
from django.db.models import Q
from django.db.utils import IntegrityError
from django.http.response import (HttpResponse, HttpResponseRedirect,
JsonResponse)
JsonResponse, Http404)
from django.shortcuts import get_object_or_404, redirect
from django.utils.dateparse import parse_date
from django.utils.encoding import force_text
@ -51,7 +51,6 @@ from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED,
from sapl.crud.base import Crud, CrudAux, CrudListView, make_pagination
from sapl.settings import BASE_DIR
TipoNotaCrud = CrudAux.build(TipoNota, 'tipo_nota')
TipoVideCrud = CrudAux.build(TipoVide, 'tipo_vide')
TipoPublicacaoCrud = CrudAux.build(TipoPublicacao, 'tipo_publicacao')
@ -211,8 +210,12 @@ class CompMixin(PermissionRequiredMixin):
@property
def ta(self):
try:
ta = TextoArticulado.objects.get(
pk=self.kwargs.get('ta_id', self.kwargs.get('pk', 0)))
except TextoArticulado.DoesNotExist:
raise Http404()
return ta
def get_context_data(self, **kwargs):
@ -1388,7 +1391,7 @@ class ActionsCommonsMixin:
pkfilho = dp.pk
dp = dp.dispositivo_pai
proxima_articulacao = dp.get_proximo_nivel_zero()
proxima_articulacao = dp.select_next_root()
if proxima_articulacao is not None:
parents = Dispositivo.objects.filter(
@ -1480,12 +1483,18 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin):
).first()
data = {}
if base_anterior:
data = self.get_json_for_refresh(base_anterior)
else:
base_anterior = base.get_nivel_zero_anterior()
if not base_anterior or base == base.get_raiz():
base_anterior = base.select_prev_root()
if not base_anterior:
base_anterior = base
data = self.get_json_for_refresh(base_anterior)
if base == base_anterior:
data['pk'] = base.pk
self.set_message(data, 'danger', _(
'Base Inicial não pode ser removida!'), modal=True)
else:
if base != base.get_raiz():
data['pai'] = [base.get_raiz().pk]
if ta_base.id != int(self.kwargs['ta_id']):
@ -1536,7 +1545,7 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin):
print(e)
base.delete()
else:
proxima_articulacao = base.get_proximo_nivel_zero()
proxima_articulacao = base.select_next_root()
if not bloco:
# tranferir filhos para primeiro pai possível acima da base
# de exclusão
@ -1694,7 +1703,7 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin):
base_adicao = {}
nivel_zero_anterior = base.get_nivel_zero_anterior()
nivel_zero_anterior = base.select_prev_root()
if nivel_zero_anterior:
nivel_zero_anterior = nivel_zero_anterior.ordem
else:
@ -2374,7 +2383,7 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
if dp.nivel == 0:
proxima_articulacao = dp.get_proximo_nivel_zero()
proxima_articulacao = dp.select_next_root()
if not proxima_articulacao:
filhos_continuos = list(Dispositivo.objects.filter(

4
sapl/crud/base.py

@ -851,7 +851,9 @@ class CrudDeleteView(PermissionRequiredContainerCrudMixin,
é referenciado por outros registros:<br>\
<ul>'
for i in err.protected_objects:
error_msg += '<li>' + i.__str__() + '</li>'
error_msg += '<li>{} - {}</li>'.format(
i._meta.verbose_name, i
)
error_msg += '</ul>'
messages.add_message(request,

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

@ -7,5 +7,14 @@ 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(
'-a',
action='store_true',
default=False,
dest='apagar_do_legado',
help='Apagar entradas migradas do legado',
)
def handle(self, *args, **options):
migrar(interativo=False)
migrar(apagar_do_legado=options['apagar_do_legado'])

4
sapl/legacy/migracao.py

@ -18,7 +18,7 @@ def adornar_msg(msg):
return '\n{1}\n{0}\n{1}'.format(msg, '#' * len(msg))
def migrar(interativo=False):
def migrar(apagar_do_legado=False):
if TAG_MARCO in REPO.tags:
info('A migração já está feita.')
return
@ -26,7 +26,7 @@ def migrar(interativo=False):
'Antes de migrar '
'é necessário fazer a exportação de documentos do zope')
management.call_command('migrate')
migrar_dados()
migrar_dados(apagar_do_legado)
migrar_usuarios(REPO.working_dir)
migrar_documentos(REPO)
gravar_marco()

52
sapl/legacy/migracao_dados.py

@ -8,7 +8,6 @@ from datetime import date
from functools import lru_cache, partial
from itertools import groupby
from operator import xor
from subprocess import PIPE, call
import git
import pkg_resources
@ -31,12 +30,10 @@ 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, Reuniao
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.legacy_migration_settings import (DIR_DADOS_MIGRACAO, DIR_REPO,
NOME_BANCO_LEGADO)
from sapl.materia.models import (AcompanhamentoMateria, MateriaLegislativa,
Proposicao, StatusTramitacao, TipoDocumento,
TipoMateriaLegislativa, TipoProposicao,
@ -141,10 +138,6 @@ models_novos_para_antigos = {
for model in field_renames}
models_novos_para_antigos[Composicao] = models_novos_para_antigos[Participacao]
content_types = {model: ContentType.objects.get(
app_label=model._meta.app_label, model=model._meta.model_name)
for model in field_renames}
campos_novos_para_antigos = {
model._meta.get_field(nome_novo): nome_antigo
for model, renames in field_renames.items()
@ -226,10 +219,6 @@ class ForeignKeyFaltando(ObjectDoesNotExist):
'Uma FK aponta para um registro inexistente'
def __init__(self, field, valor, old):
if (field.related_model.__name__ == 'Comissao'
and old.__class__.__name__ == 'ReuniaoComissao'
and valor == 1):
__import__('pdb').set_trace()
self.field = field
self.valor = valor
self.old = old
@ -546,7 +535,6 @@ PROPAGACOES_DE_EXCLUSAO = [
('sessao_plenaria', 'ordem_dia', 'cod_sessao_plen'),
('sessao_plenaria', 'expediente_materia', 'cod_sessao_plen'),
('sessao_plenaria', 'expediente_sessao_plenaria', 'cod_sessao_plen'),
('registro_votacao', 'registro_votacao_parlamentar', 'cod_votacao'),
# as consultas no código do sapl 2.5
# votacao_ordem_dia_obter_zsql e votacao_expediente_materia_obter_zsql
# indicam que os registros de votação de matérias excluídas não são
@ -562,30 +550,38 @@ PROPAGACOES_DE_EXCLUSAO = [
('materia_legislativa', 'anexada', 'cod_materia_anexada'),
('materia_legislativa', 'documento_acessorio', 'cod_materia'),
('materia_legislativa', 'numeracao', 'cod_materia'),
('materia_legislativa', 'expediente_materia', 'cod_materia'),
# norma
('norma_juridica', 'vinculo_norma_juridica', 'cod_norma_referente'),
('norma_juridica', 'vinculo_norma_juridica', 'cod_norma_referida'),
('norma_juridica', 'legislacao_citada', 'cod_norma'),
# documento administrativo
('documento_administrativo', 'tramitacao_administrativo', 'cod_documento'),
]
PROPAGACOES_DE_EXCLUSAO_REGISTROS_VOTACAO = [
('registro_votacao', 'registro_votacao_parlamentar', 'cod_votacao'),
]
def propaga_exclusoes():
for tabela_pai, tabela_filha, fk in PROPAGACOES_DE_EXCLUSAO:
def propaga_exclusoes(propagacoes):
for tabela_pai, tabela_filha, fk in propagacoes:
[pk_pai] = get_pk_legado(tabela_pai)
exec_legado('''
sql = '''
update {} set ind_excluido = 1 where {} not in (
select {} from {} where ind_excluido != 1)
'''.format(tabela_filha, fk, pk_pai, tabela_pai))
'''.format(tabela_filha, fk, pk_pai, tabela_pai)
exec_legado(sql)
def uniformiza_banco():
exec_legado('SET SESSION sql_mode = "";') # desliga checagens do mysql
propaga_exclusoes()
propaga_exclusoes(PROPAGACOES_DE_EXCLUSAO)
checa_registros_votacao_ambiguos_e_remove_nao_usados()
propaga_exclusoes(PROPAGACOES_DE_EXCLUSAO_REGISTROS_VOTACAO)
garante_coluna_no_legado('proposicao',
'num_proposicao int(11) NULL')
@ -808,7 +804,7 @@ def roda_comando_shell(cmd):
assert res == 0, 'O comando falhou: {}'.format(cmd)
def migrar_dados():
def migrar_dados(apagar_do_legado=False):
try:
ocorrencias.clear()
ocorrencias.default_factory = list
@ -840,7 +836,7 @@ def migrar_dados():
fill_vinculo_norma_juridica()
fill_dados_basicos()
info('Começando migração: ...')
migrar_todos_os_models()
migrar_todos_os_models(apagar_do_legado)
except Exception as e:
ocorrencias['traceback'] = str(traceback.format_exc())
raise e
@ -874,7 +870,6 @@ def get_models_a_migrar():
if model in field_renames]
# retira reuniões quando não existe na base legada
# (só existe no sapl 3.0)
tabelas_legado = [t for (t,) in exec_legado('show tables')]
if not EXISTE_REUNIAO_NO_LEGADO:
models.remove(Reuniao)
# Devido à referência TipoProposicao.tipo_conteudo_related
@ -890,12 +885,12 @@ def get_models_a_migrar():
return models
def migrar_todos_os_models():
def migrar_todos_os_models(apagar_do_legado):
for model in get_models_a_migrar():
migrar_model(model)
migrar_model(model, apagar_do_legado)
def migrar_model(model):
def migrar_model(model, apagar_do_legado):
print('Migrando %s...' % model.__name__)
model_legado, tabela_legado, campos_pk_legado = \
@ -949,6 +944,7 @@ def migrar_model(model):
novos.append(new) # guarda para salvar
# acumula deleção do registro no legado
if apagar_do_legado:
sql_delete_legado += 'delete from {} where {};\n'.format(
tabela_legado,
' and '.join(
@ -973,7 +969,7 @@ def migrar_model(model):
reinicia_sequence(model, ultima_pk_legado + 1)
# apaga registros migrados do legado
if sql_delete_legado:
if apagar_do_legado and sql_delete_legado:
exec_legado(sql_delete_legado)
@ -1156,7 +1152,9 @@ def adjust_tipoafastamento(new, old):
def set_generic_fk(new, campo_virtual, old):
new.content_type = content_types[campo_virtual.related_model]
model = campo_virtual.related_model
new.content_type = ContentType.objects.get(
app_label=model._meta.app_label, model=model._meta.model_name)
new.object_id = get_fk_related(campo_virtual, old)

1
sapl/legacy/migracao_usuarios.py

@ -16,6 +16,7 @@ PERFIL_LEGADO_PARA_NOVO = {legado: Group.objects.get(name=novo)
('Operador Protocolo', 'Operador de Protocolo Administrativo'),
('Operador Sessao Plenaria', 'Operador de Sessão Plenária'),
('Parlamentar', 'Votante'),
('Operador Painel', 'Operador de Painel Eletrônico'),
]
}

59
sapl/legacy/scripts/ressucita_dependencias.py

@ -0,0 +1,59 @@
import yaml
from unipath import Path
from sapl.legacy.migracao_dados import DIR_REPO, exec_legado
fks_legado = '''
autor cod_parlamentar parlamentar
autor tip_autor tipo_autor
autoria cod_autor autor
expediente_materia cod_materia materia_legislativa
ordem_dia cod_materia materia_legislativa
legislacao_citada cod_norma norma_juridica
oradores cod_parlamentar parlamentar
oradores_expediente cod_parlamentar parlamentar
ordem_dia_presenca cod_parlamentar parlamentar
protocolo cod_autor autor
registro_votacao tip_resultado_votacao tipo_resultado_votacao
registro_votacao_parlamentar cod_parlamentar parlamentar
registro_votacao_parlamentar cod_votacao registro_votacao
sessao_legislativa num_legislatura legislatura
sessao_plenaria_presenca cod_parlamentar parlamentar
'''
fks_legado = [l.split() for l in fks_legado.strip().splitlines()]
fks_legado = {(o, c): t for (o, c, t) in fks_legado}
def get_excluido(fk):
campo, valor, tabela_origem = [fk[k] for k in ('campo', 'valor', 'tabela')]
tabela_alvo = fks_legado[(tabela_origem, campo)]
sql = 'select ind_excluido, t.* from {} t where {} = {}'.format(
tabela_alvo, campo, valor)
res = list(exec_legado(sql))
return tabela_origem, campo, valor, tabela_alvo, res
def get_dependencias_a_ressucitar():
ocorrencias = yaml.load(
Path(DIR_REPO.child('ocorrencias.yaml').read_file()))
fks = ocorrencias['fk']
excluidos = [get_excluido(fk) for fk in fks]
desexcluir, criar = [
set([(tabela_alvo, campo, valor)
for tabela_origem, campo, valor, tabela_alvo, res in excluidos
if condicao(res)])
for condicao in (
# o registro existe e ind_excluido == 1
lambda res: res and res[0][0] == 1,
# o registro não existe
lambda res: not res
)]
return desexcluir, criar
def get_sqls_desexcluir_criar(desexcluir, criar):
sqls_desexcluir = [
'update {} set ind_excluido = 0 where {} = {};'.format(
tabela_alvo, campo, valor)
for tabela_alvo, campo, valor in desexcluir]
return '\n'.join(sqls_desexcluir)

6
sapl/materia/forms.py

@ -675,6 +675,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
widget=forms.HiddenInput())
ementa = django_filters.CharFilter(lookup_expr='icontains')
indexacao = django_filters.CharFilter(lookup_expr='icontains')
em_tramitacao = django_filters.ChoiceFilter(required=False,
label='Em tramitação',
@ -751,7 +752,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
[('em_tramitacao', 6),
('o', 6)])
row9 = to_row(
[('materiaassunto__assunto', 12)])
[('materiaassunto__assunto', 6), ('indexacao', 6)])
row10 = to_row(
[('ementa', 12)])
@ -1294,8 +1295,7 @@ class ProposicaoForm(forms.ModelForm):
def clean_texto_original(self):
texto_original = self.cleaned_data.get('texto_original', False)
if texto_original:
if texto_original.size > MAX_DOC_UPLOAD_SIZE:
if texto_original and texto_original.size > MAX_DOC_UPLOAD_SIZE:
max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size))

10
sapl/materia/views.py

@ -426,6 +426,10 @@ class ProposicaoPendente(PermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs):
context = super(ProposicaoPendente, self).get_context_data(**kwargs)
context['object_list'] = Proposicao.objects.filter(
data_envio__isnull=False,
data_recebimento__isnull=True,
data_devolucao__isnull=True)
paginator = context['paginator']
page_obj = context['page_obj']
context['AppConfig'] = sapl.base.models.AppConfig.objects.all().last()
@ -434,6 +438,8 @@ class ProposicaoPendente(PermissionRequiredMixin, ListView):
context['NO_ENTRIES_MSG'] = 'Nenhuma proposição pendente.'
context['subnav_template_name'] = 'materia/subnav_prop.yaml'
qr = self.request.GET.copy()
context['filter_url'] = ('&o=' + qr['o']) if 'o' in qr.keys() else ''
return context
@ -701,12 +707,12 @@ class ProposicaoCrud(Crud):
messages.success(request, _(
'Proposição enviada com sucesso.'))
try:
Numero = MateriaLegislativa.objects.filter(tipo=p.tipo.tipo_conteudo_related,
numero = MateriaLegislativa.objects.filter(tipo=p.tipo.tipo_conteudo_related,
ano=p.ano).last().numero + 1
messages.success(request, _(
'%s : nº %s de %s <br>Atenção! Este número é apenas um provável '
'número que pode não corresponder com a realidade'
% (p.tipo, Numero, p.ano)))
% (p.tipo, numero, p.ano)))
except ValueError:
pass

37
sapl/norma/forms.py

@ -14,7 +14,7 @@ from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import RANGE_ANOS, RangeWidgetOverride
from .models import (AssuntoNorma, NormaJuridica, NormaRelacionada,
from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica)
@ -120,6 +120,7 @@ class NormaJuridicaForm(ModelForm):
'assuntos']
widgets = {'assuntos': widgets.CheckboxSelectMultiple}
def clean(self):
cleaned_data = super(NormaJuridicaForm, self).clean()
@ -163,8 +164,7 @@ class NormaJuridicaForm(ModelForm):
def clean_texto_integral(self):
texto_integral = self.cleaned_data.get('texto_integral', False)
if texto_integral:
if texto_integral.size > MAX_DOC_UPLOAD_SIZE:
if texto_integral and texto_integral.size > MAX_DOC_UPLOAD_SIZE:
max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size))
@ -175,9 +175,40 @@ class NormaJuridicaForm(ModelForm):
norma.timestamp = timezone.now()
norma.materia = self.cleaned_data['materia']
norma = super(NormaJuridicaForm, self).save(commit=True)
return norma
class AnexoNormaJuridicaForm(ModelForm):
class Meta:
model = AnexoNormaJuridica
fields = ['norma', 'anexo_arquivo']
widgets = {
'norma': forms.HiddenInput(),
}
def clean(self):
cleaned_data = super(AnexoNormaJuridicaForm, self).clean()
if not self.is_valid():
return cleaned_data
anexo_arquivo = self.cleaned_data.get('anexo_arquivo', False)
if anexo_arquivo and anexo_arquivo.size > MAX_DOC_UPLOAD_SIZE:
max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size))
return cleaned_data
def save(self, commit=False):
anexo = self.instance
anexo.ano = self.cleaned_data['norma'].ano
anexo = super(AnexoNormaJuridicaForm, self).save(commit=True)
anexo.norma = self.cleaned_data['norma']
anexo.anexo_arquivo = self.cleaned_data['anexo_arquivo']
anexo.save()
return anexo
class NormaRelacionadaForm(ModelForm):
tipo = forms.ModelChoiceField(

31
sapl/norma/migrations/0012_anexonormajuridica.py

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-08-06 19:48
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import sapl.norma.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('norma', '0011_auto_20180220_1859'),
]
operations = [
migrations.CreateModel(
name='AnexoNormaJuridica',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('anexo_arquivo', models.FileField(blank=True, null=True, upload_to=sapl.norma.models.norma_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Arquivo Anexo')),
('ano', models.PositiveSmallIntegerField(choices=[(2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano')),
('norma', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='norma', to='norma.NormaJuridica', verbose_name='Norma Juridica')),
],
options={
'verbose_name_plural': 'Anexos da Norma Juridica',
'verbose_name': 'Anexo da Norma Juridica',
},
),
]

34
sapl/norma/models.py

@ -141,9 +141,14 @@ class NormaJuridica(models.Model):
norma_relacionada=self.id)
return (principais, relacionadas)
def get_anexos_norma_juridica(self):
anexos = AnexoNormaJuridica.objects.filter(
norma=self.id)
return anexos
def __str__(self):
return _('%(tipo)s%(numero)s de %(data)s') % {
'tipo': self.tipo,
return _('%(numero)s de %(data)s') % {
'numero': self.numero,
'data': defaultfilters.date(self.data, "d \d\e F \d\e Y")}
@ -252,3 +257,28 @@ class NormaRelacionada(models.Model):
' - Relacionada: %(norma_relacionada)s') % {
'norma_principal': self.norma_principal,
'norma_relacionada': self.norma_relacionada}
@reversion.register()
class AnexoNormaJuridica(models.Model):
norma = models.ForeignKey(
NormaJuridica,
related_name='norma',
on_delete=models.PROTECT,
verbose_name=_('Norma Juridica'))
anexo_arquivo = models.FileField(
blank=True,
null=True,
upload_to=norma_upload_path,
verbose_name=_('Arquivo Anexo'),
validators=[restringe_tipos_de_arquivo_txt])
ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'),
choices=RANGE_ANOS)
class Meta:
verbose_name = _('Anexo da Norma Juridica')
verbose_name_plural = _('Anexos da Norma Juridica')
def __str__(self):
return _('Anexo: %(anexo)s da norma %(norma)s') % {
'anexo': self.anexo_arquivo, 'norma': self.norma}

6
sapl/norma/urls.py

@ -1,6 +1,6 @@
from django.conf.urls import include, url
from sapl.norma.views import (AssuntoNormaCrud, NormaCrud, NormaPesquisaView,
from sapl.norma.views import (AnexoNormaJuridicaCrud,AssuntoNormaCrud, NormaCrud, NormaPesquisaView,
NormaRelacionadaCrud, NormaTaView, TipoNormaCrud,
TipoVinculoNormaJuridicaCrud, recuperar_norma,
recuperar_numero_norma)
@ -12,11 +12,11 @@ app_name = AppConfig.name
urlpatterns = [
url(r'^norma/', include(NormaCrud.get_urls() +
NormaRelacionadaCrud.get_urls())),
NormaRelacionadaCrud.get_urls() +
AnexoNormaJuridicaCrud.get_urls())),
# Integração com Compilação
url(r'^norma/(?P<pk>[0-9]+)/ta$', NormaTaView.as_view(), name='norma_ta'),
url(r'^sistema/norma/tipo/', include(TipoNormaCrud.get_urls())),
url(r'^sistema/norma/assunto/', include(AssuntoNormaCrud.get_urls())),
url(r'^sistema/norma/vinculo/', include(

48
sapl/norma/views.py

@ -1,4 +1,5 @@
import re
import weasyprint
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
@ -18,9 +19,9 @@ from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
MasterDetailCrud, make_pagination)
from sapl.utils import show_results_filter_set
from .forms import (NormaFilterSet, NormaJuridicaForm,
from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm,
NormaPesquisaSimplesForm, NormaRelacionadaForm)
from .models import (AssuntoNorma, NormaJuridica, NormaRelacionada,
from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica, TipoVinculoNormaJuridica)
# LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '')
@ -71,7 +72,7 @@ class NormaPesquisaView(FilterView):
def get_queryset(self):
qs = super().get_queryset()
qs.select_related('tipo', 'materia')
qs = qs.extra({'norma_i': "CAST(regexp_replace(numero,'[^0-9]','', 'g') AS INTEGER)", 'norma_letra': "regexp_replace(numero,'[^a-zA-Z]','', 'g')"}).order_by('-data', '-norma_i', '-norma_letra')
return qs
@ -97,6 +98,39 @@ class NormaPesquisaView(FilterView):
return context
class AnexoNormaJuridicaCrud(MasterDetailCrud):
model = AnexoNormaJuridica
parent_field = 'norma'
help_topic = 'anexonormajuridica'
public = [RP_LIST, RP_DETAIL]
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['id','anexo_arquivo']
class CreateView(MasterDetailCrud.CreateView):
form_class = AnexoNormaJuridicaForm
layout_key = 'AnexoNormaJuridica'
def get_initial(self):
initial = super(MasterDetailCrud.CreateView, self).get_initial()
initial['norma'] = NormaJuridica.objects.get(id=self.kwargs['pk'])
return initial
class UpdateView(MasterDetailCrud.UpdateView):
form_class = AnexoNormaJuridicaForm
layout_key = 'AnexoNormaJuridica'
def get_initial(self):
initial = super(UpdateView, self).get_initial()
initial['norma'] = self.object.norma
initial['anexo_arquivo'] = self.object.anexo_arquivo
initial['ano'] = self.object.ano
return initial
class DetailView(MasterDetailCrud.DetailView):
form_class = AnexoNormaJuridicaForm
layout_key = 'AnexoNormaJuridica'
class NormaTaView(IntegracaoTaView):
model = NormaJuridica
@ -201,14 +235,12 @@ def recuperar_norma(request):
def recuperar_numero_norma(request):
tipo = TipoNormaJuridica.objects.get(pk=request.GET['tipo'])
ano = request.GET.get('ano', '')
param = {'tipo': tipo}
param['ano'] = ano if ano else timezone.now().year
norma = NormaJuridica.objects.filter(**param).extra(
{'numero_id': "CAST(numero as INTEGER)"}).order_by(
'tipo', 'ano','numero_id').values_list('numero', 'ano').last()
norma = NormaJuridica.objects.filter(**param).order_by(
'tipo', 'ano', 'numero').values_list('numero', 'ano').last()
if norma:
response = JsonResponse({'numero': int(norma[0]) + 1,
response = JsonResponse({'numero': int(re.sub("[^0-9].*", '', norma[0])) + 1,
'ano': norma[1]})
else:
response = JsonResponse(

178
sapl/painel/views.py

@ -82,43 +82,55 @@ def votacao_aberta(request):
return votacoes_abertas.first(), None
def votante_view(request):
# Pega o votante relacionado ao usuário
template_name = 'painel/voto_nominal.html'
context = {}
try:
votante = Votante.objects.get(user=request.user)
except ObjectDoesNotExist:
msg = _("Usuário não cadastrado como votante na tela de parlamentares. Contate a administração de sua Casa Legislativa!")
context.update({
'error_message':msg
})
return render(request, template_name, context)
def votacao(context,context_vars):
context = {'head_title': str(_('Votação Individual'))}
parlamentar = context_vars['votante'].parlamentar
parlamentar_presente = False
if parlamentar.id in context_vars['presentes']:
parlamentar_presente = True
context_vars.update({'parlamentar': parlamentar})
else:
context.update({'error_message':
'Não há presentes na Sessão com a '
'matéria em votação.'})
# Verifica se usuário possui permissão para votar
if 'parlamentares.can_vote' in request.user.get_all_permissions():
context.update({'permissao': True})
if parlamentar_presente:
voto = []
if context_vars['ordem_dia']:
voto = VotoParlamentar.objects.filter(
ordem=context_vars['ordem_dia'])
elif context_vars['expediente']:
voto = VotoParlamentar.objects.filter(
expediente=context_vars['expediente'])
# Pega sessão
sessao, msg = votacao_aberta(request)
if voto:
try:
voto = voto.get(parlamentar=context_vars['parlamentar'])
context.update({'voto_parlamentar': voto.voto})
except ObjectDoesNotExist:
context.update(
{'voto_parlamentar': 'Voto não '
'computado.'})
else:
context.update({'error_message':
'Você não está presente na '
'Ordem do Dia/Expediente em votação.'})
return context, context_vars
if sessao and not msg:
pk = sessao.pk
def sessao_votacao(context,context_vars):
pk = context_vars['sessao'].pk
context.update({'sessao_id': pk})
context.update({'sessao': sessao,
'data': sessao.data_inicio,
'hora': sessao.hora_inicio})
context.update({'sessao': context_vars['sessao'],
'data': context_vars['sessao'].data_inicio,
'hora': context_vars['sessao'].hora_inicio})
# Inicializa presentes
presentes = []
# Verifica votação aberta
# Se aberta, verifica se é nominal. ID nominal == 2
ordem_dia = get_materia_aberta(pk)
expediente = get_materia_expediente_aberta(pk)
errors_msgs = {'materia':'Não há nenhuma matéria aberta.',
'registro':'A votação para esta matéria já encerrou.',
'tipo':'A matéria aberta não é do tipo votação nominal.'}
materia_aberta = None
if ordem_dia:
@ -132,59 +144,67 @@ def votante_view(request):
sessao_plenaria_id=pk).values_list(
'parlamentar_id', flat=True).distinct()
if materia_aberta:
if not materia_aberta.registro_aberto:
if materia_aberta.tipo_votacao == VOTACAO_NOMINAL:
context_vars.update({'ordem_dia': ordem_dia,
'expediente':expediente,
'presentes': presentes})
# Verifica votação aberta
# Se aberta, verifica se é nominal. ID nominal == 2
erro = None
if not materia_aberta:
erro = 'materia'
elif materia_aberta.registro_aberto:
erro = 'registro'
elif materia_aberta.tipo_votacao != VOTACAO_NOMINAL:
erro = 'tipo'
if not erro:
context.update({'materia': materia_aberta.materia,
'ementa': materia_aberta.materia.ementa})
parlamentar = votante.parlamentar
parlamentar_presente = False
if parlamentar.id in presentes:
parlamentar_presente = True
context, context_vars = votacao(context, context_vars)
else:
context.update({'error_message':
'Não há presentes na Sessão com a '
'matéria em votação.'})
context.update({'error_message': errors_msgs[erro]})
if parlamentar_presente:
voto = []
if ordem_dia:
voto = VotoParlamentar.objects.filter(
ordem=ordem_dia)
elif expediente:
voto = VotoParlamentar.objects.filter(
expediente=expediente)
return context, context_vars
if voto:
try:
voto = voto.get(parlamentar=parlamentar)
context.update({'voto_parlamentar': voto.voto})
except ObjectDoesNotExist:
context.update(
{'voto_parlamentar': 'Voto não '
'computado.'})
else:
context.update({'error_message':
'Você não está presente na '
'Ordem do Dia/Expediente em votação.'})
else:
context.update(
{'error_message': 'A matéria aberta não é do tipo '
'votação nominal.'})
else:
context.update(
{'error_message': 'A votação para esta matéria já encerrou.'})
else:
context.update(
{'error_message': 'Não há nenhuma matéria aberta.'})
def can_vote(context, context_vars, request):
context.update({'permissao': True})
# Pega sessão
sessao, msg = votacao_aberta(request)
context_vars.update({'sessao':sessao})
if sessao and not msg:
context, context_vars = sessao_votacao(context, context_vars)
elif not sessao and msg:
return HttpResponseRedirect('/')
else:
context.update(
{'error_message': 'Não há nenhuma sessão com matéria aberta.'})
return context, context_vars
def votante_view(request):
# Pega o votante relacionado ao usuário
template_name = 'painel/voto_nominal.html'
context = {}
context_vars = {}
try:
votante = Votante.objects.get(user=request.user)
except ObjectDoesNotExist:
msg = _("Usuário não cadastrado como votante na tela de parlamentares. Contate a administração de sua Casa Legislativa!")
context.update({
'error_message':msg
})
return render(request, template_name, context)
context_vars = {'votante': votante}
context = {'head_title': str(_('Votação Individual'))}
# Verifica se usuário possui permissão para votar
if 'parlamentares.can_vote' in request.user.get_all_permissions():
context, context_vars = can_vote(context, context_vars, request)
else:
context.update({'permissao': False,
@ -192,36 +212,36 @@ def votante_view(request):
# Salva o voto
if request.method == 'POST':
if ordem_dia:
if context_vars['ordem_dia']:
try:
voto = VotoParlamentar.objects.get(
parlamentar=parlamentar,
ordem=ordem_dia)
parlamentar=context_vars['parlamentar'],
ordem=context_vars['ordem_dia'])
except ObjectDoesNotExist:
voto = VotoParlamentar.objects.create(
parlamentar=parlamentar,
parlamentar=context_vars['parlamentar'],
voto=request.POST['voto'],
user=request.user,
ip=get_client_ip(request),
ordem=ordem_dia)
ordem=context_vars['ordem_dia'])
else:
voto.voto = request.POST['voto']
voto.ip = get_client_ip(request)
voto.user = request.user
voto.save()
elif expediente:
elif context_vars['expediente']:
try:
voto = VotoParlamentar.objects.get(
parlamentar=parlamentar,
expediente=expediente)
parlamentar=context_vars['parlamentar'],
expediente=context_vars['expediente'])
except ObjectDoesNotExist:
voto = VotoParlamentar.objects.create(
parlamentar=parlamentar,
parlamentar=context_vars['parlamentar'],
voto=request.POST['voto'],
user=request.user,
ip=get_client_ip(request),
expediente=expediente)
expediente=context_vars['expediente'])
else:
voto.voto = request.POST['voto']
voto.ip = get_client_ip(request)

27
sapl/parlamentares/forms.py

@ -99,6 +99,19 @@ class MandatoForm(ModelForm):
raise ValidationError(_("Data fim mandato fora do intervalo de"
" legislatura informada"))
data_expedicao_diploma = data['data_expedicao_diploma']
if (data_expedicao_diploma and
data_expedicao_diploma > data_inicio_mandato):
raise ValidationError(_("A data da expedição do diploma deve ser anterior "
"a data de início do mandato"))
coligacao = data['coligacao']
if coligacao and not coligacao.legislatura == legislatura:
raise ValidationError(_("A coligação selecionada não está cadastrada "
"na mesma legislatura que o presente mandato, "
"favor verificar a coligação ou fazer o cadastro "
"de uma nova coligação na legislatura correspondente"))
existe_mandato = Mandato.objects.filter(
parlamentar=data['parlamentar'],
legislatura=data['legislatura']).exists()
@ -318,9 +331,23 @@ class FrenteForm(ModelForm):
model = Frente
fields = '__all__'
def clean(self):
frente = super(FrenteForm, self).clean()
cd = self.cleaned_data
if not self.is_valid():
return self.cleaned_data
if cd['data_criacao'] >= cd['data_extincao']:
raise ValidationError(_("Data Dissolução não pode ser anterior a Data Criação"))
return cd
@transaction.atomic
def save(self, commit=True):
frente = super(FrenteForm, self).save(commit)
if not self.instance.pk:
frente = super(FrenteForm, self).save(commit)
content_type = ContentType.objects.get_for_model(Frente)
object_id = frente.pk
tipo = TipoAutor.objects.get(descricao__icontains='Frente')

20
sapl/parlamentares/migrations/0024_auto_20180814_1237.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-08-14 15:37
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('parlamentares', '0023_auto_20180626_1524'),
]
operations = [
migrations.AlterField(
model_name='mandato',
name='titular',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], db_index=True, default=True, verbose_name='Parlamentar Titular'),
),
]

2
sapl/parlamentares/models.py

@ -443,7 +443,7 @@ class Mandato(models.Model):
db_index=True,
default=True,
choices=YES_NO_CHOICES,
verbose_name=_('Vereador Titular'))
verbose_name=_('Parlamentar Titular'))
observacao = models.TextField(
blank=True, verbose_name=_('Observação'))

10
sapl/parlamentares/views.py

@ -87,8 +87,7 @@ class FrenteList(MasterDetailCrud):
CreateView, UpdateView, DeleteView = None, None, None
class BaseMixin(Crud.PublicMixin, MasterDetailCrud.BaseMixin):
list_field_names = ['nome', 'data_criacao']
list_field_names = ['nome', 'data_criacao', 'data_extincao']
@classmethod
def url_name(cls, suffix):
return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix)
@ -282,7 +281,8 @@ class FrenteCrud(CrudAux):
model = Frente
help_topic = 'tipo_situa_militar'
public = [RP_DETAIL, RP_LIST]
list_field_names = ['nome', 'data_criacao', 'parlamentares']
list_field_names = ['nome', 'data_criacao', 'data_extincao', 'parlamentares']
class CreateView(Crud.CreateView):
form_class = FrenteForm
@ -290,6 +290,10 @@ class FrenteCrud(CrudAux):
def form_valid(self, form):
return super(Crud.CreateView, self).form_valid(form)
class UpdateView(Crud.UpdateView):
form_class = FrenteForm
class MandatoCrud(MasterDetailCrud):
model = Mandato

16
sapl/protocoloadm/views.py

@ -550,6 +550,13 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
qs = self.get_queryset()
qs = qs.prefetch_related("documentoacessorioadministrativo_set",
"tramitacaoadministrativo_set",
"tipo",
"tramitacaoadministrativo_set__status",
"tramitacaoadministrativo_set__unidade_tramitacao_local",
"tramitacaoadministrativo_set__unidade_tramitacao_destino")
if status_tramitacao and unidade_destino:
lista = filtra_tramitacao_adm_destino_and_status(status_tramitacao,
unidade_destino)
@ -566,11 +573,6 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
if 'o' in self.request.GET and not self.request.GET['o']:
qs = qs.order_by('-ano', '-numero')
qs = qs.prefetch_related("documentoacessorioadministrativo_set",
"tramitacaoadministrativo_set",
"tramitacaoadministrativo_set__status",
"tramitacaoadministrativo_set__unidade_tramitacao_local",
"tramitacaoadministrativo_set__unidade_tramitacao_destino")
kwargs.update({
'queryset': qs,
@ -607,10 +609,10 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
self.filterset.form.fields['o'].label = _('Ordenação')
length = self.object_list.count()
context = self.get_context_data(filter=self.filterset,
object_list=self.object_list,
filter_url=url,
numero_res=len(self.object_list)
numero_res=length
)
context['show_results'] = show_results_filter_set(

17
sapl/redireciona_urls/urls.py

@ -2,20 +2,22 @@ from django.conf.urls import url
from .apps import AppConfig
from .views import (RedirecionaAtasList, RedirecionaComissao,
RedirecionaComposicaoComissao,
RedirecionaHistoricoTramitacoesList,
RedirecionaMateriaLegislativaDetail,
RedirecionaMateriaLegislativaList,
RedirecionaMateriasPorAnoAutorTipo,
RedirecionaMateriasPorAutor, RedirecionaMesaDiretoraView,
RedirecionaNormasJuridicasDetail,
RedirecionaNormasJuridicasList, RedirecionaParlamentar,
RedirecionaPautaSessao, RedirecionaPresencaParlamentares,
RedirecionaNormasJuridicasList,
RedirecionaNormasJuridicasTextoIntegral,
RedirecionaParlamentar, RedirecionaPautaSessao,
RedirecionaPresencaParlamentares,
RedirecionaRelatoriosList,
RedirecionaRelatoriosMateriasEmTramitacaoList,
RedirecionaSAPLIndex, RedirecionaSessaoPlenaria)
app_name = AppConfig.name
urlpatterns = [
url(r'^default_index_html$',
RedirecionaSAPLIndex.as_view(),
@ -26,6 +28,9 @@ urlpatterns = [
url(r'^consultas/comissao/comissao_',
RedirecionaComissao.as_view(),
name='redireciona_comissao'),
url(r'^consultas/comissao/composicao/composicao_index_html',
RedirecionaComposicaoComissao.as_view(),
name='redireciona_composicaio_comissao'),
url(r'^consultas/pauta_sessao/pauta_sessao_',
RedirecionaPautaSessao.as_view(),
name='redireciona_pauta_sessao_'),
@ -44,6 +49,9 @@ urlpatterns = [
url(r'^consultas/norma_juridica/norma_juridica_mostrar_proc',
RedirecionaNormasJuridicasDetail.as_view(),
name='redireciona_norma_juridica_detail'),
url(r'^sapl_documentos/norma_juridica/(?P<norma_id>[0-9]+)_texto_integral',
RedirecionaNormasJuridicasTextoIntegral.as_view(),
name='redireciona_norma_juridica_texto_integral'),
url(r'^relatorios_administrativos/relatorios_administrativos_index_html$',
RedirecionaRelatoriosList.as_view(),
name='redireciona_relatorios_list'),
@ -51,6 +59,9 @@ urlpatterns = [
RedirecionaRelatoriosMateriasEmTramitacaoList.as_view(),
name='redireciona_relatorio_materia_por_tramitacao'),
url(r'tramitacaoMaterias/materia_mostrar_proc$',
RedirecionaMateriaLegislativaDetail.as_view(),
name='redireciona_materialegislativa_detail_tramitacao'),
url(r'consultas/materia/materia_mostrar_proc$',
RedirecionaMateriaLegislativaDetail.as_view(),
name='redireciona_materialegislativa_detail'),
url(r'^generico/materia_pesquisar_',

48
sapl/redireciona_urls/views.py

@ -1,14 +1,14 @@
from django.core.urlresolvers import NoReverseMatch, reverse
from django.views.generic import RedirectView
from sapl.audiencia.apps import AppConfig as audienciaConfig
from sapl.base.apps import AppConfig as atasConfig
from sapl.comissoes.apps import AppConfig as comissoesConfig
from sapl.materia.apps import AppConfig as materiaConfig
from sapl.norma.apps import AppConfig as normaConfig
from sapl.norma.models import NormaJuridica
from sapl.parlamentares.apps import AppConfig as parlamentaresConfig
from sapl.sessao.apps import AppConfig as sessaoConfig
from sapl.audiencia.apps import AppConfig as audienciaConfig
from .exceptions import UnknownUrlNameError
EMPTY_STRING = ''
@ -142,6 +142,33 @@ class RedirecionaComissao(RedirectView):
return url
class RedirecionaComposicaoComissao(RedirectView):
permanent = True
def get_redirect_url(self):
url = EMPTY_STRING
pk_composicao = self.request.GET.get(
'cod_periodo_comp_sel', EMPTY_STRING)
pk_comissao = self.request.GET.get('cod_comissao', EMPTY_STRING)
if pk_comissao:
kwargs = {'pk': pk_comissao}
try:
url = reverse(comissao_detail, kwargs=kwargs)
except NoReverseMatch:
raise UnknownUrlNameError(comissao_detail)
else:
try:
url = reverse(comissao_list)
except NoReverseMatch:
raise UnknownUrlNameError(comissao_list)
url = has_iframe(url, self.request)
return url
class RedirecionaPautaSessao(RedirectView):
permanent = True
@ -418,6 +445,23 @@ class RedirecionaNormasJuridicasDetail(RedirectView):
return url
class RedirecionaNormasJuridicasTextoIntegral(RedirectView):
permanent = False
def get_redirect_url(self, **kwargs):
url = EMPTY_STRING
try:
norma = NormaJuridica.objects.get(pk=kwargs['norma_id'])
if norma:
url = norma.texto_integral.url
except Exception as e:
raise e
url = has_iframe(url, self.request)
return url
class RedirecionaNormasJuridicasList(RedirectView):
permanent = True

4
sapl/rules/apps.py

@ -1,15 +1,15 @@
from builtins import LookupError
import django
import reversion
from django.apps import apps
from django.contrib.auth import get_user_model
from django.contrib.auth.management import _get_all_permissions
from django.core import exceptions
from django.db import models, router
from django.db.utils import DEFAULT_DB_ALIAS
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
import reversion
from sapl.rules import (SAPL_GROUP_ADMINISTRATIVO, SAPL_GROUP_COMISSOES,
SAPL_GROUP_GERAL, SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA,

1
sapl/rules/map_rules.py

@ -135,6 +135,7 @@ rules_group_norma = {
'rules': [
(norma.NormaJuridica, __base__),
(norma.NormaRelacionada, __base__),
(norma.AnexoNormaJuridica, __base__),
# Publicacao está com permissão apenas para norma e não para matéria
# e proposições apenas por análise do contexto, não é uma limitação

18
sapl/sessao/forms.py

@ -112,6 +112,24 @@ class BancadaForm(ModelForm):
if not self.is_valid():
return self.cleaned_data
data = self.cleaned_data
legislatura = data['legislatura']
data_criacao = data['data_criacao']
if data_criacao:
if (data_criacao < legislatura.data_inicio or
data_criacao > legislatura.data_fim):
raise ValidationError(_("Data de criação da bancada fora do intervalo"
" de legislatura informada"))
data_extincao = data['data_extincao']
if data_extincao:
if (data_extincao < legislatura.data_inicio or
data_extincao > legislatura.data_fim):
raise ValidationError(_("Data fim da bancada fora do intervalo de"
" legislatura informada"))
if self.cleaned_data['data_extincao']:
if (self.cleaned_data['data_extincao'] <
self.cleaned_data['data_criacao']):

18
sapl/sessao/tests/test_sessao.py

@ -1,4 +1,5 @@
import pytest
from datetime import datetime
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from model_mommy import mommy
@ -87,9 +88,16 @@ def test_valida_campos_obrigatorios_bancada_form():
assert len(errors) == 3
def data(valor):
return datetime.strptime(valor, '%Y-%m-%d').date()
@pytest.mark.django_db(transaction=False)
def test_bancada_form_valido():
legislatura = mommy.make(Legislatura)
legislatura = mommy.make(Legislatura,
data_inicio=data('2017-11-10'),
data_fim=data('2017-12-31'),
)
partido = mommy.make(Partido)
form = forms.BancadaForm(data={'legislatura': str(legislatura.pk),
@ -105,7 +113,10 @@ def test_bancada_form_valido():
@pytest.mark.django_db(transaction=False)
def test_bancada_form_datas_invalidas():
legislatura = mommy.make(Legislatura)
legislatura = mommy.make(Legislatura,
data_inicio=data('2017-11-10'),
data_fim=data('2017-12-31'),
)
partido = mommy.make(Partido)
form = forms.BancadaForm(data={'legislatura': str(legislatura.pk),
@ -116,9 +127,6 @@ def test_bancada_form_datas_invalidas():
'descricao': 'teste'
})
assert not form.is_valid()
assert form.errors['__all__'] == [_('Data de extinção não pode ser menor '
'que a de criação')]
@pytest.mark.django_db(transaction=False)
def test_expediente_materia_form_valido():

7
sapl/settings.py

@ -94,7 +94,10 @@ INSTALLED_APPS = (
) + SAPL_APPS
# FTS = Full Text Search
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
# Desabilita a indexação textual até encontramos uma solução para a issue
# https://github.com/interlegis/sapl/issues/2055
#HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.BaseSignalProcessor'
SEARCH_BACKEND = 'haystack.backends.whoosh_backend.WhooshEngine'
SEARCH_URL = ('PATH', PROJECT_DIR.child('whoosh'))
@ -218,7 +221,7 @@ EMAIL_SEND_USER = config('EMAIL_SEND_USER', cast=str, default='')
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', cast=str, default='')
SERVER_EMAIL = config('SERVER_EMAIL', cast=str, default='')
MAX_DOC_UPLOAD_SIZE = 20 * 1024 * 1024 # 20MB
MAX_DOC_UPLOAD_SIZE = 50 * 1024 * 1024 # 50MB
MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB
# Internationalization

2
sapl/templates/base.html

@ -203,7 +203,7 @@
<span class="street-address">{{ endereco }}</span>
<br> CEP: <span class="postal-code">{{ cep }}</span> | Telefone: <span>{{ telefone }}</span>
<br>
<a href="{{endereco_web}}" class="url">{% trans 'Site da Câmara' %}</a> |
<a href="{{endereco_web}}" class="url">{% trans 'Site' %}</a> |
<a href="mailto:{{email}}" class="email">{% trans 'Fale Conosco' %}</a>
</small>
</address>

37
sapl/templates/base/RelatorioMateriasPorAutor_filter.html

@ -15,56 +15,55 @@
<table class="table table-bordered table-hover">
<thead class="thead-default" >
<tr class="active"><th colspan="2" class="text-center">QUADRO GERAL</th></tr>
<tr class="active"><th colspan="3" class="text-center">QUADRO GERAL</th></tr>
<tr class="active">
<th>Tipo Matéria</th>
<th colspan="2">Tipo Matéria</th>
<th>Quantidade</th>
</tr>
</thead>
<tbody>
{% for key, value in qtdes.items %}
<tr>
<td>{{key.sigla}} - {{key}}</td>
<td colspan="2">{{key.sigla}} - {{key}}</td>
<td>{{value}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<table class="table table-bordered table-hover">
{% for materia in object_list %}
{% ifchanged materia.autoria_set.first.autor %}
<thead class="thead-default" >
<tr style="border-left: hidden; border-right: hidden;"><th colspan="3"></th></tr>
<tr class="active"><th colspan="3" class="text-center">Autor: {{ materia.autoria_set.first.autor }}</th></tr>
<tr class="active">
<th>Matéria</th>
<th width="10%">Matéria</th>
<th>Ementa</th>
<th>Autor</th>
<th>Coautor(es)</th>
<th width="20%">Coautor(es)</th>
</tr>
</thead>
{% endifchanged %}
<tbody>
{% for materia in object_list %}
<tr>
<td><a href="{% url 'sapl.materia:materialegislativa_detail' materia.pk %}">
{{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}}
</a></td>
<td>{{materia.ementa}}</td>
<td>
{% for autor in materia.autoria_set.all %}
{% if autor.primeiro_autor %}
{{autor.autor}}<br />
{% endif %}
{% endfor %}
</td>
<td>{% autoescape off %}{{materia.ementa}}{% endautoescape %}</td>
<td>
{% if materia.autoria_set.first != materia.autoria_set.last %}
{% for autor in materia.autoria_set.all %}
{% if not autor.primeiro_autor %}
{{autor.autor}}<br />
{{ autor.autor }}<br />
{% endif %}
{% endfor %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
{% endfor %}
</table>
{% endif %}
{% endblock base_content %}

2
sapl/templates/compilacao/dispositivo_form.html

@ -29,7 +29,7 @@
</a>
<ul class="dropdown-menu">
<li>
{%for parent in object.get_niveis_zero %}
{%for parent in object.select_roots %}
<a href="{% url view.get_url_this_view parent.ta_id parent.pk %}">
{{parent.dispositivo0}} - {{parent|nomenclatura}}
</a>

10
sapl/templates/materia/materialegislativa_detail.html

@ -0,0 +1,10 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% block sub_actions %}
{{ block.super }}
{% if object.em_tramitacao %}
<div class="actions btn-group btn-group-sm" role="group">
<a href="{% url 'sapl.materia:acompanhar_materia' object.id %}" class="btn btn-default">Acompanhar Matéria</a>
</div>
{% endif %}
{% endblock sub_actions %}

9
sapl/templates/materia/prop_pendentes_list.html

@ -39,7 +39,15 @@
{% define object_list as list %}
{% endif %}
{% if 'page' in request.GET %}
{% define request.GET.page as pagina %}
{% else %}
{% define '1' as pagina %}
{% endif %}
{% for prop in list %}
{% if forloop.counter > pagina|paginacao_limite_inferior and forloop.counter <= pagina|paginacao_limite_superior %}
<tr>
<td>
<a href="{% url 'sapl.materia:proposicao_detail' prop.pk %}">{{ prop.data_envio|localtime|date:"d/m/Y H:i:s" }}</a>
@ -57,6 +65,7 @@
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>

5
sapl/templates/norma/layouts.yaml

@ -24,6 +24,11 @@ NormaJuridica:
- observacao
- assuntos
AnexoNormaJuridica:
{% trans 'Adicionar Anexos à Norma Jurídica' %}:
- anexo_arquivo
- norma
NormaJuridicaCreate:
{% trans 'Identificação Básica' %}:
- tipo ano numero

16
sapl/templates/norma/normajuridica_detail.html

@ -58,6 +58,22 @@
</div>
{% endfor %}
{% endif %}
</div>
</div>
<hr />
<div class="row-fluid">
<div class="col-sm-12">
&nbsp;<p class="control-label">Anexos Norma Jurídica</p>
{% if object.get_anexos_norma_juridica|length > 0 %}
{% for p in object.get_anexos_norma_juridica %}
<div class="form-control-static">
<a href="/media/{{p.anexo_arquivo}}">
{{ p.anexo_arquivo | to_str | split:"/" | get_last_item_from_list:-1 }}
</a>
</div>
{% endfor %}
{% endif %}
</div>
</div>
{% endblock detail_content %}

2
sapl/templates/norma/subnav.yaml

@ -5,6 +5,8 @@
- title: {% trans 'Alterações em Outras Normas' %}
url: normarelacionada_list
check_permission: norma.list_normarelacionada
- title: {% trans 'Anexos da Norma' %}
url: anexonormajuridica_list
# Opção adicionada para chamar o TextoArticulado da norma.
# para integração foram necessárias apenas criar a url norma_ta em urls.py

4
sapl/templates/painel/index.html

@ -135,7 +135,7 @@
<div class="col-md-4">
<h2><font color="#459170"><p align="center" style="font-family:Verdana">Matéria em Votação</p></font></h2>
<table style="width:75%; border:1px;" align="center">
<tr><td style="text-align:center"><h4><font color="white"><span id="materia_legislativa_texto"></span></font></h4></td></tr>
<tr><td style="text-align:center"><h4><font color="white" size="5"> <span id="materia_legislativa_texto"></span></font></h4></td></tr>
<tr><td style="text-align:center"><h4><font color="white"><span id="observacao_materia"></span></font></h4></td></tr>
</table>
@ -398,7 +398,7 @@
$("#observacao_materia").text('');
}
if (data['tipo_resultado']){
if (data['tipo_resultado'] && data['status_painel'] == true){
$("#resultado_votacao").text(data["tipo_resultado"]);
$("#resultado_votacao").css("color", "#45919D");
var resultado_votacao_upper = $("#resultado_votacao").text().toUpperCase();

2
sapl/templates/painel/voto_nominal.html

@ -74,7 +74,7 @@
<h2><font color="#459170"><p align="center" style="font-family:Verdana">Matéria em Votação</p></font></h2>
<table style="width:75%; border:1px;" align="center">
<tr><th style="text-align:center"><h4><font color="white">{{materia}}</font></h4></th></tr>
<tr><th style="text-align:center"><h4><font color="white" size="5">{{materia}}</font></h4></th></tr>
<tr><th style="text-align:center"><h4><font color="white">{{ementa}}</font></h4></th></tr>
<tr><th style="text-align:center"><font color="#45919D"><span id="resultado_votacao"></span></font></th></tr>
</table>

8
sapl/templates/protocoloadm/documentoadministrativo_filter.html

@ -44,12 +44,14 @@
{% if d.protocolo %}
<strong>Protocolo:</strong>&nbsp;<a href="{% url 'sapl.protocoloadm:protocolo_mostrar' d.protocolo.id %}">{{ d.protocolo}}</a></br>
{% endif %}
{% if d.tramitacaoadministrativo_set.last.unidade_tramitacao_destino %}
<strong>Localização Atual:</strong> &nbsp;{{d.tramitacaoadministrativo_set.last.unidade_tramitacao_destino}}
{% define d.tramitacaoadministrativo_set.last as tram %}
{% if tram.unidade_tramitacao_destino %}
<strong>Localização Atual:</strong> &nbsp;{{tram.unidade_tramitacao_destino}}
</br>
<strong>Status:</strong> {{d.tramitacaoadministrativo_set.last.status}}
<strong>Status:</strong> {{tram.status}}
</br>
{% endif %}
{% define d.documentoacessorioadministrativo_set.all as acess %}
{% if d.documentoacessorioadministrativo_set.all.exists %}
<strong>Documentos Acessórios:</strong>
<a href="{% url 'sapl.protocoloadm:documentoacessorioadministrativo_list' d.id %}">

10
sapl/templates/sessao/adicionar_varias_materias_expediente.html

@ -94,3 +94,13 @@
{% endif %}
{% endblock detail_content %}
{% block extra_js %}
<script>
$(window).on('beforeunload', function () {
$("input[type=submit], input[type=button]").prop("disabled", "disabled");
});
</script>
{% endblock extra_js%}

5
sapl/test_urls.py

@ -278,7 +278,7 @@ def test_urlpatterns(url_item, admin_client):
""" % (app_name, url)
app_name = app_name[5:]
if app_name != 'redireciona_urls':
assert app_name in apps_url_patterns_prefixs_and_users, """
A app (%s) da url (%s) não consta na lista de prefixos do teste
""" % (app_name, url)
@ -297,6 +297,9 @@ def test_urlpatterns(url_item, admin_client):
Os prefixos permitidos são:
%s
""" % (url, app_name, prefixs)
else:
# ignorando app de redirecionamento de urls no padrão do SAPL 2.5
pass
urls_publicas_excecoes = {

2
setup.py

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

Loading…
Cancel
Save