Browse Source

Merge branch '3.1.x' into 2672-data-nota-explicativa

pull/2707/head
Edward 7 years ago
committed by GitHub
parent
commit
cf831a880a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      sapl/api/views.py
  2. 20
      sapl/base/migrations/0033_auto_20190415_1050.py
  3. 3
      sapl/base/models.py
  4. 38
      sapl/materia/forms.py
  5. 20
      sapl/materia/migrations/0045_auto_20190415_1050.py
  6. 58
      sapl/materia/views.py
  7. 128
      sapl/protocoloadm/forms.py
  8. 35
      sapl/protocoloadm/migrations/0018_auto_20190314_1532.py
  9. 42
      sapl/protocoloadm/models.py
  10. 24
      sapl/protocoloadm/tests/test_protocoloadm.py
  11. 7
      sapl/protocoloadm/urls.py
  12. 162
      sapl/protocoloadm/views.py
  13. 3
      sapl/rules/map_rules.py
  14. 2
      sapl/sessao/migrations/0036_auto_20190412_1106.py
  15. 33
      sapl/sessao/migrations/0037_auto_20190415_1324.py
  16. 13
      sapl/sessao/models.py
  17. 36
      sapl/sessao/views.py
  18. 4
      sapl/templates/base/layouts.yaml
  19. 6
      sapl/templates/materia/em_lote/anexada.html
  20. 13
      sapl/templates/protocoloadm/anexado_list.html
  21. 5
      sapl/templates/protocoloadm/em_lote/anexado.html
  22. 13
      sapl/templates/protocoloadm/layouts.yaml
  23. 2
      sapl/templates/protocoloadm/subnav.yaml
  24. 98
      sapl/templates/sessao/painel.html
  25. 2
      sapl/templates/sessao/votacao/votacao_bloco_expediente.html
  26. 2
      sapl/templates/sessao/votacao/votacao_bloco_ordem.html
  27. 84
      scripts/remove_multiplos_autores.py
  28. 13
      scripts/remove_protocolos_inexistentes_materias.py

16
sapl/api/views.py

@ -26,7 +26,7 @@ from sapl.materia.models import Proposicao, TipoMateriaLegislativa,\
MateriaLegislativa, Tramitacao MateriaLegislativa, Tramitacao
from sapl.parlamentares.models import Parlamentar from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import DocumentoAdministrativo,\ from sapl.protocoloadm.models import DocumentoAdministrativo,\
DocumentoAcessorioAdministrativo, TramitacaoAdministrativo DocumentoAcessorioAdministrativo, TramitacaoAdministrativo, Anexado
from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao
from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria
@ -489,6 +489,20 @@ class _TramitacaoAdministrativoViewSet(BusinessRulesNotImplementedMixin):
return qs return qs
@customize(Anexado)
class _AnexadoViewSet(BusinessRulesNotImplementedMixin):
permission_classes = (
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission, )
def get_queryset(self):
qs = super().get_queryset()
if self.request.user.is_anonymous():
qs = qs.exclude(documento__restrito=True)
return qs
@customize(SessaoPlenaria) @customize(SessaoPlenaria)
class _SessaoPlenariaViewSet: class _SessaoPlenariaViewSet:

20
sapl/base/migrations/0033_auto_20190415_1050.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-15 13:50
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0032_merge_20190219_0941'),
]
operations = [
migrations.AlterField(
model_name='appconfig',
name='sequencia_numeracao',
field=models.CharField(choices=[('A', 'Sequencial por ano para cada autor'), ('B', 'Sequencial por ano indepententemente do autor'), ('L', 'Sequencial por legislatura'), ('U', 'Sequencial único')], default='A', max_length=1, verbose_name='Sequência de numeração'),
),
]

3
sapl/base/models.py

