Browse Source

resolvendo conflitos

pull/3001/head
ricardocanela 6 years ago
parent
commit
390ca90a1d
  1. 10
      .dockerignore
  2. 5
      Dockerfile
  3. 6
      requirements/requirements.txt
  4. 34
      sapl/api/views.py
  5. 31
      sapl/audiencia/forms.py
  6. 32
      sapl/audiencia/migrations/0013_auto_20191023_1522.py
  7. 21
      sapl/audiencia/migrations/0014_auto_20191023_1538.py
  8. 8
      sapl/audiencia/models.py
  9. 31
      sapl/base/admin.py
  10. 21
      sapl/base/forms.py
  11. 33
      sapl/base/migrations/0038_auditlog.py
  12. 16
      sapl/base/migrations/0040_merge_20191030_1655.py
  13. 20
      sapl/base/migrations/0041_auto_20191204_1130.py
  14. 42
      sapl/base/models.py
  15. 42
      sapl/base/receivers.py
  16. 3
      sapl/base/signals.py
  17. 8
      sapl/base/tests/test_login.py
  18. 2
      sapl/base/views.py
  19. 43
      sapl/comissoes/forms.py
  20. 42
      sapl/comissoes/migrations/0022_auto_20191120_1440.py
  21. 10
      sapl/comissoes/models.py
  22. 38
      sapl/compilacao/forms.py
  23. 27
      sapl/compilacao/migrations/0013_auto_20190924_0830.py
  24. 29
      sapl/compilacao/models.py
  25. 3
      sapl/compilacao/templatetags/compilacao_filters.py
  26. 380
      sapl/compilacao/views.py
  27. 5
      sapl/crispy_layout_mixin.py
  28. 51
      sapl/crud/base.py
  29. 30
      sapl/materia/forms.py
  30. 2
      sapl/materia/migrations/0058_auto_20191001_1450.py
  31. 16
      sapl/materia/migrations/0059_merge_20191003_0854.py
  32. 4
      sapl/materia/migrations/0060_auto_20190930_1136.py
  33. 37
      sapl/materia/migrations/0061_auto_20191120_1440.py
  34. 2
      sapl/materia/migrations/0062_auto_20190905_1134.py
  35. 2
      sapl/materia/migrations/0063_auto_20190905_1135.py
  36. 2
      sapl/materia/migrations/0064_remove_tramitacao_turno.py
  37. 8
      sapl/materia/models.py
  38. 25
      sapl/materia/views.py
  39. 43
      sapl/norma/forms.py
  40. 22
      sapl/norma/migrations/0028_auto_20191024_1330.py
  41. 22
      sapl/norma/migrations/0029_auto_20191024_1344.py
  42. 16
      sapl/norma/migrations/0030_merge_20191204_1129.py
  43. 18
      sapl/norma/models.py
  44. 14
      sapl/norma/views.py
  45. 9
      sapl/painel/urls.py
  46. 93
      sapl/painel/views.py
  47. 10
      sapl/parlamentares/forms.py
  48. 12
      sapl/parlamentares/tests/test_parlamentares.py
  49. 39
      sapl/parlamentares/views.py
  50. 47
      sapl/protocoloadm/forms.py
  51. 31
      sapl/protocoloadm/migrations/0026_auto_20191120_1440.py
  52. 6
      sapl/protocoloadm/models.py
  53. 49
      sapl/protocoloadm/views.py
  54. 5
      sapl/rules/map_rules.py
  55. 128
      sapl/sessao/forms.py
  56. 3
      sapl/sessao/migrations/0047_auto_20190829_1253.py
  57. 2
      sapl/sessao/migrations/0047_merge_20191009_1535.py
  58. 32
      sapl/sessao/migrations/0048_auto_20191029_1418.py
  59. 32
      sapl/sessao/migrations/0049_auto_20191029_1434.py
  60. 22
      sapl/sessao/migrations/0050_auto_20191029_1441.py
  61. 13
      sapl/sessao/migrations/0051_merge_20191209_0910.py
  62. 91
      sapl/sessao/migrations/0052_auto_20191209_0828.py
  63. 77
      sapl/sessao/models.py
  64. 28
      sapl/sessao/urls.py
  65. 136
      sapl/sessao/views.py
  66. 2
      sapl/settings.py
  67. 0
      sapl/static/sapl/frontend/css/chunk-vendors.c41142f2.css
  68. 0
      sapl/static/sapl/frontend/css/chunk-vendors.c41142f2.css.gz
  69. 1
      sapl/static/sapl/frontend/css/compilacao.3d691cd9.css
  70. BIN
      sapl/static/sapl/frontend/css/compilacao.3d691cd9.css.gz
  71. 1
      sapl/static/sapl/frontend/css/compilacao.eff62463.css
  72. BIN
      sapl/static/sapl/frontend/css/compilacao.eff62463.css.gz
  73. BIN
      sapl/static/sapl/frontend/css/painel.5d957a9b.css.gz
  74. 2
      sapl/static/sapl/frontend/css/painel.fd494bea.css
  75. BIN
      sapl/static/sapl/frontend/css/painel.fd494bea.css.gz
  76. 1
      sapl/static/sapl/frontend/css/sessao.559521f7.css
  77. 0
      sapl/static/sapl/frontend/js/chunk-09995afe.98f3367d.js
  78. 0
      sapl/static/sapl/frontend/js/chunk-09995afe.98f3367d.js.gz
  79. 0
      sapl/static/sapl/frontend/js/chunk-2d0c4a82.624674a7.js
  80. 0
      sapl/static/sapl/frontend/js/chunk-2d0c4a82.624674a7.js.gz
  81. 0
      sapl/static/sapl/frontend/js/chunk-2d0e8be2.67905a03.js
  82. 0
      sapl/static/sapl/frontend/js/chunk-2d0e8be2.67905a03.js.gz
  83. 0
      sapl/static/sapl/frontend/js/chunk-31d76f93.968a637a.js
  84. 0
      sapl/static/sapl/frontend/js/chunk-31d76f93.968a637a.js.gz
  85. 0
      sapl/static/sapl/frontend/js/chunk-681dd124.08a628b0.js
  86. 0
      sapl/static/sapl/frontend/js/chunk-681dd124.08a628b0.js.gz
  87. BIN
      sapl/static/sapl/frontend/js/chunk-vendors.3ef59900.js.gz
  88. 73
      sapl/static/sapl/frontend/js/chunk-vendors.dfcd310b.js
  89. BIN
      sapl/static/sapl/frontend/js/chunk-vendors.dfcd310b.js.gz
  90. 1
      sapl/static/sapl/frontend/js/compilacao.0684a9a9.js
  91. BIN
      sapl/static/sapl/frontend/js/compilacao.0684a9a9.js.gz
  92. 1
      sapl/static/sapl/frontend/js/compilacao.6b8172da.js
  93. BIN
      sapl/static/sapl/frontend/js/compilacao.6b8172da.js.gz
  94. 7
      sapl/static/sapl/frontend/js/global.3df943f5.js
  95. BIN
      sapl/static/sapl/frontend/js/global.3df943f5.js.gz
  96. 7
      sapl/static/sapl/frontend/js/global.ce91d2f8.js
  97. BIN
      sapl/static/sapl/frontend/js/global.ce91d2f8.js.gz
  98. 2
      sapl/static/sapl/frontend/js/online.0f0e1e97.js
  99. BIN
      sapl/static/sapl/frontend/js/online.0f0e1e97.js.gz
  100. BIN
      sapl/static/sapl/frontend/js/online.487f3d5a.js.gz

10
.dockerignore

@ -1,4 +1,14 @@
media
collected_static
.git
.gitignore
whoosh
bower
*.log
~*
*.pyc
.cache
.project
.travis.yml
.env
.idea

5
Dockerfile

@ -1,9 +1,9 @@
FROM alpine:3.8
FROM alpine:3.10
ENV BUILD_PACKAGES postgresql-dev graphviz-dev graphviz build-base git pkgconfig \
python3-dev libxml2-dev jpeg-dev libressl-dev libffi-dev libxslt-dev \
nodejs py3-lxml py3-magic postgresql-client poppler-utils antiword \
curl jq openssh-client vim bash
curl jq openssh-client vim bash postgresql-client cairo-dev
RUN apk update --update-cache && apk upgrade
@ -13,6 +13,7 @@ RUN apk add --no-cache python3 nginx tzdata && \
python3 -m ensurepip && \
rm -r /usr/lib/python*/ensurepip && \
pip3 install --upgrade pip setuptools && \
pip3 install wheel && \
rm -r /root/.cache && \
rm -f /etc/nginx/conf.d/*

6
requirements/requirements.txt

@ -20,12 +20,12 @@ easy-thumbnails==2.5
python-decouple==3.1
psycopg2-binary==2.7.6.1
pyyaml==4.2b1
pytz==2018.9
pytz==2019.3
rtyaml==0.0.5
python-magic==0.4.15
unipath==1.1
WeasyPrint==44
Pillow==5.1.0
WeasyPrint==50
Pillow==6.2.0
gunicorn==19.9.0
kombu==4.2.0
billiard==3.5.0.2

34
sapl/api/views.py

@ -3,6 +3,7 @@ import logging
from django import apps
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldError
from django.db.models import Q
from django.db.models.fields.files import FileField
from django.utils.decorators import classonlymethod
@ -26,10 +27,11 @@ from sapl.base.models import Autor, AppConfig, DOC_ADM_OSTENSIVO
from sapl.materia.models import Proposicao, TipoMateriaLegislativa,\
MateriaLegislativa, Tramitacao
from sapl.norma.models import NormaJuridica
from sapl.painel.models import Cronometro
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import DocumentoAdministrativo,\
DocumentoAcessorioAdministrativo, TramitacaoAdministrativo, Anexado
from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao
from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao, SessaoPlenariaPresenca
from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria
@ -514,7 +516,6 @@ class _SessaoPlenariaViewSet:
@action(detail=False)
def years(self, request, *args, **kwargs):
years = choice_anos_com_sessaoplenaria()
serializer = ChoiceSerializer(years, many=True)
return Response(serializer.data)
@ -533,6 +534,21 @@ class _SessaoPlenariaViewSet:
return Response(serializer.data)
@action(detail=True, methods=['GET'])
def parlamentares_presentes(self, request, *args, **kwargs):
sessao = self.get_object()
parlamentares = Parlamentar.objects.filter(sessaoplenariapresenca__sessao_plenaria=sessao)
page = self.paginate_queryset(parlamentares)
if page is not None:
serializer = SaplApiViewSetConstrutor.get_class_for_model(
Parlamentar).serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(page, many=True)
return Response(serializer.data)
@customize(NormaJuridica)
class _NormaJuridicaViewset:
@ -540,3 +556,17 @@ class _NormaJuridicaViewset:
def destaques(self, request, *args, **kwargs):
self.queryset = self.get_queryset().filter(norma_de_destaque=True)
return self.list(request, *args, **kwargs)
@customize(Cronometro)
class _CronometroViewSet:
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset()
try:
filter_condition = {k:v[0] for (k,v) in self.request.GET.items()}
qs = qs.filter(**filter_condition)
except FieldError as e:
pass
return qs

31
sapl/audiencia/forms.py

@ -1,17 +1,16 @@
import logging
from django import forms
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import transaction
from django.utils.translation import ugettext_lazy as _
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica, AnexoAudienciaPublica
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row
from crispy_forms.layout import Button, Column, Fieldset, HTML, Layout
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica, AnexoAudienciaPublica
from sapl.crispy_layout_mixin import form_actions, SaplFormHelper, SaplFormLayout, to_row
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.utils import timezone, FileFieldCheckMixin
from sapl.utils import timezone, FileFieldCheckMixin, validar_arquivo
class AudienciaForm(FileFieldCheckMixin, forms.ModelForm):
logger = logging.getLogger(__name__)
@ -119,17 +118,14 @@ class AudienciaForm(FileFieldCheckMixin, forms.ModelForm):
upload_ata = self.cleaned_data.get('upload_ata', False)
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_pauta and upload_pauta.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Pauta da Audiência Pública deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_pauta.size/1024)/1024))
if upload_pauta:
validar_arquivo(upload_pauta, "Pauta da Audiência Pública")
if upload_ata and upload_ata.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Ata da Audiência Pública deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_ata.size/1024)/1024))
if upload_ata:
validar_arquivo(upload_ata, "Ata da Audiência Pública")
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo da Audiência Pública deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
if upload_anexo:
validar_arquivo(upload_anexo, "Anexo da Audiência Pública")
return cleaned_data
@ -164,8 +160,7 @@ class AnexoAudienciaPublicaForm(forms.ModelForm):
arquivo = self.cleaned_data.get('arquivo', False)
if arquivo and arquivo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (arquivo.size/1024)/1024))
if arquivo:
validar_arquivo(arquivo, "Arquivo")
return self.cleaned_data

32
sapl/audiencia/migrations/0013_auto_20191023_1522.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-23 18:22
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.audiencia.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('audiencia', '0012_auto_20191001_1115'),
]
operations = [
migrations.AlterField(
model_name='audienciapublica',
name='upload_anexo',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.audiencia.models.anexo_upload_path, verbose_name='Anexo da Audiência Pública'),
),
migrations.AlterField(
model_name='audienciapublica',
name='upload_ata',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.audiencia.models.ata_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Ata da Audiência Pública'),
),
migrations.AlterField(
model_name='audienciapublica',
name='upload_pauta',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.audiencia.models.pauta_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Pauta da Audiência Pública'),
),
]

21
sapl/audiencia/migrations/0014_auto_20191023_1538.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-23 18:38
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('audiencia', '0013_auto_20191023_1522'),
]
operations = [
migrations.AlterField(
model_name='anexoaudienciapublica',
name='arquivo',
field=models.FileField(max_length=300, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.utils.texto_upload_path, verbose_name='Arquivo'),
),
]

8
sapl/audiencia/models.py

@ -87,7 +87,7 @@ class AudienciaPublica(models.Model):
max_length=150, blank=True,
verbose_name=_('URL Arquivo Vídeo (Formatos MP4 / FLV / WebM)'))
upload_pauta = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
upload_to=pauta_upload_path,
@ -95,7 +95,7 @@ class AudienciaPublica(models.Model):
verbose_name=_('Pauta da Audiência Pública'),
validators=[restringe_tipos_de_arquivo_txt])
upload_ata = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
upload_to=ata_upload_path,
@ -103,7 +103,7 @@ class AudienciaPublica(models.Model):
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
upload_anexo = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
upload_to=anexo_upload_path,
@ -167,7 +167,7 @@ class AnexoAudienciaPublica(models.Model):
audiencia = models.ForeignKey(AudienciaPublica,
on_delete=models.PROTECT)
arquivo = models.FileField(
max_length=200,
max_length=300,
upload_to=texto_upload_path,
storage=OverwriteStorage(),
verbose_name=_('Arquivo'))

31
sapl/base/admin.py

@ -1,8 +1,9 @@
from django.contrib import admin
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.utils.translation import ugettext_lazy as _
from reversion.models import Revision
from sapl.base.models import AuditLog
from sapl.utils import register_all_models_in_admin
register_all_models_in_admin(__name__)
@ -20,5 +21,31 @@ class RevisionAdmin(admin.ModelAdmin):
self.message_user(request, _('You cannot change history.'))
return redirect('admin:reversion_revision_changelist')
admin.site.register(Revision, RevisionAdmin)
class AuditLogAdmin(admin.ModelAdmin):
pass
def has_add_permission(self, request):
return False
# def has_change_permission(self, request, obj=None):
# return False
#
def has_delete_permission(self, request, obj=None):
return False
def save_model(self, request, obj, form, change):
pass
def delete_model(self, request, obj):
pass
def save_related(self, request, form, formsets, change):
pass
# Na linha acima register_all_models_in_admin registrou AuditLog
admin.site.unregister(AuditLog)
admin.site.register(AuditLog, AuditLogAdmin)

21
sapl/base/forms.py

@ -844,15 +844,17 @@ class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet):
self.filters['data_inicio'].label = 'Período (Inicial - Final)'
self.form.fields['legislatura'].required = True
tipo_sessao_ordinaria = self.filters['tipo'].queryset.filter(nome='Ordinária')
if tipo_sessao_ordinaria:
self.form.initial['tipo'] = tipo_sessao_ordinaria.first()
row1 = to_row([('data_inicio', 12)])
row2 = to_row([('legislatura', 4),
row1 = to_row([('legislatura', 4),
('sessao_legislativa', 4),
('tipo', 4)])
row3 = to_row([('exibir_ordem_dia', 12)])
row2 = to_row([('exibir_ordem_dia', 12)])
row3 = to_row([('data_inicio', 12)])
buttons = FormActions(
*[
@ -1326,9 +1328,14 @@ class ConfiguracoesAppForm(ModelForm):
'tramitacao_materia',
'tramitacao_documento']
def clean_mostrar_brasao_painel(self):
mostrar_brasao_painel = self.cleaned_data.get(
'mostrar_brasao_painel', False)
def clean(self):
cleaned_data = super().clean()
if not self.is_valid():
return cleaned_data
mostrar_brasao_painel = self.cleaned_data.get('mostrar_brasao_painel', False)
casa = CasaLegislativa.objects.first()
if not casa:
@ -1341,7 +1348,7 @@ class ConfiguracoesAppForm(ModelForm):
raise ValidationError("Não há logitipo configurado para esta "
"Casa legislativa.")
return mostrar_brasao_painel
return cleaned_data
class RecuperarSenhaForm(PasswordResetForm):

33
sapl/base/migrations/0038_auditlog.py

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-15 13:33
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0037_auto_20190527_0901'),
]
operations = [
migrations.CreateModel(
name='AuditLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('username', models.CharField(blank=True, db_index=True, max_length=100, verbose_name='username')),
('operation', models.CharField(db_index=True, max_length=1, verbose_name='operation')),
('timestamp', models.DateTimeField(db_index=True, verbose_name='timestamp')),
('object', models.CharField(blank=True, max_length=4096, verbose_name='object')),
('object_id', models.PositiveIntegerField(db_index=True, verbose_name='object_id')),
('model_name', models.CharField(db_index=True, max_length=100, verbose_name='model')),
('app_name', models.CharField(db_index=True, max_length=100, verbose_name='app')),
],
options={
'verbose_name': 'AuditLog',
'verbose_name_plural': 'AuditLogs',
'ordering': ('-id',),
},
),
]

16
sapl/base/migrations/0040_merge_20191030_1655.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-30 19:55
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('base', '0038_auditlog'),
('base', '0039_auto_20190913_1228'),
]
operations = [
]

20
sapl/base/migrations/0041_auto_20191204_1130.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-12-04 14:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0040_merge_20191030_1655'),
]
operations = [
migrations.AlterField(
model_name='appconfig',
name='assinatura_ata',
field=models.CharField(choices=[('M', 'Mesa Diretora da Sessão'), ('P', 'Apenas o Presidente da Sessão'), ('T', 'Todos os Parlamentares Presentes na Sessão')], default='T', max_length=1, verbose_name='Quem deve assinar a ata'),
),
]

42
sapl/base/models.py

@ -136,7 +136,7 @@ class AppConfig(models.Model):
max_length=1, choices=POLITICA_PROTOCOLO_CHOICES, default='O')
assinatura_ata = models.CharField(
verbose_name=_('Quem deve assina a ata'),
verbose_name=_('Quem deve assinar a ata'),
max_length=1, choices=ASSINATURA_ATA_CHOICES, default='T')
mostrar_brasao_painel = models.BooleanField(
@ -265,3 +265,43 @@ class AutorUser(models.Model):
def __str__(self):
return "%s - %s" % (self.autor, self.user)
class AuditLog(models.Model):
operation = ('C', 'D', 'U')
MAX_DATA_LENGTH = 4096 # 4KB de texto
username = models.CharField(max_length=100,
verbose_name=_('username'),
blank=True,
db_index=True)
operation = models.CharField(max_length=1,
verbose_name=_('operation'),
db_index=True)
timestamp = models.DateTimeField(verbose_name=_('timestamp'),
db_index=True)
object = models.CharField(max_length=MAX_DATA_LENGTH,
blank=True,
verbose_name=_('object'))
object_id = models.PositiveIntegerField(verbose_name=_('object_id'),
db_index=True)
model_name = models.CharField(max_length=100, verbose_name=_('model'),
db_index=True)
app_name = models.CharField(max_length=100,
verbose_name=_('app'),
db_index=True)
class Meta:
verbose_name = _('AuditLog')
verbose_name_plural = _('AuditLogs')
ordering = ('-id',)
def __str__(self):
return "[%s] %s %s.%s %s" % (self.timestamp,
self.operation,
self.app_name,
self.model_name,
self.username,
)

42
sapl/base/receivers.py

@ -1,7 +1,13 @@
import logging
from django.core import serializers
from django.db.models.signals import post_delete
from django.dispatch import receiver
from django.utils import timezone
from sapl.base.signals import tramitacao_signal
from sapl.base.email_utils import do_envia_email_tramitacao
from sapl.base.models import AuditLog
from sapl.base.signals import tramitacao_signal, post_delete_signal, post_save_signal
from sapl.materia.models import Tramitacao
from sapl.protocoloadm.models import TramitacaoAdministrativo
from sapl.utils import get_base_url
@ -39,3 +45,37 @@ def status_tramitacao_materia(sender, instance, **kwargs):
documento = instance.documento
documento.tramitacao = True
documento.save()
@receiver(post_delete_signal)
@receiver(post_save_signal)
def audit_log(sender, **kwargs):
logger = logging.getLogger(__name__)
instance = kwargs.get('instance')
operation = kwargs.get('operation')
user = kwargs.get('request').user
model_name = instance.__class__.__name__
app_name = instance._meta.app_label
object_id = instance.id
data = serializers.serialize('json', [instance])
if len(data) > AuditLog.MAX_DATA_LENGTH:
data = data[:AuditLog.MAX_DATA_LENGTH]
if user:
username = user.username
else:
username = ''
try:
AuditLog.objects.create(username=username,
operation=operation,
model_name=model_name,
app_name=app_name,
timestamp=timezone.now(),
object_id=object_id,
object=data)
except Exception as e:
logger.error('Error saving auditing log object')
logger.error(e)

3
sapl/base/signals.py

@ -46,3 +46,6 @@ def cria_models_tipo_autor(app_config=None, verbosity=2, interactive=True,
post_migrate.connect(receiver=cria_models_tipo_autor)
post_delete_signal = django.dispatch.Signal(providing_args=['instance', 'request'])
post_save_signal = django.dispatch.Signal(providing_args=['instance', 'operation', 'request'])

8
sapl/base/tests/test_login.py

@ -12,9 +12,13 @@ def user():
def test_login_aparece_na_barra_para_usuario_nao_logado(client):
import re
response = client.get('/')
assert '<a class="nav-link" href="/login/"><img src="/static/sapl/frontend/img/user.png"></a>' in str(
response.content)
content = str(response.content)
query = '<a class="nav-link" href="/login/"><img src=".*/user.png"></a>'
result = re.findall(query, content)
assert len(result) > 0
def test_username_do_usuario_logado_aparece_na_barra(client, user):

