Browse Source

Merge branch '3.1.x'

pull/1828/merge
Edward Ribeiro 7 years ago
parent
commit
f07c2eabb5
  1. 2
      docker-compose.yml
  2. 61
      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. 28
      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. 75
      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. 64
      sapl/legacy/migracao_dados.py
  21. 1
      sapl/legacy/migracao_usuarios.py
  22. 59
      sapl/legacy/scripts/ressucita_dependencias.py
  23. 12
      sapl/materia/forms.py
  24. 10
      sapl/materia/views.py
  25. 43
      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. 212
      sapl/painel/views.py
  31. 45
      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. 18
      sapl/protocoloadm/views.py
  36. 19
      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. 73
      sapl/templates/base/RelatorioMateriasPorAutor_filter.html
  45. 2
      sapl/templates/compilacao/dispositivo_form.html
  46. 10
      sapl/templates/materia/materialegislativa_detail.html
  47. 41
      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. 41
      sapl/test_urls.py
  56. 2
      setup.py

2
docker-compose.yml

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

61
sapl/audiencia/forms.py

@ -16,18 +16,21 @@ class AudienciaForm(forms.ModelForm):
tipo_materia = forms.ModelChoiceField( tipo_materia = forms.ModelChoiceField(
label=_('Tipo Matéria'), label=_('Tipo Matéria'),
required=True, required=False,
queryset=TipoMateriaLegislativa.objects.all(), queryset=TipoMateriaLegislativa.objects.all(),
empty_label='Selecione', empty_label='Selecione',
) )
numero_materia = forms.CharField( numero_materia = forms.CharField(
label='Número Matéria', required=True) label='Número Matéria', required=False)
ano_materia = forms.CharField( ano_materia = forms.CharField(
label='Ano Matéria', label='Ano Matéria',
initial=int(data_atual.year), required=False)
required=True)
materia = forms.ModelChoiceField(required=False,
widget=forms.HiddenInput(),
queryset=MateriaLegislativa.objects.all())
class Meta: class Meta:
model = AudienciaPublica model = AudienciaPublica
@ -36,7 +39,7 @@ class AudienciaForm(forms.ModelForm):
'observacao', 'audiencia_cancelada', 'url_audio', 'observacao', 'audiencia_cancelada', 'url_audio',
'url_video', 'upload_pauta', 'upload_ata', 'url_video', 'upload_pauta', 'upload_ata',
'upload_anexo', 'tipo_materia', 'numero_materia', 'upload_anexo', 'tipo_materia', 'numero_materia',
'ano_materia'] 'ano_materia', 'materia']
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -59,17 +62,38 @@ class AudienciaForm(forms.ModelForm):
if not self.is_valid(): if not self.is_valid():
return cleaned_data return cleaned_data
try: materia = cleaned_data['numero_materia']
materia = MateriaLegislativa.objects.get( ano_materia = cleaned_data['ano_materia']
numero=self.cleaned_data['numero_materia'], tipo_materia = cleaned_data['tipo_materia']
ano=self.cleaned_data['ano_materia'],
tipo=self.cleaned_data['tipo_materia']) if materia and ano_materia and tipo_materia:
except ObjectDoesNotExist: try:
msg = _('A matéria a ser inclusa não existe no cadastro' materia = MateriaLegislativa.objects.get(
' de matérias legislativas.') numero=materia,
raise ValidationError(msg) ano=ano_materia,
tipo=tipo_materia)
except ObjectDoesNotExist:
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: else:
cleaned_data['materia'] = materia 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_inicio'] and self.cleaned_data['hora_fim']:
if (self.cleaned_data['hora_fim'] < if (self.cleaned_data['hora_fim'] <
@ -77,9 +101,4 @@ class AudienciaForm(forms.ModelForm):
msg = _('A hora de fim não pode ser anterior a hora de ínicio') msg = _('A hora de fim não pode ser anterior a hora de ínicio')
raise ValidationError(msg) raise ValidationError(msg)
return self.cleaned_data return cleaned_data
@transaction.atomic()
def save(self, commit=True):
audiencia = super(AudienciaForm, self).save(commit)
return audiencia

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( hora_inicio = models.CharField(
max_length=5, verbose_name=_('Horário Início(hh:mm)')) max_length=5, verbose_name=_('Horário Início(hh:mm)'))
hora_fim = models.CharField( 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( observacao = models.TextField(
max_length=500, blank=True, verbose_name=_('Observação')) max_length=500, blank=True, verbose_name=_('Observação'))
audiencia_cancelada = models.BooleanField( 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 model_mommy import mommy
from sapl.audiencia import forms from sapl.audiencia import forms
from sapl.audiencia.models import TipoAudienciaPublica
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_valida_campos_obrigatorios_audiencia_form(): 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['nome'] == [_('Este campo é obrigatório.')]
assert errors['tema'] == [_('Este campo é obrigatório.')] assert errors['tema'] == [_('Este campo é obrigatório.')]
assert errors['tipo'] == [_('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['data'] == [_('Este campo é obrigatório.')]
assert errors['hora_inicio'] == [_('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 .forms import AudienciaForm
from .models import AudienciaPublica from .models import AudienciaPublica
def index(request): def index(request):
return HttpResponse("Audiência Pública") return HttpResponse("Audiência Pública")
class AudienciaCrud(Crud): class AudienciaCrud(Crud):
model = AudienciaPublica model = AudienciaPublica
public = [RP_LIST, RP_DETAIL, ] public = [RP_LIST, RP_DETAIL, ]
class BaseMixin(Crud.BaseMixin): class BaseMixin(Crud.BaseMixin):
list_field_names = ['materia', 'tipo', 'numero', 'nome', list_field_names = ['numero', 'nome', 'tipo', 'materia',
'data'] 'data']
ordering = 'nome', 'numero', 'tipo', 'data' ordering = 'nome', 'numero', 'tipo', 'data'

11
sapl/base/forms.py

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

28
sapl/base/models.py

@ -5,6 +5,7 @@ from django.db import models
from django.db.models.signals import post_migrate from django.db.models.signals import post_migrate
from django.db.utils import DEFAULT_DB_ALIAS from django.db.utils import DEFAULT_DB_ALIAS
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES, from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES,
get_settings_auth_user_model, models_with_gr_for_model) get_settings_auth_user_model, models_with_gr_for_model)
@ -178,7 +179,8 @@ class Autor(models.Model):
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null=True) 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( content_type = models.ForeignKey(
ContentType, ContentType,
@ -188,7 +190,7 @@ class Autor(models.Model):
autor_related = GenericForeignKey('content_type', 'object_id') autor_related = GenericForeignKey('content_type', 'object_id')
nome = models.CharField( 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) cargo = models.CharField(max_length=50, blank=True)
@ -199,23 +201,17 @@ class Autor(models.Model):
ordering = ('nome',) ordering = ('nome',)
def __str__(self): def __str__(self):
if self.autor_related: if self.autor_related:
return str(self.autor_related) return str(self.autor_related)
else: else:
if str(self.cargo): if self.nome:
return _('%(nome)s - %(cargo)s') % { if self.cargo:
'nome': self.nome, 'cargo': self.cargo} return '{} - {}'.format(self.nome, self.cargo)
else: else:
return str(self.nome) return str(self.nome)
"""if str(self.tipo) == 'Parlamentar' and self.parlamentar: if self.user:
return self.parlamentar.nome_parlamentar return str(self.user.username)
elif str(self.tipo) == 'Comissao' and self.comissao: return '?'
return str(self.comissao)
elif str(self.tipo) == 'Partido' and self.partido:
return str(self.partido)
else:
"""
def cria_models_tipo_autor(app_config=None, verbosity=2, interactive=True, 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) 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 @register.filter
def sort_by_keys(value, key): def sort_by_keys(value, key):
transformed = [] transformed = []
@ -63,6 +72,16 @@ def sort_by_keys(value, key):
return transformed 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 @register.filter
def lookup(d, key): def lookup(d, key):
return d[key] if key in d else [] return d[key] if key in d else []

2
sapl/base/views.py

@ -393,7 +393,7 @@ class RelatorioMateriasTramitacaoView(FilterView):
context = super(RelatorioMateriasTramitacaoView, context = super(RelatorioMateriasTramitacaoView,
self).get_context_data(**kwargs) 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 = context['object_list']
qs = qs.filter(em_tramitacao=True) qs = qs.filter(em_tramitacao=True)

2
sapl/comissoes/forms.py

@ -242,7 +242,7 @@ class ComissaoForm(forms.ModelForm):
if not self.is_valid(): if not self.is_valid():
return self.cleaned_data return self.cleaned_data
if len(self.cleaned_data['nome']) > 50: if len(self.cleaned_data['nome']) > 100:
msg = _('Nome da Comissão deve ter no máximo 50 caracteres.') msg = _('Nome da Comissão deve ter no máximo 50 caracteres.')
raise ValidationError(msg) raise ValidationError(msg)
if self.cleaned_data['data_extincao']: 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, def save(self, force_insert=False, force_update=False, using=None,
update_fields=None, clean=True): 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() self.clean()
return models.Model.save( return models.Model.save(
self, self,
force_insert=force_insert, force_insert=force_insert,
@ -1458,7 +1465,7 @@ class Dispositivo(BaseModel, TimestampedMixin):
tipo_dispositivo_id=self.tipo_dispositivo.pk)) tipo_dispositivo_id=self.tipo_dispositivo.pk))
else: # contagem continua restrita a articulacao else: # contagem continua restrita a articulacao
proxima_articulacao = self.get_proximo_nivel_zero() proxima_articulacao = self.select_next_root()
if proxima_articulacao is None: if proxima_articulacao is None:
irmaos = list(Dispositivo.objects.filter( irmaos = list(Dispositivo.objects.filter(
@ -1550,25 +1557,15 @@ class Dispositivo(BaseModel, TimestampedMixin):
irmao.clean() irmao.clean()
irmao.save() irmao.save()
def get_proximo_nivel_zero(self): def select_roots(self):
proxima_articulacao = Dispositivo.objects.order_by('ordem').filter( return Dispositivo.objects.order_by(
ordem__gt=self.ordem, 'ordem').filter(nivel=0, ta_id=self.ta_id)
nivel=0,
ta_id=self.ta_id).first() def select_next_root(self):
return proxima_articulacao return self.select_roots().filter(ordem__gt=self.ordem).first()
def get_nivel_zero_anterior(self): def select_prev_root(self):
anterior_articulacao = Dispositivo.objects.order_by('ordem').filter( return self.select_roots().filter(ordem__lt=self.ordem).last()
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
# metodo obsoleto, foi acrescentado o campo auto_inserido no modelo # metodo obsoleto, foi acrescentado o campo auto_inserido no modelo
def is_relative_auto_insert__obsoleto(self, perfil_pk=None): def is_relative_auto_insert__obsoleto(self, perfil_pk=None):

75
sapl/compilacao/views.py

@ -15,7 +15,7 @@ from django.db import connection, transaction
from django.db.models import Q from django.db.models import Q
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from django.http.response import (HttpResponse, HttpResponseRedirect, from django.http.response import (HttpResponse, HttpResponseRedirect,
JsonResponse) JsonResponse, Http404)
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.utils.dateparse import parse_date from django.utils.dateparse import parse_date
from django.utils.encoding import force_text 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.crud.base import Crud, CrudAux, CrudListView, make_pagination
from sapl.settings import BASE_DIR from sapl.settings import BASE_DIR
TipoNotaCrud = CrudAux.build(TipoNota, 'tipo_nota') TipoNotaCrud = CrudAux.build(TipoNota, 'tipo_nota')
TipoVideCrud = CrudAux.build(TipoVide, 'tipo_vide') TipoVideCrud = CrudAux.build(TipoVide, 'tipo_vide')
TipoPublicacaoCrud = CrudAux.build(TipoPublicacao, 'tipo_publicacao') TipoPublicacaoCrud = CrudAux.build(TipoPublicacao, 'tipo_publicacao')
@ -104,7 +103,7 @@ class IntegracaoTaView(TemplateView):
) % self.model._meta.verbose_name_plural) ) % self.model._meta.verbose_name_plural)
return redirect('/') return redirect('/')
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
try: try:
if settings.DEBUG or not TipoDispositivo.objects.exists(): if settings.DEBUG or not TipoDispositivo.objects.exists():
@ -211,8 +210,12 @@ class CompMixin(PermissionRequiredMixin):
@property @property
def ta(self): def ta(self):
ta = TextoArticulado.objects.get( try:
pk=self.kwargs.get('ta_id', self.kwargs.get('pk', 0))) ta = TextoArticulado.objects.get(
pk=self.kwargs.get('ta_id', self.kwargs.get('pk', 0)))
except TextoArticulado.DoesNotExist:
raise Http404()
return ta return ta
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -1237,9 +1240,9 @@ class TextEditView(CompMixin, TemplateView):
'filhos': [], 'filhos': [],
'alts': [], 'alts': [],
'pai': None, 'pai': None,
'st': None, # dispositivo substituido 'st': None, # dispositivo substituido
'sq': None, # dispositivo subsequente 'sq': None, # dispositivo subsequente
'da': None, # dispositivo atualizador 'da': None, # dispositivo atualizador
'td': tds[d.tipo_dispositivo_id], # tipo do dispositivo 'td': tds[d.tipo_dispositivo_id], # tipo do dispositivo
'na': self.nota_alteracao(d, lista_ta_publicado)\ 'na': self.nota_alteracao(d, lista_ta_publicado)\
if d.ta_id == ta_id else None if d.ta_id == ta_id else None
@ -1388,7 +1391,7 @@ class ActionsCommonsMixin:
pkfilho = dp.pk pkfilho = dp.pk
dp = dp.dispositivo_pai dp = dp.dispositivo_pai
proxima_articulacao = dp.get_proximo_nivel_zero() proxima_articulacao = dp.select_next_root()
if proxima_articulacao is not None: if proxima_articulacao is not None:
parents = Dispositivo.objects.filter( parents = Dispositivo.objects.filter(
@ -1480,30 +1483,36 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin):
).first() ).first()
data = {} data = {}
if base_anterior: if not base_anterior or base == base.get_raiz():
data = self.get_json_for_refresh(base_anterior) 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: else:
base_anterior = base.get_nivel_zero_anterior() if base != base.get_raiz():
data = self.get_json_for_refresh(base_anterior) data['pai'] = [base.get_raiz().pk]
data['pai'] = [base.get_raiz().pk]
if ta_base.id != int(self.kwargs['ta_id']): if ta_base.id != int(self.kwargs['ta_id']):
data['pai'] = [base.dispositivo_atualizador.pk] data['pai'] = [base.dispositivo_atualizador.pk]
data['pk'] = base.dispositivo_atualizador.pk data['pk'] = base.dispositivo_atualizador.pk
try: try:
with transaction.atomic(): with transaction.atomic():
message = str(self.remover_dispositivo(base, bloco)) message = str(self.remover_dispositivo(base, bloco))
if message: if message:
self.set_message(data, 'warning', message, modal=True) self.set_message(data, 'warning', message, modal=True)
else: else:
self.set_message(data, 'success', _( self.set_message(data, 'success', _(
'Exclusão efetuada com sucesso!'), modal=True) 'Exclusão efetuada com sucesso!'), modal=True)
ta_base.reagrupar_ordem_de_dispositivos() ta_base.reagrupar_ordem_de_dispositivos()
except Exception as e: except Exception as e:
data['pk'] = self.kwargs['dispositivo_id'] data['pk'] = self.kwargs['dispositivo_id']
self.set_message(data, 'danger', str(e), modal=True) self.set_message(data, 'danger', str(e), modal=True)
return data return data
@ -1536,7 +1545,7 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin):
print(e) print(e)
base.delete() base.delete()
else: else:
proxima_articulacao = base.get_proximo_nivel_zero() proxima_articulacao = base.select_next_root()
if not bloco: if not bloco:
# tranferir filhos para primeiro pai possível acima da base # tranferir filhos para primeiro pai possível acima da base
# de exclusão # de exclusão
@ -1694,7 +1703,7 @@ class ActionDeleteDispositivoMixin(ActionsCommonsMixin):
base_adicao = {} base_adicao = {}
nivel_zero_anterior = base.get_nivel_zero_anterior() nivel_zero_anterior = base.select_prev_root()
if nivel_zero_anterior: if nivel_zero_anterior:
nivel_zero_anterior = nivel_zero_anterior.ordem nivel_zero_anterior = nivel_zero_anterior.ordem
else: else:
@ -2374,7 +2383,7 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
if dp.nivel == 0: if dp.nivel == 0:
proxima_articulacao = dp.get_proximo_nivel_zero() proxima_articulacao = dp.select_next_root()
if not proxima_articulacao: if not proxima_articulacao:
filhos_continuos = list(Dispositivo.objects.filter( filhos_continuos = list(Dispositivo.objects.filter(

4
sapl/crud/base.py

@ -851,7 +851,9 @@ class CrudDeleteView(PermissionRequiredContainerCrudMixin,
é referenciado por outros registros:<br>\ é referenciado por outros registros:<br>\
<ul>' <ul>'
for i in err.protected_objects: 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>' error_msg += '</ul>'
messages.add_message(request, 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' 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): 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)) 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: if TAG_MARCO in REPO.tags:
info('A migração já está feita.') info('A migração já está feita.')
return return
@ -26,7 +26,7 @@ def migrar(interativo=False):
'Antes de migrar ' 'Antes de migrar '
'é necessário fazer a exportação de documentos do zope') 'é necessário fazer a exportação de documentos do zope')
management.call_command('migrate') management.call_command('migrate')
migrar_dados() migrar_dados(apagar_do_legado)
migrar_usuarios(REPO.working_dir) migrar_usuarios(REPO.working_dir)
migrar_documentos(REPO) migrar_documentos(REPO)
gravar_marco() gravar_marco()

64
sapl/legacy/migracao_dados.py

@ -8,7 +8,6 @@ from datetime import date
from functools import lru_cache, partial from functools import lru_cache, partial
from itertools import groupby from itertools import groupby
from operator import xor from operator import xor
from subprocess import PIPE, call
import git import git
import pkg_resources import pkg_resources
@ -31,12 +30,10 @@ from unipath import Path
from sapl.base.models import AppConfig as AppConf from sapl.base.models import AppConfig as AppConf
from sapl.base.models import Autor, TipoAutor, cria_models_tipo_autor from sapl.base.models import Autor, TipoAutor, cria_models_tipo_autor
from sapl.comissoes.models import Comissao, Composicao, Participacao, Reuniao 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 NormaJuridica as OldNormaJuridica
from sapl.legacy.models import TipoNumeracaoProtocolo from sapl.legacy.models import TipoNumeracaoProtocolo
from sapl.legacy_migration_settings import (DATABASES, DIR_DADOS_MIGRACAO, from sapl.legacy_migration_settings import (DIR_DADOS_MIGRACAO, DIR_REPO,
DIR_REPO, NOME_BANCO_LEGADO, NOME_BANCO_LEGADO)
PROJECT_DIR)
from sapl.materia.models import (AcompanhamentoMateria, MateriaLegislativa, from sapl.materia.models import (AcompanhamentoMateria, MateriaLegislativa,
Proposicao, StatusTramitacao, TipoDocumento, Proposicao, StatusTramitacao, TipoDocumento,
TipoMateriaLegislativa, TipoProposicao, TipoMateriaLegislativa, TipoProposicao,
@ -141,10 +138,6 @@ models_novos_para_antigos = {
for model in field_renames} for model in field_renames}
models_novos_para_antigos[Composicao] = models_novos_para_antigos[Participacao] 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 = { campos_novos_para_antigos = {
model._meta.get_field(nome_novo): nome_antigo model._meta.get_field(nome_novo): nome_antigo
for model, renames in field_renames.items() for model, renames in field_renames.items()
@ -226,10 +219,6 @@ class ForeignKeyFaltando(ObjectDoesNotExist):
'Uma FK aponta para um registro inexistente' 'Uma FK aponta para um registro inexistente'
def __init__(self, field, valor, old): 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.field = field
self.valor = valor self.valor = valor
self.old = old self.old = old
@ -546,7 +535,6 @@ PROPAGACOES_DE_EXCLUSAO = [
('sessao_plenaria', 'ordem_dia', 'cod_sessao_plen'), ('sessao_plenaria', 'ordem_dia', 'cod_sessao_plen'),
('sessao_plenaria', 'expediente_materia', 'cod_sessao_plen'), ('sessao_plenaria', 'expediente_materia', 'cod_sessao_plen'),
('sessao_plenaria', 'expediente_sessao_plenaria', '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 # as consultas no código do sapl 2.5
# votacao_ordem_dia_obter_zsql e votacao_expediente_materia_obter_zsql # 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 # 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', 'anexada', 'cod_materia_anexada'),
('materia_legislativa', 'documento_acessorio', 'cod_materia'), ('materia_legislativa', 'documento_acessorio', 'cod_materia'),
('materia_legislativa', 'numeracao', 'cod_materia'), ('materia_legislativa', 'numeracao', 'cod_materia'),
('materia_legislativa', 'expediente_materia', 'cod_materia'),
# norma # norma
('norma_juridica', 'vinculo_norma_juridica', 'cod_norma_referente'), ('norma_juridica', 'vinculo_norma_juridica', 'cod_norma_referente'),
('norma_juridica', 'vinculo_norma_juridica', 'cod_norma_referida'), ('norma_juridica', 'vinculo_norma_juridica', 'cod_norma_referida'),
('norma_juridica', 'legislacao_citada', 'cod_norma'),
# documento administrativo # documento administrativo
('documento_administrativo', 'tramitacao_administrativo', 'cod_documento'), ('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) [pk_pai] = get_pk_legado(tabela_pai)
exec_legado(''' sql = '''
update {} set ind_excluido = 1 where {} not in ( update {} set ind_excluido = 1 where {} not in (
select {} from {} where ind_excluido != 1) 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(): def uniformiza_banco():
exec_legado('SET SESSION sql_mode = "";') # desliga checagens do mysql 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() checa_registros_votacao_ambiguos_e_remove_nao_usados()
propaga_exclusoes(PROPAGACOES_DE_EXCLUSAO_REGISTROS_VOTACAO)
garante_coluna_no_legado('proposicao', garante_coluna_no_legado('proposicao',
'num_proposicao int(11) NULL') 'num_proposicao int(11) NULL')
@ -808,7 +804,7 @@ def roda_comando_shell(cmd):
assert res == 0, 'O comando falhou: {}'.format(cmd) assert res == 0, 'O comando falhou: {}'.format(cmd)
def migrar_dados(): def migrar_dados(apagar_do_legado=False):
try: try:
ocorrencias.clear() ocorrencias.clear()
ocorrencias.default_factory = list ocorrencias.default_factory = list
@ -840,7 +836,7 @@ def migrar_dados():
fill_vinculo_norma_juridica() fill_vinculo_norma_juridica()
fill_dados_basicos() fill_dados_basicos()
info('Começando migração: ...') info('Começando migração: ...')
migrar_todos_os_models() migrar_todos_os_models(apagar_do_legado)
except Exception as e: except Exception as e:
ocorrencias['traceback'] = str(traceback.format_exc()) ocorrencias['traceback'] = str(traceback.format_exc())
raise e raise e
@ -874,7 +870,6 @@ def get_models_a_migrar():
if model in field_renames] if model in field_renames]
# retira reuniões quando não existe na base legada # retira reuniões quando não existe na base legada
# (só existe no sapl 3.0) # (só existe no sapl 3.0)
tabelas_legado = [t for (t,) in exec_legado('show tables')]
if not EXISTE_REUNIAO_NO_LEGADO: if not EXISTE_REUNIAO_NO_LEGADO:
models.remove(Reuniao) models.remove(Reuniao)
# Devido à referência TipoProposicao.tipo_conteudo_related # Devido à referência TipoProposicao.tipo_conteudo_related
@ -890,12 +885,12 @@ def get_models_a_migrar():
return models return models
def migrar_todos_os_models(): def migrar_todos_os_models(apagar_do_legado):
for model in get_models_a_migrar(): 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__) print('Migrando %s...' % model.__name__)
model_legado, tabela_legado, campos_pk_legado = \ model_legado, tabela_legado, campos_pk_legado = \
@ -949,12 +944,13 @@ def migrar_model(model):
novos.append(new) # guarda para salvar novos.append(new) # guarda para salvar
# acumula deleção do registro no legado # acumula deleção do registro no legado
sql_delete_legado += 'delete from {} where {};\n'.format( if apagar_do_legado:
tabela_legado, sql_delete_legado += 'delete from {} where {};\n'.format(
' and '.join( tabela_legado,
'{} = "{}"'.format(campo, ' and '.join(
getattr(old, campo)) '{} = "{}"'.format(campo,
for campo in campos_pk_legado)) getattr(old, campo))
for campo in campos_pk_legado))
# salva novos registros # salva novos registros
with reversion.create_revision(): with reversion.create_revision():
@ -973,7 +969,7 @@ def migrar_model(model):
reinicia_sequence(model, ultima_pk_legado + 1) reinicia_sequence(model, ultima_pk_legado + 1)
# apaga registros migrados do legado # apaga registros migrados do legado
if sql_delete_legado: if apagar_do_legado and sql_delete_legado:
exec_legado(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): 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) 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 Protocolo', 'Operador de Protocolo Administrativo'),
('Operador Sessao Plenaria', 'Operador de Sessão Plenária'), ('Operador Sessao Plenaria', 'Operador de Sessão Plenária'),
('Parlamentar', 'Votante'), ('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)

12
sapl/materia/forms.py

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

10
sapl/materia/views.py

@ -426,6 +426,10 @@ class ProposicaoPendente(PermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(ProposicaoPendente, self).get_context_data(**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'] paginator = context['paginator']
page_obj = context['page_obj'] page_obj = context['page_obj']
context['AppConfig'] = sapl.base.models.AppConfig.objects.all().last() 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['NO_ENTRIES_MSG'] = 'Nenhuma proposição pendente.'
context['subnav_template_name'] = 'materia/subnav_prop.yaml' 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 return context
@ -701,12 +707,12 @@ class ProposicaoCrud(Crud):
messages.success(request, _( messages.success(request, _(
'Proposição enviada com sucesso.')) 'Proposição enviada com sucesso.'))
try: 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 ano=p.ano).last().numero + 1
messages.success(request, _( messages.success(request, _(
'%s : nº %s de %s <br>Atenção! Este número é apenas um provável ' '%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' 'número que pode não corresponder com a realidade'
% (p.tipo, Numero, p.ano))) % (p.tipo, numero, p.ano)))
except ValueError: except ValueError:
pass pass

43
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.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import RANGE_ANOS, RangeWidgetOverride from sapl.utils import RANGE_ANOS, RangeWidgetOverride
from .models import (AssuntoNorma, NormaJuridica, NormaRelacionada, from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica) TipoNormaJuridica)
@ -120,6 +120,7 @@ class NormaJuridicaForm(ModelForm):
'assuntos'] 'assuntos']
widgets = {'assuntos': widgets.CheckboxSelectMultiple} widgets = {'assuntos': widgets.CheckboxSelectMultiple}
def clean(self): def clean(self):
cleaned_data = super(NormaJuridicaForm, self).clean() cleaned_data = super(NormaJuridicaForm, self).clean()
@ -163,11 +164,10 @@ class NormaJuridicaForm(ModelForm):
def clean_texto_integral(self): def clean_texto_integral(self):
texto_integral = self.cleaned_data.get('texto_integral', False) texto_integral = self.cleaned_data.get('texto_integral', False)
if texto_integral: if texto_integral and texto_integral.size > MAX_DOC_UPLOAD_SIZE:
if texto_integral.size > MAX_DOC_UPLOAD_SIZE: max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024)) raise ValidationError(
raise ValidationError( "Arquivo muito grande. ( > {0}MB )".format(max_size))
"Arquivo muito grande. ( > {0}MB )".format(max_size))
return texto_integral return texto_integral
def save(self, commit=False): def save(self, commit=False):
@ -175,9 +175,40 @@ class NormaJuridicaForm(ModelForm):
norma.timestamp = timezone.now() norma.timestamp = timezone.now()
norma.materia = self.cleaned_data['materia'] norma.materia = self.cleaned_data['materia']
norma = super(NormaJuridicaForm, self).save(commit=True) norma = super(NormaJuridicaForm, self).save(commit=True)
return norma 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): class NormaRelacionadaForm(ModelForm):
tipo = forms.ModelChoiceField( 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) norma_relacionada=self.id)
return (principais, relacionadas) return (principais, relacionadas)
def get_anexos_norma_juridica(self):
anexos = AnexoNormaJuridica.objects.filter(
norma=self.id)
return anexos
def __str__(self): def __str__(self):
return _('%(tipo)s%(numero)s de %(data)s') % { return _('%(numero)s de %(data)s') % {
'tipo': self.tipo,
'numero': self.numero, 'numero': self.numero,
'data': defaultfilters.date(self.data, "d \d\e F \d\e Y")} 'data': defaultfilters.date(self.data, "d \d\e F \d\e Y")}
@ -252,3 +257,28 @@ class NormaRelacionada(models.Model):
' - Relacionada: %(norma_relacionada)s') % { ' - Relacionada: %(norma_relacionada)s') % {
'norma_principal': self.norma_principal, 'norma_principal': self.norma_principal,
'norma_relacionada': self.norma_relacionada} '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 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, NormaRelacionadaCrud, NormaTaView, TipoNormaCrud,
TipoVinculoNormaJuridicaCrud, recuperar_norma, TipoVinculoNormaJuridicaCrud, recuperar_norma,
recuperar_numero_norma) recuperar_numero_norma)
@ -12,11 +12,11 @@ app_name = AppConfig.name
urlpatterns = [ urlpatterns = [
url(r'^norma/', include(NormaCrud.get_urls() + url(r'^norma/', include(NormaCrud.get_urls() +
NormaRelacionadaCrud.get_urls())), NormaRelacionadaCrud.get_urls() +
AnexoNormaJuridicaCrud.get_urls())),
# Integração com Compilação # Integração com Compilação
url(r'^norma/(?P<pk>[0-9]+)/ta$', NormaTaView.as_view(), name='norma_ta'), 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/tipo/', include(TipoNormaCrud.get_urls())),
url(r'^sistema/norma/assunto/', include(AssuntoNormaCrud.get_urls())), url(r'^sistema/norma/assunto/', include(AssuntoNormaCrud.get_urls())),
url(r'^sistema/norma/vinculo/', include( url(r'^sistema/norma/vinculo/', include(

48
sapl/norma/views.py

@ -1,4 +1,5 @@
import re
import weasyprint import weasyprint
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
@ -18,9 +19,9 @@ from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
MasterDetailCrud, make_pagination) MasterDetailCrud, make_pagination)
from sapl.utils import show_results_filter_set from sapl.utils import show_results_filter_set
from .forms import (NormaFilterSet, NormaJuridicaForm, from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm,
NormaPesquisaSimplesForm, NormaRelacionadaForm) NormaPesquisaSimplesForm, NormaRelacionadaForm)
from .models import (AssuntoNorma, NormaJuridica, NormaRelacionada, from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica, TipoVinculoNormaJuridica) TipoNormaJuridica, TipoVinculoNormaJuridica)
# LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '') # LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '')
@ -71,7 +72,7 @@ class NormaPesquisaView(FilterView):
def get_queryset(self): def get_queryset(self):
qs = super().get_queryset() 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 return qs
@ -97,6 +98,39 @@ class NormaPesquisaView(FilterView):
return context 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): class NormaTaView(IntegracaoTaView):
model = NormaJuridica model = NormaJuridica
@ -201,14 +235,12 @@ def recuperar_norma(request):
def recuperar_numero_norma(request): def recuperar_numero_norma(request):
tipo = TipoNormaJuridica.objects.get(pk=request.GET['tipo']) tipo = TipoNormaJuridica.objects.get(pk=request.GET['tipo'])
ano = request.GET.get('ano', '') ano = request.GET.get('ano', '')
param = {'tipo': tipo} param = {'tipo': tipo}
param['ano'] = ano if ano else timezone.now().year param['ano'] = ano if ano else timezone.now().year
norma = NormaJuridica.objects.filter(**param).extra( norma = NormaJuridica.objects.filter(**param).order_by(
{'numero_id': "CAST(numero as INTEGER)"}).order_by( 'tipo', 'ano', 'numero').values_list('numero', 'ano').last()
'tipo', 'ano','numero_id').values_list('numero', 'ano').last()
if norma: if norma:
response = JsonResponse({'numero': int(norma[0]) + 1, response = JsonResponse({'numero': int(re.sub("[^0-9].*", '', norma[0])) + 1,
'ano': norma[1]}) 'ano': norma[1]})
else: else:
response = JsonResponse( response = JsonResponse(

212
sapl/painel/views.py

@ -82,10 +82,114 @@ def votacao_aberta(request):
return votacoes_abertas.first(), None return votacoes_abertas.first(), None
def votacao(context,context_vars):
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.'})
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'])
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
def sessao_votacao(context,context_vars):
pk = context_vars['sessao'].pk
context.update({'sessao_id': pk})
context.update({'sessao': context_vars['sessao'],
'data': context_vars['sessao'].data_inicio,
'hora': context_vars['sessao'].hora_inicio})
# Inicializa presentes
presentes = []
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:
materia_aberta = ordem_dia
presentes = PresencaOrdemDia.objects.filter(
sessao_plenaria_id=pk).values_list(
'parlamentar_id', flat=True).distinct()
elif expediente:
materia_aberta = expediente
presentes = SessaoPlenariaPresenca.objects.filter(
sessao_plenaria_id=pk).values_list(
'parlamentar_id', flat=True).distinct()
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})
context, context_vars = votacao(context, context_vars)
else:
context.update({'error_message': errors_msgs[erro]})
return context, context_vars
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): def votante_view(request):
# Pega o votante relacionado ao usuário # Pega o votante relacionado ao usuário
template_name = 'painel/voto_nominal.html' template_name = 'painel/voto_nominal.html'
context = {} context = {}
context_vars = {}
try: try:
votante = Votante.objects.get(user=request.user) votante = Votante.objects.get(user=request.user)
except ObjectDoesNotExist: except ObjectDoesNotExist:
@ -95,96 +199,12 @@ def votante_view(request):
}) })
return render(request, template_name, context) return render(request, template_name, context)
context_vars = {'votante': votante}
context = {'head_title': str(_('Votação Individual'))} context = {'head_title': str(_('Votação Individual'))}
# Verifica se usuário possui permissão para votar # Verifica se usuário possui permissão para votar
if 'parlamentares.can_vote' in request.user.get_all_permissions(): if 'parlamentares.can_vote' in request.user.get_all_permissions():
context.update({'permissao': True}) context, context_vars = can_vote(context, context_vars, request)
# Pega sessão
sessao, msg = votacao_aberta(request)
if sessao and not msg:
pk = sessao.pk
context.update({'sessao_id': pk})
context.update({'sessao': sessao,
'data': sessao.data_inicio,
'hora': 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)
materia_aberta = None
if ordem_dia:
materia_aberta = ordem_dia
presentes = PresencaOrdemDia.objects.filter(
sessao_plenaria_id=pk).values_list(
'parlamentar_id', flat=True).distinct()
elif expediente:
materia_aberta = expediente
presentes = SessaoPlenariaPresenca.objects.filter(
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.update({'materia': materia_aberta.materia,
'ementa': materia_aberta.materia.ementa})
parlamentar = votante.parlamentar
parlamentar_presente = False
if parlamentar.id in presentes:
parlamentar_presente = True
else:
context.update({'error_message':
'Não há presentes na Sessão com a '
'matéria em votação.'})
if parlamentar_presente:
voto = []
if ordem_dia:
voto = VotoParlamentar.objects.filter(
ordem=ordem_dia)
elif expediente:
voto = VotoParlamentar.objects.filter(
expediente=expediente)
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.'})
elif not sessao and msg:
return HttpResponseRedirect('/')
else:
context.update(
{'error_message': 'Não há nenhuma sessão com matéria aberta.'})
else: else:
context.update({'permissao': False, context.update({'permissao': False,
@ -192,36 +212,36 @@ def votante_view(request):
# Salva o voto # Salva o voto
if request.method == 'POST': if request.method == 'POST':
if ordem_dia: if context_vars['ordem_dia']:
try: try:
voto = VotoParlamentar.objects.get( voto = VotoParlamentar.objects.get(
parlamentar=parlamentar, parlamentar=context_vars['parlamentar'],
ordem=ordem_dia) ordem=context_vars['ordem_dia'])
except ObjectDoesNotExist: except ObjectDoesNotExist:
voto = VotoParlamentar.objects.create( voto = VotoParlamentar.objects.create(
parlamentar=parlamentar, parlamentar=context_vars['parlamentar'],
voto=request.POST['voto'], voto=request.POST['voto'],
user=request.user, user=request.user,
ip=get_client_ip(request), ip=get_client_ip(request),
ordem=ordem_dia) ordem=context_vars['ordem_dia'])
else: else:
voto.voto = request.POST['voto'] voto.voto = request.POST['voto']
voto.ip = get_client_ip(request) voto.ip = get_client_ip(request)
voto.user = request.user voto.user = request.user
voto.save() voto.save()
elif expediente: elif context_vars['expediente']:
try: try:
voto = VotoParlamentar.objects.get( voto = VotoParlamentar.objects.get(
parlamentar=parlamentar, parlamentar=context_vars['parlamentar'],
expediente=expediente) expediente=context_vars['expediente'])
except ObjectDoesNotExist: except ObjectDoesNotExist:
voto = VotoParlamentar.objects.create( voto = VotoParlamentar.objects.create(
parlamentar=parlamentar, parlamentar=context_vars['parlamentar'],
voto=request.POST['voto'], voto=request.POST['voto'],
user=request.user, user=request.user,
ip=get_client_ip(request), ip=get_client_ip(request),
expediente=expediente) expediente=context_vars['expediente'])
else: else:
voto.voto = request.POST['voto'] voto.voto = request.POST['voto']
voto.ip = get_client_ip(request) voto.ip = get_client_ip(request)

45
sapl/parlamentares/forms.py

@ -99,6 +99,19 @@ class MandatoForm(ModelForm):
raise ValidationError(_("Data fim mandato fora do intervalo de" raise ValidationError(_("Data fim mandato fora do intervalo de"
" legislatura informada")) " 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( existe_mandato = Mandato.objects.filter(
parlamentar=data['parlamentar'], parlamentar=data['parlamentar'],
legislatura=data['legislatura']).exists() legislatura=data['legislatura']).exists()
@ -318,18 +331,32 @@ class FrenteForm(ModelForm):
model = Frente model = Frente
fields = '__all__' 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 @transaction.atomic
def save(self, commit=True): def save(self, commit=True):
frente = super(FrenteForm, self).save(commit) frente = super(FrenteForm, self).save(commit)
content_type = ContentType.objects.get_for_model(Frente)
object_id = frente.pk if not self.instance.pk:
tipo = TipoAutor.objects.get(descricao__icontains='Frente') frente = super(FrenteForm, self).save(commit)
Autor.objects.create( content_type = ContentType.objects.get_for_model(Frente)
content_type=content_type, object_id = frente.pk
object_id=object_id, tipo = TipoAutor.objects.get(descricao__icontains='Frente')
tipo=tipo, Autor.objects.create(
nome=frente.nome content_type=content_type,
) object_id=object_id,
tipo=tipo,
nome=frente.nome
)
return frente return 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, db_index=True,
default=True, default=True,
choices=YES_NO_CHOICES, choices=YES_NO_CHOICES,
verbose_name=_('Vereador Titular')) verbose_name=_('Parlamentar Titular'))
observacao = models.TextField( observacao = models.TextField(
blank=True, verbose_name=_('Observação')) blank=True, verbose_name=_('Observação'))

10
sapl/parlamentares/views.py

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

18
sapl/protocoloadm/views.py

@ -550,9 +550,16 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
qs = self.get_queryset() 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: if status_tramitacao and unidade_destino:
lista = filtra_tramitacao_adm_destino_and_status(status_tramitacao, lista = filtra_tramitacao_adm_destino_and_status(status_tramitacao,
unidade_destino) unidade_destino)
qs = qs.filter(id__in=lista).distinct() qs = qs.filter(id__in=lista).distinct()
elif status_tramitacao: elif status_tramitacao:
@ -566,11 +573,6 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
if 'o' in self.request.GET and not self.request.GET['o']: if 'o' in self.request.GET and not self.request.GET['o']:
qs = qs.order_by('-ano', '-numero') 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({ kwargs.update({
'queryset': qs, 'queryset': qs,
@ -607,10 +609,10 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
self.filterset.form.fields['o'].label = _('Ordenação') self.filterset.form.fields['o'].label = _('Ordenação')
length = self.object_list.count()
context = self.get_context_data(filter=self.filterset, context = self.get_context_data(filter=self.filterset,
object_list=self.object_list,
filter_url=url, filter_url=url,
numero_res=len(self.object_list) numero_res=length
) )
context['show_results'] = show_results_filter_set( context['show_results'] = show_results_filter_set(

19
sapl/redireciona_urls/urls.py

@ -2,20 +2,22 @@ from django.conf.urls import url
from .apps import AppConfig from .apps import AppConfig
from .views import (RedirecionaAtasList, RedirecionaComissao, from .views import (RedirecionaAtasList, RedirecionaComissao,
RedirecionaComposicaoComissao,
RedirecionaHistoricoTramitacoesList, RedirecionaHistoricoTramitacoesList,
RedirecionaMateriaLegislativaDetail, RedirecionaMateriaLegislativaDetail,
RedirecionaMateriaLegislativaList, RedirecionaMateriaLegislativaList,
RedirecionaMateriasPorAnoAutorTipo, RedirecionaMateriasPorAnoAutorTipo,
RedirecionaMateriasPorAutor, RedirecionaMesaDiretoraView, RedirecionaMateriasPorAutor, RedirecionaMesaDiretoraView,
RedirecionaNormasJuridicasDetail, RedirecionaNormasJuridicasDetail,
RedirecionaNormasJuridicasList, RedirecionaParlamentar, RedirecionaNormasJuridicasList,
RedirecionaPautaSessao, RedirecionaPresencaParlamentares, RedirecionaNormasJuridicasTextoIntegral,
RedirecionaParlamentar, RedirecionaPautaSessao,
RedirecionaPresencaParlamentares,
RedirecionaRelatoriosList, RedirecionaRelatoriosList,
RedirecionaRelatoriosMateriasEmTramitacaoList, RedirecionaRelatoriosMateriasEmTramitacaoList,
RedirecionaSAPLIndex, RedirecionaSessaoPlenaria) RedirecionaSAPLIndex, RedirecionaSessaoPlenaria)
app_name = AppConfig.name app_name = AppConfig.name
urlpatterns = [ urlpatterns = [
url(r'^default_index_html$', url(r'^default_index_html$',
RedirecionaSAPLIndex.as_view(), RedirecionaSAPLIndex.as_view(),
@ -26,6 +28,9 @@ urlpatterns = [
url(r'^consultas/comissao/comissao_', url(r'^consultas/comissao/comissao_',
RedirecionaComissao.as_view(), RedirecionaComissao.as_view(),
name='redireciona_comissao'), 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_', url(r'^consultas/pauta_sessao/pauta_sessao_',
RedirecionaPautaSessao.as_view(), RedirecionaPautaSessao.as_view(),
name='redireciona_pauta_sessao_'), name='redireciona_pauta_sessao_'),
@ -44,6 +49,9 @@ urlpatterns = [
url(r'^consultas/norma_juridica/norma_juridica_mostrar_proc', url(r'^consultas/norma_juridica/norma_juridica_mostrar_proc',
RedirecionaNormasJuridicasDetail.as_view(), RedirecionaNormasJuridicasDetail.as_view(),
name='redireciona_norma_juridica_detail'), 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$', url(r'^relatorios_administrativos/relatorios_administrativos_index_html$',
RedirecionaRelatoriosList.as_view(), RedirecionaRelatoriosList.as_view(),
name='redireciona_relatorios_list'), name='redireciona_relatorios_list'),
@ -51,6 +59,9 @@ urlpatterns = [
RedirecionaRelatoriosMateriasEmTramitacaoList.as_view(), RedirecionaRelatoriosMateriasEmTramitacaoList.as_view(),
name='redireciona_relatorio_materia_por_tramitacao'), name='redireciona_relatorio_materia_por_tramitacao'),
url(r'tramitacaoMaterias/materia_mostrar_proc$', url(r'tramitacaoMaterias/materia_mostrar_proc$',
RedirecionaMateriaLegislativaDetail.as_view(),
name='redireciona_materialegislativa_detail_tramitacao'),
url(r'consultas/materia/materia_mostrar_proc$',
RedirecionaMateriaLegislativaDetail.as_view(), RedirecionaMateriaLegislativaDetail.as_view(),
name='redireciona_materialegislativa_detail'), name='redireciona_materialegislativa_detail'),
url(r'^generico/materia_pesquisar_', url(r'^generico/materia_pesquisar_',
@ -71,4 +82,4 @@ urlpatterns = [
url(r'propositurasAnoAutorTipo', url(r'propositurasAnoAutorTipo',
RedirecionaMateriasPorAnoAutorTipo.as_view(), RedirecionaMateriasPorAnoAutorTipo.as_view(),
name='redireciona_materia_por_ano_autor_tipo_list'), name='redireciona_materia_por_ano_autor_tipo_list'),
] ]

48
sapl/redireciona_urls/views.py

@ -1,14 +1,14 @@
from django.core.urlresolvers import NoReverseMatch, reverse from django.core.urlresolvers import NoReverseMatch, reverse
from django.views.generic import RedirectView from django.views.generic import RedirectView
from sapl.audiencia.apps import AppConfig as audienciaConfig
from sapl.base.apps import AppConfig as atasConfig from sapl.base.apps import AppConfig as atasConfig
from sapl.comissoes.apps import AppConfig as comissoesConfig from sapl.comissoes.apps import AppConfig as comissoesConfig
from sapl.materia.apps import AppConfig as materiaConfig from sapl.materia.apps import AppConfig as materiaConfig
from sapl.norma.apps import AppConfig as normaConfig from sapl.norma.apps import AppConfig as normaConfig
from sapl.norma.models import NormaJuridica
from sapl.parlamentares.apps import AppConfig as parlamentaresConfig from sapl.parlamentares.apps import AppConfig as parlamentaresConfig
from sapl.sessao.apps import AppConfig as sessaoConfig from sapl.sessao.apps import AppConfig as sessaoConfig
from sapl.audiencia.apps import AppConfig as audienciaConfig
from .exceptions import UnknownUrlNameError from .exceptions import UnknownUrlNameError
EMPTY_STRING = '' EMPTY_STRING = ''
@ -142,6 +142,33 @@ class RedirecionaComissao(RedirectView):
return url 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): class RedirecionaPautaSessao(RedirectView):
permanent = True permanent = True
@ -418,6 +445,23 @@ class RedirecionaNormasJuridicasDetail(RedirectView):
return url 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): class RedirecionaNormasJuridicasList(RedirectView):
permanent = True permanent = True

4
sapl/rules/apps.py

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

1
sapl/rules/map_rules.py

@ -135,6 +135,7 @@ rules_group_norma = {
'rules': [ 'rules': [
(norma.NormaJuridica, __base__), (norma.NormaJuridica, __base__),
(norma.NormaRelacionada, __base__), (norma.NormaRelacionada, __base__),
(norma.AnexoNormaJuridica, __base__),
# Publicacao está com permissão apenas para norma e não para matéria # 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 # 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(): if not self.is_valid():
return self.cleaned_data 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']:
if (self.cleaned_data['data_extincao'] < if (self.cleaned_data['data_extincao'] <
self.cleaned_data['data_criacao']): self.cleaned_data['data_criacao']):

18
sapl/sessao/tests/test_sessao.py

@ -1,4 +1,5 @@
import pytest import pytest
from datetime import datetime
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from model_mommy import mommy from model_mommy import mommy
@ -87,9 +88,16 @@ def test_valida_campos_obrigatorios_bancada_form():
assert len(errors) == 3 assert len(errors) == 3
def data(valor):
return datetime.strptime(valor, '%Y-%m-%d').date()
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_bancada_form_valido(): 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) partido = mommy.make(Partido)
form = forms.BancadaForm(data={'legislatura': str(legislatura.pk), form = forms.BancadaForm(data={'legislatura': str(legislatura.pk),
@ -105,7 +113,10 @@ def test_bancada_form_valido():
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_bancada_form_datas_invalidas(): 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) partido = mommy.make(Partido)
form = forms.BancadaForm(data={'legislatura': str(legislatura.pk), form = forms.BancadaForm(data={'legislatura': str(legislatura.pk),
@ -116,9 +127,6 @@ def test_bancada_form_datas_invalidas():
'descricao': 'teste' 'descricao': 'teste'
}) })
assert not form.is_valid() 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) @pytest.mark.django_db(transaction=False)
def test_expediente_materia_form_valido(): def test_expediente_materia_form_valido():

7
sapl/settings.py

@ -94,7 +94,10 @@ INSTALLED_APPS = (
) + SAPL_APPS ) + SAPL_APPS
# FTS = Full Text Search # 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_BACKEND = 'haystack.backends.whoosh_backend.WhooshEngine'
SEARCH_URL = ('PATH', PROJECT_DIR.child('whoosh')) 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='') DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', cast=str, default='')
SERVER_EMAIL = config('SERVER_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 MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB
# Internationalization # Internationalization

2
sapl/templates/base.html

@ -203,7 +203,7 @@
<span class="street-address">{{ endereco }}</span> <span class="street-address">{{ endereco }}</span>
<br> CEP: <span class="postal-code">{{ cep }}</span> | Telefone: <span>{{ telefone }}</span> <br> CEP: <span class="postal-code">{{ cep }}</span> | Telefone: <span>{{ telefone }}</span>
<br> <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> <a href="mailto:{{email}}" class="email">{% trans 'Fale Conosco' %}</a>
</small> </small>
</address> </address>

73
sapl/templates/base/RelatorioMateriasPorAutor_filter.html

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

2
sapl/templates/compilacao/dispositivo_form.html

@ -29,7 +29,7 @@
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li> <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 %}"> <a href="{% url view.get_url_this_view parent.ta_id parent.pk %}">
{{parent.dispositivo0}} - {{parent|nomenclatura}} {{parent.dispositivo0}} - {{parent|nomenclatura}}
</a> </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 %}

41
sapl/templates/materia/prop_pendentes_list.html

@ -39,24 +39,33 @@
{% define object_list as list %} {% define object_list as list %}
{% endif %} {% endif %}
{% if 'page' in request.GET %}
{% define request.GET.page as pagina %}
{% else %}
{% define '1' as pagina %}
{% endif %}
{% for prop in list %} {% for prop in list %}
<tr>
<td> {% if forloop.counter > pagina|paginacao_limite_inferior and forloop.counter <= pagina|paginacao_limite_superior %}
<a href="{% url 'sapl.materia:proposicao_detail' prop.pk %}">{{ prop.data_envio|localtime|date:"d/m/Y H:i:s" }}</a> <tr>
</td> <td>
<td>{{ prop.tipo.descricao }}</td> <a href="{% url 'sapl.materia:proposicao_detail' prop.pk %}">{{ prop.data_envio|localtime|date:"d/m/Y H:i:s" }}</a>
<td>{{ prop.descricao }}</td> </td>
<td>{{ prop.autor }}</td> <td>{{ prop.tipo.descricao }}</td>
<td> <td>{{ prop.descricao }}</td>
{% if not AppConfig.receber_recibo_proposicao %} <td>{{ prop.autor }}</td>
{%if prop.hash_code %} <td>
<a href="{% url 'sapl.materia:proposicao-confirmar' prop.hash_code|strip_hash prop.pk %}">{{ prop.hash_code }}</a> {% if not AppConfig.receber_recibo_proposicao %}
{% else %} {%if prop.hash_code %}
{{ prop.hash_code }} <a href="{% url 'sapl.materia:proposicao-confirmar' prop.hash_code|strip_hash prop.pk %}">{{ prop.hash_code }}</a>
{% else %}
{{ prop.hash_code }}
{% endif %}
{% endif %} {% endif %}
{% endif %} </td>
</td> </tr>
</tr> {% endif %}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>

5
sapl/templates/norma/layouts.yaml

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

16
sapl/templates/norma/normajuridica_detail.html

@ -58,6 +58,22 @@
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</div> </div>
</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 %} {% endblock detail_content %}

2
sapl/templates/norma/subnav.yaml

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

4
sapl/templates/painel/index.html

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

8
sapl/templates/protocoloadm/documentoadministrativo_filter.html

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

10
sapl/templates/sessao/adicionar_varias_materias_expediente.html

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

41
sapl/test_urls.py

@ -278,25 +278,28 @@ def test_urlpatterns(url_item, admin_client):
""" % (app_name, url) """ % (app_name, url)
app_name = app_name[5:] app_name = app_name[5:]
if app_name != 'redireciona_urls':
assert app_name in apps_url_patterns_prefixs_and_users, """ 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 A app (%s) da url (%s) não consta na lista de prefixos do teste
""" % (app_name, url) """ % (app_name, url)
if app_name in apps_url_patterns_prefixs_and_users: if app_name in apps_url_patterns_prefixs_and_users:
prefixs = apps_url_patterns_prefixs_and_users[app_name]['prefixs'] prefixs = apps_url_patterns_prefixs_and_users[app_name]['prefixs']
isvalid = False isvalid = False
for prefix in prefixs: for prefix in prefixs:
if url.startswith(prefix): if url.startswith(prefix):
isvalid = True isvalid = True
break break
assert isvalid, """ assert isvalid, """
O prefixo da url (%s) não está no padrão de sua app (%s). O prefixo da url (%s) não está no padrão de sua app (%s).
Os prefixos permitidos são: Os prefixos permitidos são:
%s %s
""" % (url, app_name, prefixs) """ % (url, app_name, prefixs)
else:
# ignorando app de redirecionamento de urls no padrão do SAPL 2.5
pass
urls_publicas_excecoes = { urls_publicas_excecoes = {

2
setup.py

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

Loading…
Cancel
Save