@ -18,7 +18,8 @@ TIPO_DOCUMENTO_ADMINISTRATIVO = ((DOC_ADM_OSTENSIVO, _('Ostensiva')),
RELATORIO_ATOS_ACESSADOS = (('S', _('Sim')), RELATORIO_ATOS_ACESSADOS = (('S', _('Sim')),
('N', _('Não'))) ('N', _('Não')))
SEQUENCIA_NUMERACAO = (('A', _('Sequencial por ano')), SEQUENCIA_NUMERACAO = (('A', _('Sequencial por ano para cada autor')),
('B', _('Sequencial por ano indepententemente do autor')),
('L', _('Sequencial por legislatura')), ('L', _('Sequencial por legislatura')),
('U', _('Sequencial único'))) ('U', _('Sequencial único')))

38
sapl/materia/forms.py

@ -790,6 +790,13 @@ class AnexadaForm(ModelForm):
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
data_anexacao = cleaned_data['data_anexacao']
data_desanexacao = cleaned_data['data_desanexacao'] if cleaned_data['data_desanexacao'] else data_anexacao
if data_anexacao > data_desanexacao:
self.logger.error("Data de anexação posterior à data de desanexação.")
raise ValidationError(_("Data de anexação posterior à data de desanexação."))
try: try:
self.logger.info("Tentando obter objeto MateriaLegislativa (numero={}, ano={}, tipo={})." self.logger.info("Tentando obter objeto MateriaLegislativa (numero={}, ano={}, tipo={})."
.format(cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo'])) .format(cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo']))
@ -818,6 +825,26 @@ class AnexadaForm(ModelForm):
self.logger.error("Matéria já se encontra anexada.") self.logger.error("Matéria já se encontra anexada.")
raise ValidationError(_('Matéria já se encontra anexada')) raise ValidationError(_('Matéria já se encontra anexada'))
ciclico = False
anexadas_anexada = Anexada.objects.filter(materia_principal=materia_anexada)
while anexadas_anexada and not ciclico:
anexadas = []
for anexa in anexadas_anexada:
if materia_principal == anexa.materia_anexada:
ciclico = True
else:
for a in Anexada.objects.filter(materia_principal=anexa.materia_anexada):
anexadas.append(a)
anexadas_anexada = anexadas
if ciclico:
self.logger.error("A matéria não pode ser anexada por uma de suas anexadas.")
raise ValidationError(_("A matéria não pode ser anexada por uma de suas anexadas."))
cleaned_data['materia_anexada'] = materia_anexada cleaned_data['materia_anexada'] = materia_anexada
return cleaned_data return cleaned_data
@ -1639,9 +1666,14 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
return super().save(commit) return super().save(commit)
inst.ano = timezone.now().year inst.ano = timezone.now().year
sequencia_numeracao = AppConfig.attr('sequencia_numeracao')
if sequencia_numeracao == 'A':
numero__max = Proposicao.objects.filter( numero__max = Proposicao.objects.filter(
autor=inst.autor, autor=inst.autor,
ano=timezone.now().year).aggregate(Max('numero_proposicao')) ano=timezone.now().year).aggregate(Max('numero_proposicao'))
elif sequencia_numeracao == 'B':
numero__max = Proposicao.objects.filter(
ano=timezone.now().year).aggregate(Max('numero_proposicao'))
numero__max = numero__max['numero_proposicao__max'] numero__max = numero__max['numero_proposicao__max']
inst.numero_proposicao = ( inst.numero_proposicao = (
numero__max + 1) if numero__max else 1 numero__max + 1) if numero__max else 1
@ -1741,7 +1773,7 @@ class ConfirmarProposicaoForm(ProposicaoForm):
required=False, widget=widgets.TextInput( required=False, widget=widgets.TextInput(
attrs={'readonly': 'readonly'})) attrs={'readonly': 'readonly'}))
regime_tramitacao = forms.ModelChoiceField( regime_tramitacao = forms.ModelChoiceField(label="Regime de tramitação",
required=False, queryset=RegimeTramitacao.objects.all()) required=False, queryset=RegimeTramitacao.objects.all())
gerar_protocolo = forms.ChoiceField( gerar_protocolo = forms.ChoiceField(
@ -1807,6 +1839,10 @@ class ConfirmarProposicaoForm(ProposicaoForm):
# esta chamada isola o __init__ de ProposicaoForm # esta chamada isola o __init__ de ProposicaoForm
super(ProposicaoForm, self).__init__(*args, **kwargs) super(ProposicaoForm, self).__init__(*args, **kwargs)
if self.instance.tipo.content_type.model_class() ==\
TipoMateriaLegislativa:
self.fields['regime_tramitacao'].required = True
fields = [ fields = [
Fieldset( Fieldset(
_('Dados Básicos'), _('Dados Básicos'),

20
sapl/materia/migrations/0045_auto_20190415_1050.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-15 13:50
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0044_auto_20190327_1409'),
]
operations = [
migrations.AlterField(
model_name='tipomaterialegislativa',
name='sequencia_numeracao',
field=models.CharField(blank=True, choices=[('A', 'Sequencial por ano para cada autor'), ('B', 'Sequencial por ano indepententemente do autor'), ('L', 'Sequencial por legislatura'), ('U', 'Sequencial único')], max_length=1, verbose_name='Sequência de numeração'),
),
]

58
sapl/materia/views.py

@ -2070,11 +2070,39 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView):
qr = self.request.GET.copy() qr = self.request.GET.copy()
context['object_list'] = context['object_list'].order_by( context['object_list'] = context['object_list'].order_by(
'ano', 'numero') 'numero', '-ano')
principal = MateriaLegislativa.objects.get(pk=self.kwargs['pk']) principal = MateriaLegislativa.objects.get(pk=self.kwargs['pk'])
not_list = [self.kwargs['pk']] + \ not_list = [self.kwargs['pk']] + \
[m for m in principal.materia_principal_set.all().values_list('materia_anexada_id', flat=True)] [m for m in principal.materia_principal_set.all().values_list('materia_anexada_id', flat=True)]
context['object_list'] = context['object_list'].exclude(pk__in=not_list) context['object_list'] = context['object_list'].exclude(pk__in=not_list)
context['temp_object_list'] = context['object_list']
context['object_list'] = []
for obj in context['temp_object_list']:
materia_anexada = obj
ciclico = False
anexadas_anexada = Anexada.objects.filter(
materia_principal = materia_anexada
)
while anexadas_anexada and not ciclico:
anexadas = []
for anexa in anexadas_anexada:
if principal == anexa.materia_anexada:
ciclico = True
else:
for a in Anexada.objects.filter(materia_principal=anexa.materia_anexada):
anexadas.append(a)
anexadas_anexada = anexadas
if not ciclico:
context['object_list'].append(obj)
context['numero_res'] = len(context['object_list'])
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr) context['show_results'] = show_results_filter_set(qr)
@ -2084,19 +2112,31 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
marcadas = request.POST.getlist('materia_id') marcadas = request.POST.getlist('materia_id')
if len(marcadas) == 0:
msg = _('Nenhuma máteria foi selecionada.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
data_anexacao = datetime.strptime( data_anexacao = datetime.strptime(
request.POST['data_anexacao'], "%d/%m/%Y").date() request.POST['data_anexacao'], "%d/%m/%Y").date()
if request.POST['data_desanexacao'] == '': if request.POST['data_desanexacao'] == '':
data_desanexacao = None data_desanexacao = None
v_data_desanexacao = data_anexacao
else: else:
data_desanexacao = datetime.strptime( data_desanexacao = datetime.strptime(
request.POST['data_desanexacao'], "%d/%m/%Y").date() request.POST['data_desanexacao'], "%d/%m/%Y").date()
v_data_desanexacao = data_desanexacao
if len(marcadas) == 0:
msg = _('Nenhuma máteria foi selecionada.')
messages.add_message(request, messages.ERROR, msg)
if data_anexacao > v_data_desanexacao:
msg = _('Data de anexação posterior à data de desanexação.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
if data_anexacao > v_data_desanexacao:
msg = _('Data de anexação posterior à data de desanexação.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
principal = MateriaLegislativa.objects.get(pk=kwargs['pk']) principal = MateriaLegislativa.objects.get(pk=kwargs['pk'])
for materia in MateriaLegislativa.objects.filter(id__in=marcadas): for materia in MateriaLegislativa.objects.filter(id__in=marcadas):
@ -2108,9 +2148,11 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView):
anexada.data_desanexacao = data_desanexacao anexada.data_desanexacao = data_desanexacao
anexada.save() anexada.save()
msg = _('Materia(s) anexada(s).') msg = _('Matéria(s) anexada(s).')
messages.add_message(request, messages.SUCCESS, msg) messages.add_message(request, messages.SUCCESS, msg)
return self.get(request, self.kwargs)
sucess_url = reverse('sapl_index') + 'materia/' + kwargs['pk'] + '/anexada'
return HttpResponseRedirect(sucess_url)
class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):

128
sapl/protocoloadm/forms.py

@ -29,7 +29,7 @@ from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter,
from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo, from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo, DocumentoAdministrativo,
Protocolo, TipoDocumentoAdministrativo, Protocolo, TipoDocumentoAdministrativo,
TramitacaoAdministrativo) TramitacaoAdministrativo, Anexado)
TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'), TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'),
@ -221,7 +221,7 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
) )
class AnularProcoloAdmForm(ModelForm): class AnularProtocoloAdmForm(ModelForm):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -240,7 +240,7 @@ class AnularProcoloAdmForm(ModelForm):
widget=forms.Textarea) widget=forms.Textarea)
def clean(self): def clean(self):
super(AnularProcoloAdmForm, self).clean() super(AnularProtocoloAdmForm, self).clean()
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
@ -313,7 +313,7 @@ class AnularProcoloAdmForm(ModelForm):
form_actions(label='Anular') form_actions(label='Anular')
) )
) )
super(AnularProcoloAdmForm, self).__init__( super(AnularProtocoloAdmForm, self).__init__(
*args, **kwargs) *args, **kwargs)
@ -781,6 +781,126 @@ class TramitacaoAdmEditForm(TramitacaoAdmForm):
return self.cleaned_data return self.cleaned_data
class AnexadoForm(ModelForm):
logger = logging.getLogger(__name__)
tipo = forms.ModelChoiceField(
label='Tipo',
required=True,
queryset=TipoDocumentoAdministrativo.objects.all(),
empty_label='Selecione'
)
numero = forms.CharField(label='Número', required=True)
ano = forms.CharField(label='Ano', required=True)
def __init__(self, *args, **kwargs):
return super(AnexadoForm, self).__init__(*args, **kwargs)
def clean(self):
super(AnexadoForm, self).clean()
if not self.is_valid():
return self.cleaned_data
cleaned_data = self.cleaned_data
data_anexacao = cleaned_data['data_anexacao']
data_desanexacao = cleaned_data['data_desanexacao'] if cleaned_data['data_desanexacao'] else data_anexacao
if data_anexacao > data_desanexacao:
self.logger.error("Data de anexação posterior à data de desanexação.")
raise ValidationError(_("Data de anexação posterior à data de desanexação."))
try:
self.logger.info(
"Tentando obter objeto DocumentoAdministrativo (numero={}, ano={}, tipo={})."
.format(cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo'])
)
documento_anexado = DocumentoAdministrativo.objects.get(
numero=cleaned_data['numero'],
ano=cleaned_data['ano'],
tipo=cleaned_data['tipo']
)
except ObjectDoesNotExist:
msg = _('O {} {}/{} não existe no cadastro de documentos administrativos.'
.format(cleaned_data['tipo'], cleaned_data['numero'], cleaned_data['ano']))
self.logger.error("O documento a ser anexado não existe no cadastro"
" de documentos administrativos")
raise ValidationError(msg)
documento_principal = self.instance.documento_principal
if documento_principal == documento_anexado:
self.logger.error("O documento não pode ser anexado a si mesmo.")
raise ValidationError(_("O documento não pode ser anexado a si mesmo"))
is_anexado = Anexado.objects.filter(documento_principal=documento_principal,
documento_anexado=documento_anexado
).exclude(pk=self.instance.pk).exists()
if is_anexado:
self.logger.error("Documento já se encontra anexado.")
raise ValidationError(_('Documento já se encontra anexado'))
ciclico = False
anexados_anexado = Anexado.objects.filter(documento_principal=documento_anexado)
while(anexados_anexado and not ciclico):
anexados = []
for anexo in anexados_anexado:
if documento_principal == anexo.documento_anexado:
ciclico = True
else:
for a in Anexado.objects.filter(documento_principal=anexo.documento_anexado):
anexados.append(a)
anexados_anexado = anexados
if ciclico:
self.logger.error("O documento não pode ser anexado por um de seus anexados.")
raise ValidationError(_('O documento não pode ser anexado por um de seus anexados'))
cleaned_data['documento_anexado'] = documento_anexado
return cleaned_data
def save(self, commit=False):
anexado = super(AnexadoForm, self).save(commit)
anexado.documento_anexado = self.cleaned_data['documento_anexado']
anexado.save()
return anexado
class Meta:
model = Anexado
fields = ['tipo', 'numero', 'ano', 'data_anexacao', 'data_desanexacao']
class AnexadoEmLoteFilterSet(django_filters.FilterSet):
class Meta(FilterOverridesMetaMixin):
model = DocumentoAdministrativo
fields = ['tipo', 'data']
def __init__(self, *args, **kwargs):
super(AnexadoEmLoteFilterSet, self).__init__(*args, **kwargs)
self.filters['tipo'].label = 'Tipo de Documento*'
self.filters['data'].label = 'Data (Inicial - Final)*'
row1 = to_row([('tipo', 12)])
row2 = to_row([('data', 12)])
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Documentos'),
row1, row2, form_actions(label='Pesquisar'))
)
class DocumentoAdministrativoForm(FileFieldCheckMixin, ModelForm): class DocumentoAdministrativoForm(FileFieldCheckMixin, ModelForm):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