2
sapl/base/views.py

@ -2053,8 +2053,6 @@ class AppConfigCrud(CrudAux):
reverse('sapl.base:appconfig_update',
kwargs={'pk': app_config.pk}))
def post(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
class ListView(CrudAux.ListView):

43
sapl/comissoes/forms.py

@ -12,13 +12,16 @@ from django.forms import ModelForm
from django.utils.translation import ugettext_lazy as _
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import (Comissao, Composicao, DocumentoAcessorio,
Participacao, Periodo, Reuniao)
from sapl.crispy_layout_mixin import form_actions, SaplFormHelper, to_row
from sapl.comissoes.models import (Comissao, Composicao,
DocumentoAcessorio, Participacao,
Periodo, Reuniao)
from sapl.crispy_layout_mixin import (form_actions, SaplFormHelper,
to_row)
from sapl.materia.models import MateriaEmTramitacao, PautaReuniao
from sapl.parlamentares.models import Legislatura, Mandato, Parlamentar
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import FileFieldCheckMixin, FilterOverridesMetaMixin
from sapl.parlamentares.models import (Legislatura, Mandato,
Parlamentar)
from sapl.utils import (FileFieldCheckMixin, FilterOverridesMetaMixin,
validar_arquivo)
class ComposicaoForm(forms.ModelForm):
@ -405,17 +408,14 @@ class ReuniaoForm(ModelForm):
upload_ata = self.cleaned_data.get('upload_ata', False)
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_pauta and upload_pauta.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Pauta da Reunião deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_pauta.size/1024)/1024))
if upload_pauta:
validar_arquivo(upload_pauta, "Pauta da Reunião")
if upload_ata and upload_ata.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Ata da Reunião deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_ata.size/1024)/1024))
if upload_ata:
validar_arquivo(upload_ata, "Ata da Reunião")
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo da Reunião deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
if upload_anexo:
validar_arquivo(upload_anexo, "Anexo da Reunião")
return self.cleaned_data
@ -431,9 +431,10 @@ class PautaReuniaoFilterSet(django_filters.FilterSet):
self.filters['materia__tipo'].label = "Tipo da Matéria"
self.filters['materia__ano'].label = "Ano da Matéria"
self.filters['materia__numero'].label = "Número da Matéria"
self.filters['materia__data_apresentacao'].label = "Data (Inicial - Final)"
row1 = to_row([('materia__numero', 4), ('materia__tipo', 4), ('materia__ano', 4)])
row1 = to_row([('materia__tipo', 4), ('materia__ano', 4), ('materia__numero', 4)])
row2 = to_row([('materia__data_apresentacao', 12)])
self.form.helper = SaplFormHelper()
@ -482,9 +483,8 @@ class DocumentoAcessorioCreateForm(FileFieldCheckMixin, forms.ModelForm):
arquivo = self.cleaned_data.get('arquivo', False)
if arquivo and arquivo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Texto Integral deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (arquivo.size/1024)/1024))
if arquivo:
validar_arquivo(arquivo, "Texto Integral")
return self.cleaned_data
@ -509,8 +509,7 @@ class DocumentoAcessorioEditForm(FileFieldCheckMixin, forms.ModelForm):
arquivo = self.cleaned_data.get('arquivo', False)
if arquivo and arquivo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Texto Integral deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (arquivo.size/1024)/1024))
if arquivo:
validar_arquivo(arquivo, "Texto Integral")
return self.cleaned_data

42
sapl/comissoes/migrations/0022_auto_20191120_1440.py

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-11-20 17:40
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.comissoes.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('comissoes', '0021_auto_20191001_1115'),
]
operations = [
migrations.AlterField(
model_name='documentoacessorio',
name='arquivo',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.comissoes.models.anexo_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral'),
),
migrations.AlterField(
model_name='documentoacessorio',
name='autor',
field=models.CharField(max_length=200, verbose_name='Autor'),
),
migrations.AlterField(
model_name='reuniao',
name='upload_anexo',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.comissoes.models.anexo_upload_path, verbose_name='Anexo da Reunião'),
),
migrations.AlterField(
model_name='reuniao',
name='upload_ata',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.comissoes.models.ata_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Ata da Reunião'),
),
migrations.AlterField(
model_name='reuniao',
name='upload_pauta',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.comissoes.models.pauta_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Pauta da Reunião'),
),
]

10
sapl/comissoes/models.py

@ -236,21 +236,21 @@ class Reuniao(models.Model):
max_length=150, blank=True,
verbose_name=_('URL do Arquivo de Vídeo (Formatos MP4 / FLV / WebM)'))
upload_pauta = models.FileField(
max_length=200,
max_length=300,
blank=True, null=True,
upload_to=pauta_upload_path,
verbose_name=_('Pauta da Reunião'),
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
upload_ata = models.FileField(
max_length=200,
max_length=300,
blank=True, null=True,
upload_to=ata_upload_path,
verbose_name=_('Ata da Reunião'),
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
upload_anexo = models.FileField(
max_length=200,
max_length=300,
blank=True, null=True,
upload_to=anexo_upload_path,
storage=OverwriteStorage(),
@ -317,11 +317,11 @@ class DocumentoAcessorio(models.Model):
data = models.DateField(blank=True, null=True,
default=None, verbose_name=_('Data'))
autor = models.CharField(
max_length=100, verbose_name=_('Autor'))
max_length=200, verbose_name=_('Autor'))
ementa = models.TextField(blank=True, verbose_name=_('Ementa'))
indexacao = models.TextField(blank=True)
arquivo = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
upload_to=anexo_upload_path,

38
sapl/compilacao/forms.py

@ -13,6 +13,7 @@ from django.forms.forms import Form
from django.forms.models import ModelForm
from django.template import defaultfilters
from django.utils.translation import ugettext_lazy as _
from model_utils.choices import Choices
from sapl import utils
from sapl.compilacao.models import (NOTAS_PUBLICIDADE_CHOICES,
@ -115,7 +116,7 @@ class TaForm(ModelForm):
queryset=TipoTextoArticulado.objects.all(),
required=True,
empty_label=None)
numero = forms.IntegerField(
numero = forms.CharField(
label=TextoArticulado._meta.get_field(
'numero').verbose_name,
required=True)
@ -744,6 +745,12 @@ class DispositivoEdicaoBasicaForm(ModelForm):
class DispositivoSearchModalForm(Form):
TIPO_RESULTADO_CHOICES = Choices(
('C', 'coincidentes', _('Apenas Coincidentes')),
('I', 'internos', _('Incluir Internos')),
('S', 'coin_sequentes', _('Coincidentes e seus sequentes')),
)
tipo_ta = forms.ModelChoiceField(
label=_('Tipo do Texto Articulado'),
queryset=TipoTextoArticulado.objects.all(),
@ -758,9 +765,9 @@ class DispositivoSearchModalForm(Form):
ano_ta = forms.IntegerField(
label=_('Ano do Documento'), required=False)
dispositivos_internos = forms.ChoiceField(
label=_('Dispositivos Internos?'),
choices=utils.YES_NO_CHOICES,
tipo_resultado = forms.ChoiceField(
label=_('Tipo do Resultado?'),
choices=TIPO_RESULTADO_CHOICES,
widget=forms.RadioSelect(),
required=False)
@ -769,7 +776,7 @@ class DispositivoSearchModalForm(Form):
choices=[(10, _('Dez Dispositivos')),
(30, _('Trinta Dispositivos')),
(50, _('Cinquenta Dispositivos')),
(0, _('Tudo que atender aos Critérios da Busca'))],
(100, _('Cem Dispositivos'))],
widget=forms.Select(),
required=False)
@ -789,12 +796,20 @@ class DispositivoSearchModalForm(Form):
to_column(('num_ta', 4)),
to_column(('ano_ta', 4)),
to_column(('max_results', 4))),
Row(
to_column(('tipo_resultado', 3)),
to_column(
(
Div(
Row(
to_column(('tipo_ta', 6)),
to_column(('tipo_model', 6))),
Row(to_column((InlineRadios('dispositivos_internos'), 3)),
to_column(('rotulo_dispositivo', 2)),
to_column((FieldWithButtons(
Row(
to_column(('rotulo_dispositivo', 4)),
to_column(
(
FieldWithButtons(
Field(
'texto_dispositivo',
placeholder=_('Digite palavras, letras, '
@ -802,9 +817,12 @@ class DispositivoSearchModalForm(Form):
' que estejam no texto.')),
StrictButton(
_('Buscar'),
css_class='btn-busca btn-primary')), 7))
css_class='btn-busca btn-primary')), 8))
)
), 9
)
)
))
self.helper = SaplFormHelper()
self.helper.layout = Layout(
@ -821,7 +839,7 @@ class DispositivoSearchModalForm(Form):
choice = ch(kwargs['instance'].ta.tipo_ta_id)
self.base_fields['tipo_model'].choices = choice
kwargs['initial'].update({'dispositivos_internos': False})
kwargs['initial'].update({'tipo_resultado': 'C'})
super(DispositivoSearchModalForm, self).__init__(*args, **kwargs)

27
sapl/compilacao/migrations/0013_auto_20190924_0830.py

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.24 on 2019-09-24 11:30
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0012_bug_auto_inserido'),
]
operations = [
migrations.AlterField(
model_name='textoarticulado',
name='editable_only_by_owners',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=True,
verbose_name='Editável apenas pelos donos do Texto Articulado?'),
),
migrations.AlterField(
model_name='textoarticulado',
name='editing_locked',
field=models.BooleanField(choices=[(
True, 'Sim'), (False, 'Não')], default=True, verbose_name='Texto Articulado em Edição?'),
),
]

29
sapl/compilacao/models.py

@ -221,12 +221,12 @@ class TextoArticulado(TimestampedMixin):
editable_only_by_owners = models.BooleanField(
choices=YES_NO_CHOICES,
default=True,
verbose_name=_('Editável apenas pelos donos do Texto Articulado'))
verbose_name=_('Editável apenas pelos donos do Texto Articulado?'))
editing_locked = models.BooleanField(
choices=YES_NO_CHOICES,
default=True,
verbose_name=_('Texto Articulado em Edição'))
verbose_name=_('Texto Articulado em Edição?'))
privacidade = models.IntegerField(
_('Privacidade'),
@ -416,8 +416,8 @@ class TextoArticulado(TimestampedMixin):
def clone_for(self, obj):
# O clone gera um texto válido original dada a base self,
# mesmo sendo esta base um texto compilado.
# Os dispositivos a clonar será com base no texto compilado
# mesmo sendo esta base um Texto Articulado.
# Os dispositivos a clonar será com base no Texto Articulado
assert self.tipo_ta and self.tipo_ta.content_type, _(
'Não é permitido chamar o método clone_for '
@ -1072,6 +1072,10 @@ class Dispositivo(BaseModel, TimestampedMixin):
'Permissão alteração global do dispositivo de vigência')),
)
def ws_sync(self):
return self.ta and self.ta.privacidade in (
STATUS_TA_IMMUTABLE_PUBLIC, STATUS_TA_PUBLIC)
def clean(self):
"""
Check for instances with null values in unique_together fields.
@ -1113,14 +1117,15 @@ class Dispositivo(BaseModel, TimestampedMixin):
self.contagem_continua = self.tipo_dispositivo.contagem_continua
try:
"""try:
if self.texto:
self.texto = self.texto.replace('\xa0', '')
self.texto = str(BeautifulSoup(self.texto, "html.parser"))
if self.texto_atualizador:
self.texto_atualizador = str(BeautifulSoup(
self.texto_atualizador, "html.parser"))
except:
pass
pass"""
return super().save(
force_insert=force_insert, force_update=force_update, using=using,
@ -1624,7 +1629,7 @@ class Dispositivo(BaseModel, TimestampedMixin):
yield ultimo
@staticmethod
def new_instance_based_on(dispositivo_base, tipo_base):
def new_instance_based_on(dispositivo_base, tipo_base, base_alteracao=None):
dp = Dispositivo()
dp.tipo_dispositivo = tipo_base
@ -1639,6 +1644,16 @@ class Dispositivo(BaseModel, TimestampedMixin):
dp.dispositivo_pai = dispositivo_base.dispositivo_pai
dp.publicacao = dispositivo_base.publicacao
b = base_alteracao if base_alteracao else dispositivo_base
# teste de criação inversa de itens alterados por mesmo bloco
dp.ta_publicado = b.ta_publicado
dp.dispositivo_atualizador = b.dispositivo_atualizador
if dp.ta_publicado:
dp.ordem_bloco_atualizador = b.ordem_bloco_atualizador + \
Dispositivo.INTERVALO_ORDEM
dp.dispositivo_vigencia = dispositivo_base.dispositivo_vigencia
if dp.dispositivo_vigencia:
dp.inicio_eficacia = dp.dispositivo_vigencia.inicio_eficacia

3
sapl/compilacao/templatetags/compilacao_filters.py

@ -65,6 +65,8 @@ def get_bloco_atualizador(pk_atualizador):
@register.simple_tag
def dispositivo_desativado(dispositivo, inicio_vigencia, fim_vigencia):
if dispositivo.dispositivo_de_revogacao:
return 'revogado'
if inicio_vigencia and fim_vigencia:
if dispositivo.fim_vigencia is None:
return ''
@ -292,7 +294,6 @@ def nomenclatura_heranca(d, ignore_ultimo=0, ignore_primeiro=0):
return result
@register.filter
def list(obj):
return [obj, ]

380
sapl/compilacao/views.py

@ -10,7 +10,7 @@ from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.core.exceptions import ValidationError, PermissionDenied
from django.core.signing import Signer
from django.core.urlresolvers import reverse, reverse_lazy
from django.db import transaction
@ -494,6 +494,18 @@ class TaListView(CompMixin, ListView):
~Q(owners=self.request.user.id),
privacidade=STATUS_TA_PRIVATE)
if 'check' in self.request.GET:
qs = qs.filter(
temp_check_migrations=False,
privacidade=0,
).exclude(dispositivos_set__tipo_dispositivo_id=3)
if 'check_dvt' in self.request.GET:
qs = qs.filter(
).filter(
dispositivos_set__isnull=False,
dispositivos_set__dispositivo_vigencia__isnull=True).distinct()
return qs
@ -565,13 +577,29 @@ class TaDeleteView(CompMixin, DeleteView):
template_name = "crud/confirm_delete.html"
permission_required = 'compilacao.delete_textoarticulado'
def post(self, request, *args, **kwargs):
if not request.user.is_superuser:
raise PermissionDenied
return DeleteView.post(self, request, *args, **kwargs)
@property
def detail_url(self):
return reverse_lazy('sapl.compilacao:ta_detail',
kwargs={'pk': self.kwargs['pk']})
def get_success_url(self):
return reverse_lazy('sapl.compilacao:ta_list')
messages.info(self.request, 'Texto Articulado excluido com sucesso!')
reverse_url = '%s:%s_detail' % (
self.object.content_object._meta.app_config.name,
self.object.content_object._meta.model_name)
return reverse_lazy(reverse_url,
kwargs={'pk': self.object.content_object.pk})
@property
def title(self):
return '<b>Texto Articulado:</b> %s' % self.object
class DispositivoSuccessUrlMixin(CompMixin):
@ -849,6 +877,10 @@ class TextView(CompMixin, ListView):
fim_vigencia = None
ta_vigencia = None
@property
def title(self):
return '<b>Texto Articulado:</b> %s' % self.object
def has_permission(self):
self.object = self.ta
return self.object.has_view_permission(self.request)
@ -1078,7 +1110,7 @@ class TextEditView(CompMixin, TemplateView):
self.object.content_object.save()
else:
if 'lock' in request.GET:
if 'lock' in request.GET or 'check' in request.GET:
# TODO - implementar logging de ação de usuário
notificacoes = self.get_notificacoes(
@ -1097,11 +1129,17 @@ class TextEditView(CompMixin, TemplateView):
'sapl.compilacao:ta_text_notificacoes', kwargs={
'ta_id': self.object.id}))
if 'lock' in request.GET:
self.object.editing_locked = True
self.object.privacidade = STATUS_TA_PUBLIC
self.object.save()
messages.success(request, _(
'Texto Articulado bloqueado com sucesso.'))
'Texto Articulado publicado com sucesso.'))
else:
self.object.temp_check_migrations = True
self.object.save()
messages.success(request, _(
'Texto Articulado Checado...'))
if self.object.content_object:
self.object.content_object.save()
@ -2083,6 +2121,10 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
if len(result) > 2:
result.pop()
result[0]['itens'] = result[1]['itens'] + result[0]['itens']
result[0]['tipo_insert'] = 'Inserção'
result[1]['itens'] = []
return result
except Exception as e:
@ -2097,7 +2139,6 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
dvt = Dispositivo.objects.get(pk=self.kwargs['dispositivo_id'])
if dvt.auto_inserido:
dvt = dvt.dispositivo_pai
try:
Dispositivo.objects.filter(
ta=dvt.ta, ta_publicado__isnull=True
@ -2109,7 +2150,7 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
Dispositivo.objects.filter(ta_publicado=dvt.ta
).update(
dispositivo_vigencia=dvt,
inicio_vigencia=dvt.inicio_eficacia,
inicio_vigencia=dvt.inicio_vigencia,
inicio_eficacia=dvt.inicio_eficacia)
dps = Dispositivo.objects.filter(dispositivo_vigencia=dvt)
@ -2162,6 +2203,10 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
dp_auto_insert = None
base = Dispositivo.objects.get(pk=self.kwargs['dispositivo_id'])
if base.dispositivo_atualizador:
registro_inclusao = True
tipo = TipoDispositivo.objects.get(pk=context['tipo_pk'])
pub_last = Publicacao.objects.order_by(
'data', 'hora').filter(ta=base.ta).last()
@ -2197,11 +2242,13 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
dp_pai = dp
if dp_irmao is not None:
dp = Dispositivo.new_instance_based_on(dp_irmao, tipo)
dp = Dispositivo.new_instance_based_on(
dp_irmao, tipo, base_alteracao=base)
dp.transform_in_next(variacao)
else:
# Inserção sem precedente
dp = Dispositivo.new_instance_based_on(dp_pai, tipo)
dp = Dispositivo.new_instance_based_on(
dp_pai, tipo, base_alteracao=base)
dp.dispositivo_pai = dp_pai
dp.nivel += 1
@ -2224,6 +2271,9 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
else:
dp.set_numero_completo([1, 0, 0, 0, 0, 0, ])
if dp.dispositivo_atualizador:
registro_inclusao = True
# verificar se existe restrição de quantidade de itens
if dp.dispositivo_pai:
for perfil in perfil_parents:
@ -2262,7 +2312,8 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
dp.incrementar_irmaos(variacao, [local_add, ], force=False)
dp.publicacao = pub_last
dp.save()
dp.save(clean=not registro_inclusao)
count_auto_insert = 0
if create_auto_inserts:
@ -2314,6 +2365,10 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
ordem += Dispositivo.INTERVALO_ORDEM
dp = Dispositivo.objects.get(pk=dp_pk)
dp.ta_publicado = None
dp.dispositivo_atualizador = None
dp.ordem_bloco_atualizador = 0
dp.save(clean=False)
''' Reenquadrar todos os dispositivos que possuem pai
antes da inserção atual e que são inferiores a dp,
@ -2624,8 +2679,12 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin,
ds = d
while ds.dispositivo_subsequente:
ds = ds.dispositivo_subsequente
dsps_ids.add(ds.pk)
if revogacao and ds.dispositivo_de_revogacao:
dsps_ids.remove(ds.pk)
if em_bloco:
proximo_bloco = Dispositivo.objects.filter(
ordem__gt=ds.ordem,
@ -2636,9 +2695,18 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin,
'ta_id': ds.ta_id,
'nivel__gte': ds.nivel,
'ordem__gte': ds.ordem,
'dispositivo_subsequente__isnull': True
'dispositivo_subsequente__isnull': True,
}
if revogacao:
params.update(
{
'dispositivo_de_revogacao': False,
'tipo_dispositivo__dispositivo_de_articulacao': False
}
)
if proximo_bloco:
params['ordem__lt'] = proximo_bloco.ordem
@ -2655,9 +2723,9 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin,
dsps_ids = Dispositivo.objects.filter(
id__in=dsps_ids
).values_list('id', flat="True")
for dsp in dsps_ids:
).values_list('id', flat="True").order_by('ordem')
with transaction.atomic():
for dsp in dsps_ids:
data.update(
self.registra_alteracao(
bloco_alteracao,
@ -2713,10 +2781,10 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin,
if ndp.dispositivo_vigencia:
ndp.inicio_eficacia = ndp.dispositivo_vigencia.inicio_eficacia
ndp.inicio_vigencia = ndp.dispositivo_vigencia.inicio_eficacia
ndp.inicio_vigencia = ndp.dispositivo_vigencia.inicio_vigencia
else:
ndp.inicio_eficacia = bloco_alteracao.inicio_eficacia
ndp.inicio_vigencia = bloco_alteracao.inicio_eficacia
ndp.inicio_vigencia = bloco_alteracao.inicio_vigencia
try:
ordem = dsp_a_alterar.criar_espaco(
@ -2748,6 +2816,7 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin,
).ordem_bloco_atualizador + Dispositivo.INTERVALO_ORDEM
else:
ndp.ordem_bloco_atualizador = Dispositivo.INTERVALO_ORDEM
ndp.save()
p.dispositivo_subsequente = ndp
@ -2765,10 +2834,10 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin,
filhos_diretos = dsp_a_alterar.dispositivos_filhos_set
for d in filhos_diretos.all():
d.dispositivo_pai = ndp
d.save()
d.save(clean=False)
ndp.ta.reordenar_dispositivos()
bloco_alteracao.ordenar_bloco_alteracao()
# ndp.ta.reordenar_dispositivos()
# bloco_alteracao.ordenar_bloco_alteracao()
if not revogacao:
if 'message' not in data:
@ -2980,140 +3049,31 @@ class DispositivoSearchFragmentFormView(ListView):
itens.append(item)
return JsonResponse(itens, safe=False)
response = ListView.get(self, request, *args, **kwargs)
if not self.object_list or \
not isinstance(self.object_list, list) and \
not self.object_list.exists():
messages.info(
request, _('Não foram encontrados resultados '
'com seus critérios de busca!'))
username = self.request.user.username
self.logger.error("user=" + username + ". Não foram encontrados "
"resultados com esses critérios de busca. "
"id_tipo_ta=".format(request.GET['tipo_ta']))
try:
r = response.render()
return response
except Exception as e:
messages.error(request, "Erro - %s" % str(e))
context = {}
self.template_name = 'compilacao/messages.html'
username = self.request.user.username
self.logger.error("user=" + username + ". " + str(e))
return self.render_to_response(context)
return ListView.get(self, request, *args, **kwargs)
def get_queryset(self):
try:
n = 10
if 'max_results' in self.request.GET:
n = int(self.request.GET['max_results'])
q = Q()
if 'initial_ref' in self.request.GET:
initial_ref = self.request.GET['initial_ref']
if initial_ref:
q = q & Q(pk=initial_ref)
result = Dispositivo.objects.filter(q).select_related(
'ta').exclude(
tipo_dispositivo__dispositivo_de_alteracao=True)
return result[:n]
str_texto = ''
texto = ''
rotulo = ''
num_ta = ''
ano_ta = ''
if 'texto' in self.request.GET:
str_texto = self.request.GET['texto']
result = []
try:
tipo_model = self.request.GET.get('tipo_model', '')
limit = int(self.request.GET.get('max_results', 100))
tipo_ta = self.request.GET.get('tipo_ta', '')
num_ta = self.request.GET.get('num_ta', '')
ano_ta = self.request.GET.get('ano_ta', '')
rotulo = self.request.GET.get('rotulo', '')
str_texto = self.request.GET.get('texto', '')
texto = str_texto.split(' ')
if 'rotulo' in self.request.GET:
rotulo = self.request.GET['rotulo']
if rotulo:
q = q & Q(rotulo__icontains=rotulo)
tipo_resultado = self.request.GET.get('tipo_resultado', '')
tipo_resultado = '' if tipo_resultado == 'False' else tipo_resultado
for item in texto:
if not item:
continue
if q:
q = q & (Q(texto__icontains=item) |
Q(texto_atualizador__icontains=item))
else:
q = (Q(texto__icontains=item) |
Q(texto_atualizador__icontains=item))
model_class = None
if 'tipo_ta' in self.request.GET:
tipo_ta = self.request.GET['tipo_ta']
if tipo_ta:
q = q & Q(ta__tipo_ta_id=tipo_ta)
if 'num_ta' in self.request.GET:
num_ta = self.request.GET['num_ta']
if num_ta:
q = q & Q(ta__numero=num_ta)
if 'ano_ta' in self.request.GET:
ano_ta = self.request.GET['ano_ta']
if ano_ta:
q = q & Q(ta__ano=ano_ta)
if not q.children and not n:
n = 10
q = q & Q(nivel__gt=0)
result = Dispositivo.objects.order_by(
'-ta__data',
'-ta__ano',
'-ta__numero',
'ta',
'ordem').filter(q).select_related('ta')
if 'data_type_selection' in self.request.GET and\
self.request.GET['data_type_selection'] == 'checkbox':
result = result.exclude(
tipo_dispositivo__dispositivo_de_alteracao=True)
else:
if 'data_function' in self.request.GET and\
self.request.GET['data_function'] == 'alterador':
result = result.exclude(
tipo_dispositivo__dispositivo_de_alteracao=False,
)
result = result.exclude(
tipo_dispositivo__dispositivo_de_articulacao=False,
)
print(str(result.query))
def resultados(r):
if n:
return r[:n]
else:
return r
"""if num_ta and ano_ta and not rotulo and not str_texto and\
'data_type_selection' in self.request.GET and\
self.request.GET['data_type_selection'] == 'checkbox':
return r
else:
return r[:n]"""
if 'tipo_model' not in self.request.GET:
return resultados(result)
tipo_model = self.request.GET['tipo_model']
if not tipo_model:
return resultados(result)
integrations_view_names = get_integrations_view_names()
tipo_ta = TipoTextoArticulado.objects.get(pk=tipo_ta)
model_class = None
if tipo_ta and tipo_model:
integrations_view_names = get_integrations_view_names()
for item in integrations_view_names:
if hasattr(item, 'model_type_foreignkey') and\
hasattr(item, 'model'):
@ -3128,53 +3088,135 @@ class DispositivoSearchFragmentFormView(ListView):
pk=tipo_model)
break
if not model_class:
return resultados(result)
column_field = ''
if model_class:
for field in model_class._meta.fields:
if field.related_model == model_type_class:
column_field = field.column
break
if not column_field:
return resultados(result)
dts = self.request.GET.get('data_type_selection', '')
df = self.request.GET.get('data_function', '')
AND_CONTROLS = ''
if dts == 'checkbox':
AND_CONTROLS = 'AND td.dispositivo_de_alteracao = false'
else:
if df == 'alterador':
AND_CONTROLS = '''AND td.dispositivo_de_alteracao = true
AND td.dispositivo_de_articulacao = true'''
texto = list(map("d.texto ~* '{}'".format, texto))
AND_TEXTO_ROTULO = ''
if str_texto and rotulo:
AND_TEXTO_ROTULO = '''AND ( ({BUSCA_TEXTO} AND d.rotulo ~* '{BUSCA_ROTULO}') OR
({BUSCA_TEXTO} AND d.rotulo = '' AND dp.rotulo ~* '{BUSCA_ROTULO}')
)'''.format(
BUSCA_TEXTO=' AND '.join(texto),
BUSCA_ROTULO=rotulo
)
elif str_texto:
AND_TEXTO_ROTULO = ' AND %s' % ' AND '.join(texto)
elif rotulo:
AND_TEXTO_ROTULO = "AND d.rotulo ~* '{BUSCA_ROTULO}'".format(
BUSCA_ROTULO=rotulo)
else:
AND_TEXTO_ROTULO = ''
jtms = '' # JOIN_TYPE_MODEL_SELECTED
atms = '' # AND_TYPE_MODEL_SELECTED
if tipo_model:
jtms = 'JOIN {gfk_table} gfkt on (gfkt.id = ta.object_id)'.format(
gfk_table=model_class._meta.db_table)
atms = 'AND gfkt.{gfk_field_type} = {gfk_field_type_id}'.format(
gfk_field_type=column_field,
gfk_field_type_id=tipo_model.id,
)
sql = '''
SELECT d.* FROM compilacao_dispositivo d
JOIN compilacao_dispositivo dp on (d.dispositivo_pai_id = dp.id)
JOIN compilacao_tipodispositivo td on (d.tipo_dispositivo_id = td.id)
JOIN compilacao_textoarticulado ta on (d.ta_id = ta.id)
r = []
{JOIN_TYPE_MODEL_SELECTED}
"""
ao integrar um model ao app de compilação, se este model possuir
where d.nivel > 0
texto_articulado = GenericRelation(
TextoArticulado, related_query_name='texto_articulado')
{AND_TYPE_MODEL_SELECTED}
será uma integração mais eficiente para as buscas de Dispositivos
"""
if hasattr(model_class, 'texto_articulado'):
q = q & Q(**{
'ta__texto_articulado__' + column_field: tipo_model.pk
})
if n:
result = result.filter(q)[:n]
else:
result = result.filter(q)
{AND_TEXTO_ROTULO}
{AND1_NUMERO}
{AND2_ANO}
{AND3_TIPO_TA}
{AND_CONTROLS}
for d in result:
if not d.ta.content_object or\
not hasattr(d.ta.content_object, column_field):
continue
order by ta.data desc,
ta.numero desc,
ta.id desc,
d.ordem
{limit};
'''.format(
limit='limit {}'.format(limit) if limit else '',
JOIN_TYPE_MODEL_SELECTED=jtms,
AND_TYPE_MODEL_SELECTED=atms,
AND3_TIPO_TA="AND ta.tipo_ta_id = {}".format(
tipo_ta.id) if tipo_ta else '',
if tipo_model.pk == getattr(d.ta.content_object, column_field):
AND2_ANO="AND ta.ano = {}".format(
ano_ta) if ano_ta else '',
AND1_NUMERO="AND ta.numero ~* '{}'".format(
num_ta) if num_ta else '',
AND_TEXTO_ROTULO=AND_TEXTO_ROTULO if AND_TEXTO_ROTULO else '',
AND_CONTROLS=AND_CONTROLS if AND_CONTROLS else ''
)
result = Dispositivo.objects.raw(sql)
r = []
ids = set()
def proc_dispositivos(ds):
for d in ds:
if d.id not in ids:
r.append(d)
ids.add(d.id)
if tipo_resultado == 'I':
if ds != result:
d.I = True
proc_dispositivos(d.dispositivos_filhos_set.filter(
tipo_dispositivo__dispositivo_de_alteracao=False
))
elif tipo_resultado == 'S' and ds == result:
seq = Dispositivo.objects.filter(
ta=d.ta,
ordem__gt=d.ordem,
nivel__gt=0,
tipo_dispositivo__dispositivo_de_alteracao=False
)
proc_dispositivos(seq[:limit])
elif tipo_resultado == 'S':
d.S = True
proc_dispositivos(result)
if (len(r) == n and (not num_ta or
not ano_ta or rotulo or str_texto)):
break
return r
except Exception as e:
username = self.request.user.username
self.logger.error("user=" + username + ". " + str(e))
return []
pass
class DispositivoSearchModalView(FormView):

5
sapl/crispy_layout_mixin.py

@ -257,6 +257,11 @@ class CrispyLayoutFormMixin:
if func:
verbose_name, text = getattr(self, func)(obj, fieldname)
else:
hook_fieldname = 'hook_%s' % fieldname
if hasattr(self, hook_fieldname):
verbose_name, text = getattr(
self, hook_fieldname)(obj)
else:
verbose_name, text = get_field_display(obj, fieldname)

51
sapl/crud/base.py

@ -16,7 +16,6 @@ from django.http.response import Http404
from django.shortcuts import redirect
from django.utils.decorators import classonlymethod
from django.utils.encoding import force_text
from django.utils.functional import cached_property
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
from django.views.generic import (CreateView, DeleteView, DetailView, ListView,
@ -24,16 +23,15 @@ from django.views.generic import (CreateView, DeleteView, DetailView, ListView,
from django.views.generic.base import ContextMixin
from django.views.generic.list import MultipleObjectMixin
from sapl.base.signals import post_delete_signal, post_save_signal
from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.rules.map_rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL,
RP_LIST)
from sapl.utils import normalize
logger = logging.getLogger(settings.BASE_DIR.name)
ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \
'list', 'create', 'detail', 'update', 'delete'
@ -82,7 +80,6 @@ def make_pagination(index, num_pages):
head = from_to(1, PAGINATION_LENGTH - len(tail) - 1)
return head + [None] + tail
"""
variáveis do crud:
help_topic
@ -392,6 +389,7 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
@classmethod
def get_url_regex(cls):
return r'^$'
paginate_by = 10
no_entries_msg = _('Nenhum registro encontrado.')
@ -423,7 +421,13 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
if hasattr(f, 'related_model') and f.related_model:
m = f.related_model
if f:
hook = 'hook_header_{}'.format(''.join(fn))
if hasattr(self, hook):
header = getattr(self, hook)()
s.append(header)
else:
s.append(force_text(f.verbose_name))
s = ' / '.join(s)
r.append(s)
return r
@ -618,8 +622,37 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
return queryset
class AuditLogMixin(object):
def delete(self, request, *args, **kwargs):
# Classe deve implementar um get_object(), i.e., deve ser uma View
deleted_object = self.get_object()
try:
return super(AuditLogMixin, self).delete(request, args, kwargs)
finally:
post_delete_signal.send(sender=None,
instance=deleted_object,
operation='D',
request=self.request)
# SAVE/UPDATE method
def form_valid(self, form):
try:
if not form.instance.pk:
operation = 'C'
else:
operation = 'U'
return super(AuditLogMixin, self).form_valid(form)
finally:
post_save_signal.send(sender=None,
instance=form.instance,
operation=operation,
request=self.request
)
class CrudCreateView(PermissionRequiredContainerCrudMixin,
FormMessagesMixin, CreateView):
FormMessagesMixin, AuditLogMixin, CreateView):
permission_required = (RP_ADD,)
logger = logging.getLogger(__name__)
@ -837,7 +870,7 @@ class CrudDetailView(PermissionRequiredContainerCrudMixin,
class CrudUpdateView(PermissionRequiredContainerCrudMixin,
FormMessagesMixin, UpdateView):
FormMessagesMixin, AuditLogMixin, UpdateView):
permission_required = (RP_CHANGE,)
logger = logging.getLogger(__name__)
@ -868,7 +901,7 @@ class CrudUpdateView(PermissionRequiredContainerCrudMixin,
class CrudDeleteView(PermissionRequiredContainerCrudMixin,
FormMessagesMixin, DeleteView):
FormMessagesMixin, AuditLogMixin, DeleteView):
permission_required = (RP_DELETE,)
logger = logging.getLogger(__name__)
@ -929,10 +962,12 @@ class Crud:
def _add_base(view):
if view:
class CrudViewWithBase(cls.BaseMixin, view):
model = cls.model
help_topic = cls.help_topic
crud = cls
CrudViewWithBase.__name__ = view.__name__
return CrudViewWithBase
@ -966,11 +1001,13 @@ class Crud:
def build(cls, _model, _help_topic, _model_set=None, list_field_names=[]):
def create_class(_list_field_names):
class ModelCrud(cls):
model = _model
model_set = _model_set
help_topic = _help_topic
list_field_names = _list_field_names
return ModelCrud
ModelCrud = create_class(list_field_names)