35
sapl/protocoloadm/migrations/0018_auto_20190314_1532.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-03-14 18:32
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0017_merge_20190121_1552'),
]
operations = [
migrations.CreateModel(
name='Anexado',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('data_anexacao', models.DateField(verbose_name='Data Anexação')),
('data_desanexacao', models.DateField(blank=True, null=True, verbose_name='Data Desanexação')),
('documento_anexado', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documento_anexado_set', to='protocoloadm.DocumentoAdministrativo', verbose_name='Documento Anexado')),
('documento_principal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documento_principal_set', to='protocoloadm.DocumentoAdministrativo', verbose_name='Documento Principal')),
],
options={
'verbose_name': 'Anexado',
'verbose_name_plural': 'Anexados',
},
),
migrations.AddField(
model_name='documentoadministrativo',
name='anexados',
field=models.ManyToManyField(blank=True, related_name='anexo_de', through='protocoloadm.Anexado', to='protocoloadm.DocumentoAdministrativo'),
),
]

42
sapl/protocoloadm/models.py

@ -170,6 +170,18 @@ class DocumentoAdministrativo(models.Model):
verbose_name=_('Acesso Restrito'), verbose_name=_('Acesso Restrito'),
blank=True) blank=True)
anexados = models.ManyToManyField(
'self',
blank=True,
through='Anexado',
symmetrical=False,
related_name='anexo_de',
through_fields=(
'documento_principal',
'documento_anexado'
)
)
class Meta: class Meta:
verbose_name = _('Documento Administrativo') verbose_name = _('Documento Administrativo')
verbose_name_plural = _('Documentos Administrativos') verbose_name_plural = _('Documentos Administrativos')
@ -317,6 +329,36 @@ class TramitacaoAdministrativo(models.Model):
} }
@reversion.register()
class Anexado(models.Model):
documento_principal = models.ForeignKey(
DocumentoAdministrativo, related_name='documento_principal_set',
on_delete = models.CASCADE,
verbose_name=_('Documento Principal')
)
documento_anexado = models.ForeignKey(
DocumentoAdministrativo, related_name='documento_anexado_set',
on_delete = models.CASCADE,
verbose_name=_('Documento Anexado')
)
data_anexacao = models.DateField(verbose_name=_('Data Anexação'))
data_desanexacao = models.DateField(
blank=True, null=True, verbose_name=_('Data Desanexação')
)
class Meta:
verbose_name = _('Anexado')
verbose_name_plural = _('Anexados')
def __str__(self):
return _('Anexado: %(documento_anexado_tipo)s %(documento_anexado_numero)s'
'/%(documento_anexado_ano)s\n') % {
'documento_anexado_tipo': self.documento_anexado.tipo,
'documento_anexado_numero': self.documento_anexado.numero,
'documento_anexado_ano': self.documento_anexado.ano
}
@reversion.register() @reversion.register()
class AcompanhamentoDocumento(models.Model): class AcompanhamentoDocumento(models.Model):
usuario = models.CharField(max_length=50) usuario = models.CharField(max_length=50)

24
sapl/protocoloadm/tests/test_protocoloadm.py

@ -9,7 +9,7 @@ import pytest
from sapl.base.models import AppConfig from sapl.base.models import AppConfig
from sapl.materia.models import UnidadeTramitacao from sapl.materia.models import UnidadeTramitacao
from sapl.protocoloadm.forms import (AnularProcoloAdmForm, from sapl.protocoloadm.forms import (AnularProtocoloAdmForm,
DocumentoAdministrativoForm, DocumentoAdministrativoForm,
MateriaLegislativa, ProtocoloDocumentForm, MateriaLegislativa, ProtocoloDocumentForm,
ProtocoloMateriaForm) ProtocoloMateriaForm)
@ -51,7 +51,7 @@ def test_anular_protocolo_submit(admin_client):
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_form_anular_protocolo_inexistente(): def test_form_anular_protocolo_inexistente():
form = AnularProcoloAdmForm({'numero': '1', form = AnularProtocoloAdmForm({'numero': '1',
'ano': '2016', 'ano': '2016',
'justificativa_anulacao': 'TESTE'}) 'justificativa_anulacao': 'TESTE'})
@ -64,7 +64,7 @@ def test_form_anular_protocolo_inexistente():
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_form_anular_protocolo_valido(): def test_form_anular_protocolo_valido():
mommy.make(Protocolo, numero='1', ano='2016', anulado=False) mommy.make(Protocolo, numero='1', ano='2016', anulado=False)
form = AnularProcoloAdmForm({'numero': '1', form = AnularProtocoloAdmForm({'numero': '1',
'ano': '2016', 'ano': '2016',
'justificativa_anulacao': 'TESTE'}) 'justificativa_anulacao': 'TESTE'})
if not form.is_valid(): if not form.is_valid():
@ -74,7 +74,7 @@ def test_form_anular_protocolo_valido():
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_form_anular_protocolo_anulado(): def test_form_anular_protocolo_anulado():
mommy.make(Protocolo, numero='1', ano='2016', anulado=True) mommy.make(Protocolo, numero='1', ano='2016', anulado=True)
form = AnularProcoloAdmForm({'numero': '1', form = AnularProtocoloAdmForm({'numero': '1',
'ano': '2016', 'ano': '2016',
'justificativa_anulacao': 'TESTE'}) 'justificativa_anulacao': 'TESTE'})
assert form.errors['__all__'] == \ assert form.errors['__all__'] == \
@ -88,7 +88,7 @@ def test_form_anular_protocolo_campos_obrigatorios():
# TODO: generalizar para diminuir o tamanho deste método # TODO: generalizar para diminuir o tamanho deste método
# numero ausente # numero ausente
form = AnularProcoloAdmForm({'numero': '', form = AnularProtocoloAdmForm({'numero': '',
'ano': '2016', 'ano': '2016',
'justificativa_anulacao': 'TESTE'}) 'justificativa_anulacao': 'TESTE'})
if form.is_valid(): if form.is_valid():
@ -98,7 +98,7 @@ def test_form_anular_protocolo_campos_obrigatorios():
assert form.errors['numero'] == [_('Este campo é obrigatório.')] assert form.errors['numero'] == [_('Este campo é obrigatório.')]
# ano ausente # ano ausente
form = AnularProcoloAdmForm({'numero': '1', form = AnularProtocoloAdmForm({'numero': '1',
'ano': '', 'ano': '',
'justificativa_anulacao': 'TESTE'}) 'justificativa_anulacao': 'TESTE'})
if form.is_valid(): if form.is_valid():
@ -108,7 +108,7 @@ def test_form_anular_protocolo_campos_obrigatorios():
assert form.errors['ano'] == [_('Este campo é obrigatório.')] assert form.errors['ano'] == [_('Este campo é obrigatório.')]
# justificativa_anulacao ausente # justificativa_anulacao ausente
form = AnularProcoloAdmForm({'numero': '1', form = AnularProtocoloAdmForm({'numero': '1',
'ano': '2016', 'ano': '2016',
'justificativa_anulacao': ''}) 'justificativa_anulacao': ''})
if form.is_valid(): if form.is_valid():
@ -261,7 +261,7 @@ def test_create_tramitacao(admin_client):
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_anular_protocolo_dados_invalidos(): def test_anular_protocolo_dados_invalidos():
form = AnularProcoloAdmForm(data={}) form = AnularProtocoloAdmForm(data={})
assert not form.is_valid() assert not form.is_valid()
@ -276,7 +276,7 @@ def test_anular_protocolo_dados_invalidos():
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_anular_protocolo_form_anula_protocolo_inexistente(): def test_anular_protocolo_form_anula_protocolo_inexistente():
form = AnularProcoloAdmForm(data={'numero': '1', form = AnularProtocoloAdmForm(data={'numero': '1',
'ano': '2017', 'ano': '2017',
'justificativa_anulacao': 'teste' 'justificativa_anulacao': 'teste'
}) })
@ -291,7 +291,7 @@ def test_anular_protocolo_form_anula_protocolo_inexistente():
def test_anular_protocolo_form_anula_protocolo_anulado(): def test_anular_protocolo_form_anula_protocolo_anulado():
mommy.make(Protocolo, numero=1, ano=2017, anulado=True) mommy.make(Protocolo, numero=1, ano=2017, anulado=True)
form = AnularProcoloAdmForm(data={'numero': '1', form = AnularProtocoloAdmForm(data={'numero': '1',
'ano': '2017', 'ano': '2017',
'justificativa_anulacao': 'teste' 'justificativa_anulacao': 'teste'
}) })
@ -316,7 +316,7 @@ def test_anular_protocolo_form_anula_protocolo_com_doc_vinculado():
ano=2017, ano=2017,
numero_protocolo=1) numero_protocolo=1)
form = AnularProcoloAdmForm(data={'numero': '1', form = AnularProtocoloAdmForm(data={'numero': '1',
'ano': '2017', 'ano': '2017',
'justificativa_anulacao': 'teste' 'justificativa_anulacao': 'teste'
}) })
@ -338,7 +338,7 @@ def test_anular_protocolo_form_anula_protocolo_com_doc_vinculado():
mommy.make(DocumentoAdministrativo, mommy.make(DocumentoAdministrativo,
protocolo=protocolo_documento) protocolo=protocolo_documento)
form = AnularProcoloAdmForm(data={'numero': '2', form = AnularProtocoloAdmForm(data={'numero': '2',
'ano': '2017', 'ano': '2017',
'justificativa_anulacao': 'teste' 'justificativa_anulacao': 'teste'
}) })

7
sapl/protocoloadm/urls.py

@ -21,7 +21,8 @@ from sapl.protocoloadm.views import (AcompanhamentoDocumentoView,
atualizar_numero_documento, atualizar_numero_documento,
doc_texto_integral, doc_texto_integral,
DesvincularDocumentoView, DesvincularDocumentoView,
DesvincularMateriaView) DesvincularMateriaView,
AnexadoCrud, DocumentoAnexadoEmLoteView)
from .apps import AppConfig from .apps import AppConfig
@ -30,6 +31,7 @@ app_name = AppConfig.name
urlpatterns_documento_administrativo = [ urlpatterns_documento_administrativo = [
url(r'^docadm/', url(r'^docadm/',
include(DocumentoAdministrativoCrud.get_urls() + include(DocumentoAdministrativoCrud.get_urls() +
AnexadoCrud.get_urls() +
TramitacaoAdmCrud.get_urls() + TramitacaoAdmCrud.get_urls() +
DocumentoAcessorioAdministrativoCrud.get_urls())), DocumentoAcessorioAdministrativoCrud.get_urls())),
@ -38,6 +40,9 @@ urlpatterns_documento_administrativo = [
url(r'^docadm/texto_integral/(?P<pk>\d+)$', doc_texto_integral, url(r'^docadm/texto_integral/(?P<pk>\d+)$', doc_texto_integral,
name='doc_texto_integral'), name='doc_texto_integral'),
url(r'^docadm/(?P<pk>\d+)/anexado_em_lote', DocumentoAnexadoEmLoteView.as_view(),
name='anexado_em_lote'),
] ]
urlpatterns_protocolo = [ urlpatterns_protocolo = [

162
sapl/protocoloadm/views.py

@ -17,7 +17,7 @@ from django.http.response import HttpResponseRedirect
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import ListView, CreateView from django.views.generic import ListView, CreateView, UpdateView
from django.views.generic.base import RedirectView, TemplateView from django.views.generic.base import RedirectView, TemplateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django_filters.views import FilterView from django_filters.views import FilterView
@ -27,7 +27,8 @@ from sapl.base.email_utils import do_envia_email_confirmacao
from sapl.base.models import Autor, CasaLegislativa from sapl.base.models import Autor, CasaLegislativa
from sapl.base.signals import tramitacao_signal from sapl.base.signals import tramitacao_signal
from sapl.comissoes.models import Comissao from sapl.comissoes.models import Comissao
from sapl.crud.base import Crud, CrudAux, MasterDetailCrud, make_pagination from sapl.crud.base import (Crud, CrudAux, MasterDetailCrud, make_pagination,
RP_LIST, RP_DETAIL)
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.materia.views import gerar_pdf_impressos from sapl.materia.views import gerar_pdf_impressos
from sapl.parlamentares.models import Legislatura, Parlamentar from sapl.parlamentares.models import Legislatura, Parlamentar
@ -36,7 +37,7 @@ from sapl.utils import (create_barcode, get_base_url, get_client_ip,
get_mime_type_from_file_extension, get_mime_type_from_file_extension,
show_results_filter_set, mail_service_configured) show_results_filter_set, mail_service_configured)
from .forms import (AcompanhamentoDocumentoForm, AnularProcoloAdmForm, from .forms import (AcompanhamentoDocumentoForm, AnularProtocoloAdmForm,
DocumentoAcessorioAdministrativoForm, DocumentoAcessorioAdministrativoForm,
DocumentoAdministrativoFilterSet, DocumentoAdministrativoFilterSet,
DocumentoAdministrativoForm, FichaPesquisaAdmForm, FichaSelecionaAdmForm, ProtocoloDocumentForm, DocumentoAdministrativoForm, FichaPesquisaAdmForm, FichaSelecionaAdmForm, ProtocoloDocumentForm,
@ -44,10 +45,11 @@ from .forms import (AcompanhamentoDocumentoForm, AnularProcoloAdmForm,
TramitacaoAdmEditForm, TramitacaoAdmForm, TramitacaoAdmEditForm, TramitacaoAdmForm,
DesvincularDocumentoForm, DesvincularMateriaForm, DesvincularDocumentoForm, DesvincularMateriaForm,
filtra_tramitacao_adm_destino_and_status, filtra_tramitacao_adm_destino_and_status,
filtra_tramitacao_adm_destino, filtra_tramitacao_adm_status) filtra_tramitacao_adm_destino, filtra_tramitacao_adm_status,
AnexadoForm, AnexadoEmLoteFilterSet)
from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo, from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo, StatusTramitacaoAdministrativo, DocumentoAdministrativo, StatusTramitacaoAdministrativo,
TipoDocumentoAdministrativo, TramitacaoAdministrativo) TipoDocumentoAdministrativo, TramitacaoAdministrativo, Anexado)
TipoDocumentoAdministrativoCrud = CrudAux.build( TipoDocumentoAdministrativoCrud = CrudAux.build(
@ -482,7 +484,7 @@ class ProtocoloListView(PermissionRequiredMixin, ListView):
class AnularProtocoloAdmView(PermissionRequiredMixin, CreateView): class AnularProtocoloAdmView(PermissionRequiredMixin, CreateView):
template_name = 'protocoloadm/anular_protocoloadm.html' template_name = 'protocoloadm/anular_protocoloadm.html'
form_class = AnularProcoloAdmForm form_class = AnularProtocoloAdmForm
form_valid_message = _('Protocolo anulado com sucesso!') form_valid_message = _('Protocolo anulado com sucesso!')
permission_required = ('protocoloadm.action_anular_protocolo', ) permission_required = ('protocoloadm.action_anular_protocolo', )
@ -941,6 +943,154 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
return self.render_to_response(context) return self.render_to_response(context)
class AnexadoCrud(MasterDetailCrud):
model = Anexado
parent_field = 'documento_principal'
help_topic = 'documento_anexado'
public = [RP_LIST, RP_DETAIL]
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['documento_anexado', 'data_anexacao']
class CreateView(MasterDetailCrud.CreateView):
form_class = AnexadoForm
class UpdateView(MasterDetailCrud.UpdateView):
form_class = AnexadoForm
def get_initial(self):
initial = super(UpdateView, self).get_initial()
initial['tipo'] = self.object.documento_anexado.tipo.id
initial['numero'] = self.object.documento_anexado.numero
initial['ano'] = self.object.documento_anexado.ano
return initial
class DetailView(MasterDetailCrud.DetailView):
@property
def layout_key(self):
return 'AnexadoDetail'
class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = AnexadoEmLoteFilterSet
template_name = 'protocoloadm/em_lote/anexado.html'
permission_required = ('protocoloadm.add_anexado', )
def get_context_data(self, **kwargs):
context = super(
DocumentoAnexadoEmLoteView,self
).get_context_data(**kwargs)
context['root_pk'] = self.kwargs['pk']
context['subnav_template_name'] = 'protocoloadm/subnav.yaml'
context['title'] = _('Documentos Anexados em Lote')
# Verifica se os campos foram preenchidos
if not self.request.GET.get('tipo', " "):
msg =_('Por favor, selecione um tipo de documento.')
messages.add_message(self.request, messages.ERROR, msg)
if not self.request.GET.get('data_0', " ") or not self.request.GET.get('data_1', " "):
msg =_('Por favor, preencha as datas.')
messages.add_message(self.request, messages.ERROR, msg)
return context
if not self.request.GET.get('data_0', " ") or not self.request.GET.get('data_1', " "):
msg =_('Por favor, preencha as datas.')
messages.add_message(self.request, messages.ERROR, msg)
return context
qr = self.request.GET.copy()
context['temp_object_list'] = context['object_list'].order_by(
'numero', '-ano'
)
context['object_list'] = []
for obj in context['temp_object_list']:
if not obj.pk == int(context['root_pk']):
documento_principal = DocumentoAdministrativo.objects.get(id=context['root_pk'])
documento_anexado = obj
is_anexado = Anexado.objects.filter(documento_principal=documento_principal,
documento_anexado=documento_anexado).exists()
if not is_anexado:
ciclico = False
anexados_anexado = Anexado.objects.filter(documento_principal=documento_anexado)
while anexados_anexado and not ciclico:
anexados = []
for anexo in anexados_anexado:
if documento_principal == anexo.documento_anexado:
ciclico = True
else:
for a in Anexado.objects.filter(documento_principal=anexo.documento_anexado):
anexados.append(a)
anexados_anexado = anexados
if not ciclico:
context['object_list'].append(obj)
context['numero_res'] = len(context['object_list'])
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr)
return context
def post(self, request, *args, **kwargs):
marcados = request.POST.getlist('documento_id')
data_anexacao = datetime.strptime(
request.POST['data_anexacao'], "%d/%m/%Y"
).date()
if request.POST['data_desanexacao'] == '':
data_desanexacao = None
v_data_desanexacao = data_anexacao
else:
data_desanexacao = datetime.strptime(
request.POST['data_desanexacao'], "%d/%m/%Y"
).date()
v_data_desanexacao = data_desanexacao
if len(marcados) == 0:
msg =_('Nenhum documento foi selecionado')
messages.add_message(request, messages.ERROR, msg)
if data_anexacao > v_data_desanexacao:
msg=_('Data de anexação posterior à data de desanexação.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
if data_anexacao > v_data_desanexacao:
msg =_('Data de anexação posterior à data de desanexação.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, messages.ERROR, msg)
principal = DocumentoAdministrativo.objects.get(pk = kwargs['pk'])
for documento in DocumentoAdministrativo.objects.filter(id__in = marcados):
anexado = Anexado()
anexado.documento_principal = principal
anexado.documento_anexado = documento
anexado.data_anexacao = data_anexacao
anexado.data_desanexacao = data_desanexacao
anexado.save()
msg = _('Documento(s) anexado(s).')
messages.add_message(request, messages.SUCCESS, msg)
success_url = reverse('sapl_index') + 'docadm/' + kwargs['pk'] + '/anexado'
return HttpResponseRedirect(success_url)
class TramitacaoAdmCrud(MasterDetailCrud): class TramitacaoAdmCrud(MasterDetailCrud):
model = TramitacaoAdministrativo model = TramitacaoAdministrativo
parent_field = 'documento' parent_field = 'documento'

3
sapl/rules/map_rules.py

@ -60,6 +60,7 @@ rules_group_administrativo = {
'can_access_impressos'], __perms_publicas__), 'can_access_impressos'], __perms_publicas__),
# TODO: tratar em sapl.api a questão de ostencivo e restritivo # TODO: tratar em sapl.api a questão de ostencivo e restritivo
(protocoloadm.DocumentoAdministrativo, __base__, set()), (protocoloadm.DocumentoAdministrativo, __base__, set()),
(protocoloadm.Anexado, __base__, set()),
(protocoloadm.DocumentoAcessorioAdministrativo, __base__, set()), (protocoloadm.DocumentoAcessorioAdministrativo, __base__, set()),
(protocoloadm.TramitacaoAdministrativo, __base__, set()), (protocoloadm.TramitacaoAdministrativo, __base__, set()),
] ]
@ -118,6 +119,8 @@ rules_group_materia = {
(materia.Autoria, __base__, __perms_publicas__), (materia.Autoria, __base__, __perms_publicas__),
(materia.DespachoInicial, __base__, __perms_publicas__), (materia.DespachoInicial, __base__, __perms_publicas__),
(materia.DocumentoAcessorio, __base__, __perms_publicas__), (materia.DocumentoAcessorio, __base__, __perms_publicas__),
(materia.MateriaAssunto, __base__, __perms_publicas__),
(materia.AssuntoMateria, __base__, __perms_publicas__),
(materia.MateriaLegislativa, __base__ + (materia.MateriaLegislativa, __base__ +
['can_access_impressos'], __perms_publicas__), ['can_access_impressos'], __perms_publicas__),

2
sapl/sessao/migrations/0036_auto_20190410_0836.py → sapl/sessao/migrations/0036_auto_20190412_1106.py

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-10 11:36 # Generated by Django 1.11.20 on 2019-04-12 14:06
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

33
sapl/sessao/migrations/0037_auto_20190415_1324.py

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-04-15 16:24
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('sessao', '0036_auto_20190412_1106'),
]
operations = [
migrations.AddField(
model_name='registrovotacao',
name='data_hora',
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Data/Hora'),
),
migrations.AddField(
model_name='registrovotacao',
name='ip',
field=models.CharField(blank=True, default='', max_length=30, verbose_name='IP'),
),
migrations.AddField(
model_name='registrovotacao',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
]

13
sapl/sessao/models.py

@ -456,6 +456,19 @@ class RegistroVotacao(models.Model):
verbose_name=_('Abstenções')) verbose_name=_('Abstenções'))
observacao = models.TextField( observacao = models.TextField(
blank=True, verbose_name=_('Observações')) blank=True, verbose_name=_('Observações'))
user = models.ForeignKey(get_settings_auth_user_model(),
on_delete=models.PROTECT,
null=True,
blank=True)
ip = models.CharField(verbose_name=_('IP'),
max_length=30,
blank=True,
default='')
data_hora = models.DateTimeField(
verbose_name=_('Data/Hora'),
auto_now_add=True,
blank=True,
null=True)
class Meta: class Meta:
verbose_name = _('Votação') verbose_name = _('Votação')