30
sapl/materia/forms.py

@ -4,8 +4,8 @@ import os
import sapl
from crispy_forms.bootstrap import Alert, InlineRadios
from crispy_forms.layout import (HTML, Button, Field, Fieldset,
Layout, Row)
from crispy_forms.layout import (Button, Field, Fieldset, HTML, Layout, Row)
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError
@ -24,11 +24,12 @@ from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from sapl.base.models import AppConfig, Autor, TipoAutor
from sapl.base.signals import post_save_signal
from sapl.comissoes.models import Comissao, Composicao, Participacao
from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC,
STATUS_TA_PRIVATE)
from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions,
to_column, to_row, SaplFormHelper)
from sapl.crispy_layout_mixin import (form_actions, SaplFormHelper,
SaplFormLayout, to_column, to_row)
from sapl.materia.models import (AssuntoMateria, Autoria, MateriaAssunto,
MateriaLegislativa, Orgao,
RegimeTramitacao, StatusTramitacao,
@ -46,7 +47,7 @@ from sapl.utils import (ChoiceWithoutValidationField, choice_anos_com_materias,
autor_label, autor_modal, gerar_hash_arquivo, proposicao_modal,
models_with_gr_for_model, qs_override_django_filter,
RangeWidgetOverride, SEPARADOR_HASH_PROPOSICAO,
YES_NO_CHOICES)
validar_arquivo, YES_NO_CHOICES)
from .models import (AcompanhamentoMateria, Anexada, Autoria,
DespachoInicial, DocumentoAcessorio, Numeracao,
@ -116,7 +117,7 @@ class ReceberProposicaoForm(Form):
row1 = to_row([('cod_hash', 12)])
row2 = to_row(
[(Button('pesquisar-cod-proposicao',
'Pesquisar Código de Proposicoes',
'Pesquisar Código de Proposições por Data',
css_class='btn btn-primary btn-sm'), 4)
])
self.helper = SaplFormHelper()
@ -258,9 +259,8 @@ class MateriaLegislativaForm(FileFieldCheckMixin, ModelForm):
texto_original = self.cleaned_data.get('texto_original', False)
if texto_original and texto_original.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Texto Original deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb"
.format((MAX_DOC_UPLOAD_SIZE / 1024) / 1024, (texto_original.size / 1024) / 1024))
if texto_original:
validar_arquivo(texto_original, "Texto Original")
return cleaned_data
@ -361,9 +361,8 @@ class DocumentoAcessorioForm(FileFieldCheckMixin, ModelForm):
arquivo = self.cleaned_data.get('arquivo', False)
if arquivo and arquivo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Texto Integral deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb"
.format((MAX_DOC_UPLOAD_SIZE / 1024) / 1024, (arquivo.size / 1024) / 1024))
if arquivo:
validar_arquivo(arquivo, "Texto Integral")
return self.cleaned_data
@ -1938,9 +1937,8 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
def clean_texto_original(self):
texto_original = self.cleaned_data.get('texto_original', False)
if texto_original and texto_original.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Texto Original deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb"
.format((MAX_DOC_UPLOAD_SIZE / 1024) / 1024, (texto_original.size / 1024) / 1024))
if texto_original:
validar_arquivo(texto_original, "Texto Original")
return texto_original
@ -2469,7 +2467,7 @@ class ConfirmarProposicaoForm(ProposicaoForm):
# dados básicos
doc = DocumentoAcessorio()
doc.materia = proposicao.materia_de_vinculo
doc.autor = str(proposicao.autor)
doc.autor = str(proposicao.autor)[:200]
doc.tipo = proposicao.tipo.tipo_conteudo_related
doc.ementa = proposicao.descricao

2
sapl/materia/migrations/0059_auto_20191001_1450.py → sapl/materia/migrations/0058_auto_20191001_1450.py

@ -8,7 +8,7 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0058_auto_20191001_1115'),
('materia', '0057_materiaemtramitacao'),
]
operations = [

16
sapl/materia/migrations/0059_merge_20191003_0854.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-03 11:54
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0058_auto_20191001_1450'),
('materia', '0058_auto_20191001_1115'),
]
operations = [
]

4
sapl/materia/migrations/0063_auto_20191010_1228.py → sapl/materia/migrations/0060_auto_20190930_1136.py

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-10 15:28
# Generated by Django 1.11.20 on 2019-09-30 14:36
from __future__ import unicode_literals
from django.db import migrations, models
@ -8,7 +8,7 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0062_remove_tramitacao_turno'),
('materia', '0059_merge_20191003_0854'),
]
operations = [

37
sapl/materia/migrations/0061_auto_20191120_1440.py

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-11-20 17:40
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.materia.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('materia', '0060_auto_20190930_1136'),
]
operations = [
migrations.AlterField(
model_name='documentoacessorio',
name='arquivo',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.materia.models.anexo_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral'),
),
migrations.AlterField(
model_name='documentoacessorio',
name='autor',
field=models.CharField(blank=True, max_length=200, verbose_name='Autor'),
),
migrations.AlterField(
model_name='materialegislativa',
name='texto_original',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.materia.models.materia_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Original'),
),
migrations.AlterField(
model_name='proposicao',
name='texto_original',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.materia.models.materia_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Original'),
),
]

2
sapl/materia/migrations/0060_auto_20190905_1134.py → sapl/materia/migrations/0062_auto_20190905_1134.py

@ -9,7 +9,7 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('materia', '0059_auto_20191001_1450'),
('materia', '0061_auto_20191120_1440'),
]
operations = [

2
sapl/materia/migrations/0061_auto_20190905_1135.py → sapl/materia/migrations/0063_auto_20190905_1135.py

@ -37,7 +37,7 @@ def migra_tipos_turnos_tramitacao(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [
('materia', '0060_auto_20190905_1134'),
('materia', '0062_auto_20190905_1134')
]
operations = [

2
sapl/materia/migrations/0062_remove_tramitacao_turno.py → sapl/materia/migrations/0064_remove_tramitacao_turno.py

@ -8,7 +8,7 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0061_auto_20190905_1135'),
('materia', '0063_auto_20190905_1135')
]
operations = [

8
sapl/materia/models.py

@ -257,7 +257,7 @@ class MateriaLegislativa(models.Model):
'materia_principal',
'materia_anexada'))
texto_original = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
upload_to=materia_upload_path,
@ -535,13 +535,13 @@ class DocumentoAcessorio(models.Model):
data = models.DateField(blank=True, null=True,
default=None, verbose_name=_('Data'))
autor = models.CharField(
max_length=50, blank=True, verbose_name=_('Autor'))
max_length=200, blank=True, verbose_name=_('Autor'))
ementa = models.TextField(blank=True, verbose_name=_('Ementa'))
indexacao = models.TextField(blank=True)
arquivo = models.FileField(
blank=True,
null=True,
max_length=255,
max_length=300,
upload_to=anexo_upload_path,
verbose_name=_('Texto Integral'),
storage=OverwriteStorage(),
@ -807,7 +807,7 @@ class Proposicao(models.Model):
('I', 'Incorporada')),
verbose_name=_('Status Proposição'))
texto_original = models.FileField(
max_length=200,
max_length=300,
upload_to=materia_upload_path,
blank=True,
null=True,

25
sapl/materia/views.py

@ -33,13 +33,13 @@ from django_filters.views import FilterView
from sapl.base.email_utils import do_envia_email_confirmacao
from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig
from sapl.base.signals import tramitacao_signal
from sapl.base.signals import tramitacao_signal, post_delete_signal, post_save_signal
from sapl.comissoes.models import Comissao, Participacao, Composicao
from sapl.compilacao.models import STATUS_TA_IMMUTABLE_RESTRICT, STATUS_TA_PRIVATE
from sapl.compilacao.views import IntegracaoTaView
from sapl.crispy_layout_mixin import form_actions, SaplFormHelper, SaplFormLayout
from sapl.crud.base import (Crud, CrudAux, make_pagination, MasterDetailCrud,
PermissionRequiredForAppCrudMixin, RP_DETAIL, RP_LIST)
PermissionRequiredForAppCrudMixin, RP_DETAIL, RP_LIST,)
from sapl.materia.forms import (AnexadaForm, AutoriaForm, AutoriaMultiCreateForm,
ConfirmarProposicaoForm, DevolverProposicaoForm,
DespachoInicialCreateForm, LegislacaoCitadaForm,
@ -324,7 +324,7 @@ def recuperar_proposicao(request):
data = request.GET.get('data')
proposicoes = Proposicao.objects.filter(
data_envio__date=datetime.strptime(data, '%d/%m/%Y').date(),
data_envio__date=datetime.strptime(data, '%Y-%m-%d').date(),
data_envio__isnull=False,
data_recebimento__isnull=True,
data_devolucao__isnull=True
@ -807,7 +807,7 @@ class ProposicaoCrud(Crud):
p = Proposicao.objects.get(id=kwargs['pk'])
msg_error = ''
if p and p.autor.user == user:
if p and p.autor == user.autoruser.autor:
if action == 'send':
if p.data_envio and p.data_recebimento:
msg_error = _('Proposição já foi enviada e recebida.')
@ -1408,7 +1408,7 @@ class TramitacaoCrud(MasterDetailCrud):
messages.add_message(request, messages.ERROR, msg)
return HttpResponseRedirect(url)
else:
tramitacoes_deletar = [tramitacao.id]
tramitacoes_deletar = [tramitacao]
if materia.tramitacao_set.count() == 0:
materia.em_tramitacao = False
materia.save()
@ -1419,11 +1419,18 @@ class TramitacaoCrud(MasterDetailCrud):
for ma in mat_anexadas:
tram_anexada = ma.tramitacao_set.last()
if compara_tramitacoes_mat(tram_anexada, tramitacao):
tramitacoes_deletar.append(tram_anexada.id)
tramitacoes_deletar.append(tram_anexada)
if ma.tramitacao_set.count() == 0:
ma.em_tramitacao = False
ma.save()
Tramitacao.objects.filter(id__in=tramitacoes_deletar).delete()
Tramitacao.objects.filter(id__in=[t.id for t in tramitacoes_deletar]).delete()
# TODO: otimizar para passar a lista de matérias
for tramitacao in tramitacoes_deletar:
post_delete_signal.send(sender=None,
instance=tramitacao,
operation='C',
request=self.request)
return HttpResponseRedirect(url)
@ -2678,8 +2685,8 @@ class MateriaPesquisaSimplesView(PermissionRequiredMixin, FormView):
kwargs.update({'tipo': form.cleaned_data['tipo_materia']})
if form.cleaned_data.get('data_inicial'):
kwargs.update({'data__gte': form.cleaned_data['data_inicial'],
'data__lte': form.cleaned_data['data_final']})
kwargs.update({'data_apresentacao__gte': form.cleaned_data['data_inicial'],
'data_apresentacao__lte': form.cleaned_data['data_final']})
materias = MateriaLegislativa.objects.filter(
**kwargs).order_by('-numero', 'ano')

43
sapl/norma/forms.py

@ -1,27 +1,28 @@
import django_filters
import logging
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import Fieldset, Layout
from django import forms
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import models
from django.db.models import Q
from django.forms import ModelForm, widgets, ModelChoiceField
from django.forms import ModelChoiceField, ModelForm, widgets
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.base.models import Autor, TipoAutor
from sapl.crispy_layout_mixin import form_actions, to_row
from sapl.crispy_layout_mixin import form_actions, SaplFormHelper, to_row
from sapl.materia.forms import choice_anos_com_materias
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import NormaPesquisaOrderingFilter, RangeWidgetOverride, \
choice_anos_com_normas, FilterOverridesMetaMixin, FileFieldCheckMixin, ANO_CHOICES
from sapl.materia.models import (MateriaLegislativa,
TipoMateriaLegislativa)
from sapl.utils import (ANO_CHOICES, choice_anos_com_normas,
FileFieldCheckMixin, FilterOverridesMetaMixin,
NormaPesquisaOrderingFilter, RangeWidgetOverride,
validar_arquivo)
from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica, AutoriaNorma)
from .models import (AnexoNormaJuridica, AssuntoNorma, AutoriaNorma,
NormaJuridica, NormaRelacionada, TipoNormaJuridica)
def get_esferas():
@ -217,9 +218,8 @@ class NormaJuridicaForm(FileFieldCheckMixin, ModelForm):
texto_integral = self.cleaned_data.get('texto_integral', False)
if texto_integral and texto_integral.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Texto Integral deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (texto_integral.size/1024)/1024))
if texto_integral:
validar_arquivo(texto_integral, "Texto Integral")
return texto_integral
@ -283,6 +283,14 @@ class AutoriaNormaForm(ModelForm):
class AnexoNormaJuridicaForm(FileFieldCheckMixin, ModelForm):
logger = logging.getLogger(__name__)
anexo_arquivo = forms.FileField(
required=True,
label="Arquivo Anexo"
)
class Meta:
model = AnexoNormaJuridica
fields = ['norma', 'anexo_arquivo', 'assunto_anexo']
@ -290,8 +298,6 @@ class AnexoNormaJuridicaForm(FileFieldCheckMixin, ModelForm):
'norma': forms.HiddenInput(),
}
logger = logging.getLogger(__name__)
def clean(self):
cleaned_data = super(AnexoNormaJuridicaForm, self).clean()
@ -300,9 +306,8 @@ class AnexoNormaJuridicaForm(FileFieldCheckMixin, ModelForm):
anexo_arquivo = self.cleaned_data.get('anexo_arquivo', False)
if anexo_arquivo and anexo_arquivo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O Arquivo Anexo deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (anexo_arquivo.size/1024)/1024))
if anexo_arquivo:
validar_arquivo(anexo_arquivo, "Arquivo Anexo")
return cleaned_data

22
sapl/norma/migrations/0028_auto_20191024_1330.py

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-24 16:30
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.norma.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('norma', '0027_auto_20191001_1115'),
]
operations = [
migrations.AlterField(
model_name='normajuridica',
name='texto_integral',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.norma.models.norma_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral'),
),
]

22
sapl/norma/migrations/0029_auto_20191024_1344.py

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-24 16:44
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.norma.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('norma', '0028_auto_20191024_1330'),
]
operations = [
migrations.AlterField(
model_name='anexonormajuridica',
name='anexo_arquivo',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.norma.models.norma_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Arquivo Anexo'),
),
]

16
sapl/norma/migrations/0030_merge_20191204_1129.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-12-04 14:29
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('norma', '0029_auto_20191024_1344'),
('norma', '0028_merge_20191003_0909'),
]
operations = [
]

18
sapl/norma/models.py

@ -79,7 +79,7 @@ class NormaJuridica(models.Model):
)
texto_integral = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
upload_to=norma_upload_path,
@ -184,10 +184,14 @@ class NormaJuridica(models.Model):
return anexos
def __str__(self):
return _('%(tipo)s%(numero)s de %(data)s') % {
numero_norma = self.numero
if numero_norma.isnumeric():
numero_norma = '{0:,}'.format(int(self.numero)).replace(',', '.')
return _('%(tipo)s%(numero)s, de %(data)s') % {
'tipo': self.tipo,
'numero': self.numero,
'data': defaultfilters.date(self.data, "d \d\e F \d\e Y")}
'numero': numero_norma,
'data': defaultfilters.date(self.data, "d \d\e F \d\e Y").lower()}
@property
def epigrafe(self):
@ -346,8 +350,8 @@ class NormaRelacionada(models.Model):
def __str__(self):
return _('Principal: %(norma_principal)s'
' - Relacionada: %(norma_relacionada)s') % {
'norma_principal': self.norma_principal,
'norma_relacionada': self.norma_relacionada}
'norma_principal': str(self.norma_principal),
'norma_relacionada': str(self.norma_relacionada)}
@reversion.register()
@ -364,7 +368,7 @@ class AnexoNormaJuridica(models.Model):
max_length=250
)
anexo_arquivo = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
upload_to=norma_upload_path,

14
sapl/norma/views.py

@ -78,7 +78,7 @@ class NormaDestaquesView(ListView):
class NormaPesquisaView(FilterView):
model = NormaJuridica
filterset_class = NormaFilterSet
paginate_by = 10
paginate_by = 50
def get_queryset(self):
qs = super().get_queryset()
@ -274,7 +274,8 @@ class NormaCrud(Crud):
pk=self.kwargs['pk']
)
# Feito desta forma para que sejam materializados os assuntos antigos
# Feito desta forma para que sejam materializados os assuntos
# antigos
assuntos_antigos = set(norma_antiga.assuntos.all())
dict_objeto_antigo = norma_antiga.__dict__
@ -295,7 +296,8 @@ class NormaCrud(Crud):
self.object.save()
break
# Campo Assuntos não veio no __dict__, então é comparado separadamente
# Campo Assuntos não veio no __dict__, então é comparado
# separadamente
assuntos_novos = set(self.object.assuntos.all())
if assuntos_antigos != assuntos_novos:
self.object.user = self.request.user
@ -391,7 +393,8 @@ class ImpressosView(PermissionRequiredMixin, TemplateView):
def gerar_pdf_impressos(request, context, template_name):
template = loader.get_template(template_name)
html = template.render(context, request)
pdf = weasyprint.HTML(string=html, base_url=request.build_absolute_uri()).write_pdf()
pdf = weasyprint.HTML(
string=html, base_url=request.build_absolute_uri()).write_pdf()
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = 'inline; filename="relatorio_impressos.pdf"'
@ -418,7 +421,8 @@ class NormaPesquisaSimplesView(PermissionRequiredMixin, FormView):
kwargs.update({'data__gte': form.cleaned_data['data_inicial'],
'data__lte': form.cleaned_data['data_final']})
normas = NormaJuridica.objects.filter(**kwargs).order_by('-numero', 'ano')
normas = NormaJuridica.objects.filter(
**kwargs).order_by('-numero', 'ano')
quantidade_normas = normas.count()
normas = normas[:2000] if quantidade_normas > 2000 else normas

9
sapl/painel/urls.py

@ -4,7 +4,8 @@ from .apps import AppConfig
from .views import (cronometro_painel, get_dados_painel, painel_mensagem_view,
painel_parlamentar_view, painel_view, painel_votacao_view,
switch_painel, verifica_painel, votante_view, CronometroPainelCrud,
PainelConfigCrud,ordena_cronometro, painel_parcial_view)
PainelConfigCrud, ordena_cronometro, painel_parcial_view, painel_discurso_view,
get_dados_painel_discurso)
app_name = AppConfig.name
@ -12,6 +13,7 @@ urlpatterns = [
url(r'^painel-principal/(?P<pk>\d+)$', painel_view,
name="painel_principal"),
url(r'^painel/(?P<pk>\d+)/dados$', get_dados_painel, name='dados_painel'),
url(r'^painel/mensagem$', painel_mensagem_view, name="painel_mensagem"),
url(r'^painel/parlamentar$', painel_parlamentar_view,
name='painel_parlamentar'),
@ -31,4 +33,9 @@ urlpatterns = [
url(r'^painel-parcial/(?P<pk>\d+)/(?P<opcoes>\d+)/$', painel_parcial_view,
name="painel_parcial"),
url(r'^painel-discurso/(?P<sessao_pk>\d+)/(?P<lista_pk>\d+)/$', painel_discurso_view,
name="painel_discurso"),
url(r'^painel/(?P<pk>\d+)/(?P<lista_pk>\d+)/dados-discurso$', get_dados_painel_discurso,
name='dados_painel_discurso'),
]

93
sapl/painel/views.py

@ -1,6 +1,7 @@
import html
import json
import logging
import os
from django.contrib import messages
from django.contrib.auth.decorators import user_passes_test
@ -21,7 +22,8 @@ from sapl.parlamentares.models import Legislatura, Parlamentar, Votante
from sapl.sessao.models import (ExpedienteMateria, OradorExpediente, OrdemDia,
PresencaOrdemDia, RegistroVotacao,
SessaoPlenaria, SessaoPlenariaPresenca,
VotoParlamentar, RegistroLeitura)
VotoParlamentar, CronometroLista, ListaDiscurso,
ParlamentarLista, RegistroLeitura)
from sapl.utils import filiacao_data, get_client_ip, sort_lista_chave
from .forms import CronometroForm, ConfiguracoesPainelForm
@ -353,12 +355,15 @@ def painel_view(request, pk):
'resultado': True,
'materia': True
}
now = timezone.localtime(timezone.now())
utc_offset = now.utcoffset().total_seconds() / 60
context = {'head_title': str(_('Painel Plenário')),
'sessao_id': pk,
'cronometros': Cronometro.objects.filter(ativo=True).order_by('ordenacao'),
'painel_config': PainelConfig.objects.first(),
'casa': CasaLegislativa.objects.last(),
'exibicao': exibicao
'exibicao': exibicao,
'utc_offset': utc_offset,
}
return render(request, 'painel/index.html', context)
@ -375,12 +380,15 @@ def painel_parcial_view(request, pk, opcoes):
'resultado': bit_is_set(opcoes, 3),
'materia': bit_is_set(opcoes, 4)
}
now = timezone.localtime(timezone.now())
utc_offset = now.utcoffset().total_seconds() / 60
context = {'head_title': str(_('Painel Plenário')),
'sessao_id': pk,
'cronometros': Cronometro.objects.filter(ativo=True).order_by('ordenacao'),
'painel_config': PainelConfig.objects.first(),
'casa': CasaLegislativa.objects.last(),
'exibicao': exibicao
'exibicao': exibicao,
'utc_offset': utc_offset,
}
return render(request, 'painel/index.html', context)
@ -727,16 +735,21 @@ def get_dados_painel(request, pk):
expediente__sessao_plenaria=sessao).order_by('data_hora').last()
# Obtém última matéria que foi votada, através do timestamp mais recente
ordem_expediente = None
ultimo_timestamp = None
if last_ordem_voto:
ordem_expediente = last_ordem_voto.ordem
ultimo_timestamp = last_ordem_voto.data_hora
if last_expediente_voto and last_expediente_voto.data_hora > ultimo_timestamp:
if (last_expediente_voto and ultimo_timestamp and last_expediente_voto.data_hora > ultimo_timestamp) or \
(not ultimo_timestamp and last_expediente_voto):
ordem_expediente = last_expediente_voto.expediente
ultimo_timestamp = last_expediente_voto.data_hora
if last_ordem_leitura and last_ordem_leitura.data_hora > ultimo_timestamp:
if (last_ordem_leitura and ultimo_timestamp and last_ordem_leitura.data_hora > ultimo_timestamp) or \
(not ultimo_timestamp and last_ordem_leitura):
ordem_expediente = last_ordem_leitura.ordem
ultimo_timestamp = last_ordem_leitura.data_hora
if last_expediente_leitura and last_expediente_leitura.data_hora > ultimo_timestamp:
if (last_expediente_leitura and ultimo_timestamp and last_expediente_leitura.data_hora > ultimo_timestamp) or \
(not ultimo_timestamp and last_expediente_leitura):
ordem_expediente = last_expediente_leitura.expediente
ultimo_timestamp = last_expediente_leitura.data_hora
@ -748,3 +761,71 @@ def get_dados_painel(request, pk):
# Retorna que não há nenhuma matéria já votada ou aberta
return response_nenhuma_materia(get_presentes(pk, response, None))
@user_passes_test(check_permission)
def painel_discurso_view(request, sessao_pk, lista_pk):
cronometros_ids = CronometroLista.objects.filter(tipo_lista_id=lista_pk).values_list('cronometro', flat=True)
cronometros = Cronometro.objects.filter(id__in=cronometros_ids)
lista = ListaDiscurso.objects.get(tipo_id=lista_pk, sessao_plenaria_id=sessao_pk)
context = {
'head_title': str(_('Painel de Discurso')),
'sessao_id': sessao_pk,
'lista': lista,
'cronometros': cronometros,
'casa': CasaLegislativa.objects.last(),
'painel_config': PainelConfig.objects.first(),
}
return render(request, 'painel/painel_discurso.html', context)
@user_passes_test(check_permission)
def get_dados_painel_discurso(request, pk, lista_pk):
sessao = SessaoPlenaria.objects.get(id=pk)
casa = CasaLegislativa.objects.first()
app_config = ConfiguracoesAplicacao.objects.first()
brasao = None
if casa and app_config and (bool(casa.logotipo)):
brasao = casa.logotipo.url \
if app_config.mostrar_brasao_painel else None
CRONOMETRO_STATUS = {
'I': 'start',
'R': 'reset',
'S': 'stop',
'C': 'increment'
}
cronometros = Cronometro.objects.filter(cronometrolista__tipo_lista_id=lista_pk)
dict_status_cronometros = dict(cronometros.order_by('ordenacao').values_list('id', 'status'))
for key, value in dict_status_cronometros.items():
dict_status_cronometros[key] = CRONOMETRO_STATUS[dict_status_cronometros[key]]
dict_duracao_cronometros = dict(cronometros.values_list('id', 'duracao_cronometro'))
for key, value in dict_duracao_cronometros.items():
dict_duracao_cronometros[key] = value.seconds
lista = ListaDiscurso.objects.get(tipo_id=lista_pk, sessao_plenaria_id=pk)
orador = lista.orador_atual
oradores = ParlamentarLista.objects.filter(lista=lista).order_by('ordenacao').values_list('parlamentar__nome_parlamentar', flat=True)
response = {
'sessao_plenaria': str(sessao),
'sessao_plenaria_data': sessao.data_inicio.strftime('%d/%m/%Y'),
'sessao_plenaria_hora_inicio': sessao.hora_inicio,
'cronometros': dict_status_cronometros,
'duracao_cronometros': dict_duracao_cronometros,
'sessao_finalizada': sessao.finalizada,
'brasao': brasao,
'orador': orador.nome_parlamentar if orador else '',
'orador_img': orador.fotografia.url if orador and os.path.isfile(orador.fotografia.path) else None,
'oradores': list(oradores)
}
return JsonResponse(response)