36
sapl/sessao/views.py

@ -35,7 +35,7 @@ from sapl.parlamentares.models import (Filiacao, Legislatura, Mandato,
Parlamentar, SessaoLegislativa) Parlamentar, SessaoLegislativa)
from sapl.sessao.apps import AppConfig from sapl.sessao.apps import AppConfig
from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm from sapl.sessao.forms import ExpedienteMateriaForm, OrdemDiaForm
from sapl.utils import show_results_filter_set, remover_acentos from sapl.utils import show_results_filter_set, remover_acentos, get_client_ip
from .forms import (AdicionarVariasMateriasFilterSet, BancadaForm, BlocoForm, from .forms import (AdicionarVariasMateriasFilterSet, BancadaForm, BlocoForm,
ExpedienteForm, JustificativaAusenciaForm, OcorrenciaSessaoForm, ListMateriaForm, ExpedienteForm, JustificativaAusenciaForm, OcorrenciaSessaoForm, ListMateriaForm,
@ -2163,6 +2163,8 @@ class VotacaoView(SessaoPermissionMixin):
votacao.ordem_id = ordem_id votacao.ordem_id = ordem_id
votacao.tipo_resultado_votacao_id = int( votacao.tipo_resultado_votacao_id = int(
request.POST['resultado_votacao']) request.POST['resultado_votacao'])
votacao.user = request.user
votacao.ip = get_client_ip(request)
votacao.save() votacao.save()
except Exception as e: except Exception as e:
username = request.user.username username = request.user.username
@ -2383,6 +2385,8 @@ class VotacaoNominalAbstract(SessaoPermissionMixin):
votacao.numero_votos_nao = votos_nao votacao.numero_votos_nao = votos_nao
votacao.numero_abstencoes = abstencoes votacao.numero_abstencoes = abstencoes
votacao.observacao = request.POST.get('observacao', None) votacao.observacao = request.POST.get('observacao', None)
votacao.user = request.user
votacao.ip = get_client_ip(request)
votacao.materia_id = materia_votacao.materia.id votacao.materia_id = materia_votacao.materia.id
if self.ordem: if self.ordem:
@ -2410,6 +2414,8 @@ class VotacaoNominalAbstract(SessaoPermissionMixin):
voto_parlamentar.voto = voto voto_parlamentar.voto = voto
voto_parlamentar.parlamentar_id = parlamentar_id voto_parlamentar.parlamentar_id = parlamentar_id
voto_parlamentar.votacao_id = votacao.id voto_parlamentar.votacao_id = votacao.id
voto_parlamentar.user = request.user
voto_parlamentar.ip = get_client_ip(request)
voto_parlamentar.save() voto_parlamentar.save()
resultado = form.cleaned_data['resultado_votacao'] resultado = form.cleaned_data['resultado_votacao']
@ -2847,10 +2853,10 @@ class VotacaoExpedienteView(SessaoPermissionMixin):
if (int(request.POST['voto_presidente']) == 0): if (int(request.POST['voto_presidente']) == 0):
qtde_presentes -= 1 qtde_presentes -= 1
if (qtde_votos > qtde_presentes or qtde_votos < qtde_presentes): if qtde_votos != qtde_presentes:
form._errors["total_votos"] = ErrorList([u""]) form._errors["total_votos"] = ErrorList([u""])
return self.render_to_response(context) return self.render_to_response(context)
elif (qtde_presentes == qtde_votos): else:
try: try:
votacao = RegistroVotacao() votacao = RegistroVotacao()
votacao.numero_votos_sim = int(request.POST['votos_sim']) votacao.numero_votos_sim = int(request.POST['votos_sim'])
@ -2861,6 +2867,8 @@ class VotacaoExpedienteView(SessaoPermissionMixin):
votacao.expediente_id = expediente_id votacao.expediente_id = expediente_id
votacao.tipo_resultado_votacao_id = int( votacao.tipo_resultado_votacao_id = int(
request.POST['resultado_votacao']) request.POST['resultado_votacao'])
votacao.user = request.user
votacao.ip = get_client_ip(request)
votacao.save() votacao.save()
except Exception as e: except Exception as e:
username = request.user.username username = request.user.username
@ -3544,9 +3552,13 @@ class VotacaoEmBlocoExpediente(PermissionRequiredForAppCrudMixin, ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(VotacaoEmBlocoExpediente, context = super(VotacaoEmBlocoExpediente,
self).get_context_data(**kwargs) self).get_context_data(**kwargs)
context['turno_choices'] = Tramitacao.TURNO_CHOICES
context['pk'] = self.kwargs['pk'] context['pk'] = self.kwargs['pk']
context['root_pk'] = self.kwargs['pk'] context['root_pk'] = self.kwargs['pk']
if not verifica_sessao_iniciada(self.request, self.kwargs['pk']):
context['sessao_iniciada'] = False
return context
context['sessao_iniciada'] = True
context['turno_choices'] = Tramitacao.TURNO_CHOICES
context['title'] = SessaoPlenaria.objects.get(id=self.kwargs['pk']) context['title'] = SessaoPlenaria.objects.get(id=self.kwargs['pk'])
return context return context
@ -3566,9 +3578,13 @@ class VotacaoEmBlocoOrdemDia(PermissionRequiredForAppCrudMixin, ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(VotacaoEmBlocoOrdemDia, context = super(VotacaoEmBlocoOrdemDia,
self).get_context_data(**kwargs) self).get_context_data(**kwargs)
context['turno_choices'] = Tramitacao.TURNO_CHOICES
context['pk'] = self.kwargs['pk'] context['pk'] = self.kwargs['pk']
context['root_pk'] = self.kwargs['pk'] context['root_pk'] = self.kwargs['pk']
if not verifica_sessao_iniciada(self.request, self.kwargs['pk']):
context['sessao_iniciada'] = False
return context
context['sessao_iniciada'] = True
context['turno_choices'] = Tramitacao.TURNO_CHOICES
context['title'] = SessaoPlenaria.objects.get(id=self.kwargs['pk']) context['title'] = SessaoPlenaria.objects.get(id=self.kwargs['pk'])
return context return context
@ -3640,6 +3656,8 @@ class VotacaoEmBlocoSimbolicaView(PermissionRequiredForAppCrudMixin, TemplateVie
resultado = TipoResultadoVotacao.objects.get( resultado = TipoResultadoVotacao.objects.get(
id=request.POST['resultado_votacao']) id=request.POST['resultado_votacao'])
votacao.tipo_resultado_votacao = resultado votacao.tipo_resultado_votacao = resultado
votacao.user = request.user
votacao.ip = get_client_ip(request)
votacao.save() votacao.save()
except Exception as e: except Exception as e:
username = request.user.username username = request.user.username
@ -3671,6 +3689,8 @@ class VotacaoEmBlocoSimbolicaView(PermissionRequiredForAppCrudMixin, TemplateVie
resultado = TipoResultadoVotacao.objects.get( resultado = TipoResultadoVotacao.objects.get(
id=request.POST['resultado_votacao']) id=request.POST['resultado_votacao'])
votacao.tipo_resultado_votacao = resultado votacao.tipo_resultado_votacao = resultado
votacao.user = request.user
votacao.ip = get_client_ip(request)
votacao.save() votacao.save()
except Exception as e: except Exception as e:
username = request.user.username username = request.user.username
@ -3862,6 +3882,8 @@ class VotacaoEmBlocoNominalView(PermissionRequiredForAppCrudMixin, TemplateView)
voto_parlamentar.voto = voto voto_parlamentar.voto = voto
voto_parlamentar.parlamentar_id = parlamentar_id voto_parlamentar.parlamentar_id = parlamentar_id
voto_parlamentar.votacao_id = votacao.id voto_parlamentar.votacao_id = votacao.id
voto_parlamentar.user = request.user
voto_parlamentar.ip = get_client_ip(request)
voto_parlamentar.save() voto_parlamentar.save()
ordem.resultado = form.cleaned_data['resultado_votacao'].nome ordem.resultado = form.cleaned_data['resultado_votacao'].nome
@ -3889,6 +3911,8 @@ class VotacaoEmBlocoNominalView(PermissionRequiredForAppCrudMixin, TemplateView)
votacao.materia = expediente.materia votacao.materia = expediente.materia
votacao.expediente = expediente votacao.expediente = expediente
votacao.tipo_resultado_votacao = form.cleaned_data['resultado_votacao'] votacao.tipo_resultado_votacao = form.cleaned_data['resultado_votacao']
votacao.user = request.user
votacao.ip = get_client_ip(request)
votacao.save() votacao.save()
# Salva os votos de cada parlamentar # Salva os votos de cada parlamentar
@ -3904,6 +3928,8 @@ class VotacaoEmBlocoNominalView(PermissionRequiredForAppCrudMixin, TemplateView)
voto_parlamentar.voto = voto voto_parlamentar.voto = voto
voto_parlamentar.parlamentar_id = parlamentar_id voto_parlamentar.parlamentar_id = parlamentar_id
voto_parlamentar.votacao_id = votacao.id voto_parlamentar.votacao_id = votacao.id
voto_parlamentar.user = request.user
voto_parlamentar.ip = get_client_ip(request)
voto_parlamentar.save() voto_parlamentar.save()
expediente.resultado = form.cleaned_data['resultado_votacao'].nome expediente.resultado = form.cleaned_data['resultado_votacao'].nome

4
sapl/templates/base/layouts.yaml

@ -18,8 +18,8 @@ AppConfig:
- esfera_federacao - esfera_federacao
{% trans 'Proposições e Protocolo' %}: {% trans 'Proposições e Protocolo' %}:
- sequencia_numeracao proposicao_incorporacao_obrigatoria receber_recibo_proposicao - sequencia_numeracao protocolo_manual receber_recibo_proposicao
- escolher_numero_materia_proposicao protocolo_manual - proposicao_incorporacao_obrigatoria escolher_numero_materia_proposicao
{% trans 'Textos Articulados' %}: {% trans 'Textos Articulados' %}:
- texto_articulado_proposicao texto_articulado_materia texto_articulado_norma - texto_articulado_proposicao texto_articulado_materia texto_articulado_norma

6
sapl/templates/materia/em_lote/anexada.html

@ -8,11 +8,11 @@
{% endif %} {% endif %}
{% if show_results %} {% if show_results %}
{% if object_list.count > 0 %} {% if numero_res > 0 %}
{% if object_list.count == 1 %} {% if numero_res == 1 %}
<h3 style="text-align: right;">{% trans 'Pesquisa concluída com sucesso! Foi encontrada 1 matéria.'%}</h3> <h3 style="text-align: right;">{% trans 'Pesquisa concluída com sucesso! Foi encontrada 1 matéria.'%}</h3>
{% else %} {% else %}
<h3 style="text-align: right;">{% blocktrans with object_list.count as total_materias %}Foram encontradas {{total_materias}} matérias.{% endblocktrans %}</h3> <h3 style="text-align: right;">Foram encontradas {{ numero_res }} matérias.</h3>
{% endif %} {% endif %}
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}

13
sapl/templates/protocoloadm/anexado_list.html

@ -0,0 +1,13 @@
{% extends "crud/list.html" %}
{% load i18n %}
{% load common_tags %}
{% block more_buttons %}
{% if perms|get_add_perm:view %}
<a href="{% url 'sapl.protocoloadm:anexado_em_lote' root_pk %}" class="btn btn-outline-primary">
{% trans "Adicionar Anexado em Lote" %}
</a>
{% endif %}
{% endblock more_buttons %}

5
sapl/templates/protocoloadm/em_lote/anexado.html

@ -54,7 +54,7 @@
<tr> <tr>
<td> <td>
<input type="checkbox" name="documento_id" value="{{documento.id}}" {% if check %} checked {% endif %}/> <input type="checkbox" name="documento_id" value="{{documento.id}}" {% if check %} checked {% endif %}/>
{{documento.tipo.sigla}} {{documento.numero}}/{{documento.ano}} - {{documento.tipo.descricao}} <a href="{% url 'sapl_index' %}docadm/{{documento.pk}}">{{documento.tipo.sigla}} {{documento.numero}}/{{documento.ano}} - {{documento.tipo.descricao}}</a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -63,6 +63,7 @@
</fieldset> </fieldset>
<input type="submit" value="Salvar" class="btn btn-primary"S> <input type="submit" value="Salvar" class="btn btn-primary"S>
</form> </form>
<br/>
{% else %} {% else %}
<tr> <tr>
<td> <td>
@ -75,7 +76,7 @@
{% block extra_js %} {% block extra_js %}
<script language="JavaScript"> <script language="JavaScript">
function checkAll(elem) { function checkAll(elem) {
let checkboxes = document.getElementsByName('materia_id'); let checkboxes = document.getElementsByName('documento_id');
for (let i = 0; i < checkboxes.length; i++) { for (let i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].type == 'checkbox') if (checkboxes[i].type == 'checkbox')
checkboxes[i].checked = elem.checked; checkboxes[i].checked = elem.checked;

13
sapl/templates/protocoloadm/layouts.yaml

@ -10,6 +10,8 @@ DocumentoAdministrativo:
- assunto - assunto
- interessado tramitacao - interessado tramitacao
- texto_integral - texto_integral
- documento_anexado_set__documento_principal|m2m_urlize_for_detail
- documento_principal_set__documento_anexado|m2m_urlize_for_detail
{% trans 'Outras Informações' %}: {% trans 'Outras Informações' %}:
- numero_externo - numero_externo
- dias_prazo data_fim_prazo - dias_prazo data_fim_prazo
@ -33,6 +35,17 @@ TramitacaoAdministrativo:
- data_encaminhamento data_fim_prazo - data_encaminhamento data_fim_prazo
- texto - texto
Anexado:
{% trans 'Documento Anexado' %}:
- tipo numero ano
- data_anexacao data_desanexacao
AnexadoDetail:
{% trans 'Documento Anexado' %}:
- documento_principal|fk_urlize_for_detail
- documento_anexado|fk_urlize_for_detail
- data_anexacao data_desanexacao
Protocolo: Protocolo:
{% trans 'Indentificação Documento' %}: {% trans 'Indentificação Documento' %}:
- tipo_protocolo - tipo_protocolo

2
sapl/templates/protocoloadm/subnav.yaml

@ -1,6 +1,8 @@
{% load i18n common_tags %} {% load i18n common_tags %}
- title: {% trans 'Início' %} - title: {% trans 'Início' %}
url: documentoadministrativo_detail url: documentoadministrativo_detail
- title: {% trans 'Anexado' %}
url: anexado_list
- title: {% trans 'Tramitação' %} - title: {% trans 'Tramitação' %}
url: tramitacaoadministrativo_list url: tramitacaoadministrativo_list
- title: {% trans 'Documento Acessório' %} - title: {% trans 'Documento Acessório' %}

98
sapl/templates/sessao/painel.html

@ -116,11 +116,11 @@ $(function() {
} }
startTime(); startTime();
var audioAlertFinish = document.getElementById("audio");
$('#discurso').prop('disabled', true); $('#discurso').prop('disabled', false);
$('#aparte').prop('disabled', true); $('#aparte').prop('disabled', false);
$('#ordem').prop('disabled', true); $('#ordem').prop('disabled', false);
$('#consideracoes').prop('disabled', true); $('#consideracoes').prop('disabled', false);
$('#discurso').runner({ $('#discurso').runner({
autostart: false, autostart: false,
@ -130,16 +130,10 @@ $(function() {
milliseconds: false milliseconds: false
}).on('runnerFinish', function(eventObject, info){ }).on('runnerFinish', function(eventObject, info){
$.get('/painel/cronometro', { tipo: 'discurso', action: 'stop' } ); $.get('/painel/cronometro', { tipo: 'discurso', action: 'stop' } );
audioAlertFinish.play();
$('#discursoReset').show(); $('#discursoReset').show();
$('#discurso').runner('stop'); $('#discurso').runner('stop');
$('#discursoStart').text('Iniciar'); $('#discursoStart').text('Iniciar');
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
}); });
@ -152,12 +146,6 @@ $(function() {
$('#discursoReset').hide(); $('#discursoReset').hide();
$('#discurso').runner('start'); $('#discurso').runner('start');
$('#discursoStart').text('Parar'); $('#discursoStart').text('Parar');
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} else { } else {
@ -166,12 +154,6 @@ $(function() {
$('#discursoReset').show(); $('#discursoReset').show();
$('#discurso').runner('stop'); $('#discurso').runner('stop');
$('#discursoStart').text('Iniciar'); $('#discursoStart').text('Iniciar');
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} }
}); });
@ -191,16 +173,10 @@ $(function() {
milliseconds: false milliseconds: false
}).on('runnerFinish', function(eventObject, info){ }).on('runnerFinish', function(eventObject, info){
$.get('/painel/cronometro', { tipo: 'aparte', action: 'stop' } ); $.get('/painel/cronometro', { tipo: 'aparte', action: 'stop' } );
audioAlertFinish.play();
$('#aparteReset').show(); $('#aparteReset').show();
$('#aparte').runner('stop'); $('#aparte').runner('stop');
$('#aparteStart').text('Iniciar'); $('#aparteStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
}); });
@ -212,12 +188,7 @@ $(function() {
$('#aparteReset').hide(); $('#aparteReset').hide();
$('#aparte').runner('start'); $('#aparte').runner('start');
$('#aparteStart').text('Parar'); $('#aparteStart').text('Parar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} else { } else {
$.get('/painel/cronometro', { tipo: 'aparte', action: 'stop' } ); $.get('/painel/cronometro', { tipo: 'aparte', action: 'stop' } );
@ -225,12 +196,7 @@ $(function() {
$('#aparteReset').show(); $('#aparteReset').show();
$('#aparte').runner('stop'); $('#aparte').runner('stop');
$('#aparteStart').text('Iniciar'); $('#aparteStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} }
}); });
@ -250,16 +216,11 @@ $(function() {
milliseconds: false milliseconds: false
}).on('runnerFinish', function(eventObject, info){ }).on('runnerFinish', function(eventObject, info){
$.get('/painel/cronometro', { tipo: 'ordem', action: 'stop' } ); $.get('/painel/cronometro', { tipo: 'ordem', action: 'stop' } );
audioAlertFinish.play();
$('#ordemReset').show(); $('#ordemReset').show();
$('#ordem').runner('stop'); $('#ordem').runner('stop');
$('#ordemStart').text('Iniciar'); $('#ordemStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
}); });
$('#ordemStart').click(function() { $('#ordemStart').click(function() {
@ -270,12 +231,7 @@ $(function() {
$('#ordemReset').hide(); $('#ordemReset').hide();
$('#ordem').runner('start'); $('#ordem').runner('start');
$('#ordemStart').text('Parar'); $('#ordemStart').text('Parar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} else { } else {
@ -284,12 +240,7 @@ $(function() {
$('#ordemReset').show(); $('#ordemReset').show();
$('#ordem').runner('stop'); $('#ordem').runner('stop');
$('#ordemStart').text('Iniciar'); $('#ordemStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
$('#consideracoesStart').prop('disabled', false);
$('#consideracoesReset').prop('disabled', false);
} }
}); });
@ -309,16 +260,11 @@ $(function() {
milliseconds: false milliseconds: false
}).on('runnerFinish', function(eventObject, info){ }).on('runnerFinish', function(eventObject, info){
$.get('/painel/cronometro', { tipo: 'consideracoes', action: 'stop' } ); $.get('/painel/cronometro', { tipo: 'consideracoes', action: 'stop' } );
audioAlertFinish.play();
$('#consideracoesReset').show(); $('#consideracoesReset').show();
$('#consideracoes').runner('stop'); $('#consideracoes').runner('stop');
$('#consideracoesStart').text('Iniciar'); $('#consideracoesStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
}); });
@ -330,12 +276,7 @@ $(function() {
$('#consideracoesReset').hide(); $('#consideracoesReset').hide();
$('#consideracoes').runner('start'); $('#consideracoes').runner('start');
$('#consideracoesStart').text('Parar'); $('#consideracoesStart').text('Parar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
} else { } else {
$.get('/painel/cronometro', { tipo: 'consideracoes', action: 'stop' } ); $.get('/painel/cronometro', { tipo: 'consideracoes', action: 'stop' } );
@ -343,12 +284,7 @@ $(function() {
$('#consideracoesReset').show(); $('#consideracoesReset').show();
$('#consideracoes').runner('stop'); $('#consideracoes').runner('stop');
$('#consideracoesStart').text('Iniciar'); $('#consideracoesStart').text('Iniciar');
$('#discursoStart').prop('disabled', false);
$('#discursoReset').prop('disabled', false);
$('#ordemStart').prop('disabled', false);
$('#ordemReset').prop('disabled', false);
$('#aparteStart').prop('disabled', false);
$('#aparteReset').prop('disabled', false);
} }
}); });

2
sapl/templates/sessao/votacao/votacao_bloco_expediente.html

@ -3,6 +3,7 @@
{% block base_content %} {% block base_content %}
{% if sessao_iniciada %}
<form method="POST" enctype="application/x-www-form-urlencoded" id="form" action="{% url 'sapl.sessao:votacaoblocosimb' pk %}"> <form method="POST" enctype="application/x-www-form-urlencoded" id="form" action="{% url 'sapl.sessao:votacaoblocosimb' pk %}">
{% csrf_token %} {% csrf_token %}
<br><br> <br><br>
@ -91,6 +92,7 @@
<input type="hidden" id="origem" name="origem" value="expediente"> <input type="hidden" id="origem" name="origem" value="expediente">
</form> </form>
{% endif %}
{% endblock base_content %} {% endblock base_content %}

2
sapl/templates/sessao/votacao/votacao_bloco_ordem.html

@ -3,6 +3,7 @@
{% block base_content %} {% block base_content %}
{% if sessao_iniciada %}
<form method="POST" enctype="application/x-www-form-urlencoded" id="form" action="{% url 'sapl.sessao:votacaoblocosimb' pk %}"> <form method="POST" enctype="application/x-www-form-urlencoded" id="form" action="{% url 'sapl.sessao:votacaoblocosimb' pk %}">
{% csrf_token %} {% csrf_token %}
<br><br> <br><br>
@ -91,6 +92,7 @@
<input type="hidden" id="origem" name="origem" value="ordem"> <input type="hidden" id="origem" name="origem" value="ordem">
</form> </form>
{% endif %}
{% endblock base_content %} {% endblock base_content %}

84
scripts/remove_multiplos_autores.py

@ -0,0 +1,84 @@
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Count
from sapl.base.models import Autor
from sapl.parlamentares.models import Parlamentar
def pega_autores():
return [[autor for autor in Autor.objects.filter(nome=nome)]
for nome in Autor.objects.values_list('nome', flat=True).annotate(qntd=Count('nome')).filter(qntd__gt=1)]
def pega_parlamentares_autores():
parlamentares = [[parlamentar for parlamentar in Parlamentar.objects.filter(nome_parlamentar=nome_parlamentar)]
for nome_parlamentar in Parlamentar.objects.values_list('nome_parlamentar', flat=True)
.annotate(qntd=Count('nome_parlamentar')).filter(qntd__gt=1)]
parlamentares_autores = []
for parlamentar in parlamentares:
parlamentar_autor = []
for clone in parlamentar[1:]:
try:
autor_principal = Autor.objects.get(parlamentar_set=parlamentar[0])
except ObjectDoesNotExist:
try:
autor_clonado = Autor.objects.get(parlamentar_set=clone)
except ObjectDoesNotExist:
pass
else:
autor_clonado.object_id = parlamentar[0].id
autor_clonado.save()
parlamentares_autores.append(autor_clonado)
else:
if len(parlamentar_autor) == 0:
parlamentar_autor.append(autor_principal)
try:
autor_clonado = Autor.objects.get(parlamentar_set=clone)
except ObjectDoesNotExist:
pass
else:
parlamentar_autor.append(autor_clonado)
parlamentares_autores.extend(parlamentar_autor)
return parlamentares_autores
def transfere_valeres(autores):
for autor in autores:
for clone in autor[1:]:
for autoria in clone.autoria_set.all():
autoria.autor_id = autor[0]
autoria.save()
for proposicao in clone.proposicao_set.all():
proposicao.autor_id = autor[0]
proposicao.save()
for autorianorma in clone.autorianorma_set.all():
autorianorma.autor_id = autor[0]
autorianorma.save()
for documentoadministrativo in clone.documentoadministrativo_set.all():
documentoadministrativo.autor_id = autor[0]
documentoadministrativo.save()
for protocolo in clone.protocolo_set.all():
protocolo.autor_id = autor[0]
protocolo.save()
clone.delete()
def main():
autores = pega_autores()
parlamentares_autores = pega_parlamentares_autores()
autores.append(parlamentares_autores)
transfere_valeres(autores)
if __name__ == '__main__':
main()

13
scripts/remove_protocolos_inexistentes_materias.py

@ -0,0 +1,13 @@
from sapl.materia.models import MateriaLegislativa
from sapl.protocoloadm.models import Protocolo
def main():
for materia in MateriaLegislativa.objects.filter(numero_protocolo__isnull=False):
if not Protocolo.objects.filter(ano=materia.ano, numero=materia.numero_protocolo).exists():
materia.numero_protocolo = None
materia.save()
if __name__ == '__main__':
main()
Loading…
Cancel
Save