10
sapl/parlamentares/forms.py

@ -79,6 +79,8 @@ def validar_datas_legislatura(eleicao, inicio, fim, pk=None):
class MandatoForm(ModelForm):
logger = logging.getLogger(__name__)
data_fim_mandato = forms.DateField(label=_('Fim do Mandato'))
class Meta:
model = Mandato
fields = ['legislatura', 'coligacao', 'votos_recebidos',
@ -145,8 +147,12 @@ class MandatoForm(ModelForm):
existe_mandato = Mandato.objects.filter(
parlamentar=data['parlamentar'],
legislatura=data['legislatura']).exists()
if existe_mandato and data['titular']:
legislatura=data['legislatura'])
if self.instance.pk:
existe_mandato = existe_mandato.exclude(id=self.instance.pk)
if existe_mandato.exists() and data['titular']:
self.logger.error("Mandato nesta legislatura (parlamentar={}, legislatura={}) já existe."
.format(data['parlamentar'], data['legislatura']))
raise ValidationError(_('Mandato nesta legislatura já existe.'))

12
sapl/parlamentares/tests/test_parlamentares.py

@ -121,13 +121,17 @@ def test_mandato_submit(admin_client):
admin_client.post(reverse('sapl.parlamentares:mandato_create',
kwargs={'pk': 14}),
{'parlamentar': 14, # hidden field
{
'parlamentar': 14, # hidden field
'legislatura': 5,
'data_inicio_mandato': \
Legislatura.objects.get(id=5).data_inicio,
'data_fim_mandato': \
Legislatura.objects.get(id=5).data_fim,
'data_expedicao_diploma': '2016-03-22',
'observacao': 'Observação do mandato',
'salvar': 'salvar'},
'salvar': 'salvar'
},
follow=True)
mandato = Mandato.objects.first()
@ -166,13 +170,15 @@ def test_mandato_form_duplicado():
Mandato.objects.create(parlamentar=parlamentar,
legislatura=legislatura,
data_expedicao_diploma='2017-07-25',
data_inicio_mandato=legislatura.data_inicio,)
data_inicio_mandato=legislatura.data_inicio,
data_fim_mandato=legislatura.data_fim)
form = MandatoForm(data={
'parlamentar': str(parlamentar.pk),
'legislatura': str(legislatura.pk),
'data_expedicao_diploma': '01/07/2015',
'data_inicio_mandato': legislatura.data_inicio,
'data_fim_mandato': legislatura.data_fim,
'titular':True,
})

39
sapl/parlamentares/views.py

@ -447,6 +447,9 @@ class MandatoCrud(MasterDetailCrud):
return {'parlamentar': Parlamentar.objects.get(
pk=self.kwargs['pk'])}
class UpdateView(MasterDetailCrud.UpdateView):
form_class = MandatoForm
class ComposicaoColigacaoCrud(MasterDetailCrud):
model = ComposicaoColigacao
@ -534,8 +537,7 @@ class ParlamentarCrud(Crud):
list_field_names = [
'nome_parlamentar',
'filiacao_atual',
'ativo',
'mandato_titular']
'ativo']
class DetailView(Crud.DetailView):
@ -611,8 +613,7 @@ class ParlamentarCrud(Crud):
username = self.request.user.username
if legislatura_id >= 0:
return queryset.filter(
mandato__legislatura_id=legislatura_id).annotate(
mandato_titular=F('mandato__titular')).distinct()
mandato__legislatura_id=legislatura_id).distinct()
else:
try:
self.logger.debug(
@ -628,8 +629,7 @@ class ParlamentarCrud(Crud):
". Objeto encontrado com sucesso.")
if l is None:
return Legislatura.objects.all()
return queryset.filter(mandato__legislatura_id=l).annotate(
mandato_titular=F('mandato__titular'))
return queryset.filter(mandato__legislatura_id=l)
def get_headers(self):
return [_('Parlamentar'), _('Partido'),
@ -644,19 +644,38 @@ class ParlamentarCrud(Crud):
context['legislaturas'] = legislaturas
context['legislatura_id'] = self.take_legislatura_id()
# Pega a Legislatura
legislatura = Legislatura.objects.get(
id=context['legislatura_id'])
for row in context['rows']:
# Pega o Parlamentar por meio da pk
parlamentar = Parlamentar.objects.get(
id=(row[0][1].split('/')[-1]))
# Conserta a issue do github https://github.com/interlegis/sapl/issues/3028
# Inicialmente a titularidade era conseguida através do código
# queryset.filter(mandato__legislatura_id=legislatura_id).annotate(
# mandato_titular=F('mandato__titular')).distinct()
# em get_queryset(), MAS não funciona se o parlamentar tem vários
# mandatos na mesma legislatura, sendo ao menos um titular e outro não,
# pois isso gera entradas repetidas. Este código corrige essa situação.
mandato = Mandato.objects.filter(
parlamentar=parlamentar,
data_inicio_mandato__gte=legislatura.data_inicio,
data_fim_mandato__lte=legislatura.data_fim
).order_by('-data_inicio_mandato').first()
if mandato:
titular = 'Sim' if mandato.titular else 'Não'
row.append((titular, None))
else:
row.append(('-', None))
for index, value in enumerate(row):
row[index] += (None if index else parlamentar,)
# Pega a Legislatura
legislatura = Legislatura.objects.get(
id=context['legislatura_id'])
# Coloca a filiação atual ao invés da última
# As condições para mostrar a filiação são:
# A data de filiacao deve ser menor que a data de fim

47
sapl/protocoloadm/forms.py

@ -1,11 +1,11 @@
import django_filters
import logging
from crispy_forms.bootstrap import InlineRadios, Alert, FormActions
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout, Div, Submit
from crispy_forms.layout import (Button, Column, Div, Fieldset, HTML,
Layout, Submit)
from django import forms
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from django.core.exceptions import (MultipleObjectsReturned,
ObjectDoesNotExist, ValidationError)
from django.db import models, transaction
@ -13,24 +13,28 @@ from django.db.models import Max
from django.forms import ModelForm
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.base.models import Autor, TipoAutor, AppConfig
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row
from sapl.materia.models import (MateriaLegislativa, TipoMateriaLegislativa,
from sapl.base.signals import post_save_signal
from sapl.crispy_layout_mixin import (form_actions, SaplFormHelper,
SaplFormLayout, to_row)
from sapl.materia.models import (MateriaLegislativa,
TipoMateriaLegislativa,
UnidadeTramitacao)
from sapl.protocoloadm.models import Protocolo
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter,
RangeWidgetOverride, autor_label, autor_modal,
choice_anos_com_protocolo, choice_force_optional,
from sapl.utils import (AnoNumeroOrderingFilter, autor_label, autor_modal,
choice_anos_com_documentoadministrativo,
FilterOverridesMetaMixin, choice_anos_com_materias,
FileFieldCheckMixin, lista_anexados)
choice_anos_com_materias,
choice_anos_com_protocolo, choice_force_optional,
FileFieldCheckMixin, FilterOverridesMetaMixin,
lista_anexados, RangeWidgetOverride, RANGE_ANOS,
validar_arquivo, YES_NO_CHOICES)
from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo,
Protocolo, TipoDocumentoAdministrativo,
TramitacaoAdministrativo, Anexado)
from .models import (Anexado, AcompanhamentoDocumento,
DocumentoAcessorioAdministrativo,
DocumentoAdministrativo, Protocolo,
TipoDocumentoAdministrativo,
TramitacaoAdministrativo)
TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'),
@ -664,9 +668,8 @@ class DocumentoAcessorioAdministrativoForm(FileFieldCheckMixin, ModelForm):
arquivo = self.cleaned_data.get('arquivo', False)
if arquivo and arquivo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (arquivo.size/1024)/1024))
if arquivo:
validar_arquivo(arquivo, "Arquivo")
return self.cleaned_data
@ -1157,9 +1160,8 @@ class DocumentoAdministrativoForm(FileFieldCheckMixin, ModelForm):
texto_integral = self.cleaned_data.get('texto_integral', False)
if texto_integral and texto_integral.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Texto Integral deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (texto_integral.size/1024)/1024))
if texto_integral:
validar_arquivo(texto_integral, "Texto Integral")
return self.cleaned_data
@ -1563,7 +1565,6 @@ class TramitacaoEmLoteAdmForm(ModelForm):
)
)
def clean(self):
cleaned_data = super(TramitacaoEmLoteAdmForm, self).clean()

31
sapl/protocoloadm/migrations/0026_auto_20191120_1440.py

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-11-20 17:40
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0025_auto_20191001_1115'),
]
operations = [
migrations.AlterField(
model_name='documentoacessorioadministrativo',
name='arquivo',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.utils.texto_upload_path, verbose_name='Arquivo'),
),
migrations.AlterField(
model_name='documentoacessorioadministrativo',
name='autor',
field=models.CharField(blank=True, max_length=200, verbose_name='Autor'),
),
migrations.AlterField(
model_name='documentoadministrativo',
name='texto_integral',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.utils.texto_upload_path, verbose_name='Texto Integral'),
),
]

6
sapl/protocoloadm/models.py

@ -164,7 +164,7 @@ class DocumentoAdministrativo(models.Model):
observacao = models.TextField(
blank=True, verbose_name=_('Observação'))
texto_integral = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
storage=OverwriteStorage(),
@ -232,7 +232,7 @@ class DocumentoAcessorioAdministrativo(models.Model):
verbose_name=_('Tipo'))
nome = models.CharField(max_length=30, verbose_name=_('Nome'))
arquivo = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
upload_to=texto_upload_path,
@ -240,7 +240,7 @@ class DocumentoAcessorioAdministrativo(models.Model):
verbose_name=_('Arquivo'))
data = models.DateField(blank=True, null=True, verbose_name=_('Data'))
autor = models.CharField(
max_length=50, blank=True, verbose_name=_('Autor'))
max_length=200, blank=True, verbose_name=_('Autor'))
assunto = models.TextField(
blank=True, verbose_name=_('Assunto'))
indexacao = models.TextField(blank=True)

49
sapl/protocoloadm/views.py

@ -26,7 +26,7 @@ from django_filters.views import FilterView
import sapl
from sapl.base.email_utils import do_envia_email_confirmacao
from sapl.base.models import Autor, CasaLegislativa, AppConfig
from sapl.base.signals import tramitacao_signal
from sapl.base.signals import tramitacao_signal, post_delete_signal
from sapl.comissoes.models import Comissao
from sapl.crud.base import (Crud, CrudAux, MasterDetailCrud, make_pagination,
RP_LIST, RP_DETAIL)
@ -37,7 +37,7 @@ from sapl.protocoloadm.models import Protocolo
from sapl.relatorios.views import relatorio_doc_administrativos
from sapl.utils import (create_barcode, get_base_url, get_client_ip,
get_mime_type_from_file_extension, lista_anexados,
show_results_filter_set, mail_service_configured)
show_results_filter_set, mail_service_configured, from_date_to_datetime_utc)
from .forms import (AcompanhamentoDocumentoForm, AnularProtocoloAdmForm,
DocumentoAcessorioAdministrativoForm,
@ -564,12 +564,24 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
legislatura = Legislatura.objects.filter(
data_inicio__year__lte=timezone.now().year,
data_fim__year__gte=timezone.now().year).first()
data_inicio = legislatura.data_inicio
data_fim = legislatura.data_fim
data_inicio_utc = from_date_to_datetime_utc(data_inicio)
data_fim_utc = from_date_to_datetime_utc(data_fim)
numero = Protocolo.objects.filter(
Q(data__isnull=False,
data__gte=data_inicio,
data__lte=data_fim).aggregate(
Max('numero'))
data__lte=data_fim) |
Q(timestamp__isnull=False,
timestamp__gte=data_inicio_utc,
timestamp__lte=data_fim_utc) |
Q(timestamp_data_hora_manual__isnull=False,
timestamp_data_hora_manual__gte=data_inicio_utc,
timestamp_data_hora_manual__lte=data_fim_utc,)).\
aggregate(Max('numero'))
elif numeracao == 'U':
numero = Protocolo.objects.all().aggregate(Max('numero'))
@ -760,10 +772,22 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
data_fim__year__gte=timezone.now().year).first()
data_inicio = legislatura.data_inicio
data_fim = legislatura.data_fim
data_inicio_utc = from_date_to_datetime_utc(data_inicio)
data_fim_utc = from_date_to_datetime_utc(data_fim)
numero = Protocolo.objects.filter(
Q(data__isnull=False,
data__gte=data_inicio,
data__lte=data_fim).aggregate(
Max('numero'))
data__lte=data_fim) |
Q(timestamp__isnull=False,
timestamp__gte=data_inicio_utc,
timestamp__lte=data_fim_utc) |
Q(timestamp_data_hora_manual__isnull=False,
timestamp_data_hora_manual__gte=data_inicio_utc,
timestamp_data_hora_manual__lte=data_fim_utc,)).\
aggregate(Max('numero'))
elif numeracao == 'U':
numero = Protocolo.objects.all().aggregate(Max('numero'))
@ -1268,7 +1292,7 @@ class TramitacaoAdmCrud(MasterDetailCrud):
messages.add_message(request, messages.ERROR, msg)
return HttpResponseRedirect(url)
else:
tramitacoes_deletar = [tramitacao.id]
tramitacoes_deletar = [tramitacao]
if documento.tramitacaoadministrativo_set.count() == 0:
documento.tramitacao = False
documento.save()
@ -1278,12 +1302,19 @@ class TramitacaoAdmCrud(MasterDetailCrud):
for da in docs_anexados:
tram_anexada = da.tramitacaoadministrativo_set.last()
if compara_tramitacoes_doc(tram_anexada, tramitacao):
tramitacoes_deletar.append(tram_anexada.id)
tramitacoes_deletar.append(tram_anexada)
if da.tramitacaoadministrativo_set.count() == 0:
da.tramitacao = False
da.save()
TramitacaoAdministrativo.objects.filter(
id__in=tramitacoes_deletar).delete()
id__in=[t.id for t in tramitacoes_deletar]).delete()
# TODO: otimizar para passar a lista de matérias
for tramitacao in tramitacoes_deletar:
post_delete_signal.send(sender=None,
instance=tramitacao,
operation='C',
request=self.request)
return HttpResponseRedirect(url)

5
sapl/rules/map_rules.py

@ -188,6 +188,10 @@ rules_group_sessao = {
(sessao.JustificativaAusencia, __base__, __perms_publicas__),
(sessao.RetiradaPauta, __base__, __perms_publicas__),
(sessao.RegistroLeitura, __base__, __perms_publicas__),
(sessao.ListaDiscurso, __base__, __perms_publicas__),
(sessao.TipoListaDiscurso, __base__, __perms_publicas__),
(sessao.CronometroLista, __base__, __perms_publicas__),
(sessao.ParlamentarLista, __base__, __perms_publicas__),
]
}
@ -236,6 +240,7 @@ rules_group_geral = {
(base.TipoAutor, __base__, __perms_publicas__),
(base.Autor, __base__, __perms_publicas__),
(base.AutorUser, __base__, __perms_publicas__),
(base.AuditLog, __base__, set()),
(protocoloadm.StatusTramitacaoAdministrativo, __base__, set()),
(protocoloadm.TipoDocumentoAdministrativo, __base__, set()),

128
sapl/sessao/forms.py

@ -1,35 +1,44 @@
import django_filters
from crispy_forms.layout import Button, Fieldset, HTML, Layout
from datetime import datetime
from crispy_forms.layout import HTML, Button, Fieldset, Layout
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import transaction
from django.db.models import Q
from django.forms import ModelForm
from django.forms import ModelForm, widgets
from django.forms.widgets import CheckboxSelectMultiple
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.base.models import Autor, TipoAutor
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.crispy_layout_mixin import form_actions, to_row, SaplFormLayout
from sapl.crispy_layout_mixin import (form_actions, to_row,
SaplFormHelper, SaplFormLayout)
from sapl.materia.forms import MateriaLegislativaFilterSet
from sapl.materia.models import (MateriaLegislativa, StatusTramitacao,
TipoMateriaLegislativa)
from sapl.painel.models import Cronometro
from sapl.parlamentares.models import Parlamentar, Mandato
from sapl.utils import (RANGE_DIAS_MES, RANGE_MESES,
MateriaPesquisaOrderingFilter, autor_label,
autor_modal, timezone, choice_anos_com_sessaoplenaria,
FileFieldCheckMixin, verifica_afastamento_parlamentar)
from .models import (ExpedienteMateria, JustificativaAusencia,
Orador, OradorExpediente, OrdemDia, PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca, TipoResultadoVotacao,
OcorrenciaSessao, RetiradaPauta, TipoRetiradaPauta, OradorOrdemDia, ORDENACAO_RESUMO,
ResumoOrdenacao, RegistroLeitura)
from sapl.parlamentares.models import Mandato, Parlamentar
from sapl.utils import (autor_label, autor_modal,
choice_anos_com_sessaoplenaria,
FileFieldCheckMixin,
MateriaPesquisaOrderingFilter,
RANGE_DIAS_MES, RANGE_MESES,
timezone, validar_arquivo)
from .models import (ExpedienteMateria,
JustificativaAusencia, OcorrenciaSessao, Orador,
OradorExpediente, OradorOrdemDia, OrdemDia,
ORDENACAO_RESUMO, PresencaOrdemDia,
RegistroLeitura, ResumoOrdenacao, RetiradaPauta,
SessaoPlenaria, SessaoPlenariaPresenca,
TipoResultadoVotacao, TipoRetiradaPauta,
CronometroLista)
MES_CHOICES = RANGE_MESES
DIA_CHOICES = RANGE_DIAS_MES
@ -155,18 +164,14 @@ class SessaoPlenariaForm(FileFieldCheckMixin, ModelForm):
upload_ata = self.cleaned_data.get('upload_ata', False)
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_pauta and upload_pauta.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Pauta da Sessão deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_pauta.size/1024)/1024))
if upload_pauta:
validar_arquivo(upload_pauta, "Pauta da Sessão")
if upload_ata and upload_ata.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Ata da Sessão deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_ata.size/1024)/1024))
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo da Sessão deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
if upload_ata:
validar_arquivo(upload_ata, "Ata da Sessão")
if upload_anexo:
validar_arquivo(upload_anexo, "Anexo da Sessão")
return self.cleaned_data
@ -526,7 +531,7 @@ class AdicionarVariasMateriasFilterSet(MateriaLegislativaFilterSet):
o = MateriaPesquisaOrderingFilter()
tramitacao__status = django_filters.ModelChoiceFilter(
required=True,
required=False,
queryset=StatusTramitacao.objects.all(),
label=_('Status da Matéria'))
@ -547,7 +552,11 @@ class AdicionarVariasMateriasFilterSet(MateriaLegislativaFilterSet):
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Colocar super().__init__(*args, **kwargs) quebra a tela
# de adicionar várias matérias em expediente e ordem dia.
# pois herda da classe AdicionarVariasMateriasFilterSet em
# vez de MateriaLegislativaFilterSet
super(MateriaLegislativaFilterSet, self).__init__(*args, **kwargs)
self.filters['tipo'].label = 'Tipo de Matéria'
self.filters['autoria__autor__tipo'].label = 'Tipo de Autor'
@ -632,9 +641,8 @@ class OradorForm(ModelForm):
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo do Orador deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
if upload_anexo:
validar_arquivo(upload_anexo, "Anexo do Orador")
return self.cleaned_data
@ -677,9 +685,8 @@ class OradorExpedienteForm(ModelForm):
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo do Orador deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
if upload_anexo:
validar_arquivo(upload_anexo, "Anexo do Orador")
return self.cleaned_data
@ -720,9 +727,8 @@ class OradorOrdemDiaForm(ModelForm):
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo do Orador deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
if upload_anexo:
validar_arquivo(upload_anexo, "Anexo do Orador")
return self.cleaned_data
@ -824,9 +830,8 @@ class JustificativaAusenciaForm(ModelForm):
upload_anexo = self.cleaned_data.get('upload_anexo', False)
if upload_anexo and upload_anexo.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("O arquivo Anexo de Justificativa deve ser menor que {0:.1f} mb, o tamanho atual desse arquivo é {1:.1f} mb" \
.format((MAX_DOC_UPLOAD_SIZE/1024)/1024, (upload_anexo.size/1024)/1024))
if upload_anexo:
validar_arquivo(upload_anexo, "Anexo de Justificativa")
if not sessao_plenaria.finalizada or sessao_plenaria.finalizada is None:
raise ValidationError(
@ -890,3 +895,52 @@ class OrdemExpedienteLeituraForm(forms.ModelForm):
form_actions(more=actions),
)
)
class CronometroListaForm(ModelForm):
cronometro = forms.ModelChoiceField(
queryset=Cronometro.objects.all(),
label="Cronômetro"
)
nome_lista = forms.CharField(
label='Lista de Discussão',
widget=widgets.TextInput(attrs={'readonly': 'readonly'})
)
class Meta:
model = CronometroLista
exclude = []
widgets = {
'tipo_lista': forms.HiddenInput(),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
row1 = to_row(
[('nome_lista', 6),('cronometro', 6),])
row2 = to_row(
[('tipo_lista', 6)]
)
actions = [HTML('<a href="{{ view.cancel_url }}"'
' class="btn btn-dark">Cancelar</a>')]
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Vincular Cronômetro à Lista de Discussão'),
row1, row2,
HTML("&nbsp;"),
form_actions(more=actions)
)
)
def save(self):
cd = self.cleaned_data
cronometro = cd['cronometro']
tipo_lista = cd['tipo_lista']
cronometro_lista = CronometroLista.objects.create(cronometro=cronometro, tipo_lista=tipo_lista)
return cronometro_lista

3
sapl/sessao/migrations/0048_auto_20190829_1253.py → sapl/sessao/migrations/0047_auto_20190829_1253.py

@ -8,7 +8,8 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sessao', '0047_merge_20191009_1535'),
('sessao', '0046_auto_20190827_1228'),
('sessao', '0046_auto_20191001_1115'),
]
operations = [

2
sapl/sessao/migrations/0047_merge_20191009_1535.py

@ -2,7 +2,7 @@
# Generated by Django 1.11.20 on 2019-10-09 18:35
from __future__ import unicode_literals
from django.db import migrations
from django.db import migrations, models
class Migration(migrations.Migration):

32
sapl/sessao/migrations/0048_auto_20191029_1418.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-29 17:18
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.sessao.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('sessao', '0047_auto_20190829_1253')
]
operations = [
migrations.AlterField(
model_name='sessaoplenaria',
name='upload_anexo',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.sessao.models.anexo_upload_path, verbose_name='Anexo da Sessão'),
),
migrations.AlterField(
model_name='sessaoplenaria',
name='upload_ata',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.sessao.models.ata_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Ata da Sessão'),
),
migrations.AlterField(
model_name='sessaoplenaria',
name='upload_pauta',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.sessao.models.pauta_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Pauta da Sessão'),
),
]

32
sapl/sessao/migrations/0049_auto_20191029_1434.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-29 17:34
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.sessao.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('sessao', '0048_auto_20191029_1418'),
]
operations = [
migrations.AlterField(
model_name='orador',
name='upload_anexo',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.sessao.models.anexo_upload_path, verbose_name='Anexo do Orador'),
),
migrations.AlterField(
model_name='oradorexpediente',
name='upload_anexo',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.sessao.models.anexo_upload_path, verbose_name='Anexo do Orador'),
),
migrations.AlterField(
model_name='oradorordemdia',
name='upload_anexo',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.sessao.models.anexo_upload_path, verbose_name='Anexo do Orador'),
),
]

22
sapl/sessao/migrations/0050_auto_20191029_1441.py

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-29 17:41
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.sessao.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('sessao', '0049_auto_20191029_1434'),
]
operations = [
migrations.AlterField(
model_name='justificativaausencia',
name='upload_anexo',
field=models.FileField(blank=True, max_length=300, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.sessao.models.anexo_upload_path, verbose_name='Anexo de Justificativa'),
),
]

13
sapl/sessao/migrations/0051_merge_20191209_0910.py

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-09 18:35
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('sessao', '0047_merge_20191009_1535'),
('sessao', '0050_auto_20191029_1441')
]

91
sapl/sessao/migrations/0052_auto_20191209_0828.py

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-12-09 11:28
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('parlamentares', '0035_merge_20190802_0954'),
('painel', '0011_cronometro_last_stop_duration'),
('sessao', '0051_merge_20191209_0910'),
]
operations = [
migrations.CreateModel(
name='CronometroLista',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cronometro', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='painel.Cronometro')),
],
options={
'verbose_name': 'Tipo de Lista de Discurso - Cronômetro',
'verbose_name_plural': 'Tipo de Listas de Discurso - Cronômetros',
'ordering': ['tipo_lista', 'cronometro'],
},
),
migrations.CreateModel(
name='ListaDiscurso',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('orador_atual', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='parlamentares.Parlamentar')),
('sessao_plenaria', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sessao.SessaoPlenaria', verbose_name='Sessão Plenária')),
],
options={
'verbose_name': 'Lista de Discurso',
'verbose_name_plural': 'Listas de Discurso',
'ordering': ['tipo', 'sessao_plenaria'],
},
),
migrations.CreateModel(
name='ParlamentarLista',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ordenacao', models.PositiveIntegerField(blank=True, null=True, verbose_name='Ordenação')),
('lista', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sessao.ListaDiscurso')),
('parlamentar', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='parlamentares.Parlamentar')),
],
options={
'verbose_name': 'Lista de Discurso - Parlamentar',
'verbose_name_plural': 'Listas de Discurso - Parlamentares',
'ordering': ['lista', 'parlamentar', 'ordenacao'],
},
),
migrations.CreateModel(
name='TipoListaDiscurso',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nome', models.CharField(max_length=100, verbose_name='Tipo')),
],
options={
'verbose_name': 'Tipo de Lista de Discurso',
'verbose_name_plural': 'Tipos de Lista de Discurso',
'ordering': ['nome'],
},
),
migrations.AddField(
model_name='listadiscurso',
name='tipo',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sessao.TipoListaDiscurso', verbose_name='Tipo de Lista de Discurso'),
),
migrations.AddField(
model_name='cronometrolista',
name='tipo_lista',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sessao.TipoListaDiscurso'),
),
migrations.AlterUniqueTogether(
name='parlamentarlista',
unique_together=set([('lista', 'parlamentar')]),
),
migrations.AlterUniqueTogether(
name='listadiscurso',
unique_together=set([('tipo', 'sessao_plenaria')]),
),
migrations.AlterUniqueTogether(
name='cronometrolista',
unique_together=set([('tipo_lista', 'cronometro')]),
),
]

77
sapl/sessao/models.py

@ -10,6 +10,7 @@ import reversion
from sapl.base.models import Autor
from sapl.materia.models import MateriaLegislativa
from sapl.painel.models import Cronometro
from sapl.parlamentares.models import (CargoMesa, Legislatura, Parlamentar,
Partido, SessaoLegislativa)
from sapl.utils import (YES_NO_CHOICES, SaplGenericRelation,
@ -127,7 +128,7 @@ class SessaoPlenaria(models.Model):
max_length=150, blank=True,
verbose_name=_('URL Arquivo Vídeo (Formatos MP4 / FLV / WebM)'))
upload_pauta = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
upload_to=pauta_upload_path,
@ -135,7 +136,7 @@ class SessaoPlenaria(models.Model):
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
upload_ata = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
upload_to=ata_upload_path,
@ -143,7 +144,7 @@ class SessaoPlenaria(models.Model):
verbose_name=_('Ata da Sessão'),
validators=[restringe_tipos_de_arquivo_txt])
upload_anexo = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
storage=OverwriteStorage(),
@ -381,7 +382,7 @@ class AbstractOrador(models.Model): # Oradores
observacao = models.CharField(
max_length=150, blank=True, verbose_name=_('Observação'))
upload_anexo = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
storage=OverwriteStorage(),
@ -733,7 +734,7 @@ class JustificativaAusencia(models.Model):
OrdemDia, blank=True, verbose_name=_('Matérias do Ordem do Dia'))
upload_anexo = models.FileField(
max_length=200,
max_length=300,
blank=True,
null=True,
storage=OverwriteStorage(),
@ -874,3 +875,69 @@ class RegistroLeitura(models.Model):
'RegistroLeitura deve ter exatamente um dos campos '
'ordem ou expediente preenchido. Ambos estão preenchidos: '
'{}, {}'. format(self.ordem, self.expediente))
class TipoListaDiscurso(models.Model):
nome = models.CharField(max_length=100, verbose_name=_('Tipo'))
class Meta:
verbose_name = _('Tipo de Lista de Discurso')
verbose_name_plural = _('Tipos de Lista de Discurso')
ordering = ['nome']
def __str__(self):
return self.nome
@reversion.register()
class ListaDiscurso(models.Model):
tipo = models.ForeignKey(TipoListaDiscurso,
on_delete=models.PROTECT,
verbose_name=_('Tipo de Lista de Discurso'))
sessao_plenaria = models.ForeignKey(SessaoPlenaria,
on_delete=models.PROTECT,
verbose_name=_('Sessão Plenária'))
orador_atual = models.ForeignKey(Parlamentar,
on_delete=models.PROTECT,
blank=True,
null=True)
class Meta:
verbose_name = _('Lista de Discurso')
verbose_name_plural = _('Listas de Discurso')
ordering = ['tipo', 'sessao_plenaria']
unique_together = (('tipo', 'sessao_plenaria'),)
def __str__(self):
return str(self.tipo) + ' - ' + str(self.sessao_plenaria)
@reversion.register()
class CronometroLista(models.Model):
cronometro = models.ForeignKey(Cronometro, on_delete=models.PROTECT)
tipo_lista = models.ForeignKey(TipoListaDiscurso, on_delete=models.PROTECT)
class Meta:
verbose_name = _('Tipo de Lista de Discurso - Cronômetro')
verbose_name_plural = _('Tipo de Listas de Discurso - Cronômetros')
ordering = ['tipo_lista', 'cronometro']
unique_together = (('tipo_lista', 'cronometro'),)
def __str__(self):
return str(self.tipo_lista) + ' - ' + str(self.cronometro)
@reversion.register()
class ParlamentarLista(models.Model):
parlamentar = models.ForeignKey(Parlamentar, on_delete=models.PROTECT)
lista = models.ForeignKey(ListaDiscurso, on_delete=models.PROTECT)
ordenacao = models.PositiveIntegerField(
blank=True,
null=True,
verbose_name=_("Ordenação")
)
class Meta:
verbose_name = _('Lista de Discurso - Parlamentar')
verbose_name_plural = _('Listas de Discurso - Parlamentares')
ordering = ['lista', 'parlamentar', 'ordenacao']
unique_together = (('lista', 'parlamentar'),)
def __str__(self):
return str(self.lista) + ' - ' + str(self.parlamentar)

28
sapl/sessao/urls.py

@ -38,7 +38,13 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente,
voto_nominal_parlamentar,
ExpedienteLeituraView,
OrdemDiaLeituraView,
retirar_leitura)
retirar_leitura,
ListaDiscursoView,
TipoListaDiscursoCrud,
CronometroListaFormView,
salva_listadiscurso_parlamentares,
salva_orador_listadiscurso,
get_orador_listadiscurso)
from .apps import AppConfig
@ -102,6 +108,10 @@ urlpatterns = [
include(TipoJustificativaCrud.get_urls())),
url(r'^sistema/sessao-plenaria/tipo-retirada-pauta/',
include(TipoRetiradaPautaCrud.get_urls())),
url(r'^sistema/sessao-plenaria/tipo-lista-discurso/',
include(TipoListaDiscursoCrud.get_urls())),
url(r'^sistema/resumo-ordenacao/',
resumo_ordenacao,
name='resumo_ordenacao'),
@ -202,4 +212,20 @@ urlpatterns = [
url(r'^sessao/(?P<pk>\d+)/(?P<iso>\d+)/(?P<oid>\d+)/retirar-leitura$',
retirar_leitura, name='retirar_leitura'),
# Lista de Discurso
url(r'^sessao/(?P<pk>\d+)/lista-discurso/',
ListaDiscursoView.as_view(),
name='lista_discurso'),
url(r'^sistema/sessao-plenaria/lista-discurso/(?P<pk>\d+)/cronometro/create',
CronometroListaFormView.as_view(), name='cronometrolista_form'),
url(r'^sistema/salva-listadiscurso-parlamentares/$',
salva_listadiscurso_parlamentares, name='salva_listadiscurso_parlamentares'),
url(r'^sistema/salva-orador-listadiscurso/$',
salva_orador_listadiscurso, name='salva_orador_listadiscurso'),
url(r'^sistema/get-orador-lista/(?P<spk>\d+)/(?P<tpk>\d+)/$',
get_orador_listadiscurso, name='get_orador_listadiscurso'),
]

136
sapl/sessao/views.py

@ -48,14 +48,15 @@ from .forms import (AdicionarVariasMateriasFilterSet, ExpedienteForm,
PresencaForm, SessaoPlenariaFilterSet,
SessaoPlenariaForm, VotacaoEditForm, VotacaoForm,
VotacaoNominalForm, RetiradaPautaForm, OradorOrdemDiaForm,
OrdemExpedienteLeituraForm)
OrdemExpedienteLeituraForm,
CronometroListaForm)
from .models import (CargoMesa, ExpedienteMateria, ExpedienteSessao, OcorrenciaSessao, IntegranteMesa,
MateriaLegislativa, Orador, OradorExpediente, OrdemDia,
PresencaOrdemDia, RegistroVotacao, ResumoOrdenacao,
SessaoPlenaria, SessaoPlenariaPresenca, TipoExpediente,
TipoResultadoVotacao, TipoSessaoPlenaria, VotoParlamentar, TipoRetiradaPauta,
RetiradaPauta, TipoJustificativa, JustificativaAusencia, OradorOrdemDia,
ORDENACAO_RESUMO, RegistroLeitura)
RetiradaPauta, TipoJustificativa, JustificativaAusencia, OradorOrdemDia, ORDENACAO_RESUMO,
RegistroLeitura, TipoListaDiscurso, ListaDiscurso, CronometroLista, ParlamentarLista)
TipoSessaoCrud = CrudAux.build(TipoSessaoPlenaria, 'tipo_sessao_plenaria')
@ -65,6 +66,19 @@ TipoResultadoVotacaoCrud = CrudAux.build(
TipoRetiradaPautaCrud = CrudAux.build(TipoRetiradaPauta, 'tipo_retirada_pauta')
class TipoListaDiscursoCrud(CrudAux):
model = TipoListaDiscurso
class DetailView(CrudAux.DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tipo_lista = context['object']
cronometros_lista = CronometroLista.objects.filter(tipo_lista=tipo_lista)
context['cronometros_lista'] = cronometros_lista
return context
def reordernar_materias_expediente(request, pk):
expedientes = ExpedienteMateria.objects.filter(
sessao_plenaria_id=pk
@ -386,7 +400,7 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
resultado = obj.registrovotacao_set.filter(
materia_id=obj.materia_id).last()
resultado_descricao = obj.resultado
resultado_observacao = obj.observacao
resultado_observacao = resultado.observacao
if has_permission:
url = ''
@ -502,18 +516,16 @@ def customize_link_materia(context, pk, has_permission, is_expediente):
def get_presencas_generic(model, sessao, legislatura):
presencas = model.objects.filter(
sessao_plenaria=sessao)
presentes = [p.parlamentar for p in presencas]
presentes = sorted(
presentes, key=lambda x: remover_acentos(x.nome_parlamentar))
presentes = [p.parlamentar for p in model.objects.filter(sessao_plenaria=sessao)]
mandato = Mandato.objects.filter(
legislatura=legislatura).order_by('parlamentar__nome_parlamentar')
parlamentares_mandato = Mandato.objects.filter(
legislatura=legislatura,
data_inicio_mandato__lte=sessao.data_inicio,
data_fim_mandato__gte=sessao.data_inicio
).distinct().order_by(
'parlamentar__nome_parlamentar')
for m in mandato:
for m in parlamentares_mandato:
parlamentar = m.parlamentar
p_afastado = verifica_afastamento_parlamentar(parlamentar, sessao.data_inicio, sessao.data_fim)
if parlamentar in presentes:
@ -788,6 +800,7 @@ class OradorExpedienteCrud(OradorCrud):
class ListView(MasterDetailCrud.ListView):
ordering = ['numero_ordem']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -1620,11 +1633,11 @@ def get_presenca_sessao(sessao_plenaria):
parlamentares_sessao = [p.parlamentar for p in SessaoPlenariaPresenca.objects.filter(
sessao_plenaria_id=sessao_plenaria.id
).order_by('parlamentar__nome_parlamentar')]
).order_by('parlamentar__nome_parlamentar').distinct()]
ausentes_sessao = JustificativaAusencia.objects.filter(
sessao_plenaria_id=sessao_plenaria.id
).order_by('parlamentar__nome_parlamentar')
).distinct().order_by('parlamentar__nome_parlamentar')
return ({'presenca_sessao': parlamentares_sessao,
'justificativa_ausencia': ausentes_sessao})
@ -1718,7 +1731,7 @@ def get_oradores_expediente(sessao_plenaria):
def get_presenca_ordem_do_dia(sessao_plenaria):
parlamentares_ordem = [p.parlamentar for p in PresencaOrdemDia.objects.filter(
sessao_plenaria_id=sessao_plenaria.id
).order_by('parlamentar__nome_parlamentar')]
).distinct().order_by('parlamentar__nome_parlamentar')]
return {'presenca_ordem': parlamentares_ordem}
@ -4513,3 +4526,92 @@ def retirar_leitura(request, pk, iso, oid):
ordem_expediente.save()
return HttpResponseRedirect(succ_url)
class ListaDiscursoView(TemplateView):
template_name = 'sessao/lista_discurso.html'
def get(self, request, *args, **kwargs):
return TemplateView.get(self, request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = TemplateView.get_context_data(self, **kwargs)
sessao_pk = kwargs['pk']
sessao = SessaoPlenaria.objects.get(id=sessao_pk)
context.update({
'root_pk': sessao_pk,
'subnav_template_name': 'sessao/subnav.yaml',
'sessaoplenaria': sessao,
})
return context
class CronometroListaFormView(FormView):
form_class = CronometroListaForm
template_name = 'sessao/cronometrolista_form.html'
def get_initial(self):
initial = super().get_initial()
tipo_lista_pk = self.kwargs['pk']
tipo_lista = TipoListaDiscurso.objects.get(id=tipo_lista_pk)
initial['tipo_lista'] = tipo_lista
initial['nome_lista'] = tipo_lista.nome
return initial
def form_valid(self, form):
form.save()
return super().form_valid(form)
@property
def cancel_url(self):
return reverse('sapl.sessao:tipolistadiscurso_detail',
kwargs={'pk': self.kwargs['pk']})
def get_success_url(self):
return reverse('sapl.sessao:tipolistadiscurso_detail',
kwargs={'pk': self.kwargs['pk']})
def salva_listadiscurso_parlamentares(request):
parlamentares_selecionados_ids = request.GET.getlist('parlamentares_selecionados[]')
tipo_lista_id = request.GET.get('lista_selecionada')
sessao_id = request.GET.get('sessao_pk')
if parlamentares_selecionados_ids:
lista = ListaDiscurso.objects.get_or_create(tipo_id=tipo_lista_id, sessao_plenaria_id=sessao_id)[0]
ParlamentarLista.objects.filter(lista=lista).delete()
parlamentares_lista = []
for i,parlamentar_id in enumerate(parlamentares_selecionados_ids):
parlamentares_lista.append(ParlamentarLista(lista=lista, parlamentar_id=parlamentar_id, ordenacao=i+1))
ParlamentarLista.objects.bulk_create(parlamentares_lista)
else:
ParlamentarLista.objects.filter(lista__tipo_id=tipo_lista_id, lista__sessao_plenaria_id=sessao_id).delete()
ListaDiscurso.objects.filter(tipo_id=tipo_lista_id, sessao_plenaria_id=sessao_id).delete()
return JsonResponse({})
def salva_orador_listadiscurso(request):
tipo_id=request.GET.get('tipo_lista_pk')
sessao_id=request.GET.get('sessao_pk')
lista = ListaDiscurso.objects.get(tipo_id=tipo_id, sessao_plenaria_id=sessao_id)
orador_pk = request.GET.get('orador_pk')
if orador_pk != '-1':
lista.orador_atual = Parlamentar.objects.get(id=orador_pk)
else:
lista.orador_atual = None
lista.save()
return JsonResponse({})
def get_orador_listadiscurso(request, spk, tpk):
# Não foi utilizado .get para não quebrar caso não exista registro
lista = ListaDiscurso.objects.filter(tipo_id=tpk, sessao_plenaria_id=spk).first()
parlamentares = []
orador_pk = -1
orador_nome = ''
if lista:
if lista.orador_atual:
orador_pk = lista.orador_atual.pk
orador_nome = lista.orador_atual.nome_parlamentar
parlamentares = ParlamentarLista.objects.filter(lista=lista).order_by('ordenacao').values_list('parlamentar__id', 'parlamentar__nome_parlamentar')
return JsonResponse({'orador': (orador_pk, orador_nome),
'parlamentares': list(parlamentares)})

2
sapl/settings.py

@ -263,7 +263,7 @@ DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', cast=str, default='')
SERVER_EMAIL = config('SERVER_EMAIL', cast=str, default='')
EMAIL_RUNNING = None
MAX_DOC_UPLOAD_SIZE = 80 * 1024 * 1024 # 80MB
MAX_DOC_UPLOAD_SIZE = 150 * 1024 * 1024 # 150MB
MAX_IMAGE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB
DATA_UPLOAD_MAX_MEMORY_SIZE= 10 * 1024 * 1024 # 10MB

0
sapl/static/sapl/frontend/css/chunk-vendors.f0333cff.css → sapl/static/sapl/frontend/css/chunk-vendors.c41142f2.css

0
sapl/static/sapl/frontend/css/chunk-vendors.f0333cff.css.gz → sapl/static/sapl/frontend/css/chunk-vendors.c41142f2.css.gz

1
sapl/static/sapl/frontend/css/compilacao.3d691cd9.css

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/css/compilacao.3d691cd9.css.gz

Binary file not shown.

1
sapl/static/sapl/frontend/css/compilacao.eff62463.css

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/css/compilacao.eff62463.css.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/css/painel.5d957a9b.css.gz

Binary file not shown.

2
sapl/static/sapl/frontend/css/painel.5d957a9b.css → sapl/static/sapl/frontend/css/painel.fd494bea.css

@ -1 +1 @@
.painel-principal{background:#1c1b1b;font-family:Verdana;font-size:x-large}.painel-principal .text-title{color:#4fa64d;margin:.5rem;font-weight:700}.painel-principal .text-subtitle{color:#459170;font-weight:700}.painel-principal .data-hora{font-size:180%}.painel-principal .text-value{color:#fff}.painel-principal .logo-painel{max-width:100%}.painel-principal .painels{-ms-flex-wrap:wrap;flex-wrap:wrap}.painel-principal .painel{margin-top:1rem}.painel-principal .painel table{width:100%}.painel-principal .painel h2{margin-bottom:.5rem}.painel-principal .painel #oradores_list,.painel-principal .painel #votacao{text-align:left;display:inline-block;margin-bottom:1rem}
.painel-principal{background:#1c1b1b;font-family:Verdana;font-size:x-large}.painel-principal .text-title{color:#4fa64d;margin:.5rem;font-weight:700}.painel-principal .text-subtitle{color:#459170;font-weight:700}.painel-principal .data-hora{font-size:180%}.painel-principal .text-value{color:#fff}.painel-principal .logo-painel{max-width:100%}.painel-principal .painels{-ms-flex-wrap:wrap;flex-wrap:wrap}.painel-principal .painel{margin-top:1rem}.painel-principal .painel table{width:100%}.painel-principal .painel h2{margin-bottom:.5rem}.painel-principal .painel #oradores_list,.painel-principal .painel #votacao{text-align:left;display:inline-block;margin-bottom:1rem}.painel-principal body,.painel-principal html{max-width:100%;overflow-x:hidden}@media screen{.painel-principal li,.painel-principal ul{list-style-type:none}}

BIN
sapl/static/sapl/frontend/css/painel.fd494bea.css.gz

Binary file not shown.

1
sapl/static/sapl/frontend/css/sessao.559521f7.css

@ -0,0 +1 @@
.current{background:#38b883;background-color:#ff0}.btSelecionado{background-color:#00aeff}

0
sapl/static/sapl/frontend/js/chunk-09995afe.a6f4abba.js → sapl/static/sapl/frontend/js/chunk-09995afe.98f3367d.js

0
sapl/static/sapl/frontend/js/chunk-09995afe.a6f4abba.js.gz → sapl/static/sapl/frontend/js/chunk-09995afe.98f3367d.js.gz

0
sapl/static/sapl/frontend/js/chunk-2d0c4a82.75cb4df5.js → sapl/static/sapl/frontend/js/chunk-2d0c4a82.624674a7.js

0
sapl/static/sapl/frontend/js/chunk-2d0c4a82.75cb4df5.js.gz → sapl/static/sapl/frontend/js/chunk-2d0c4a82.624674a7.js.gz

0
sapl/static/sapl/frontend/js/chunk-2d0e8be2.d53f5a24.js → sapl/static/sapl/frontend/js/chunk-2d0e8be2.67905a03.js

0
sapl/static/sapl/frontend/js/chunk-2d0e8be2.d53f5a24.js.gz → sapl/static/sapl/frontend/js/chunk-2d0e8be2.67905a03.js.gz

0
sapl/static/sapl/frontend/js/chunk-31d76f93.fb085f5d.js → sapl/static/sapl/frontend/js/chunk-31d76f93.968a637a.js

0
sapl/static/sapl/frontend/js/chunk-31d76f93.fb085f5d.js.gz → sapl/static/sapl/frontend/js/chunk-31d76f93.968a637a.js.gz

0
sapl/static/sapl/frontend/js/chunk-681dd124.b923be8d.js → sapl/static/sapl/frontend/js/chunk-681dd124.08a628b0.js

0
sapl/static/sapl/frontend/js/chunk-681dd124.b923be8d.js.gz → sapl/static/sapl/frontend/js/chunk-681dd124.08a628b0.js.gz

BIN
sapl/static/sapl/frontend/js/chunk-vendors.3ef59900.js.gz

Binary file not shown.

73
sapl/static/sapl/frontend/js/chunk-vendors.3ef59900.js → sapl/static/sapl/frontend/js/chunk-vendors.dfcd310b.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/js/chunk-vendors.dfcd310b.js.gz

Binary file not shown.

1
sapl/static/sapl/frontend/js/compilacao.0684a9a9.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/js/compilacao.0684a9a9.js.gz

Binary file not shown.

1
sapl/static/sapl/frontend/js/compilacao.6b8172da.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/js/compilacao.6b8172da.js.gz

Binary file not shown.

7
sapl/static/sapl/frontend/js/global.3df943f5.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/js/global.3df943f5.js.gz

Binary file not shown.

7
sapl/static/sapl/frontend/js/global.ce91d2f8.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/js/global.ce91d2f8.js.gz

Binary file not shown.

2
sapl/static/sapl/frontend/js/online.487f3d5a.js → sapl/static/sapl/frontend/js/online.0f0e1e97.js

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/frontend/js/online.0f0e1e97.js.gz

Binary file not shown.

BIN
sapl/static/sapl/frontend/js/online.487f3d5a.js.gz

Binary file not shown.

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save