Browse Source

Merge tag '3.1.160-RC12' into migracao

migracao
Marcio Mazza 5 years ago
parent
commit
b0a3c1f4fa
  1. 10
      .dockerignore
  2. 2
      .github/ISSUE_TEMPLATE.md
  3. 6
      .github/PULL_REQUEST_TEMPLATE.md
  4. 5
      Dockerfile
  5. 7
      docker-compose.yml
  6. 49
      health_check.py
  7. 18
      release.sh
  8. 8
      requirements/requirements.txt
  9. 109
      sapl/api/serializers.py
  10. 45
      sapl/api/urls.py
  11. 67
      sapl/api/views.py
  12. 31
      sapl/audiencia/forms.py
  13. 37
      sapl/audiencia/migrations/0011_auto_20190712_1053.py
  14. 37
      sapl/audiencia/migrations/0012_auto_20191001_1115.py
  15. 32
      sapl/audiencia/migrations/0013_auto_20191023_1522.py
  16. 21
      sapl/audiencia/migrations/0014_auto_20191023_1538.py
  17. 41
      sapl/audiencia/models.py
  18. 2
      sapl/audiencia/views.py
  19. 31
      sapl/base/admin.py
  20. 446
      sapl/base/forms.py
  21. 33
      sapl/base/migrations/0038_auditlog.py
  22. 20
      sapl/base/migrations/0039_auto_20191202_1114.py
  23. 70
      sapl/base/models.py
  24. 50
      sapl/base/receivers.py
  25. 48
      sapl/base/signals.py
  26. 6
      sapl/base/templatetags/common_tags.py
  27. 32
      sapl/base/tests/test_view_base.py
  28. 8
      sapl/base/urls.py
  29. 398
      sapl/base/views.py
  30. 103
      sapl/comissoes/forms.py
  31. 37
      sapl/comissoes/migrations/0020_auto_20190712_1053.py
  32. 37
      sapl/comissoes/migrations/0021_auto_20191001_1115.py
  33. 42
      sapl/comissoes/migrations/0022_auto_20191120_1440.py
  34. 33
      sapl/comissoes/migrations/0023_auto_20191211_1752.py
  35. 54
      sapl/comissoes/models.py
  36. 11
      sapl/comissoes/urls.py
  37. 74
      sapl/comissoes/views.py
  38. 58
      sapl/compilacao/forms.py
  39. 27
      sapl/compilacao/migrations/0013_auto_20190924_0830.py
  40. 39
      sapl/compilacao/models.py
  41. 3
      sapl/compilacao/templatetags/compilacao_filters.py
  42. 435
      sapl/compilacao/views.py
  43. 25
      sapl/crispy_layout_mixin.py
  44. 100
      sapl/crud/base.py
  45. 176
      sapl/materia/forms.py
  46. 32
      sapl/materia/migrations/0052_auto_20190712_1053.py
  47. 28
      sapl/materia/migrations/0052_auto_20190731_1554.py
  48. 20
      sapl/materia/migrations/0053_proposicao_ultima_edicao.py
  49. 16
      sapl/materia/migrations/0054_merge_20190802_1112.py
  50. 19
      sapl/materia/migrations/0055_auto_20190816_0943.py
  51. 25
      sapl/materia/migrations/0056_auto_20190829_1206.py
  52. 23
      sapl/materia/migrations/0056_popula_materiaemtramitacao.py
  53. 25
      sapl/materia/migrations/0057_materiaemtramitacao.py
  54. 27
      sapl/materia/migrations/0058_auto_20191001_1115.py
  55. 20
      sapl/materia/migrations/0058_auto_20191001_1450.py
  56. 16
      sapl/materia/migrations/0058_merge_20190926_1250.py
  57. 16
      sapl/materia/migrations/0059_merge_20191003_0854.py
  58. 20
      sapl/materia/migrations/0060_auto_20190930_1136.py
  59. 37
      sapl/materia/migrations/0061_auto_20191120_1440.py
  60. 16
      sapl/materia/migrations/0061_merge_20191009_1814.py
  61. 16
      sapl/materia/migrations/0062_merge_20191209_1531.py
  62. 21
      sapl/materia/migrations/0063_auto_20191220_1351.py
  63. 35
      sapl/materia/migrations/0064_auto_20200114_1121.py
  64. 106
      sapl/materia/models.py
  65. 4
      sapl/materia/tests/test_materia.py
  66. 6
      sapl/materia/tests/test_materia_form.py
  67. 222
      sapl/materia/views.py
  68. 50
      sapl/norma/forms.py
  69. 27
      sapl/norma/migrations/0026_auto_20190712_1053.py
  70. 27
      sapl/norma/migrations/0027_auto_20191001_1115.py
  71. 20
      sapl/norma/migrations/0027_normajuridica_ultima_edicao.py
  72. 22
      sapl/norma/migrations/0028_auto_20191024_1330.py
  73. 16
      sapl/norma/migrations/0028_merge_20191009_1814.py
  74. 22
      sapl/norma/migrations/0029_auto_20191024_1344.py
  75. 16
      sapl/norma/migrations/0030_merge_20191209_1531.py
  76. 31
      sapl/norma/migrations/0031_auto_20200114_1121.py
  77. 51
      sapl/norma/models.py
  78. 38
      sapl/norma/views.py
  79. 94
      sapl/painel/views.py
  80. 12
      sapl/parlamentares/forms.py
  81. 24
      sapl/parlamentares/tests/test_parlamentares.py
  82. 89
      sapl/parlamentares/views.py
  83. 101
      sapl/protocoloadm/forms.py
  84. 26
      sapl/protocoloadm/migrations/0022_auto_20190712_1053.py
  85. 32
      sapl/protocoloadm/migrations/0022_deduplica_protocolos.py
  86. 19
      sapl/protocoloadm/migrations/0023_auto_20190711_1755.py
  87. 16
      sapl/protocoloadm/migrations/0023_merge_20190802_1112.py
  88. 16
      sapl/protocoloadm/migrations/0024_merge_20190821_1418.py
  89. 20
      sapl/protocoloadm/migrations/0024_tramitacaoadministrativo_ultima_edicao.py
  90. 20
      sapl/protocoloadm/migrations/0025_auto_20190815_1539.py
  91. 26
      sapl/protocoloadm/migrations/0025_auto_20191001_1115.py
  92. 33
      sapl/protocoloadm/migrations/0026_auto_20190815_1737.py
  93. 31
      sapl/protocoloadm/migrations/0026_auto_20191120_1440.py
  94. 16
      sapl/protocoloadm/migrations/0027_merge_20190926_1250.py
  95. 16
      sapl/protocoloadm/migrations/0028_merge_20191009_1814.py
  96. 16
      sapl/protocoloadm/migrations/0029_merge_20191209_1531.py
  97. 25
      sapl/protocoloadm/migrations/0030_auto_20200114_1121.py
  98. 52
      sapl/protocoloadm/models.py
  99. 274
      sapl/protocoloadm/views.py
  100. 14
      sapl/relatorios/templates/pdf_pauta_sessao_gerar.py

10
.dockerignore

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

2
.github/ISSUE_TEMPLATE.md

@ -26,7 +26,7 @@
## Imagens do Ocorrido
<!--- Representação visual em vídeo ou imagem do ocorrido -->
<!--- Se está descrevendo um bug poste imagens ou vídeos na repordução do bug citado, caso se aplique -->
<!--- Se está descrevendo um bug poste imagens ou vídeos na reprodução do bug citado, caso se aplique -->
## Seu Ambiente
<!--- Inclua detalhes relevantes sobre o ambiente em que você presenciou/experienciou o bug. -->

6
.github/PULL_REQUEST_TEMPLATE.md

@ -1,7 +1,7 @@
<!--- Forneça um resumo geral das suas alterações no título acima -->
## Descrição
<!--- Decreva suas alterações detalhadamente -->
<!--- Descreva suas alterações detalhadamente -->
## _Issue_ Relacionada
<!--- Este projeto apenas aceita _pull requests_ relacionadas à _issues_ abertas. -->
@ -18,7 +18,7 @@
<!--- para ver como a sua alteração afeta outras áreas do código, etc. -->
## Capturas de Tela (se apropriado):
<!--- Insera imagens, se apropriado, da adição e/ou correção que foi feita -->
<!--- Insira imagens, se apropriado, da adição e/ou correção que foi feita -->
## Tipos de Mudanças
<!--- Quais os tipos de alterações introduzidos pelo seu código? Coloque um `x` em todas as caixas que se aplicam: -->
@ -30,7 +30,7 @@
<!--- Passe por todos os pontos a seguir e coloque um `x` em todas as caixas que se aplicam. -->
<!--- Se você não tem certeza sobre nenhum destes, não hesite em perguntar. Nós estamos aqui para ajudar! -->
- [ ] Eu li o documento de Contribuição (**CONTRIBUTING**).
- [ ] Meu código segue o estilo de código desse projeto.
- [ ] Meu código segue o estilo de código deste projeto.
- [ ] Minha alteração requer uma alteração na documentação.
- [ ] Eu atualizei a documentação de acordo.
- [ ] Eu adicionei testes para cobrir minhas mudanças.

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/*

7
docker-compose.yml

@ -9,9 +9,10 @@ sapldb:
volumes:
- sapldb_data:/var/lib/postgresql/data/
ports:
- "5432:5432"
- "5433:5432"
sapl:
image: interlegis/sapl:3.1.159
image: interlegis/sapl:3.1.160-RC12
#build: .
restart: always
environment:
ADMIN_PASSWORD: interlegis
@ -30,4 +31,4 @@ sapl:
links:
- sapldb
ports:
- "80:80"
- "80:80"

49
health_check.py

@ -0,0 +1,49 @@
from flask import Flask
import requests
import psycopg2
import json
from sapl.settings import DATABASES, USE_SOLR, SOLR_URL
app = Flask(__name__)
@app.route('/health')
def health():
try:
db = DATABASES['default']
conn = psycopg2.connect(host=db['HOST'],
user=db['USER'],
password=db['PASSWORD'],
database=db['NAME'],
port=db['PORT'])
cursor = conn.cursor()
cursor.execute("SELECT 1;")
resp = {'DATABASE': 'OK'}
except Exception as e:
resp = {'DATABASE': 'ERROR'}
finally:
if cursor:
cursor.close()
conn.close()
if USE_SOLR:
r = requests.get(SOLR_URL)
if r.ok:
resp.update({'SEARCH_ENGINE': 'OK'})
else:
resp.update({'SEARCH_ENGINE': 'ERROR'})
else:
resp.update({'SEARCH_ENGINE': 'NOT_ENABLED'})
return json.dumps(resp)
@app.route('/ping')
def ping():
return "pong"
if __name__ == '__main__':
app.run()

18
release.sh

@ -13,13 +13,14 @@ SED_AWKWARD_PATTERN="[0-9]+\.[0-9]+\.[0-9]+(-RC[0-9]+){0,1}"
LATEST_VERSION=$(git tag | egrep $VERSION_PATTERN | sort --version-sort | tail -1)
MAJOR_VERSION=$(echo $LATEST_VERSION | cut -d"-" -f1)
IS_RC=$(echo $LATEST_VERSION | egrep '(-RC)')
MAJOR_TAG_CREATED=$(git tag | egrep $MAJOR_VERSION"$")
if [ -n "$MAJOR_TAG_CREATED" ]; then
LATEST_VERSION=$MAJOR_VERSION
fi
IS_RC=$(echo $LATEST_VERSION | egrep '(-RC)')
LAST_DIGIT=`echo $MAJOR_VERSION | cut -f 3 -d '.'`
MAIN_REV=`echo $MAJOR_VERSION | cut -f 1,2 -d '.'`
NEXT_NUMBER=$(($LAST_DIGIT + 1))
@ -33,17 +34,13 @@ function change_files {
echo "Atualizando de "$OLD_VERSION" para "$FINAL_VERSION
sed -E s/$OLD_VERSION/$FINAL_VERSION/g docker-compose.yml > tmp1
mv tmp1 docker-compose.yml
sed -E -i "s|$OLD_VERSION|$FINAL_VERSION|g" docker-compose.yml
sed -E s/$OLD_VERSION/$FINAL_VERSION/g setup.py > tmp2
mv tmp2 setup.py
sed -E -i "s|$OLD_VERSION|$FINAL_VERSION|g" setup.py
sed -E s/$OLD_VERSION/$FINAL_VERSION/g sapl/templates/base.html > tmp3
mv tmp3 sapl/templates/base.html
sed -E -i "s|$OLD_VERSION|$FINAL_VERSION|g" sapl/templates/base.html
sed -E s/$OLD_VERSION/$FINAL_VERSION/g sapl/settings.py > tmp4
mv tmp4 sapl/settings.py
sed -E -i "s|$OLD_VERSION|$FINAL_VERSION|g" sapl/settings.py
}
function set_major_version {
@ -72,9 +69,12 @@ function commit_and_push {
git commit -m "Release: $FINAL_VERSION"
git tag $FINAL_VERSION
echo "================================================================================"
echo " Versão criada e gerada localmente."
echo "Para enviar pro github execute..."
echo "git push origin 3.1.x"
echo "git push origin "$FINAL_VERSION
echo "================================================================================"
echo "done."
}

8
requirements/requirements.txt

@ -1,4 +1,4 @@
django>=1.11.19,<2.0
django>=1.11.27,<2.0
django-haystack==2.8.1
django-filter==2.0.0
djangorestframework==3.9.1
@ -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
pysolr==3.6.0

109
sapl/api/serializers.py

@ -1,8 +1,13 @@
import logging
from django.conf import settings
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from django.db.models import F, Q
from rest_framework import serializers
from rest_framework.relations import StringRelatedField
from sapl.parlamentares.models import Parlamentar, Mandato, Filiacao, Legislatura
from sapl.base.models import Autor, CasaLegislativa
from sapl.utils import filiacao_data
from image_cropping.utils import get_backend
class IntRelatedField(StringRelatedField):
@ -56,3 +61,105 @@ class CasaLegislativaSerializer(serializers.ModelSerializer):
class Meta:
model = CasaLegislativa
fields = '__all__'
class ParlamentarResumeSerializer(serializers.ModelSerializer):
titular = serializers.SerializerMethodField('check_titular')
partido = serializers.SerializerMethodField('check_partido')
fotografia_cropped = serializers.SerializerMethodField('crop_fotografia')
logger = logging.getLogger(__name__)
def crop_fotografia(self,obj):
thumbnail_url = ""
try:
import os
#import pdb;pdb.set_trace()
if not obj.fotografia or not os.path.exists(obj.fotografia.path):
return thumbnail_url
thumbnail_url = get_backend().get_thumbnail_url(
obj.fotografia,
{
'size': (128, 128),
'box': obj.cropping,
'crop': True,
'detail': True,
}
)
except Exception as e:
self.logger.error(e)
self.logger.error('erro processando arquivo: %s' % obj.fotografia.path)
return thumbnail_url
def check_titular(self,obj):
is_titular = None
if not Legislatura.objects.exists():
self.logger.error("Não há legislaturas cadastradas.")
return ""
try:
legislatura = Legislatura.objects.get(id=self.context.get('legislatura'))
except ObjectDoesNotExist:
legislatura = Legislatura.objects.first()
mandato = Mandato.objects.filter(
parlamentar=obj,
data_inicio_mandato__gte=legislatura.data_inicio,
data_fim_mandato__lte=legislatura.data_fim
).order_by('-data_inicio_mandato').first()
if mandato:
is_titular = 'Sim' if mandato.titular else 'Não'
else:
is_titular = '-'
return is_titular
def check_partido(self,obj):
# 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
# da legislatura e data de desfiliação deve nula, ou maior,
# ou igual a data de fim da legislatura
username = self.context['request'].user.username
if not Legislatura.objects.exists():
self.logger.error("Não há legislaturas cadastradas.")
return ""
try:
legislatura = Legislatura.objects.get(id=self.context.get('legislatura'))
except ObjectDoesNotExist:
legislatura = Legislatura.objects.first()
try:
self.logger.debug("user=" + username + ". Tentando obter filiação do parlamentar com (data<={} e data_desfiliacao>={}) "
"ou (data<={} e data_desfiliacao=Null))."
.format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim))
filiacao = obj.filiacao_set.get(Q(
data__lte=legislatura.data_fim,
data_desfiliacao__gte=legislatura.data_fim) | Q(
data__lte=legislatura.data_fim,
data_desfiliacao__isnull=True))
# Caso não exista filiação com essas condições
except ObjectDoesNotExist:
self.logger.error("user=" + username + ". Parlamentar com (data<={} e data_desfiliacao>={}) "
"ou (data<={} e data_desfiliacao=Null)) não possui filiação."
.format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim))
filiacao = 'Não possui filiação'
# Caso exista mais de uma filiação nesse intervalo
# Entretanto, NÃO DEVE OCORRER
except MultipleObjectsReturned:
self.logger.error("user=" + username + ". O Parlamentar com (data<={} e data_desfiliacao>={}) "
"ou (data<={} e data_desfiliacao=Null)) possui duas filiações conflitantes"
.format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim))
filiacao = 'O Parlamentar possui duas filiações conflitantes'
# Caso encontre UMA filiação nessas condições
else:
self.logger.debug("user=" + username +
". Filiação encontrada com sucesso.")
filiacao = filiacao.partido.sigla
return filiacao
class Meta:
model = Parlamentar
fields = ['id', 'nome_parlamentar', 'fotografia_cropped','fotografia', 'ativo', 'partido', 'titular']

45
sapl/api/urls.py

@ -1,7 +1,5 @@
from django.conf import settings
from django.conf.urls import include, url
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework import permissions
from rest_framework.routers import DefaultRouter
@ -29,26 +27,29 @@ for app, built_sets in SaplApiViewSetConstrutor._built_sets.items():
urlpatterns_router = router.urls
schema_view = get_schema_view(
openapi.Info(
title="Sapl API - docs",
default_version='v1',
description="Sapl API - Docs - Configuração Básica",
),
url=settings.SITE_URL,
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns_api_doc = [
url(r'^docs/swagger(?P<format>\.json|\.yaml)$',
schema_view.without_ui(cache_timeout=0), name='schema-json'),
url(r'^docs/swagger/$',
schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
url(r'^docs/redoc/$',
schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
urlpatterns_api_doc = []
if 'drf_yasg' in settings.INSTALLED_APPS:
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
schema_view = get_schema_view(
openapi.Info(
title="Sapl API - docs",
default_version='v1',
description="Sapl API - Docs - Configuração Básica",
),
url=settings.SITE_URL,
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns_api_doc = [
url(r'^docs/swagger(?P<format>\.json|\.yaml)$',
schema_view.without_ui(cache_timeout=0), name='schema-json'),
url(r'^docs/swagger/$',
schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
url(r'^docs/redoc/$',
schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
# TODO: refatorar para customização da api automática
deprecated_urlpatterns_api = [

67
sapl/api/views.py

@ -13,22 +13,27 @@ from django_filters.filters import CharFilter
from django_filters.rest_framework.backends import DjangoFilterBackend
from django_filters.rest_framework.filterset import FilterSet
from django_filters.utils import resolve_field
from django.utils import timezone
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import serializers as rest_serializers
from rest_framework.decorators import action
from rest_framework.fields import SerializerMethodField
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from sapl.api.forms import SaplFilterSetMixin
from sapl.api.permissions import SaplModelPermissions
from sapl.api.serializers import ChoiceSerializer
from sapl.api.serializers import ChoiceSerializer, ParlamentarResumeSerializer
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.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import DocumentoAdministrativo,\
DocumentoAcessorioAdministrativo, TramitacaoAdministrativo, Anexado
from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao
from sapl.utils import models_with_gr_for_model, choice_anos_com_sessaoplenaria
from sapl.parlamentares.models import Mandato, Parlamentar, Legislatura
class BusinessRulesNotImplementedMixin:
@ -93,10 +98,15 @@ class SaplApiViewSetConstrutor():
# Define uma classe padrão para serializer caso não tenha sido
# criada a classe sapl.api.serializers.{model}Serializer
class SaplSerializer(rest_serializers.ModelSerializer):
__str__ = SerializerMethodField()
class Meta:
model = _model
fields = '__all__'
def get___str__(self, obj):
return str(obj)
# Define uma classe padrão para filtro caso não tenha sido
# criada a classe sapl.api.forms.{model}FilterSet
class SaplFilterSet(SaplFilterSetMixin):
@ -201,6 +211,8 @@ SaplApiViewSetConstrutor.build_class()
# rest_framework.viewsets.ModelViewSet conforme exemplo para a classe autor
# decorator para recuperar e transformar o default
class customize(object):
def __init__(self, model):
self.model = model
@ -294,6 +306,16 @@ class _AutorViewSet:
@customize(Parlamentar)
class _ParlamentarViewSet:
class ParlamentarPermission(SaplModelPermissions):
def has_permission(self, request, view):
if request.method == 'GET':
return True
else:
perm = super().has_permission(request, view)
return perm
permission_classes = (ParlamentarPermission, )
@action(detail=True)
def proposicoes(self, request, *args, **kwargs):
"""
@ -324,6 +346,40 @@ class _ParlamentarViewSet:
serializer = self.get_serializer(page, many=True)
return Response(serializer.data)
@action(detail=True)
def parlamentares_by_legislatura(self,request,*args,**kwargs):
"""
Pega lista de parlamentares pelo id da legislatura.
"""
try:
legislatura = Legislatura.objects.get(pk=kwargs['pk'])
except ObjectDoesNotExist:
return Response("")
data_atual = timezone.now().date()
filter_params = {
'legislatura':legislatura,
'data_inicio_mandato__gte':legislatura.data_inicio,
'data_fim_mandato__gte':legislatura.data_fim,
}
if legislatura.data_inicio < data_atual < legislatura.data_fim:
filter_params['data_fim_mandato__gte'] = data_atual
mandatos = Mandato.objects.filter(**filter_params).order_by('-data_inicio_mandato')
parlamentares = Parlamentar.objects.filter(mandato__in=mandatos).distinct()
serializer_class = ParlamentarResumeSerializer(parlamentares,
many=True,
context={'request':request,'legislatura':kwargs['pk']})
return Response(serializer_class.data)
@action(detail=False,methods=['GET'])
def search_parlamentares(self,request,*args,**kwargs):
nome = request.query_params.get('nome_parlamentar','')
parlamentares = Parlamentar.objects.filter(nome_parlamentar__icontains=nome)
serializer_class= ParlamentarResumeSerializer(parlamentares,many=True,context={'request':request})
return Response(serializer_class.data)
@customize(Proposicao)
@ -522,3 +578,12 @@ class _SessaoPlenariaViewSet:
serializer = self.get_serializer(page, many=True)
return Response(serializer.data)
@customize(NormaJuridica)
class _NormaJuridicaViewset:
@action(detail=False, methods=['GET'])
def destaques(self, request, *args, **kwargs):
self.queryset = self.get_queryset().filter(norma_de_destaque=True)
return self.list(request, *args, **kwargs)

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

37
sapl/audiencia/migrations/0011_auto_20190712_1053.py

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-12 13:53
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.audiencia.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('audiencia', '0010_auto_20190219_1511'),
]
operations = [
migrations.AlterField(
model_name='anexoaudienciapublica',
name='arquivo',
field=models.FileField(storage=sapl.utils.OverwriteStorage(), upload_to=sapl.utils.texto_upload_path, verbose_name='Arquivo'),
),
migrations.AlterField(
model_name='audienciapublica',
name='upload_anexo',
field=models.FileField(blank=True, 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, 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, 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'),
),
]

37
sapl/audiencia/migrations/0012_auto_20191001_1115.py

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-01 14:15
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.audiencia.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('audiencia', '0011_auto_20190712_1053'),
]
operations = [
migrations.AlterField(
model_name='anexoaudienciapublica',
name='arquivo',
field=models.FileField(max_length=200, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.utils.texto_upload_path, verbose_name='Arquivo'),
),
migrations.AlterField(
model_name='audienciapublica',
name='upload_anexo',
field=models.FileField(blank=True, max_length=200, 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=200, 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=200, 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'),
),
]

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'),
),
]

41
sapl/audiencia/models.py

@ -7,7 +7,8 @@ from sapl.materia.models import MateriaLegislativa
from sapl.parlamentares.models import (CargoMesa, Parlamentar)
from sapl.utils import (YES_NO_CHOICES, SaplGenericRelation,
restringe_tipos_de_arquivo_txt, texto_upload_path)
restringe_tipos_de_arquivo_txt, texto_upload_path,
OverwriteStorage)
def get_audiencia_media_path(instance, subpath, filename):
@ -86,21 +87,27 @@ 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=300,
blank=True,
null=True,
upload_to=pauta_upload_path,
storage=OverwriteStorage(),
verbose_name=_('Pauta da Audiência Pública'),
validators=[restringe_tipos_de_arquivo_txt])
upload_ata = models.FileField(
max_length=300,
blank=True,
null=True,
upload_to=ata_upload_path,
verbose_name=_('Ata da Audiência Pública'),
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
upload_anexo = models.FileField(
max_length=300,
blank=True,
null=True,
upload_to=anexo_upload_path,
storage=OverwriteStorage(),
verbose_name=_('Anexo da Audiência Pública'))
class Meta:
@ -112,17 +119,22 @@ class AudienciaPublica(models.Model):
return self.nome
def delete(self, using=None, keep_parents=False):
if self.upload_pauta:
self.upload_pauta.delete()
upload_pauta = self.upload_pauta
upload_ata = self.upload_ata
upload_anexo = self.upload_anexo
if self.upload_ata:
self.upload_ata.delete()
result = super().delete(using=using, keep_parents=keep_parents)
if self.upload_anexo:
self.upload_anexo.delete()
if upload_pauta:
upload_pauta.delete(save=False)
return models.Model.delete(
self, using=using, keep_parents=keep_parents)
if upload_ata:
upload_ata.delete(save=False)
if upload_anexo:
upload_anexo.delete(save=False)
return result
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
@ -155,7 +167,9 @@ class AnexoAudienciaPublica(models.Model):
audiencia = models.ForeignKey(AudienciaPublica,
on_delete=models.PROTECT)
arquivo = models.FileField(
max_length=300,
upload_to=texto_upload_path,
storage=OverwriteStorage(),
verbose_name=_('Arquivo'))
data = models.DateField(
auto_now=timezone.now)
@ -170,10 +184,13 @@ class AnexoAudienciaPublica(models.Model):
return self.assunto
def delete(self, using=None, keep_parents=False):
if self.arquivo:
self.arquivo.delete()
arquivo = self.arquivo
result = super().delete(using=using, keep_parents=keep_parents)
if arquivo:
arquivo.delete(save=False)
return models.Model.delete(self, using=using, keep_parents=keep_parents)
return result
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
if not self.pk and self.arquivo:

2
sapl/audiencia/views.py

@ -21,7 +21,7 @@ class AudienciaCrud(Crud):
class BaseMixin(Crud.BaseMixin):
list_field_names = ['numero', 'nome', 'tipo', 'materia',
'data']
ordering = 'nome', 'numero', 'tipo', 'data'
ordering = '-data', 'nome', 'numero', 'tipo'
class ListView(Crud.ListView):
paginate_by = 10

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)

446
sapl/base/forms.py

@ -1,8 +1,9 @@
import logging
import os
from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton
from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row
from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton, FormActions
from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row, Submit
from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
@ -18,27 +19,25 @@ from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
from sapl.audiencia.models import AudienciaPublica
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Reuniao, Comissao
from sapl.comissoes.models import Reuniao, Comissao
from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
to_row)
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.materia.models import (
MateriaLegislativa, UnidadeTramitacao, StatusTramitacao)
from sapl.norma.models import (NormaJuridica, NormaEstatisticas)
from sapl.parlamentares.models import SessaoLegislativa, Partido
from sapl.comissoes.models import Reuniao
from sapl.crispy_layout_mixin import (form_actions, to_column, to_row,
SaplFormHelper, SaplFormLayout)
from sapl.materia.models import (DocumentoAcessorio, MateriaEmTramitacao,
MateriaLegislativa, UnidadeTramitacao,
StatusTramitacao)
from sapl.norma.models import NormaJuridica
from sapl.parlamentares.models import Partido, SessaoLegislativa
from sapl.protocoloadm.models import DocumentoAdministrativo
from sapl.sessao.models import SessaoPlenaria
from sapl.settings import MAX_IMAGE_UPLOAD_SIZE
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
ChoiceWithoutValidationField, ImageThumbnailFileInput,
RangeWidgetOverride, autor_label, autor_modal,
models_with_gr_for_model, qs_override_django_filter,
from sapl.utils import (autor_label, autor_modal, ChoiceWithoutValidationField,
choice_anos_com_normas, choice_anos_com_materias,
FilterOverridesMetaMixin, FileFieldCheckMixin)
FilterOverridesMetaMixin, FileFieldCheckMixin,
AnoNumeroOrderingFilter, ImageThumbnailFileInput,
models_with_gr_for_model, qs_override_django_filter,
RangeWidgetOverride, RANGE_ANOS, YES_NO_CHOICES)
from .models import AppConfig, CasaLegislativa
@ -604,7 +603,7 @@ class AutorForm(ModelForm):
self.logger.error(
'Já existe um Autor para este usuário ({}).'.format(cd['username']))
raise ValidationError(
_('Já existe um Autor para este usuário.'))
_('Já existe um usuário vinculado a esse autor'))
"""
'if' não é necessário por ser campo obrigatório e o framework
@ -618,6 +617,10 @@ class AutorForm(ModelForm):
tipo = cd['tipo']
if 'nome' in cd and \
Autor.objects.filter(nome=cd['nome']).exists():
raise ValidationError("Autor '%s' já existente!" % cd['nome'])
if not tipo.content_type:
if 'nome' not in cd or not cd['nome']:
self.logger.error('Nome do Autor não informado.')
@ -731,6 +734,58 @@ class AutorFormForAdmin(AutorForm):
'status_user']
class RelatorioDocumentosAcessoriosFilterSet(django_filters.FilterSet):
@property
def qs(self):
parent = super(RelatorioDocumentosAcessoriosFilterSet, self).qs
return parent.distinct().order_by('-data')
class Meta(FilterOverridesMetaMixin):
model = DocumentoAcessorio
fields = ['tipo', 'materia__tipo', 'data']
def __init__(self, *args, **kwargs):
super(
RelatorioDocumentosAcessoriosFilterSet, self
).__init__(*args, **kwargs)
self.filters['tipo'].label = 'Tipo de Documento'
self.filters['materia__tipo'].label = 'Tipo de Matéria do Documento'
self.filters['data'].label = 'Período (Data Inicial - Data Final)'
self.form.fields['tipo'].required = True
row0 = to_row([('tipo', 6),
('materia__tipo', 6)])
row1 = to_row([('data', 12)])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa'),
row0, row1,
buttons)
)
class RelatorioAtasFilterSet(django_filters.FilterSet):
class Meta(FilterOverridesMetaMixin):
@ -747,19 +802,33 @@ class RelatorioAtasFilterSet(django_filters.FilterSet):
super(RelatorioAtasFilterSet, self).__init__(
*args, **kwargs)
self.filters['data_inicio'].label = 'Período (Inicial - Final)'
self.form.fields['data_inicio'].required = True
self.filters['data_inicio'].label = 'Período de Abertura (Inicial - Final)'
self.form.fields['data_inicio'].required = False
row1 = to_row([('data_inicio', 12)])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Atas das Sessões Plenárias'),
row1, form_actions(label='Pesquisar'))
row1, buttons, )
)
def ultimo_ano_com_norma():
anos_normas = choice_anos_com_normas()
@ -788,11 +857,26 @@ class RelatorioNormasMesFilterSet(django_filters.FilterSet):
row1 = to_row([('ano', 12)])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Normas por mês do ano.'),
row1, form_actions(label='Pesquisar'))
row1, buttons, )
)
@property
@ -817,11 +901,26 @@ class EstatisticasAcessoNormasForm(Form):
row1 = to_row([('ano', 12)])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.helper = SaplFormHelper()
self.helper.form_method = 'GET'
self.helper.layout = Layout(
Fieldset(_('Normas por acessos nos meses do ano.'),
row1, form_actions(label='Pesquisar'))
row1, buttons)
)
def clean(self):
@ -855,12 +954,27 @@ class RelatorioNormasVigenciaFilterSet(django_filters.FilterSet):
row1 = to_row([('ano', 12)])
row2 = to_row([('vigencia', 12)])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Normas por vigência.'),
row1, row2,
form_actions(label='Pesquisar'))
buttons, )
)
@property
@ -886,22 +1000,39 @@ class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet):
self.form.initial['exibir_ordem_dia'] = True
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(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Presença dos parlamentares nas sessões plenárias'),
row1, row2, row3, form_actions(label='Pesquisar'))
row1, row2, row3, buttons, )
)
@property
@ -911,6 +1042,8 @@ class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet):
class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput())
@property
def qs(self):
parent = super(RelatorioHistoricoTramitacaoFilterSet, self).qs
@ -939,12 +1072,39 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
[('tipo', 6),
('tramitacao__status', 6)])
row4 = to_row([
('autoria__autor', 0),
(Button('pesquisar',
'Pesquisar Autor',
css_class='btn btn-primary btn-sm'), 2),
(Button('limpar',
'limpar Autor',
css_class='btn btn-primary btn-sm'), 2)
])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_(''),
row1, row2, row3,
form_actions(label='Pesquisar'))
Fieldset(_('Pesquisar'),
row1, row2, row3, row4,
HTML(autor_label),
HTML(autor_modal),
buttons, )
)
@ -978,12 +1138,27 @@ class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet):
[('tipo', 6),
('tramitacao__status', 6)])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Tramitações'),
row1, row2, row3,
form_actions(label='Pesquisar'))
buttons, )
)
@ -1009,12 +1184,27 @@ class RelatorioReuniaoFilterSet(django_filters.FilterSet):
('nome', 4),
('tema', 4)])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Reunião de Comissão'),
row1, row2,
form_actions(label='Pesquisar'))
buttons, )
)
@ -1039,18 +1229,33 @@ class RelatorioAudienciaFilterSet(django_filters.FilterSet):
[('tipo', 4),
('nome', 4)])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Audiência Pública'),
row1, row2,
form_actions(label='Pesquisar'))
buttons, )
)
class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
class RelatorioMateriasTramitacaoFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True,
materia__ano = django_filters.ChoiceFilter(required=True,
label='Ano da Matéria',
choices=choice_anos_com_materias)
@ -1064,31 +1269,49 @@ class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
@property
def qs(self):
parent = super(RelatorioMateriasTramitacaoilterSet, self).qs
return parent.distinct().order_by('-ano', 'tipo', '-numero')
parent = super(RelatorioMateriasTramitacaoFilterSet, self).qs
return parent.distinct().order_by(
'-materia__ano', 'materia__tipo', '-materia__numero'
)
class Meta:
model = MateriaLegislativa
fields = ['ano', 'tipo', 'tramitacao__unidade_tramitacao_destino',
model = MateriaEmTramitacao
fields = ['materia__ano', 'materia__tipo',
'tramitacao__unidade_tramitacao_destino',
'tramitacao__status']
def __init__(self, *args, **kwargs):
super(RelatorioMateriasTramitacaoilterSet, self).__init__(
super(RelatorioMateriasTramitacaoFilterSet, self).__init__(
*args, **kwargs)
self.filters['tipo'].label = 'Tipo de Matéria'
self.filters['materia__tipo'].label = 'Tipo de Matéria'
row1 = to_row([('ano', 12)])
row2 = to_row([('tipo', 12)])
row1 = to_row([('materia__ano', 12)])
row2 = to_row([('materia__tipo', 12)])
row3 = to_row([('tramitacao__unidade_tramitacao_destino', 12)])
row4 = to_row([('tramitacao__status', 12)])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Matéria em Tramitação'),
row1, row2, row3, row4,
form_actions(label='Pesquisar'))
buttons,)
)
@ -1109,22 +1332,38 @@ class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet):
row1 = to_row(
[('ano', 12)])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisar'),
Fieldset(_('Pesquisa de Matéria por Ano Autor Tipo'),
row1,
form_actions(label='Pesquisar'))
buttons, )
)
class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet):
autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput())
@property
def qs(self):
parent = super(RelatorioMateriasPorAutorFilterSet, self).qs
parent = super().qs
return parent.distinct().filter(autoria__primeiro_autor=True)\
.order_by('autoria__autor', '-autoria__primeiro_autor', 'tipo', '-ano', '-numero')
@ -1133,8 +1372,7 @@ class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet):
fields = ['tipo', 'data_apresentacao']
def __init__(self, *args, **kwargs):
super(RelatorioMateriasPorAutorFilterSet, self).__init__(
*args, **kwargs)
super().__init__(*args, **kwargs)
self.filters['tipo'].label = 'Tipo de Matéria'
@ -1151,15 +1389,30 @@ class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet):
'limpar Autor',
css_class='btn btn-primary btn-sm'), 10)])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisar'),
Fieldset(_('Pesquisa de Matéria por Autor'),
row1, row2,
HTML(autor_label),
HTML(autor_modal),
row3,
form_actions(label='Pesquisar'))
buttons, )
)
@ -1257,9 +1510,13 @@ class ConfiguracoesAppForm(ModelForm):
self.fields['cronometro_ordem'].widget.attrs['class'] = 'cronometro'
self.fields['cronometro_consideracoes'].widget.attrs['class'] = 'cronometro'
def clean_mostrar_brasao_painel(self):
mostrar_brasao_painel = self.cleaned_data.get(
'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:
@ -1272,7 +1529,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):
@ -1486,10 +1743,83 @@ class RelatorioHistoricoTramitacaoAdmFilterSet(django_filters.FilterSet):
[('tipo', 6),
('tramitacaoadministrativo__status', 6)])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_(''),
row1, row2, row3,
buttons, )
)
class RelatorioNormasPorAutorFilterSet(django_filters.FilterSet):
autorianorma__autor = django_filters.CharFilter(widget=forms.HiddenInput())
@property
def qs(self):
parent = super().qs
return parent.distinct().filter(autorianorma__primeiro_autor=True)\
.order_by('autorianorma__autor', '-autorianorma__primeiro_autor', 'tipo', '-ano', '-numero')
class Meta(FilterOverridesMetaMixin):
model = NormaJuridica
fields = ['tipo', 'data']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.filters['tipo'].label = 'Tipo de Norma'
row1 = to_row(
[('tipo', 12)])
row2 = to_row(
[('data', 12)])
row3 = to_row(
[('autorianorma__autor', 0),
(Button('pesquisar',
'Pesquisar Autor',
css_class='btn btn-primary btn-sm'), 2),
(Button('limpar',
'Limpar Autor',
css_class='btn btn-primary btn-sm'), 10)])
buttons = FormActions(
*[
HTML('''
<div class="form-check">
<input name="relatorio" type="checkbox" class="form-check-input" id="relatorio">
<label class="form-check-label" for="relatorio">Gerar relatório PDF</label>
</div>
''')
],
Submit('pesquisar', _('Pesquisar'), css_class='float-right',
onclick='return true;'),
css_class='form-group row justify-content-between'
,
)
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisar'),
row1, row2,
HTML(autor_label),
HTML(autor_modal),
row3,
form_actions(label='Pesquisar'))
)

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',),
},
),
]

20
sapl/base/migrations/0039_auto_20191202_1114.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-12-02 14:14
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0038_auditlog'),
]
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'),
),
]

70
sapl/base/models.py

@ -137,7 +137,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')
cronometro_discurso = models.DurationField(
@ -271,38 +271,42 @@ class Autor(models.Model):
return '?'
def cria_models_tipo_autor(app_config=None, verbosity=2, interactive=True,
using=DEFAULT_DB_ALIAS, **kwargs):
class AuditLog(models.Model):
models = models_with_gr_for_model(Autor)
operation = ('C', 'D', 'U')
print("\n\033[93m\033[1m{}\033[0m".format(
_('Atualizando registros TipoAutor do SAPL:')))
for model in models:
content_type = ContentType.objects.get_for_model(model)
tipo_autor = TipoAutor.objects.filter(
content_type=content_type.id).exists()
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,
)
if tipo_autor:
msg1 = "Carga de {} não efetuada.".format(
TipoAutor._meta.verbose_name)
msg2 = " Já Existe um {} {} relacionado...".format(
TipoAutor._meta.verbose_name,
model._meta.verbose_name)
msg = " {}{}".format(msg1, msg2)
else:
novo_autor = TipoAutor()
novo_autor.content_type_id = content_type.id
novo_autor.descricao = model._meta.verbose_name
novo_autor.save()
msg1 = "Carga de {} efetuada.".format(
TipoAutor._meta.verbose_name)
msg2 = " {} {} criado...".format(
TipoAutor._meta.verbose_name, content_type.model)
msg = " {}{}".format(msg1, msg2)
print(msg)
# Disconecta função para evitar a chamada repetidas vezes.
post_migrate.disconnect(receiver=cria_models_tipo_autor)
post_migrate.connect(receiver=cria_models_tipo_autor)

50
sapl/base/receivers.py

@ -1,13 +1,17 @@
from django.db.models.signals import post_delete, post_save
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.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.base.signals import tramitacao_signal
from sapl.utils import get_base_url
from sapl.base.email_utils import do_envia_email_tramitacao
@receiver(tramitacao_signal)
def handle_tramitacao_signal(sender, **kwargs):
@ -30,13 +34,47 @@ def handle_tramitacao_signal(sender, **kwargs):
@receiver(post_delete)
def status_tramitacao_materia(sender, instance, **kwargs):
if isinstance(sender, TramitacaoAdministrativo):
if sender == Tramitacao:
if instance.status.indicador == 'F':
materia = instance.materia
materia.em_tramitacao = True
materia.save()
elif isinstance(sender, TramitacaoAdministrativo):
elif sender == TramitacaoAdministrativo:
if instance.status.indicador == 'F':
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)

48
sapl/base/signals.py

@ -1,3 +1,51 @@
from django.contrib.contenttypes.models import ContentType
from django.db.models.signals import post_migrate
from django.db.utils import DEFAULT_DB_ALIAS
import django.dispatch
from django.utils.translation import ugettext_lazy as _
from sapl.base.models import Autor, TipoAutor
from sapl.utils import models_with_gr_for_model
tramitacao_signal = django.dispatch.Signal(providing_args=['post', 'request'])
def cria_models_tipo_autor(app_config=None, verbosity=2, interactive=True,
using=DEFAULT_DB_ALIAS, **kwargs):
models = models_with_gr_for_model(Autor)
print("\n\033[93m\033[1m{}\033[0m".format(
_('Atualizando registros TipoAutor do SAPL:')))
for model in models:
content_type = ContentType.objects.get_for_model(model)
tipo_autor = TipoAutor.objects.filter(
content_type=content_type.id).exists()
if tipo_autor:
msg1 = "Carga de {} não efetuada.".format(
TipoAutor._meta.verbose_name)
msg2 = " Já Existe um {} {} relacionado...".format(
TipoAutor._meta.verbose_name,
model._meta.verbose_name)
msg = " {}{}".format(msg1, msg2)
else:
novo_autor = TipoAutor()
novo_autor.content_type_id = content_type.id
novo_autor.descricao = model._meta.verbose_name
novo_autor.save()
msg1 = "Carga de {} efetuada.".format(
TipoAutor._meta.verbose_name)
msg2 = " {} {} criado...".format(
TipoAutor._meta.verbose_name, content_type.model)
msg = " {}{}".format(msg1, msg2)
print(msg)
# Disconecta função para evitar a chamada repetidas vezes.
post_migrate.disconnect(receiver=cria_models_tipo_autor)
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'])

6
sapl/base/templatetags/common_tags.py

@ -249,8 +249,10 @@ def facebook_url(value):
def youtube_id(value):
from urllib.parse import urlparse, parse_qs
u_pars = urlparse(value)
quer_v = parse_qs(u_pars.query).get('v')[0]
return quer_v
quer_v = parse_qs(u_pars.query).get('v')
if quer_v:
return quer_v[0]
return ''
@register.filter

32
sapl/base/tests/test_view_base.py

@ -24,32 +24,6 @@ from sapl.base.views import (protocolos_duplicados, protocolos_com_materias,
bancada_comissao_autor_externo, anexados_ciclicos)
@pytest.mark.django_db(transaction=False)
def test_lista_protocolos_duplicados():
mommy.make(
Protocolo,
numero=15,
ano=2031
)
mommy.make(
Protocolo,
numero=15,
ano=2031
)
mommy.make(
Protocolo,
numero=33,
ano=2033
)
lista_protocolos_duplicados = protocolos_duplicados()
assert len(lista_protocolos_duplicados) == 1
assert lista_protocolos_duplicados[0][1] == 2
assert lista_protocolos_duplicados[0][0].numero == 15
assert lista_protocolos_duplicados[0][0].ano == 2031
@pytest.mark.django_db(transaction=False)
def test_lista_protocolos_com_materias():
mommy.make(
@ -194,13 +168,13 @@ def test_lista_parlamentares_duplicados():
sexo='M'
)
lista_dict_values_parlamentares_duplicados = parlamentares_duplicados()
lista_dict_parlamentares_duplicados = parlamentares_duplicados()
parlamentar_duplicado = list(
lista_dict_values_parlamentares_duplicados[0]
lista_dict_parlamentares_duplicados[0].values()
)
parlamentar_duplicado.sort(key=str)
assert len(lista_dict_values_parlamentares_duplicados) == 1
assert len(lista_dict_parlamentares_duplicados) == 1
assert parlamentar_duplicado == [2, "Nome_Parlamentar_Teste"]

8
sapl/base/urls.py

@ -36,7 +36,8 @@ from .views import (AlterarSenha, AppConfigCrud, CasaLegislativaCrud,
ListarAutoresDuplicadosView, ListarBancadaComissaoAutorExternoView,
ListarLegislaturaInfindavelView, ListarAnexadasCiclicasView,
ListarAnexadosCiclicosView, pesquisa_textual,
RelatorioHistoricoTramitacaoAdmView)
RelatorioHistoricoTramitacaoAdmView, RelatorioDocumentosAcessoriosView,
RelatorioNormasPorAutorView)
app_name = AppConfig.name
@ -135,6 +136,11 @@ urlpatterns = [
url(r'^sistema/relatorios/historico-tramitacoesadm$',
RelatorioHistoricoTramitacaoAdmView.as_view(),
name='historico_tramitacoes_adm'),
url(r'^sistema/relatorios/documentos_acessorios$',
RelatorioDocumentosAcessoriosView.as_view(),
name='relatorio_documentos_acessorios'),
url(r'^sistema/relatorios/normas-por-autor$',
RelatorioNormasPorAutorView.as_view(), name='normas_por_autor'),
url(r'^email/validate/(?P<uidb64>[0-9A-Za-z_\-]+)/'
'(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})$',

398
sapl/base/views.py

@ -13,7 +13,8 @@ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, Validat
from django.core.mail import send_mail
from django.core.urlresolvers import reverse, reverse_lazy
from django.db import connection
from django.db.models import Count, Q, ProtectedError
from django.db.models import Count, Q, ProtectedError, Max
from django.shortcuts import render
from django.http import Http404, HttpResponseRedirect, JsonResponse
from django.template import TemplateDoesNotExist
from django.template.loader import get_template
@ -29,24 +30,36 @@ from django_filters.views import FilterView
from haystack.views import SearchView
from haystack.query import SearchQuerySet
from sapl.relatorios.views import (relatorio_materia_em_tramitacao, relatorio_materia_por_autor,
relatorio_materia_por_ano_autor, relatorio_presenca_sessao,
relatorio_historico_tramitacao, relatorio_fim_prazo_tramitacao,
relatorio_atas, relatorio_audiencia, relatorio_normas_mes,
relatorio_normas_vigencia, relatorio_historico_tramitacao_adm,
relatorio_reuniao, relatorio_estatisticas_acesso_normas,
relatorio_normas_por_autor, relatorio_documento_acessorio)
from sapl import settings
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Reuniao, Comissao
from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm
from sapl.comissoes.models import Comissao, Reuniao
from sapl.crud.base import CrudAux, make_pagination
from sapl.materia.models import (Autoria, MateriaLegislativa, Proposicao, Anexada,
TipoMateriaLegislativa, StatusTramitacao, UnidadeTramitacao)
from sapl.norma.models import (NormaJuridica, NormaEstatisticas)
from sapl.parlamentares.models import Parlamentar, Legislatura, Mandato, Filiacao, SessaoLegislativa
from sapl.protocoloadm.models import (Protocolo, TipoDocumentoAdministrativo,
StatusTramitacaoAdministrativo,
DocumentoAdministrativo, Anexado)
from sapl.sessao.models import (PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca, Bancada, TipoSessaoPlenaria)
from sapl.utils import (parlamentares_ativos, gerar_hash_arquivo, SEPARADOR_HASH_PROPOSICAO,
show_results_filter_set, mail_service_configured,
intervalos_tem_intersecao, remover_acentos)
from sapl.materia.models import (Anexada, Autoria, DocumentoAcessorio,
MateriaEmTramitacao, MateriaLegislativa, Proposicao,
StatusTramitacao, TipoDocumento,
TipoMateriaLegislativa, UnidadeTramitacao, Tramitacao)
from sapl.norma.models import NormaJuridica, TipoNormaJuridica
from sapl.parlamentares.models import (Filiacao, Legislatura, Mandato, Parlamentar,
SessaoLegislativa)
from sapl.protocoloadm.models import (Anexado, DocumentoAdministrativo, Protocolo,
StatusTramitacaoAdministrativo,
TipoDocumentoAdministrativo)
from sapl.sessao.models import (Bancada, PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca, TipoSessaoPlenaria)
from sapl.utils import (gerar_hash_arquivo, intervalos_tem_intersecao,
mail_service_configured, parlamentares_ativos,
SEPARADOR_HASH_PROPOSICAO, show_results_filter_set)
from .forms import (AlterarSenhaForm, CasaLegislativaForm,
ConfiguracoesAppForm, RelatorioAtasFilterSet,
RelatorioAudienciaFilterSet,
@ -54,29 +67,18 @@ from .forms import (AlterarSenhaForm, CasaLegislativaForm,
RelatorioHistoricoTramitacaoFilterSet,
RelatorioMateriasPorAnoAutorTipoFilterSet,
RelatorioMateriasPorAutorFilterSet,
RelatorioMateriasTramitacaoilterSet,
RelatorioMateriasTramitacaoFilterSet,
RelatorioPresencaSessaoFilterSet,
RelatorioReuniaoFilterSet, UsuarioCreateForm,
UsuarioEditForm, RelatorioNormasMesFilterSet,
RelatorioNormasVigenciaFilterSet,
EstatisticasAcessoNormasForm, UsuarioFilterSet,
RelatorioHistoricoTramitacaoAdmFilterSet)
RelatorioHistoricoTramitacaoAdmFilterSet,
RelatorioDocumentosAcessoriosFilterSet,
RelatorioNormasPorAutorFilterSet)
from .models import AppConfig, CasaLegislativa
def filtra_url_materias_em_tramitacao(qr, qs, campo_url, local_ou_status):
id_materias = []
filtro_url = qr[campo_url]
if local_ou_status == 'local':
id_materias = [item.id for item in qs if item.tramitacao_set.order_by(
'-id').first().unidade_tramitacao_destino_id == int(filtro_url)]
elif local_ou_status == 'status':
id_materias = [item.id for item in qs if item.tramitacao_set.order_by(
'-id').first().status_id == int(filtro_url)]
return qs.filter(em_tramitacao=True, id__in=id_materias)
def get_casalegislativa():
return CasaLegislativa.objects.first()
@ -302,14 +304,71 @@ class RelatoriosListView(TemplateView):
return context
class RelatorioAtasView(FilterView):
class RelatorioMixin:
def get(self, request, *args, **kwargs):
super(RelatorioMixin, self).get(request)
is_relatorio = request.GET.get('relatorio')
context = self.get_context_data(filter=self.filterset)
if is_relatorio:
return self.relatorio(request, context)
else:
return self.render_to_response(context)
class RelatorioDocumentosAcessoriosView(RelatorioMixin, FilterView):
model = DocumentoAcessorio
filterset_class = RelatorioDocumentosAcessoriosFilterSet
template_name = 'base/RelatorioDocumentosAcessorios_filter.html'
relatorio = relatorio_documento_acessorio
def get_context_data(self, **kwargs):
context = super(
RelatorioDocumentosAcessoriosView, self
).get_context_data(**kwargs)
context['title'] = _('Documentos Acessórios das Matérias Legislativas')
if not self.filterset.form.is_valid():
return context
query_dict = self.request.GET.copy()
context['show_results'] = show_results_filter_set(query_dict)
context['tipo_documento'] = str(
TipoDocumento.objects.get(pk=self.request.GET['tipo'])
)
tipo_materia = self.request.GET['materia__tipo']
if tipo_materia:
context['tipo_materia'] = str(
TipoMateriaLegislativa.objects.get(pk=tipo_materia)
)
else:
context['tipo_materia'] = "Não selecionado"
data_inicial = self.request.GET['data_0']
data_final = self.request.GET['data_1']
if not data_inicial:
data_inicial = "Data Inicial não definida"
if not data_final:
data_final = "Data Final não definida"
context['periodo'] = (
data_inicial + ' - ' + data_final
)
return context
class RelatorioAtasView(RelatorioMixin, FilterView):
model = SessaoPlenaria
filterset_class = RelatorioAtasFilterSet
template_name = 'base/RelatorioAtas_filter.html'
relatorio = relatorio_atas
def get_context_data(self, **kwargs):
context = super(RelatorioAtasView,
self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context['title'] = _('Atas das Sessões Plenárias')
# Verifica se os campos foram preenchidos
@ -327,11 +386,12 @@ class RelatorioAtasView(FilterView):
return context
class RelatorioPresencaSessaoView(FilterView):
class RelatorioPresencaSessaoView(RelatorioMixin, FilterView):
logger = logging.getLogger(__name__)
model = SessaoPlenaria
filterset_class = RelatorioPresencaSessaoFilterSet
template_name = 'base/RelatorioPresencaSessao_filter.html'
relatorio = relatorio_presenca_sessao
def get_context_data(self, **kwargs):
@ -493,10 +553,11 @@ class RelatorioPresencaSessaoView(FilterView):
return context
class RelatorioHistoricoTramitacaoView(FilterView):
class RelatorioHistoricoTramitacaoView(RelatorioMixin, FilterView):
model = MateriaLegislativa
filterset_class = RelatorioHistoricoTramitacaoFilterSet
template_name = 'base/RelatorioHistoricoTramitacao_filter.html'
relatorio = relatorio_historico_tramitacao
def get_context_data(self, **kwargs):
context = super(RelatorioHistoricoTramitacaoView,
@ -538,13 +599,21 @@ class RelatorioHistoricoTramitacaoView(FilterView):
else:
context['tramitacao__unidade_tramitacao_destino'] = ''
if self.request.GET['autoria__autor']:
context['autoria__autor'] = \
(str(Autor.objects.get(
id=self.request.GET['autoria__autor'])))
else:
context['autoria__autor'] = ''
return context
class RelatorioDataFimPrazoTramitacaoView(FilterView):
class RelatorioDataFimPrazoTramitacaoView(RelatorioMixin, FilterView):
model = MateriaLegislativa
filterset_class = RelatorioDataFimPrazoTramitacaoFilterSet
template_name = 'base/RelatorioDataFimPrazoTramitacao_filter.html'
relatorio = relatorio_fim_prazo_tramitacao
def get_context_data(self, **kwargs):
context = super(RelatorioDataFimPrazoTramitacaoView,
@ -590,10 +659,11 @@ class RelatorioDataFimPrazoTramitacaoView(FilterView):
return context
class RelatorioReuniaoView(FilterView):
class RelatorioReuniaoView(RelatorioMixin, FilterView):
model = Reuniao
filterset_class = RelatorioReuniaoFilterSet
template_name = 'base/RelatorioReuniao_filter.html'
relatorio = relatorio_reuniao
def get_filterset_kwargs(self, filterset_class):
super(RelatorioReuniaoView,
@ -623,10 +693,11 @@ class RelatorioReuniaoView(FilterView):
return context
class RelatorioAudienciaView(FilterView):
class RelatorioAudienciaView(RelatorioMixin, FilterView):
model = AudienciaPublica
filterset_class = RelatorioAudienciaFilterSet
template_name = 'base/RelatorioAudiencia_filter.html'
relatorio = relatorio_audiencia
def get_filterset_kwargs(self, filterset_class):
super(RelatorioAudienciaView,
@ -656,69 +727,118 @@ class RelatorioAudienciaView(FilterView):
return context
class RelatorioMateriasTramitacaoView(FilterView):
model = MateriaLegislativa
filterset_class = RelatorioMateriasTramitacaoilterSet
class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView):
model = MateriaEmTramitacao
filterset_class = RelatorioMateriasTramitacaoFilterSet
template_name = 'base/RelatorioMateriasPorTramitacao_filter.html'
relatorio = relatorio_materia_em_tramitacao
paginate_by = 100
total_resultados_tipos = {}
def get_filterset_kwargs(self, filterset_class):
data = super().get_filterset_kwargs(filterset_class)
if data['data']:
qs = data['queryset']
ano_materia = data['data']['materia__ano']
tipo_materia = data['data']['materia__tipo']
unidade_tramitacao_destino = data['data']['tramitacao__unidade_tramitacao_destino']
status_tramitacao = data['data']['tramitacao__status']
kwargs = {}
if ano_materia:
kwargs['materia__ano'] = ano_materia
if tipo_materia:
kwargs['materia__tipo'] = tipo_materia
if unidade_tramitacao_destino:
kwargs['tramitacao__unidade_tramitacao_destino'] = unidade_tramitacao_destino
if status_tramitacao:
kwargs['tramitacao__status'] = status_tramitacao
qs = qs.filter(**kwargs)
data['queryset'] = qs
qtdes = { tipo:0 for tipo in TipoMateriaLegislativa.objects.all() }
for i in qs:
qtdes[i.materia.tipo] += 1
# remove as entradas de valor igual a zero
qtdes = {k:v for k,v in qtdes.items() if v > 0}
self.total_resultados_tipos = qtdes
return data
def get_queryset(self):
qs = super().get_queryset()
qs = qs.select_related('materia__tipo').filter(
materia__em_tramitacao=True
).exclude(
tramitacao__status__indicador='F'
).order_by('-materia__ano', '-materia__numero')
return qs
def get_context_data(self, **kwargs):
context = super(RelatorioMateriasTramitacaoView,
self).get_context_data(**kwargs)
context = super(
RelatorioMateriasTramitacaoView, self
).get_context_data(**kwargs)
context['title'] = _('Matérias em Tramitação')
if not self.filterset.form.is_valid():
return context
qr = self.request.GET.copy()
qs = context['object_list']
qs = qs.filter(em_tramitacao=True)
if qr.get('tramitacao__unidade_tramitacao_destino'):
qs = filtra_url_materias_em_tramitacao(
qr, qs, 'tramitacao__unidade_tramitacao_destino', 'local')
if qr.get('tramitacao__status'):
qs = filtra_url_materias_em_tramitacao(
qr, qs, 'tramitacao__status', 'status')
li = [li1 for li1 in qs if li1.tramitacao_set.last() and li1.tramitacao_set.last().status.indicador != 'F']
context['object_list'] = li
context['qtdes'] = self.total_resultados_tipos
context['ano'] = (self.request.GET['materia__ano'])
qtdes = {}
for tipo in TipoMateriaLegislativa.objects.all():
li = context['object_list']
qtde = sum(1 for i in li if i.tipo_id==tipo.id)
if qtde > 0:
qtdes[tipo] = qtde
context['qtdes'] = qtdes
context['ano'] = (self.request.GET['ano'])
if self.request.GET['tipo']:
tipo = self.request.GET['tipo']
if self.request.GET['materia__tipo']:
tipo = self.request.GET['materia__tipo']
context['tipo'] = (
str(TipoMateriaLegislativa.objects.get(id=tipo)))
str(TipoMateriaLegislativa.objects.get(id=tipo))
)
else:
context['tipo'] = ''
if self.request.GET['tramitacao__status']:
tramitacao_status = self.request.GET['tramitacao__status']
context['tramitacao__status'] = (
str(StatusTramitacao.objects.get(id=tramitacao_status)))
str(StatusTramitacao.objects.get(id=tramitacao_status))
)
else:
context['tramitacao__status'] = ''
if self.request.GET['tramitacao__unidade_tramitacao_destino']:
context['tramitacao__unidade_tramitacao_destino'] = (str(UnidadeTramitacao.objects.get(
id=self.request.GET['tramitacao__unidade_tramitacao_destino'])))
context['tramitacao__unidade_tramitacao_destino'] = (
str(UnidadeTramitacao.objects.get(
id=self.request.GET['tramitacao__unidade_tramitacao_destino']
))
)
else:
context['tramitacao__unidade_tramitacao_destino'] = ''
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages
)
context['NO_ENTRIES_MSG'] = 'Nenhum encontrado.'
return context
class RelatorioMateriasPorAnoAutorTipoView(FilterView):
class RelatorioMateriasPorAnoAutorTipoView(RelatorioMixin, FilterView):
model = MateriaLegislativa
filterset_class = RelatorioMateriasPorAnoAutorTipoFilterSet
template_name = 'base/RelatorioMateriasPorAnoAutorTipo_filter.html'
relatorio = relatorio_materia_por_ano_autor
def get_materias_autor_ano(self, ano, primeiro_autor):
@ -776,7 +896,7 @@ class RelatorioMateriasPorAnoAutorTipoView(FilterView):
return context
qtdes = {}
for tipo in TipoMateriaLegislativa.objects.all():
qs = kwargs['object_list']
qs = context['object_list']
qtde = len(qs.filter(tipo_id=tipo.id))
if qtde > 0:
qtdes[tipo] = qtde
@ -798,21 +918,19 @@ class RelatorioMateriasPorAnoAutorTipoView(FilterView):
return context
class RelatorioMateriasPorAutorView(FilterView):
class RelatorioMateriasPorAutorView(RelatorioMixin, FilterView):
model = MateriaLegislativa
filterset_class = RelatorioMateriasPorAutorFilterSet
template_name = 'base/RelatorioMateriasPorAutor_filter.html'
relatorio = relatorio_materia_por_autor
def get_filterset_kwargs(self, filterset_class):
super(RelatorioMateriasPorAutorView,
self).get_filterset_kwargs(filterset_class)
super().get_filterset_kwargs(filterset_class)
kwargs = {'data': self.request.GET or None}
return kwargs
def get_context_data(self, **kwargs):
context = super(RelatorioMateriasPorAutorView,
self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context['title'] = _('Matérias por Autor')
if not self.filterset.form.is_valid():
@ -820,7 +938,7 @@ class RelatorioMateriasPorAutorView(FilterView):
qtdes = {}
for tipo in TipoMateriaLegislativa.objects.all():
qs = kwargs['object_list']
qs = context['object_list']
qtde = len(qs.filter(tipo_id=tipo.id))
if qtde > 0:
qtdes[tipo] = qtde
@ -848,10 +966,11 @@ class RelatorioMateriasPorAutorView(FilterView):
return context
class RelatorioNormasPublicadasMesView(FilterView):
class RelatorioNormasPublicadasMesView(RelatorioMixin, FilterView):
model = NormaJuridica
filterset_class = RelatorioNormasMesFilterSet
template_name = 'base/RelatorioNormaMes_filter.html'
relatorio = relatorio_normas_mes
def get_context_data(self, **kwargs):
context = super(RelatorioNormasPublicadasMesView,
@ -887,10 +1006,11 @@ class RelatorioNormasPublicadasMesView(FilterView):
return context
class RelatorioNormasVigenciaView(FilterView):
class RelatorioNormasVigenciaView(RelatorioMixin, FilterView):
model = NormaJuridica
filterset_class = RelatorioNormasVigenciaFilterSet
template_name = 'base/RelatorioNormasVigencia_filter.html'
relatorio = relatorio_normas_vigencia
def get_filterset_kwargs(self, filterset_class):
super(RelatorioNormasVigenciaView,
@ -990,7 +1110,15 @@ class EstatisticasAcessoNormas(TemplateView):
context['normas_mes'] = normas_mes
return self.render_to_response(context)
is_relatorio = request.GET.get('relatorio')
context['show_results'] = show_results_filter_set(
self.request.GET.copy())
if is_relatorio:
return relatorio_estatisticas_acesso_normas(self, request, context)
else:
return self.render_to_response(context)
class ListarInconsistenciasView(PermissionRequiredMixin, ListView):
@ -1070,7 +1198,7 @@ class ListarInconsistenciasView(PermissionRequiredMixin, ListView):
tabela.append(
('anexadas_ciclicas',
'Matérias Anexadas cíclicas',
len(anexados_ciclicos(True))
len(materias_anexadas_ciclicas())
)
)
tabela.append(
@ -1081,10 +1209,43 @@ class ListarInconsistenciasView(PermissionRequiredMixin, ListView):
)
return tabela
def materias_anexadas_ciclicas():
ciclos = []
for a in Anexada.objects.select_related('materia_principal',
'materia_anexada',
'materia_principal__tipo',
'materia_anexada__tipo'):
visitados = [a.materia_principal]
anexadas = [a.materia_anexada]
while len(anexadas) > 0:
ma = anexadas.pop()
if ma not in visitados:
visitados.append(ma)
anexadas.extend([a.materia_anexada for a in Anexada.objects.filter(materia_principal=ma)])
else:
ciclo_list = visitados + [ma]
ciclos.append(ciclo_list)
"""
Remove ciclos repetidos (ou semanticamente equivalentes).
Exemplo: A -> B -> A e B -> A -> B
"""
ciclos_set = []
ciclos_unique = [e for e in ciclos if is_ciclo_unique(e, ciclos_set)]
return ciclos_unique
def is_ciclo_unique(ciclo, ciclos_set):
if set(ciclo) not in ciclos_set:
ciclos_set.append(set(ciclo))
return True
else:
return False
def anexados_ciclicos(ofMateriaLegislativa):
ciclicos = []
if ofMateriaLegislativa:
principais = Anexada.objects.values(
'materia_principal'
@ -1171,7 +1332,7 @@ class ListarAnexadasCiclicasView(PermissionRequiredMixin, ListView):
paginate_by = 10
def get_queryset(self):
return anexados_ciclicos(True)
return materias_anexadas_ciclicas()
def get_context_data(self, **kwargs):
context = super(
@ -1387,7 +1548,7 @@ class ListarParlMandatosIntersecaoView(PermissionRequiredMixin, ListView):
def parlamentares_duplicados():
return [parlamentar.values() for parlamentar in Parlamentar.objects.values(
return [parlamentar for parlamentar in Parlamentar.objects.values(
'nome_parlamentar').order_by('nome_parlamentar').annotate(count=Count(
'nome_parlamentar')).filter(count__gt=1)]
@ -1572,14 +1733,10 @@ class ListarProtocolosComMateriasView(PermissionRequiredMixin, ListView):
def protocolos_duplicados():
protocolos = {}
for p in Protocolo.objects.order_by('-ano', 'numero'):
key = "{}/{}".format(p.numero, p.ano)
val = protocolos.get(key, list())
val.append(p)
protocolos[key] = val
return [(v[0], len(v)) for (k, v) in protocolos.items() if len(v) > 1]
return [
protocolo for protocolo in Protocolo.objects.values(
'numero', 'ano').order_by('-ano', 'numero').annotate(total=Count('numero')).filter(total__gt=1)
]
class ListarProtocolosDuplicadosView(PermissionRequiredMixin, ListView):
@ -1866,8 +2023,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):
@ -1991,10 +2146,11 @@ def pesquisa_textual(request):
return JsonResponse(json_dict)
class RelatorioHistoricoTramitacaoAdmView(FilterView):
class RelatorioHistoricoTramitacaoAdmView(RelatorioMixin, FilterView):
model = DocumentoAdministrativo
filterset_class = RelatorioHistoricoTramitacaoAdmFilterSet
template_name = 'base/RelatorioHistoricoTramitacaoAdm_filter.html'
relatorio = relatorio_historico_tramitacao_adm
def get_context_data(self, **kwargs):
context = super(RelatorioHistoricoTramitacaoAdmView,
@ -2036,4 +2192,52 @@ class RelatorioHistoricoTramitacaoAdmView(FilterView):
else:
context['tramitacaoadministrativo__unidade_tramitacao_destino'] = ''
return context
return context
class RelatorioNormasPorAutorView(RelatorioMixin, FilterView):
model = NormaJuridica
filterset_class = RelatorioNormasPorAutorFilterSet
template_name = 'base/RelatorioNormasPorAutor_filter.html'
relatorio = relatorio_normas_por_autor
def get_filterset_kwargs(self, filterset_class):
super().get_filterset_kwargs(filterset_class)
kwargs = {'data': self.request.GET or None}
return kwargs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = _('Normas por Autor')
if not self.filterset.form.is_valid():
return context
qtdes = {}
for tipo in TipoNormaJuridica.objects.all():
qs = context['object_list']
qtde = len(qs.filter(tipo_id=tipo.id))
if qtde > 0:
qtdes[tipo] = qtde
context['qtdes'] = qtdes
qr = self.request.GET.copy()
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr)
if self.request.GET['tipo']:
tipo = int(self.request.GET['tipo'])
context['tipo'] = (
str(TipoNormaJuridica.objects.get(id=tipo)))
else:
context['tipo'] = ''
if self.request.GET['autorianorma__autor']:
autor = int(self.request.GET['autorianorma__autor'])
context['autor'] = (str(Autor.objects.get(id=autor)))
else:
context['autor'] = ''
context['periodo'] = (
self.request.GET['data_0'] +
' - ' + self.request.GET['data_1'])
return context

103
sapl/comissoes/forms.py

@ -1,7 +1,9 @@
import django_filters
import logging
from crispy_forms.layout import Fieldset, Layout
from django import forms
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import transaction
@ -10,11 +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, Reuniao, Periodo)
from sapl.materia.models import PautaReuniao
from sapl.parlamentares.models import Legislatura, Mandato, Parlamentar
from sapl.utils import FileFieldCheckMixin
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.utils import (FileFieldCheckMixin,
FilterOverridesMetaMixin, validar_arquivo)
class ComposicaoForm(forms.ModelForm):
@ -40,17 +47,31 @@ class ComposicaoForm(forms.ModelForm):
periodo = cleaned_data['periodo']
comissao_pk = self.initial['comissao'].id
cleaned_data['comissao'] = self.initial['comissao']
intersecao_periodo = Composicao.objects.filter(
Q(periodo__data_inicio__lte=periodo.data_fim,
periodo__data_fim__gte=periodo.data_fim) |
Q(periodo__data_inicio__gte=periodo.data_inicio,
periodo__data_fim__lte=periodo.data_inicio),
comissao_id=comissao_pk)
if periodo.data_fim:
intersecao_periodo = Composicao.objects.filter(
Q(periodo__data_inicio__lte=periodo.data_fim,
periodo__data_fim__gte=periodo.data_fim) |
Q(periodo__data_inicio__gte=periodo.data_inicio,
periodo__data_fim__lte=periodo.data_inicio),
comissao_id=comissao_pk)
else:
intersecao_periodo = Composicao.objects.filter(
Q(periodo__data_inicio__gte=periodo.data_inicio,
periodo__data_fim__lte=periodo.data_inicio),
comissao_id=comissao_pk)
if intersecao_periodo:
self.logger.error('O período informado ({} a {})'
'choca com períodos já '
'cadastrados para esta comissão'.format(periodo.data_inicio, periodo.data_fim))
if periodo.data_fim:
self.logger.error('O período informado ({} a {})'
'choca com períodos já '
'cadastrados para esta comissão'
.format(periodo.data_inicio, periodo.data_fim))
else:
self.logger.error('O período informado ({} - )'
'choca com períodos já '
'cadastrados para esta comissão'
.format(periodo.data_inicio))
raise ValidationError('O período informado '
'choca com períodos já '
'cadastrados para esta comissão')
@ -387,21 +408,45 @@ 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
class PautaReuniaoFilterSet(django_filters.FilterSet):
class Meta(FilterOverridesMetaMixin):
model = MateriaEmTramitacao
fields = ['materia__tipo', 'materia__ano', 'materia__numero', 'materia__data_apresentacao']
def __init__(self, *args, **kwargs):
super(PautaReuniaoFilterSet, self).__init__(*args, **kwargs)
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__tipo', 4), ('materia__ano', 4), ('materia__numero', 4)])
row2 = to_row([('materia__data_apresentacao', 12)])
self.form.helper = SaplFormHelper()
self.form.helper.form_method = "GET"
self.form.helper.layout = Layout(
Fieldset(
_("Pesquisa de Matérias"), row1, row2,
form_actions(label="Pesquisar")
)
)
class PautaReuniaoForm(forms.ModelForm):
class Meta:
@ -438,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
@ -465,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

37
sapl/comissoes/migrations/0020_auto_20190712_1053.py

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-12 13:53
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.comissoes.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('comissoes', '0019_auto_20181214_1023'),
]
operations = [
migrations.AlterField(
model_name='documentoacessorio',
name='arquivo',
field=models.FileField(blank=True, 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='reuniao',
name='upload_anexo',
field=models.FileField(blank=True, 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, 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, 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'),
),
]

37
sapl/comissoes/migrations/0021_auto_20191001_1115.py

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-01 14:15
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.comissoes.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('comissoes', '0020_auto_20190712_1053'),
]
operations = [
migrations.AlterField(
model_name='documentoacessorio',
name='arquivo',
field=models.FileField(blank=True, max_length=200, 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='reuniao',
name='upload_anexo',
field=models.FileField(blank=True, max_length=200, 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=200, 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=200, 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'),
),
]

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'),
),
]

33
sapl/comissoes/migrations/0023_auto_20191211_1752.py

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-12-11 20:52
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('comissoes', '0022_auto_20191120_1440'),
]
operations = [
migrations.AlterModelOptions(
name='cargocomissao',
options={'ordering': ['id_ordenacao'], 'verbose_name': 'Cargo de Comissão', 'verbose_name_plural': 'Cargos de Comissão'},
),
migrations.AlterModelOptions(
name='participacao',
options={'ordering': ['-titular', 'cargo__id_ordenacao'], 'verbose_name': 'Participação em Comissão', 'verbose_name_plural': 'Participações em Comissão'},
),
migrations.AddField(
model_name='cargocomissao',
name='id_ordenacao',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Posição na Ordenação'),
),
migrations.AlterField(
model_name='cargocomissao',
name='nome',
field=models.CharField(max_length=50, verbose_name='Nome do Cargo'),
),
]

54
sapl/comissoes/models.py

@ -6,7 +6,8 @@ from model_utils import Choices
from sapl.base.models import Autor
from sapl.parlamentares.models import Parlamentar
from sapl.utils import (YES_NO_CHOICES, SaplGenericRelation,
restringe_tipos_de_arquivo_txt, texto_upload_path)
restringe_tipos_de_arquivo_txt, texto_upload_path,
OverwriteStorage)
@reversion.register()
@ -120,13 +121,18 @@ class Periodo(models.Model): # PeriodoCompComissao
@reversion.register()
class CargoComissao(models.Model):
nome = models.CharField(max_length=50, verbose_name=_('Cargo'))
id_ordenacao = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Posição na Ordenação'),
)
nome = models.CharField(max_length=50, verbose_name=_('Nome do Cargo'))
unico = models.BooleanField(
choices=YES_NO_CHOICES, verbose_name=_('Único'), default=True)
choices=YES_NO_CHOICES, verbose_name=_('Único'), default=True
)
class Meta:
verbose_name = _('Cargo de Comissão')
verbose_name_plural = _('Cargos de Comissão')
ordering = ['id_ordenacao']
def __str__(self):
return self.nome
@ -179,6 +185,7 @@ class Participacao(models.Model): # ComposicaoComissao
class Meta:
verbose_name = _('Participação em Comissão')
verbose_name_plural = _('Participações em Comissão')
ordering = ['-titular', 'cargo__id_ordenacao']
def __str__(self):
return '%s : %s' % (self.cargo, self.parlamentar)
@ -235,18 +242,24 @@ 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=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=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=300,
blank=True, null=True,
upload_to=anexo_upload_path,
storage=OverwriteStorage(),
verbose_name=_('Anexo da Reunião'))
class Meta:
@ -257,17 +270,22 @@ class Reuniao(models.Model):
return self.nome
def delete(self, using=None, keep_parents=False):
if self.upload_pauta:
self.upload_pauta.delete()
upload_pauta = self.upload_pauta
upload_ata = self.upload_ata
upload_anexo = self.upload_anexo
if self.upload_ata:
self.upload_ata.delete()
result = super().delete(using=using, keep_parents=keep_parents)
if self.upload_anexo:
self.upload_anexo.delete()
if upload_pauta:
upload_pauta.delete(save=False)
return models.Model.delete(
self, using=using, keep_parents=keep_parents)
if upload_ata:
upload_ata.delete(save=False)
if upload_anexo:
upload_anexo.delete(save=False)
return result
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
@ -305,14 +323,16 @@ 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=300,
blank=True,
null=True,
upload_to=anexo_upload_path,
verbose_name=_('Texto Integral'),
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
data_ultima_atualizacao = models.DateTimeField(
@ -330,11 +350,13 @@ class DocumentoAcessorio(models.Model):
'autor': self.autor}
def delete(self, using=None, keep_parents=False):
if self.arquivo:
self.arquivo.delete()
arquivo = self.arquivo
result = super().delete(using=using, keep_parents=keep_parents)
if arquivo:
arquivo.delete(save=False)
return models.Model.delete(
self, using=using, keep_parents=keep_parents)
return result
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):

11
sapl/comissoes/urls.py

@ -1,8 +1,9 @@
from django.conf.urls import include, url
from sapl.comissoes.views import (CargoCrud, ComissaoCrud, ComposicaoCrud,
DocumentoAcessorioCrud, MateriasTramitacaoListView, ParticipacaoCrud,
PeriodoComposicaoCrud, ReuniaoCrud, TipoComissaoCrud, get_participacoes_comissao,
AdicionaPautaView, RemovePautaView)
from sapl.comissoes.views import (AdicionaPautaView, CargoComissaoCrud, ComissaoCrud,
ComposicaoCrud, DocumentoAcessorioCrud,
MateriasTramitacaoListView, ParticipacaoCrud,
get_participacoes_comissao, PeriodoComposicaoCrud,
RemovePautaView, ReuniaoCrud, TipoComissaoCrud)
from .apps import AppConfig
@ -21,7 +22,7 @@ urlpatterns = [
url(r'^comissao/(?P<pk>\d+)/pauta/add', AdicionaPautaView.as_view(), name='pauta_add'),
url(r'^comissao/(?P<pk>\d+)/pauta/remove', RemovePautaView.as_view(), name='pauta_remove'),
url(r'^sistema/comissao/cargo/', include(CargoCrud.get_urls())),
url(r'^sistema/comissao/cargo/', include(CargoComissaoCrud.get_urls())),
url(r'^sistema/comissao/periodo-composicao/',
include(PeriodoComposicaoCrud.get_urls())),
url(r'^sistema/comissao/tipo/', include(TipoComissaoCrud.get_urls())),

74
sapl/comissoes/views.py

@ -1,28 +1,34 @@
import logging
from django.core.urlresolvers import reverse
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.urlresolvers import reverse
from django.db.models import F
from django.http.response import HttpResponseRedirect, JsonResponse
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import ListView, CreateView, DeleteView
from django.views.generic import CreateView, DeleteView, FormView, ListView
from django.views.generic.base import RedirectView
from django.views.generic.detail import DetailView
from django.views.generic.edit import FormMixin, UpdateView
from django.utils.translation import ugettext_lazy as _
from django_filters.views import FilterView
from sapl.base.models import AppConfig as AppsAppConfig
from sapl.comissoes.apps import AppConfig
from sapl.comissoes.forms import (ComissaoForm, ComposicaoForm,
DocumentoAcessorioCreateForm,
DocumentoAcessorioEditForm,
ParticipacaoCreateForm, ParticipacaoEditForm,
PeriodoForm, ReuniaoForm, PautaReuniaoForm)
from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
MasterDetailCrud,
PermissionRequiredForAppCrudMixin)
from sapl.materia.models import MateriaLegislativa, Tramitacao, PautaReuniao
ParticipacaoCreateForm,
ParticipacaoEditForm,
PautaReuniaoFilterSet, PautaReuniaoForm,
PeriodoForm, ReuniaoForm)
from sapl.crud.base import (Crud, CrudAux, MasterDetailCrud,
PermissionRequiredForAppCrudMixin, RP_DETAIL,
RP_LIST)
from sapl.materia.models import (MateriaEmTramitacao, MateriaLegislativa,
PautaReuniao, Tramitacao)
from sapl.utils import show_results_filter_set
from .models import (CargoComissao, Comissao, Composicao, DocumentoAcessorio,
Participacao, Periodo, Reuniao, TipoComissao)
@ -41,7 +47,10 @@ def pegar_url_reuniao(pk):
url = reverse('sapl.comissoes:reuniao_detail', kwargs={'pk': r_pk})
return url
CargoCrud = CrudAux.build(CargoComissao, 'cargo_comissao')
CargoComissaoCrud = CrudAux.build(
CargoComissao, 'cargo_comissao',
list_field_names=['nome', 'id_ordenacao', 'unico']
)
TipoComissaoCrud = CrudAux.build(
TipoComissao, 'tipo_comissao', list_field_names=[
@ -138,7 +147,7 @@ class ComposicaoCrud(MasterDetailCrud):
context['participacao_set'] = Participacao.objects.filter(
composicao__pk=context['composicao_pk']
).order_by('id')
).order_by('-titular', 'cargo__id_ordenacao', 'id')
return context
@ -165,17 +174,12 @@ class ComissaoCrud(Crud):
return super(Crud.UpdateView, self).form_valid(form)
# Essa função retorna objetos MateriaEmTramitacao
def lista_materias_comissao(comissao_pk):
ts = Tramitacao.objects.order_by(
'materia', '-data_tramitacao', '-id').annotate(
comissao=F('unidade_tramitacao_destino__comissao')).distinct(
'materia').values_list('materia', 'comissao')
ts = [m for (m,c) in ts if c == int(comissao_pk)]
materias = MateriaLegislativa.objects.filter(
pk__in=ts).order_by('tipo', '-ano', '-numero')
materias = MateriaEmTramitacao.objects.filter(
tramitacao__unidade_tramitacao_destino__comissao=comissao_pk
).order_by('materia__tipo', '-materia__ano', '-materia__numero')
return materias
@ -184,13 +188,13 @@ class MateriasTramitacaoListView(ListView):
paginate_by = 10
def get_queryset(self):
return lista_materias_comissao(self.kwargs['pk'])
return list(lista_materias_comissao(self.kwargs['pk']))
def get_context_data(self, **kwargs):
context = super(
MateriasTramitacaoListView, self).get_context_data(**kwargs)
context['object'] = Comissao.objects.get(id=self.kwargs['pk'])
context['qtde'] = self.object_list.count()
context['qtde'] = len(self.object_list)
return context
@ -222,7 +226,7 @@ class ReuniaoCrud(MasterDetailCrud):
context['mats'] = MateriaLegislativa.objects.filter(
pk__in=materias_pk
).order_by('tipo', '-ano', '-numero')
).order_by('tipo', '-ano', 'numero')
context['num_mats'] = len(context['mats'])
context['reuniao_pk'] = self.kwargs['pk']
@ -300,8 +304,8 @@ class RemovePautaView(PermissionRequiredMixin, CreateView):
context['materias'] = MateriaLegislativa.objects.filter(
pk__in=materias_pk
).order_by('tipo', '-ano', '-numero')
context['num_materias'] = len(context['materias'])
).order_by('tipo', '-ano', 'numero')
context['numero_materias'] = len(context['materias'])
return context
@ -323,9 +327,8 @@ class RemovePautaView(PermissionRequiredMixin, CreateView):
return HttpResponseRedirect(success_url)
class AdicionaPautaView(PermissionRequiredMixin, CreateView):
model = PautaReuniao
form_class = PautaReuniaoForm
class AdicionaPautaView(PermissionRequiredMixin, FilterView):
filterset_class = PautaReuniaoFilterSet
template_name = 'comissoes/pauta.html'
permission_required = ('comissoes.add_reuniao', )
@ -340,12 +343,19 @@ class AdicionaPautaView(PermissionRequiredMixin, CreateView):
context['object'] = Reuniao.objects.get(pk=self.kwargs['pk'])
context['root_pk'] = context['object'].comissao.pk
materias_comissao = lista_materias_comissao(context['object'].comissao.pk)
materias_pauta = PautaReuniao.objects.filter(reuniao=context['object'])
qr = self.request.GET.copy()
materias_pauta = PautaReuniao.objects.filter(reuniao=context['object'])
nao_listar = [mp.materia.pk for mp in materias_pauta]
context['materias'] = materias_comissao.exclude(pk__in=nao_listar)
context['num_materias'] = len(context['materias'])
context['object_list'] = context['object_list'].filter(
tramitacao__unidade_tramitacao_destino__comissao=context['root_pk']
).exclude(materia__pk__in=nao_listar).order_by(
"materia__tipo", "-materia__ano", "materia__numero"
)
context['numero_resultados'] = len(context['object_list'])
context['show_results'] = show_results_filter_set(qr)
return context

58
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,22 +796,33 @@ class DispositivoSearchModalForm(Form):
to_column(('num_ta', 4)),
to_column(('ano_ta', 4)),
to_column(('max_results', 4))),
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(
Field(
'texto_dispositivo',
placeholder=_('Digite palavras, letras, '
'números ou algo'
' que estejam no texto.')),
StrictButton(
_('Buscar'),
css_class='btn-busca btn-primary')), 7))
to_column(('tipo_resultado', 3)),
to_column(
(
Div(
Row(
to_column(('tipo_ta', 6)),
to_column(('tipo_model', 6))),
Row(
to_column(('rotulo_dispositivo', 4)),
to_column(
(
FieldWithButtons(
Field(
'texto_dispositivo',
placeholder=_('Digite palavras, letras, '
'números ou algo'
' que estejam no texto.')),
StrictButton(
_('Buscar'),
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?'),
),
]

39
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'),
@ -251,10 +251,14 @@ class TextoArticulado(TimestampedMixin):
'property "epigrafe"')
return str(self.content_object.epigrafe)
else:
return _('%(tipo)s%(numero)s de %(data)s') % {
numero = self.numero
if numero.isnumeric():
numero = '{0:,}'.format(int(self.numero)).replace(',', '.')
return _('%(tipo)s%(numero)s, de %(data)s') % {
'tipo': self.tipo_ta,
'numero': self.numero,
'data': defaultfilters.date(self.data, "d \d\e F \d\e Y")}
'numero': numero,
'data': defaultfilters.date(self.data, "d \d\e F \d\e Y").lower()}
def hash(self):
from django.core import serializers
@ -416,8 +420,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 +1076,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 +1121,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 +1633,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 +1648,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, ]

435
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
@ -195,8 +195,13 @@ class IntegracaoTaView(TemplateView):
return redirect(to=reverse_lazy('sapl.compilacao:ta_text_edit',
kwargs={'ta_id': ta.pk}))
else:
return redirect(to=reverse_lazy('sapl.compilacao:ta_text',
kwargs={'ta_id': ta.pk}))
return redirect(
to='%s?%s' % (
reverse_lazy('sapl.compilacao:ta_text',
kwargs={'ta_id': ta.pk}),
request.META['QUERY_STRING']
)
)
class Meta:
abstract = True
@ -489,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
@ -560,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):
@ -844,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)
@ -1073,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(
@ -1092,11 +1129,17 @@ class TextEditView(CompMixin, TemplateView):
'sapl.compilacao:ta_text_notificacoes', kwargs={
'ta_id': self.object.id}))
self.object.editing_locked = True
self.object.privacidade = STATUS_TA_PUBLIC
self.object.save()
messages.success(request, _(
'Texto Articulado bloqueado com sucesso.'))
if 'lock' in request.GET:
self.object.editing_locked = True
self.object.privacidade = STATUS_TA_PUBLIC
self.object.save()
messages.success(request, _(
'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()
@ -2078,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:
@ -2092,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
@ -2104,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)
@ -2157,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()
@ -2192,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
@ -2219,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:
@ -2257,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:
@ -2309,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,
@ -2619,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,
@ -2631,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
@ -2650,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:
with transaction.atomic():
).values_list('id', flat="True").order_by('ordem')
with transaction.atomic():
for dsp in dsps_ids:
data.update(
self.registra_alteracao(
bloco_alteracao,
@ -2708,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(
@ -2743,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
@ -2760,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:
@ -2975,201 +3049,174 @@ 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)
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))
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_resultado = self.request.GET.get('tipo_resultado', '')
tipo_resultado = '' if tipo_resultado == 'False' else tipo_resultado
tipo_model = self.request.GET['tipo_model']
if not tipo_model:
return resultados(result)
integrations_view_names = get_integrations_view_names()
model_class = None
tipo_ta = TipoTextoArticulado.objects.get(pk=tipo_ta)
if tipo_ta:
tipo_ta = TipoTextoArticulado.objects.get(pk=tipo_ta)
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'):
if (tipo_ta.content_type.model ==
item.model.__name__.lower() and
tipo_ta.content_type.app_label ==
item.model._meta.app_label):
model_class = item.model
model_type_class = item.model_type_foreignkey
tipo_model = item.model_type_foreignkey.objects.get(
pk=tipo_model)
break
model_class = None
for item in integrations_view_names:
if hasattr(item, 'model_type_foreignkey') and\
hasattr(item, 'model'):
if (tipo_ta.content_type.model ==
item.model.__name__.lower() and
tipo_ta.content_type.app_label ==
item.model._meta.app_label):
model_class = item.model
model_type_class = item.model_type_foreignkey
tipo_model = item.model_type_foreignkey.objects.get(
pk=tipo_model)
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 model_class:
return resultados(result)
dts = self.request.GET.get('data_type_selection', '')
df = self.request.GET.get('data_function', '')
column_field = ''
for field in model_class._meta.fields:
if field.related_model == model_type_class:
column_field = field.column
break
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)
{JOIN_TYPE_MODEL_SELECTED}
where d.nivel > 0
{AND_TYPE_MODEL_SELECTED}
{AND_TEXTO_ROTULO}
{AND1_NUMERO}
{AND2_ANO}
{AND3_TIPO_TA}
{AND_CONTROLS}
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 '',
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 ''
)
if not column_field:
return resultados(result)
result = Dispositivo.objects.raw(sql)
r = []
ids = set()
"""
ao integrar um model ao app de compilação, se este model possuir
def proc_dispositivos(ds):
texto_articulado = GenericRelation(
TextoArticulado, related_query_name='texto_articulado')
for d in ds:
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)
if d.id not in ids:
r.append(d)
ids.add(d.id)
for d in result:
if not d.ta.content_object or\
not hasattr(d.ta.content_object, column_field):
continue
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:
if tipo_model.pk == getattr(d.ta.content_object, column_field):
r.append(d)
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):

25
sapl/crispy_layout_mixin.py

@ -55,28 +55,28 @@ def form_actions(more=[Div(css_class='clearfix')],
class SaplFormHelper(FormHelper):
render_hidden_fields = True # default = False
"""
até a release 1.6.1 do django-crispy-forms, os fields em Meta.Fields eram
até a release 1.6.1 do django-crispy-forms, os fields em Meta.Fields eram
renderizados mesmo se não mencionados no helper.
Com esta mudança (https://github.com/django-crispy-forms/django-crispy-forms/commit/6b93e8a362422db8fe54aa731319c7cbc39990ba)
render_hidden_fields foi adicionado uma condição em que a cada
instância do Helper, fosse decidido se os fields não mencionados serião ou
não renderizados...
não renderizados...
O Sapl até este commit: https://github.com/interlegis/sapl/commit/22b87f36ebc8659a6ecaf8831ab0f425206b0993
utilizou o django-crispy-forms na versão 1.6.1, ou seja,
sem a condição render_hidden_fields o que fazia o FormHelper, na 1.6.1
sem a condição render_hidden_fields o que fazia o FormHelper, na 1.6.1
set comportar como se, agora, na 1.7.2 o default fosse True.
Como todos os Forms do Sapl foram construídos assumindo que fields
Como todos os Forms do Sapl foram construídos assumindo que fields
não incluídos explicitamente no Helper, o helper o incluiria implicitamente,
e assim o era, de acordo com commit acima do django-crispy-forms, então
cria-se essa classe:
class SaplFormHelper(FormHelper):
render_hidden_fields = True
render_hidden_fields = True
onde torna o default, antes False, agora = True, o esperado pelos forms do sapl,
e substituí-se todos os FormHelper por SaplFormHelper dentro do projeto Sapl
esta explicação ficará aqui dentro do código, via commit, e na issue #2456.
"""
@ -258,7 +258,12 @@ class CrispyLayoutFormMixin:
if func:
verbose_name, text = getattr(self, func)(obj, fieldname)
else:
verbose_name, text = get_field_display(obj, fieldname)
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)
return {
'id': fieldname,

100
sapl/crud/base.py

@ -4,18 +4,18 @@ from braces.views import FormMessagesMixin
from crispy_forms.bootstrap import FieldWithButtons, StrictButton
from crispy_forms.layout import Field, Layout
from django import forms
from django.conf import settings
from django.conf.urls import url
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models.fields.related import ForeignKey
from django.db.models.fields.related import ForeignKey, ManyToManyField
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,
@ -23,13 +23,14 @@ 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.settings import BASE_DIR
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'
@ -79,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
@ -122,7 +122,6 @@ class SearchMixin(models.Model):
except Exception as e:
username = self.request.user.username
self.logger.error("user=" + username + ". " + str(e))
pass
else:
_self = self
for field in fields:
@ -206,6 +205,7 @@ class PermissionRequiredContainerCrudMixin(PermissionRequiredMixin):
if not self.model.objects.filter(**params).exists():
raise Http404()
elif self.container_field:
container = self.container_field.split('__')
@ -230,14 +230,14 @@ class PermissionRequiredContainerCrudMixin(PermissionRequiredMixin):
return super(PermissionRequiredMixin, self).dispatch(
request, *args, **kwargs)
@cached_property
@property
def container_field(self):
if hasattr(self, 'crud') and not hasattr(self.crud, 'container_field'):
self.crud.container_field = ''
if hasattr(self, 'crud'):
return self.crud.container_field
@cached_property
@property
def container_field_set(self):
if hasattr(self, 'crud') and\
not hasattr(self.crud, 'container_field_set'):
@ -245,7 +245,7 @@ class PermissionRequiredContainerCrudMixin(PermissionRequiredMixin):
if hasattr(self, 'crud'):
return self.crud.container_field_set
@cached_property
@property
def is_contained(self):
return self.container_field_set or self.container_field
@ -383,12 +383,13 @@ class CrudBaseMixin(CrispyLayoutFormMixin):
class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
permission_required = (RP_LIST, )
permission_required = (RP_LIST,)
logger = logging.getLogger(__name__)
@classmethod
def get_url_regex(cls):
return r'^$'
paginate_by = 10
no_entries_msg = _('Nenhum registro encontrado.')
@ -420,7 +421,13 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
if hasattr(f, 'related_model') and f.related_model:
m = f.related_model
if f:
s.append(force_text(f.verbose_name))
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
@ -595,12 +602,12 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
model_ordering = (model_ordering,)
for mo in model_ordering:
if mo not in ordering:
ordering = ordering + (mo, )
ordering = ordering + (mo,)
queryset = queryset.order_by(*ordering)
# print(ordering)
except Exception as e:
print(string_concat(_(
logger.error(string_concat(_(
'ERRO: construção da tupla de ordenação.'), str(e)))
# print(queryset.query)
@ -615,9 +622,38 @@ 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):
permission_required = (RP_ADD, )
FormMessagesMixin, AuditLogMixin, CreateView):
permission_required = (RP_ADD,)
logger = logging.getLogger(__name__)
@classmethod
@ -689,7 +725,7 @@ class CrudCreateView(PermissionRequiredContainerCrudMixin,
class CrudDetailView(PermissionRequiredContainerCrudMixin,
DetailView, MultipleObjectMixin):
permission_required = (RP_DETAIL, )
permission_required = (RP_DETAIL,)
no_entries_msg = _('Nenhum registro Associado.')
paginate_by = 10
logger = logging.getLogger(__name__)
@ -834,8 +870,8 @@ class CrudDetailView(PermissionRequiredContainerCrudMixin,
class CrudUpdateView(PermissionRequiredContainerCrudMixin,
FormMessagesMixin, UpdateView):
permission_required = (RP_CHANGE, )
FormMessagesMixin, AuditLogMixin, UpdateView):
permission_required = (RP_CHANGE,)
logger = logging.getLogger(__name__)
def form_valid(self, form):
@ -865,8 +901,8 @@ class CrudUpdateView(PermissionRequiredContainerCrudMixin,
class CrudDeleteView(PermissionRequiredContainerCrudMixin,
FormMessagesMixin, DeleteView):
permission_required = (RP_DELETE, )
FormMessagesMixin, AuditLogMixin, DeleteView):
permission_required = (RP_DELETE,)
logger = logging.getLogger(__name__)
@classmethod
@ -926,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
@ -963,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)
@ -1108,12 +1148,14 @@ class MasterDetailCrud(Crud):
permission_required = RP_LIST,
logger = logging.getLogger(__name__)
def get(self, request, *args, **kwargs):
return Crud.ListView.get(self, request, *args, **kwargs)
@classmethod
def get_url_regex(cls):
return r'^(?P<pk>\d+)/%s$' % cls.model._meta.model_name
def get_context_data(self, **kwargs):
obj = self.crud if hasattr(self, 'crud') else self
context = CrudListView.get_context_data(self, **kwargs)
@ -1133,7 +1175,12 @@ class MasterDetailCrud(Crud):
else:
parent_model = getattr(
self.model, obj.parent_field).field.related_model
self.model, obj.parent_field)
if isinstance(parent_model.field, (
ForeignKey, ManyToManyField)):
parent_model = parent_model.field.related_model
else:
parent_model = parent_model.rel.related_model
params = {'pk': kwargs['root_pk']}
@ -1165,6 +1212,9 @@ class MasterDetailCrud(Crud):
return qs.filter(**kwargs)
def dispatch(self, request, *args, **kwargs):
return PermissionRequiredMixin.dispatch(self, request, *args, **kwargs)
class CreateView(Crud.CreateView):
permission_required = RP_ADD,
logger = logging.getLogger(__name__)
@ -1229,8 +1279,12 @@ class MasterDetailCrud(Crud):
parent_object = getattr(parent_object, field)
else:
parent_model = getattr(
parent_model, obj.parent_field).field.related_model
parent_model = getattr(self.model, obj.parent_field)
if isinstance(parent_model.field, ForeignKey):
parent_model = parent_model.field.related_model
else:
parent_model = parent_model.rel.related_model
parent_object = parent_model.objects.get(**params)
context['root_pk'] = parent_object.pk

176
sapl/materia/forms.py

@ -1,17 +1,18 @@
import django_filters
import logging
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
from django.core.files.base import File
from django.core.urlresolvers import reverse
from django.db import models, transaction
from django.db.models import Max, Q, F
from django.db.models import F, Max, Q
from django.forms import ModelChoiceField, ModelForm, widgets
from django.forms.forms import Form
from django.forms.models import ModelMultipleChoiceField
@ -21,36 +22,37 @@ from django.utils.encoding import force_text
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
import django_filters
import sapl
from sapl.base.models import AppConfig, Autor, TipoAutor
from sapl.comissoes.models import Comissao, Participacao, Composicao
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)
from sapl.crispy_layout_mixin import 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,
TipoDocumento, TipoProposicao, StatusTramitacao,
MateriaLegislativa, Orgao,
RegimeTramitacao, StatusTramitacao,
TipoDocumento, TipoProposicao,
UnidadeTramitacao)
from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
TipoNormaJuridica)
from sapl.parlamentares.models import Legislatura, Partido, Parlamentar
from sapl.protocoloadm.models import Protocolo, DocumentoAdministrativo, Anexado
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import (YES_NO_CHOICES, SEPARADOR_HASH_PROPOSICAO,
from sapl.protocoloadm.models import (Anexado, DocumentoAdministrativo,
Protocolo)
from sapl.utils import (autor_label, autor_modal,
ChoiceWithoutValidationField,
MateriaPesquisaOrderingFilter, RangeWidgetOverride,
autor_label, autor_modal, gerar_hash_arquivo,
choice_anos_com_materias, FileFieldCheckMixin,
FilterOverridesMetaMixin, gerar_hash_arquivo,
lista_anexados, MateriaPesquisaOrderingFilter,
models_with_gr_for_model, qs_override_django_filter,
choice_anos_com_materias, FilterOverridesMetaMixin, FileFieldCheckMixin,
lista_anexados)
RangeWidgetOverride, SEPARADOR_HASH_PROPOSICAO,
validar_arquivo, YES_NO_CHOICES)
from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial,
DocumentoAcessorio, Numeracao, Proposicao, Relatoria,
TipoMateriaLegislativa, Tramitacao, UnidadeTramitacao)
from .models import (AcompanhamentoMateria, Anexada, Autoria,
DespachoInicial, DocumentoAcessorio, Numeracao,
Proposicao, Relatoria, TipoMateriaLegislativa,
Tramitacao, UnidadeTramitacao)
def CHOICE_TRAMITACAO():
@ -177,7 +179,8 @@ class MateriaLegislativaForm(FileFieldCheckMixin, ModelForm):
'anexadas', 'data_ultima_atualizacao']
widgets = {
'user': forms.HiddenInput(),
'ip': forms.HiddenInput()
'ip': forms.HiddenInput(),
'ultima_edicao': forms.HiddenInput()
}
def __init__(self, *args, **kwargs):
@ -251,9 +254,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
@ -343,8 +345,9 @@ class DocumentoAcessorioForm(FileFieldCheckMixin, ModelForm):
class Meta:
model = DocumentoAcessorio
fields = ['tipo', 'nome', 'data', 'autor', 'ementa', 'arquivo']
fields = ['tipo', 'nome', 'data', 'autor',
'ementa', 'indexacao', 'arquivo']
def clean(self):
super(DocumentoAcessorioForm, self).clean()
@ -353,9 +356,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
@ -462,9 +464,12 @@ class TramitacaoForm(ModelForm):
'data_fim_prazo',
'texto',
'user',
'ip']
'ip',
'ultima_edicao']
widgets = {'user': forms.HiddenInput(),
'ip': forms.HiddenInput()}
'ip': forms.HiddenInput(),
'ultima_edicao': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
super(TramitacaoForm, self).__init__(*args, **kwargs)
@ -577,7 +582,8 @@ class TramitacaoForm(ModelForm):
texto=tramitacao.texto,
data_fim_prazo=tramitacao.data_fim_prazo,
user=tramitacao.user,
ip=tramitacao.ip
ip=tramitacao.ip,
ultima_edicao=tramitacao.ultima_edicao
))
Tramitacao.objects.bulk_create(lista_tramitacao)
@ -619,14 +625,16 @@ class TramitacaoUpdateForm(TramitacaoForm):
'data_fim_prazo',
'texto',
'user',
'ip'
'ip',
'ultima_edicao'
]
widgets = {
'data_encaminhamento': forms.DateInput(format='%d/%m/%Y'),
'data_fim_prazo': forms.DateInput(format='%d/%m/%Y'),
'user': forms.HiddenInput(),
'ip': forms.HiddenInput()
'ip': forms.HiddenInput(),
'ultima_edicao': forms.HiddenInput()
}
def clean(self):
@ -657,6 +665,17 @@ class TramitacaoUpdateForm(TramitacaoForm):
'Você não pode mudar a Unidade de Destino desta '
'tramitação, pois irá conflitar com a Unidade '
'Local da tramitação seguinte')
if not (cd['data_tramitacao'] != obj.data_tramitacao or \
cd['unidade_tramitacao_destino'] != obj.unidade_tramitacao_destino or \
cd['status'] != obj.status or cd['texto'] != obj.texto or \
cd['data_encaminhamento'] != obj.data_encaminhamento or \
cd['data_fim_prazo'] != obj.data_fim_prazo or cd['urgente'] != str(obj.urgente) or \
cd['turno'] != obj.turno):
### Se não ocorreram alterações, o usuário, ip, data e hora da última edição (real) são mantidos
cd['user'] = obj.user
cd['ip'] = obj.ip
cd['ultima_edicao'] = obj.ultima_edicao
cd['data_tramitacao'] = obj.data_tramitacao
cd['unidade_tramitacao_local'] = obj.unidade_tramitacao_local
@ -689,6 +708,7 @@ class TramitacaoUpdateForm(TramitacaoForm):
tram_anexada.data_fim_prazo = nova_tram_principal.data_fim_prazo
tram_anexada.user = nova_tram_principal.user
tram_anexada.ip = nova_tram_principal.ip
tram_anexada.ultima_edicao = nova_tram_principal.ultima_edicao
tram_anexada.save()
ma.em_tramitacao = False if nova_tram_principal.status.indicador == "F" else True
@ -906,17 +926,20 @@ class AnexadaForm(ModelForm):
anexadas_anexada = Anexada.objects.filter(
materia_principal=materia_anexada)
while anexadas_anexada and not ciclico:
anexadas_visitadas = [materia_principal]
while anexadas_anexada:
anexadas = []
for anexa in anexadas_anexada:
if materia_principal == anexa.materia_anexada:
if anexa.materia_anexada in anexadas_visitadas:
ciclico = True
break
else:
anexadas_visitadas.append(anexa.materia_anexada)
for a in Anexada.objects.filter(materia_principal=anexa.materia_anexada):
anexadas.append(a)
if ciclico:
break
anexadas_anexada = anexadas
if ciclico:
@ -995,11 +1018,15 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
'autoria__primeiro_autor',
'autoria__autor__parlamentar_set__filiacao__partido',
'relatoria__parlamentar_id',
'local_origem_externa',
'tramitacao__unidade_tramitacao_destino',
'tramitacao__status',
'materiaassunto__assunto',
'em_tramitacao',
'tipo_origem_externa',
'numero_origem_externa',
'ano_origem_externa',
'data_origem_externa',
'local_origem_externa',
]
def filter_ementa(self, queryset, name, value):
@ -1011,7 +1038,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
return queryset.filter(q)
def __init__(self, *args, **kwargs):
super(MateriaLegislativaFilterSet, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
# self.filters['tipo'].label = 'Tipo de Matéria'
self.filters[
@ -1054,11 +1081,10 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
])
row6 = to_row(
[('relatoria__parlamentar_id', 6),
('local_origem_externa', 6)])
('em_tramitacao', 6)])
row7 = to_row(
[('tramitacao__unidade_tramitacao_destino', 5),
('tramitacao__status', 5),
('em_tramitacao', 2)
[('tramitacao__unidade_tramitacao_destino', 6),
('tramitacao__status', 6),
])
row9 = to_row(
[('materiaassunto__assunto', 6), ('indexacao', 6)])
@ -1069,6 +1095,16 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
('tipo_listagem', 4)
])
row10 = to_row([
('tipo_origem_externa', 4),
('numero_origem_externa', 4),
('ano_origem_externa', 4),
])
row11 = to_row([
('data_origem_externa', 8),
('local_origem_externa', 4)
])
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
@ -1078,6 +1114,9 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
Fieldset(_('Como listar os resultados da pesquisa'),
row8
),
Fieldset(_('Origem externa'),
row10, row11
),
Fieldset(_('Pesquisa Avançada'),
row3,
HTML(autor_label),
@ -1597,9 +1636,12 @@ class TramitacaoEmLoteForm(ModelForm):
'data_fim_prazo',
'texto',
'user',
'ip']
'ip',
'ultima_edicao']
widgets = {'user': forms.HiddenInput(),
'ip': forms.HiddenInput()}
'ip': forms.HiddenInput(),
'ultima_edicao': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
@ -1614,7 +1656,7 @@ class TramitacaoEmLoteForm(ModelForm):
[(ut.pk, ut) for ut in ust if ut.parlamentar])
self.fields['unidade_tramitacao_destino'].choices = unidade_tramitacao_destino
self.fields['urgente'].label = "Urgente? *"
row1 = to_row([
('data_tramitacao', 4),
('data_encaminhamento', 4),
@ -1674,7 +1716,6 @@ class TramitacaoEmLoteForm(ModelForm):
)
)
def clean(self):
cleaned_data = super(TramitacaoEmLoteForm, self).clean()
@ -1722,9 +1763,12 @@ class TramitacaoEmLoteForm(ModelForm):
@transaction.atomic
def save(self, commit=True):
cd = self.cleaned_data
materias = self.initial['materias']
user = self.initial['user'] if 'user' in self.initial else None
ip = self.initial['ip'] if 'ip' in self.initial else ''
ultima_edicao = self.initial['ultima_edicao'] if 'ultima_edicao' in self.initial else ''
tramitar_anexadas = AppConfig.attr('tramitacao_materia')
for mat_id in materias:
mat = MateriaLegislativa.objects.get(id=mat_id)
@ -1740,7 +1784,8 @@ class TramitacaoEmLoteForm(ModelForm):
texto=cd['texto'],
data_fim_prazo=cd['data_fim_prazo'],
user=user,
ip=ip
ip=ip,
ultima_edicao=ultima_edicao
)
mat.em_tramitacao = False if tramitacao.status.indicador == "F" else True
mat.save()
@ -1750,8 +1795,8 @@ class TramitacaoEmLoteForm(ModelForm):
anexadas = lista_anexados(mat)
for ml in anexadas:
if not ml.tramitacao_set.all() \
or ml.tramitacao_set.last() \
.unidade_tramitacao_destino == tramitacao.unidade_tramitacao_local:
or ml.tramitacao_set.last() \
.unidade_tramitacao_destino == tramitacao.unidade_tramitacao_local:
ml.em_tramitacao = False if tramitacao.status.indicador == "F" else True
ml.save()
lista_tramitacao.append(Tramitacao(
@ -1766,9 +1811,10 @@ class TramitacaoEmLoteForm(ModelForm):
texto=tramitacao.texto,
data_fim_prazo=tramitacao.data_fim_prazo,
user=tramitacao.user,
ip=tramitacao.ip
ip=tramitacao.ip,
ultima_edicao=tramitacao.ultima_edicao
))
Tramitacao.objects.bulk_create(lista_tramitacao)
Tramitacao.objects.bulk_create(lista_tramitacao)
return tramitacao
@ -1826,12 +1872,19 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
'ano_materia',
'tipo_texto',
'hash_code',
'numero_materia_futuro']
'numero_materia_futuro',
'user',
'ip',
'ultima_edicao']
widgets = {
'descricao': widgets.Textarea(attrs={'rows': 4}),
'tipo': TipoProposicaoSelect(),
'hash_code': forms.HiddenInput(), }
'hash_code': forms.HiddenInput(),
'user': forms.HiddenInput(),
'ip': forms.HiddenInput(),
'ultima_edicao': forms.HiddenInput()
}
def __init__(self, *args, **kwargs):
self.texto_articulado_proposicao = AppConfig.attr(
@ -1909,9 +1962,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
@ -2440,7 +2492,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

32
sapl/materia/migrations/0052_auto_20190712_1053.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-12 13:53
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.materia.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('materia', '0051_auto_20190703_1414'),
]
operations = [
migrations.AlterField(
model_name='documentoacessorio',
name='arquivo',
field=models.FileField(blank=True, max_length=255, 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='materialegislativa',
name='texto_original',
field=models.FileField(blank=True, 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, 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'),
),
]

28
sapl/materia/migrations/0052_auto_20190731_1554.py

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-31 18:54
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('materia', '0051_auto_20190703_1414'),
]
operations = [
migrations.AddField(
model_name='proposicao',
name='ip',
field=models.CharField(blank=True, default='', max_length=30, verbose_name='IP'),
),
migrations.AddField(
model_name='proposicao',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Usuário'),
),
]

20
sapl/materia/migrations/0053_proposicao_ultima_edicao.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-31 22:26
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0052_auto_20190731_1554'),
]
operations = [
migrations.AddField(
model_name='proposicao',
name='ultima_edicao',
field=models.DateTimeField(blank=True, null=True, verbose_name='Data e Hora da Edição'),
),
]

16
sapl/materia/migrations/0054_merge_20190802_1112.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-08-02 14:12
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0052_auto_20190712_1053'),
('materia', '0053_proposicao_ultima_edicao'),
]
operations = [
]

19
sapl/materia/migrations/0055_auto_20190816_0943.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-08-16 12:43
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0054_merge_20190802_1112'),
]
operations = [
migrations.AlterModelOptions(
name='materialegislativa',
options={'ordering': ['-id'], 'permissions': (('can_access_impressos', 'Can access impressos'),), 'verbose_name': 'Matéria Legislativa', 'verbose_name_plural': 'Matérias Legislativas'},
),
]

25
sapl/materia/migrations/0056_auto_20190829_1206.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-08-29 15:06
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0055_auto_20190816_0943'),
]
operations = [
migrations.AddField(
model_name='materialegislativa',
name='ultima_edicao',
field=models.DateTimeField(blank=True, null=True, verbose_name='Data e Hora da Edição'),
),
migrations.AddField(
model_name='tramitacao',
name='ultima_edicao',
field=models.DateTimeField(blank=True, null=True, verbose_name='Data e Hora da Edição'),
),
]

23
sapl/materia/migrations/0056_popula_materiaemtramitacao.py

@ -0,0 +1,23 @@
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0055_auto_20190816_0943'),
]
operations = [
migrations.RunSQL("""
create or replace view materia_materiaemtramitacao as
select m.id as id,
m.id as materia_id,
t.id as tramitacao_id
from materia_materialegislativa m
inner join materia_tramitacao t on (m.id = t.materia_id)
where t.id = (select max(id) from materia_tramitacao where materia_id = m.id)
order by m.id DESC
"""),
]

25
sapl/materia/migrations/0057_materiaemtramitacao.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-08-27 20:13
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0056_popula_materiaemtramitacao'),
]
operations = [
migrations.CreateModel(
name='MateriaEmTramitacao',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'db_table': 'materia_materiaemtramitacao',
'managed': False,
},
),
]

27
sapl/materia/migrations/0058_auto_20191001_1115.py

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-01 14:15
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.materia.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('materia', '0057_materiaemtramitacao'),
]
operations = [
migrations.AlterField(
model_name='materialegislativa',
name='texto_original',
field=models.FileField(blank=True, max_length=200, 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=200, 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'),
),
]

20
sapl/materia/migrations/0058_auto_20191001_1450.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-01 17:50
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0057_materiaemtramitacao'),
]
operations = [
migrations.AlterField(
model_name='tramitacao',
name='turno',
field=models.CharField(blank=True, choices=[('P', 'Primeiro'), ('S', 'Segundo'), ('U', 'Único'), ('L', 'Suplementar'), ('F', 'Final'), ('A', 'Votação Única em Regime de Urgência'), ('B', '1ª Votação'), ('C', '2ª e 3ª Votações'), ('D', 'Deliberação'), ('G', '1ª e 2ª Votações'), ('E', '1ª e 2ª Votações em Regime de Urgência')], max_length=1, verbose_name='Turno'),
),
]

16
sapl/materia/migrations/0058_merge_20190926_1250.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-09-26 15:50
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0057_materiaemtramitacao'),
('materia', '0056_auto_20190829_1206'),
]
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 = [
]

20
sapl/materia/migrations/0060_auto_20190930_1136.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-09-30 14:36
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0059_merge_20191003_0854'),
]
operations = [
migrations.AlterField(
model_name='tramitacao',
name='texto',
field=models.TextField(blank=True, verbose_name='Texto da Ação'),
),
]

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'),
),
]

16
sapl/materia/migrations/0061_merge_20191009_1814.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-09 21:14
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0060_auto_20190930_1136'),
('materia', '0058_merge_20190926_1250'),
]
operations = [
]

16
sapl/materia/migrations/0062_merge_20191209_1531.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-12-09 18:31
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('materia', '0061_auto_20191120_1440'),
('materia', '0061_merge_20191009_1814'),
]
operations = [
]

21
sapl/materia/migrations/0063_auto_20191220_1351.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-12-20 16:51
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('materia', '0062_merge_20191209_1531'),
]
operations = [
migrations.AlterField(
model_name='pautareuniao',
name='materia',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='materia_set', to='materia.MateriaLegislativa', verbose_name='Matéria'),
),
]

35
sapl/materia/migrations/0064_auto_20200114_1121.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2020-01-14 14:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0063_auto_20191220_1351'),
]
operations = [
migrations.AlterField(
model_name='materialegislativa',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='materialegislativa',
name='ano_origem_externa',
field=models.PositiveSmallIntegerField(blank=True, choices=[(2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], null=True, verbose_name='Ano'),
),
migrations.AlterField(
model_name='numeracao',
name='ano_materia',
field=models.PositiveSmallIntegerField(choices=[(2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='proposicao',
name='ano',
field=models.PositiveSmallIntegerField(blank=True, choices=[(2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], default=None, null=True, verbose_name='Ano'),
),
]

106
sapl/materia/models.py

@ -18,7 +18,8 @@ from sapl.parlamentares.models import Parlamentar
#from sapl.protocoloadm.models import Protocolo
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericForeignKey,
SaplGenericRelation, restringe_tipos_de_arquivo_txt,
texto_upload_path, get_settings_auth_user_model)
texto_upload_path, get_settings_auth_user_model,
OverwriteStorage)
EM_TRAMITACAO = [(1, 'Sim'),
@ -256,10 +257,12 @@ class MateriaLegislativa(models.Model):
'materia_principal',
'materia_anexada'))
texto_original = models.FileField(
max_length=300,
blank=True,
null=True,
upload_to=materia_upload_path,
verbose_name=_('Texto Original'),
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
texto_articulado = GenericRelation(
@ -292,12 +295,16 @@ class MateriaLegislativa(models.Model):
blank=True,
default=''
)
ultima_edicao = models.DateTimeField(
verbose_name=_('Data e Hora da Edição'),
blank=True, null=True
)
class Meta:
verbose_name = _('Matéria Legislativa')
verbose_name_plural = _('Matérias Legislativas')
unique_together = (("tipo", "numero", "ano"),)
ordering = ['-id']
permissions = (("can_access_impressos", "Can access impressos"),)
def __str__(self):
@ -335,16 +342,18 @@ class MateriaLegislativa(models.Model):
return ''
def delete(self, using=None, keep_parents=False):
if self.texto_original:
self.texto_original.delete()
texto_original = self.texto_original
result = super().delete(using=using, keep_parents=keep_parents)
if texto_original:
texto_original.delete(save=False)
for p in self.proposicao.all():
p.conteudo_gerado_related = None
p.cancelado = True
p.save()
return models.Model.delete(
self, using=using, keep_parents=keep_parents)
return result
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
@ -424,6 +433,7 @@ class PautaReuniao(models.Model):
)
materia = models.ForeignKey(
MateriaLegislativa, related_name='materia_set',
on_delete=models.PROTECT,
verbose_name=_('Matéria')
)
@ -436,7 +446,7 @@ class PautaReuniao(models.Model):
' - Matéria: %(materia)s') % {
'reuniao': self.reuniao,
'materia': self.materia
}
}
@reversion.register()
@ -485,7 +495,8 @@ class AssuntoMateria(models.Model):
@reversion.register()
class DespachoInicial(models.Model):
materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE)
comissao = models.ForeignKey(Comissao, on_delete=models.CASCADE, verbose_name="Comissão")
comissao = models.ForeignKey(
Comissao, on_delete=models.CASCADE, verbose_name="Comissão")
class Meta:
verbose_name = _('Despacho Inicial')
@ -529,15 +540,16 @@ 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(),
validators=[restringe_tipos_de_arquivo_txt])
proposicao = GenericRelation(
@ -556,19 +568,22 @@ class DocumentoAcessorio(models.Model):
return _('%(tipo)s - %(nome)s de %(data)s por %(autor)s') % {
'tipo': self.tipo,
'nome': self.nome,
'data': self.data,
'data': formats.date_format(
self.data, "SHORT_DATE_FORMAT") if self.data else '',
'autor': self.autor}
def delete(self, using=None, keep_parents=False):
if self.arquivo:
self.arquivo.delete()
arquivo = self.arquivo
result = super().delete(using=using, keep_parents=keep_parents)
if arquivo:
arquivo.delete(save=False)
for p in self.proposicao.all():
p.conteudo_gerado_related = None
p.save()
return models.Model.delete(
self, using=using, keep_parents=keep_parents)
return result
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
@ -709,11 +724,11 @@ class Relatoria(models.Model):
return _('%(materia)s - %(tipo)s - %(data)s') % {
'materia': self.materia,
'tipo': self.tipo_fim_relatoria,
'data': self.data_designacao_relator.strftime("%d/%m/%Y")}
'data': self.data_designacao_relator.strftime("%d/%m/%Y")}
else:
return _('%(materia)s - %(data)s') % {
'materia': self.materia,
'data': self.data_designacao_relator.strftime("%d/%m/%Y")}
'materia': self.materia,
'data': self.data_designacao_relator.strftime("%d/%m/%Y")}
@reversion.register()
@ -797,10 +812,12 @@ class Proposicao(models.Model):
('I', 'Incorporada')),
verbose_name=_('Status Proposição'))
texto_original = models.FileField(
max_length=300,
upload_to=materia_upload_path,
blank=True,
null=True,
verbose_name=_('Texto Original'),
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
texto_articulado = GenericRelation(
@ -833,6 +850,24 @@ class Proposicao(models.Model):
documento_gerado = models.ForeignKey(
DocumentoAcessorio, blank=True, null=True)"""
user = models.ForeignKey(
get_settings_auth_user_model(),
verbose_name=_('Usuário'),
on_delete=models.PROTECT,
null=True,
blank=True
)
ip = models.CharField(
verbose_name=_('IP'),
max_length=30,
blank=True,
default=''
)
ultima_edicao = models.DateTimeField(
verbose_name=_('Data e Hora da Edição'),
blank=True, null=True
)
@property
def perfis(self):
return self.tipo.perfis.all()
@ -884,11 +919,13 @@ class Proposicao(models.Model):
)}
def delete(self, using=None, keep_parents=False):
if self.texto_original:
self.texto_original.delete()
texto_original = self.texto_original
result = super().delete(using=using, keep_parents=keep_parents)
return models.Model.delete(
self, using=using, keep_parents=keep_parents)
if texto_original:
texto_original.delete(save=False)
return result
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
@ -996,12 +1033,13 @@ class Tramitacao(models.Model):
('U', 'unico', _('Único')),
('L', 'suplementar', _('Suplementar')),
('F', 'final', _('Final')),
('A', 'votacao_unica', _('Votação única em Regime de Urgência')),
('A', 'votacao_unica', _('Votação Única em Regime de Urgência')),
('B', 'primeira_votacao', _('1ª Votação')),
('C', 'segunda_terceira_votacao', _('2ª e 3ª Votação')),
('C', 'segunda_terceira_votacao', _('2ª e 3ª Votações')),
('D', 'deliberacao', _('Deliberação')),
('G', 'primeria_segunda_votacoes', _('1ª e 2ª Votações')),
('E', 'primeira_segunda_votacao_urgencia', _(
'1ª e 2ª votações em regime de urgência'))
'1ª e 2ª Votações em Regime de Urgência')),
)
@ -1038,7 +1076,7 @@ class Tramitacao(models.Model):
turno = models.CharField(
max_length=1, blank=True, verbose_name=_('Turno'),
choices=TURNO_CHOICES)
texto = models.TextField(verbose_name=_('Texto da Ação'))
texto = models.TextField(verbose_name=_('Texto da Ação'), blank=True)
data_fim_prazo = models.DateField(
blank=True, null=True, verbose_name=_('Data Fim Prazo'))
user = models.ForeignKey(get_settings_auth_user_model(),
@ -1050,6 +1088,10 @@ class Tramitacao(models.Model):
max_length=30,
blank=True,
default='')
ultima_edicao = models.DateTimeField(
verbose_name=_('Data e Hora da Edição'),
blank=True, null=True
)
class Meta:
verbose_name = _('Tramitação')
@ -1060,3 +1102,15 @@ class Tramitacao(models.Model):
'materia': self.materia,
'status': self.status,
'data': self.data_tramitacao.strftime("%d/%m/%Y")}
class MateriaEmTramitacao(models.Model):
materia = models.ForeignKey(MateriaLegislativa, on_delete=models.DO_NOTHING)
tramitacao = models.ForeignKey(Tramitacao, on_delete=models.DO_NOTHING)
class Meta:
managed = False
db_table = "materia_materiaemtramitacao"
def __str__(self):
return '{}/{}'.format(self.materia, self.tramitacao)

4
sapl/materia/tests/test_materia.py

@ -521,9 +521,7 @@ def test_form_errors_tramitacao(admin_client):
['Este campo é obrigatório.'])
assert (response.context_data['form'].errors[
'unidade_tramitacao_destino'] == ['Este campo é obrigatório.'])
assert (response.context_data['form'].errors['texto'] ==
['Este campo é obrigatório.'])
@pytest.mark.django_db(transaction=False)
def test_form_errors_relatoria(admin_client):

6
sapl/materia/tests/test_materia_form.py

@ -206,13 +206,12 @@ def test_valida_campos_obrigatorios_tramitacao_form():
errors = form.errors
assert errors['unidade_tramitacao_local'] == [_('Este campo é obrigatório.')]
assert errors['texto'] == [_('Este campo é obrigatório.')]
assert errors['status'] == [_('Este campo é obrigatório.')]
assert errors['data_tramitacao'] == [_('Este campo é obrigatório.')]
assert errors['unidade_tramitacao_destino'] == [_('Este campo é obrigatório.')]
assert errors['urgente'] == [_('Este campo é obrigatório.')]
assert len(errors) == 6
assert len(errors) == 5
@pytest.mark.django_db(transaction=False)
@ -224,13 +223,12 @@ def test_valida_campos_obrigatorios_tramitacao_update_form():
errors = form.errors
assert errors['unidade_tramitacao_local'] == [_('Este campo é obrigatório.')]
assert errors['texto'] == [_('Este campo é obrigatório.')]
assert errors['status'] == [_('Este campo é obrigatório.')]
assert errors['data_tramitacao'] == [_('Este campo é obrigatório.')]
assert errors['unidade_tramitacao_destino'] == [_('Este campo é obrigatório.')]
assert errors['urgente'] == [_('Este campo é obrigatório.')]
assert len(errors) == 6
assert len(errors) == 5
@pytest.mark.django_db(transaction=False)

222
sapl/materia/views.py

@ -1,13 +1,17 @@
from datetime import datetime
import itertools
import logging
import os
from random import choice
import sapl
import shutil
from string import ascii_letters, digits
import tempfile
import weasyprint
from crispy_forms.layout import HTML
from datetime import datetime
from random import choice
from string import ascii_letters, digits
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import permission_required
@ -18,65 +22,53 @@ from django.db.models import Max, Q
from django.http import HttpResponse, JsonResponse
from django.http.response import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.template import RequestContext, loader
from django.template import loader, RequestContext
from django.utils import formats, timezone
from django.utils.translation import ugettext_lazy as _
from django.views.generic import ListView, TemplateView, CreateView, UpdateView
from django.views.generic import CreateView, ListView, TemplateView, UpdateView
from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView
from django_filters.views import FilterView
import weasyprint
import weasyprint
import sapl
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.models import STATUS_TA_IMMUTABLE_RESTRICT, STATUS_TA_PRIVATE
from sapl.compilacao.views import IntegracaoTaView
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions
from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
MasterDetailCrud,
PermissionRequiredForAppCrudMixin, make_pagination)
from sapl.materia.forms import (AnexadaForm, AutoriaForm,
AutoriaMultiCreateForm,
ConfirmarProposicaoForm,
DevolverProposicaoForm, LegislacaoCitadaForm,
OrgaoForm, ProposicaoForm, TipoProposicaoForm,
TramitacaoForm, TramitacaoUpdateForm, MateriaPesquisaSimplesForm,
DespachoInicialCreateForm)
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,)
from sapl.materia.forms import (AnexadaForm, AutoriaForm, AutoriaMultiCreateForm,
ConfirmarProposicaoForm, DevolverProposicaoForm,
DespachoInicialCreateForm, LegislacaoCitadaForm,
MateriaPesquisaSimplesForm, OrgaoForm, ProposicaoForm,
TipoProposicaoForm, TramitacaoForm, TramitacaoUpdateForm)
from sapl.norma.models import LegislacaoCitada
from sapl.parlamentares.models import Legislatura
from sapl.protocoloadm.models import Protocolo
from sapl.settings import MEDIA_ROOT, MAX_DOC_UPLOAD_SIZE
from sapl.utils import (YES_NO_CHOICES, autor_label, autor_modal, SEPARADOR_HASH_PROPOSICAO,
gerar_hash_arquivo, get_base_url, get_client_ip,
get_mime_type_from_file_extension, montar_row_autor,
show_results_filter_set, mail_service_configured, lista_anexados)
from sapl.settings import MAX_DOC_UPLOAD_SIZE, MEDIA_ROOT
from sapl.utils import (autor_label, autor_modal, gerar_hash_arquivo, get_base_url,
get_client_ip, get_mime_type_from_file_extension, lista_anexados,
mail_service_configured, montar_row_autor, SEPARADOR_HASH_PROPOSICAO,
show_results_filter_set, YES_NO_CHOICES)
from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
AnexadaEmLoteFilterSet,
AdicionarVariasAutoriasFilterSet, DespachoInicialForm,
DocumentoAcessorioForm, EtiquetaPesquisaForm,
FichaPesquisaForm, FichaSelecionaForm, MateriaAssuntoForm,
MateriaLegislativaFilterSet, MateriaLegislativaForm,
AnexadaEmLoteFilterSet, AdicionarVariasAutoriasFilterSet,
compara_tramitacoes_mat, DespachoInicialForm, DocumentoAcessorioForm,
EtiquetaPesquisaForm, ExcluirTramitacaoEmLote, FichaPesquisaForm,
FichaSelecionaForm, filtra_tramitacao_destino,
filtra_tramitacao_destino_and_status, filtra_tramitacao_status,
MateriaAssuntoForm, MateriaLegislativaFilterSet, MateriaLegislativaForm,
MateriaSimplificadaForm, PrimeiraTramitacaoEmLoteFilterSet,
ReceberProposicaoForm, RelatoriaForm,
TramitacaoEmLoteFilterSet, UnidadeTramitacaoForm,
filtra_tramitacao_destino,
filtra_tramitacao_destino_and_status,
filtra_tramitacao_status,
ExcluirTramitacaoEmLote, compara_tramitacoes_mat,
TramitacaoEmLoteForm)
from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria,
DespachoInicial, DocumentoAcessorio, MateriaAssunto,
MateriaLegislativa, Numeracao, Orgao, Origem, Proposicao,
RegimeTramitacao, Relatoria, StatusTramitacao,
TipoDocumento, TipoFimRelatoria, TipoMateriaLegislativa,
TipoProposicao, Tramitacao, UnidadeTramitacao)
ReceberProposicaoForm, RelatoriaForm, TramitacaoEmLoteFilterSet,
TramitacaoEmLoteForm, UnidadeTramitacaoForm)
from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria, DespachoInicial,
DocumentoAcessorio, MateriaAssunto, MateriaLegislativa, Numeracao, Orgao,
Origem, Proposicao, RegimeTramitacao, Relatoria, StatusTramitacao,
TipoDocumento, TipoFimRelatoria, TipoMateriaLegislativa, TipoProposicao,
Tramitacao, UnidadeTramitacao)
AssuntoMateriaCrud = CrudAux.build(AssuntoMateria, 'assunto_materia')
@ -231,7 +223,16 @@ class CriarProtocoloMateriaView(CreateView):
return context
def form_valid(self, form):
materia = form.save()
materia = form.save()
materia.user = self.request.user
materia.ip = get_client_ip(self.request)
tz = timezone.get_current_timezone()
materia.ultima_edicao = tz.localize(datetime.now())
materia.save()
username = self.request.user.username
try:
@ -772,12 +773,18 @@ class ProposicaoCrud(Crud):
context['title'] = '%s <small>(%s)</small>' % (
self.object, self.object.autor)
context['user'] = self.request.user
context['proposicao'] = Proposicao.objects.get(
pk=self.kwargs['pk']
)
return context
def get(self, request, *args, **kwargs):
action = request.GET.get('action', '')
username = request.user.username
user = request.user
username = user.username
if not action:
return Crud.DetailView.get(self, request, *args, **kwargs)
@ -785,7 +792,7 @@ class ProposicaoCrud(Crud):
p = Proposicao.objects.get(id=kwargs['pk'])
msg_error = ''
if p:
if p and p.autor.user == user:
if action == 'send':
if p.data_envio and p.data_recebimento:
msg_error = _('Proposição já foi enviada e recebida.')
@ -940,6 +947,40 @@ class ProposicaoCrud(Crud):
class UpdateView(BaseLocalMixin, Crud.UpdateView):
logger = logging.getLogger(__name__)
form_class = ProposicaoForm
def form_valid(self, form):
tz = timezone.get_current_timezone()
objeto_antigo = Proposicao.objects.get(
pk=self.kwargs['pk']
)
dict_objeto_antigo = objeto_antigo.__dict__
tipo_texto = self.request.POST.get('tipo_texto', '')
if tipo_texto=='D' and objeto_antigo.texto_articulado.exists() or tipo_texto=='T' and not objeto_antigo.texto_articulado.exists():
self.object.user = self.request.user
self.object.ip = get_client_ip(self.request)
self.object.ultima_edicao = tz.localize(datetime.now())
self.object.save()
self.object = form.save()
dict_objeto_novo = self.object.__dict__
atributos = [
'tipo_id', 'descricao', 'observacao', 'texto_original',
'materia_de_vinculo_id'
]
for atributo in atributos:
if dict_objeto_antigo[atributo] != dict_objeto_novo[atributo]:
self.object.user = self.request.user
self.object.ip = get_client_ip(self.request)
self.object.ultima_edicao = tz.localize(datetime.now())
self.object.save()
break
return super().form_valid(form)
def _action_is_valid(self, request, *args, **kwargs):
@ -994,6 +1035,17 @@ class ProposicaoCrud(Crud):
form_class = ProposicaoForm
layout_key = None
def get_initial(self):
initial = super().get_initial()
initial['user'] = self.request.user
initial['ip'] = get_client_ip(self.request)
tz = timezone.get_current_timezone()
initial['ultima_edicao'] = tz.localize(datetime.now())
return initial
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['subnav_template_name'] = ''
@ -1192,6 +1244,10 @@ class TramitacaoCrud(MasterDetailCrud):
initial['data_tramitacao'] = timezone.now().date()
initial['ip'] = get_client_ip(self.request)
initial['user'] = self.request.user
tz = timezone.get_current_timezone()
initial['ultima_edicao'] = tz.localize(datetime.now())
return initial
def get_context_data(self, **kwargs):
@ -1258,28 +1314,20 @@ class TramitacaoCrud(MasterDetailCrud):
layout_key = 'TramitacaoUpdate'
def form_valid(self, form):
dict_objeto_antigo = Tramitacao.objects.get(
pk=self.kwargs['pk']).__dict__
self.object = form.save()
dict_objeto_novo = self.object.__dict__
def get_initial(self):
initial = super(UpdateView, self).get_initial()
initial['ip'] = get_client_ip(self.request)
initial['user'] = self.request.user
user = self.request.user
tz = timezone.get_current_timezone()
initial['ultima_edicao'] = tz.localize(datetime.now())
atributos = [
'data_tramitacao', 'unidade_tramitacao_destino_id', 'status_id', 'texto',
'data_encaminhamento', 'data_fim_prazo', 'urgente', 'turno'
]
return initial
# Se não houve qualquer alteração em um dos dados, mantém o usuário
# e ip
for atributo in atributos:
if dict_objeto_antigo[atributo] != dict_objeto_novo[atributo]:
self.object.user = user
self.object.ip = get_client_ip(self.request)
self.object.save()
break
def form_valid(self, form):
self.object = form.save()
user = self.request.user
try:
self.logger.debug("user=" + user.username + ". Tentando enviar Tramitacao (sender={}, post={}, request={}"
@ -1331,7 +1379,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()
@ -1342,11 +1390,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)
@ -1668,6 +1723,9 @@ class MateriaLegislativaCrud(Crud):
initial['user'] = self.request.user
initial['ip'] = get_client_ip(self.request)
tz = timezone.get_current_timezone()
initial['ultima_edicao'] = tz.localize(datetime.now())
return initial
@property
@ -1699,6 +1757,10 @@ class MateriaLegislativaCrud(Crud):
if dict_objeto_antigo[atributo] != dict_objeto_novo[atributo]:
self.object.user = self.request.user
self.object.ip = get_client_ip(self.request)
tz = timezone.get_current_timezone()
self.object.ultima_edicao = tz.localize(datetime.now())
self.object.save()
break
@ -1859,8 +1921,7 @@ class MateriaLegislativaPesquisaView(FilterView):
paginate_by = 50
def get_filterset_kwargs(self, filterset_class):
super(MateriaLegislativaPesquisaView,
self).get_filterset_kwargs(filterset_class)
super().get_filterset_kwargs(filterset_class)
kwargs = {'data': self.request.GET or None}
@ -1916,8 +1977,7 @@ class MateriaLegislativaPesquisaView(FilterView):
return kwargs
def get_context_data(self, **kwargs):
context = super(MateriaLegislativaPesquisaView,
self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context['title'] = _('Pesquisar Matéria Legislativa')
@ -2350,6 +2410,9 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
def post(self, request, *args, **kwargs):
user = request.user
ip = get_client_ip(request)
tz = timezone.get_current_timezone()
ultima_edicao = tz.localize(datetime.now())
materias_ids = request.POST.getlist('materias')
if not materias_ids:
@ -2359,7 +2422,8 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
form = TramitacaoEmLoteForm(request.POST,
initial= {'materias': materias_ids,
'user': user, 'ip':ip})
'user': user, 'ip':ip,
'ultima_edicao': ultima_edicao})
if form.is_valid():
form.save()
@ -2603,9 +2667,9 @@ 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')

50
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():
@ -134,10 +135,13 @@ class NormaJuridicaForm(FileFieldCheckMixin, ModelForm):
'texto_integral',
'assuntos',
'user',
'ip']
'ip',
'ultima_edicao']
widgets = {'assuntos': widgets.CheckboxSelectMultiple,
'user': forms.HiddenInput(),
'ip': forms.HiddenInput()}
'ip': forms.HiddenInput(),
'ultima_edicao': forms.HiddenInput()}
def clean(self):
@ -200,9 +204,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
@ -266,6 +269,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']
@ -273,8 +284,6 @@ class AnexoNormaJuridicaForm(FileFieldCheckMixin, ModelForm):
'norma': forms.HiddenInput(),
}
logger = logging.getLogger(__name__)
def clean(self):
cleaned_data = super(AnexoNormaJuridicaForm, self).clean()
@ -283,9 +292,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

27
sapl/norma/migrations/0026_auto_20190712_1053.py

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-12 13:53
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.norma.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('norma', '0025_auto_20190704_1403'),
]
operations = [
migrations.AlterField(
model_name='anexonormajuridica',
name='anexo_arquivo',
field=models.FileField(blank=True, 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'),
),
migrations.AlterField(
model_name='normajuridica',
name='texto_integral',
field=models.FileField(blank=True, 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'),
),
]

27
sapl/norma/migrations/0027_auto_20191001_1115.py

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-01 14:15
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.norma.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('norma', '0026_auto_20190712_1053'),
]
operations = [
migrations.AlterField(
model_name='anexonormajuridica',
name='anexo_arquivo',
field=models.FileField(blank=True, max_length=200, 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'),
),
migrations.AlterField(
model_name='normajuridica',
name='texto_integral',
field=models.FileField(blank=True, max_length=200, 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'),
),
]

20
sapl/norma/migrations/0027_normajuridica_ultima_edicao.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-08-29 14:41
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0026_auto_20190712_1053'),
]
operations = [
migrations.AddField(
model_name='normajuridica',
name='ultima_edicao',
field=models.DateTimeField(blank=True, null=True, verbose_name='Data e Hora da Edição'),
),
]

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'),
),
]

16
sapl/norma/migrations/0028_merge_20191009_1814.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-09 21:14
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('norma', '0027_normajuridica_ultima_edicao'),
('norma', '0027_auto_20191001_1115'),
]
operations = [
]

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_20191209_1531.py

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

31
sapl/norma/migrations/0031_auto_20200114_1121.py

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2020-01-14 14:21
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.norma.models
class Migration(migrations.Migration):
dependencies = [
('norma', '0030_merge_20191209_1531'),
]
operations = [
migrations.AlterField(
model_name='anexonormajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='normaestatisticas',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], default=sapl.norma.models.get_ano_atual, verbose_name='Ano'),
),
migrations.AlterField(
model_name='normajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
]

51
sapl/norma/models.py

@ -12,7 +12,8 @@ from sapl.materia.models import MateriaLegislativa
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
restringe_tipos_de_arquivo_txt,
texto_upload_path,
get_settings_auth_user_model)
get_settings_auth_user_model,
OverwriteStorage)
@reversion.register()
@ -78,10 +79,12 @@ class NormaJuridica(models.Model):
)
texto_integral = models.FileField(
max_length=300,
blank=True,
null=True,
upload_to=norma_upload_path,
verbose_name=_('Texto Integral'),
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
tipo = models.ForeignKey(
TipoNormaJuridica,
@ -153,6 +156,10 @@ class NormaJuridica(models.Model):
blank=True,
default=''
)
ultima_edicao = models.DateTimeField(
verbose_name=_('Data e Hora da Edição'),
blank=True, null=True
)
class Meta:
verbose_name = _('Norma Jurídica')
@ -174,24 +181,27 @@ 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):
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")}
return self.__str__()
def delete(self, using=None, keep_parents=False):
if self.texto_integral:
self.texto_integral.delete()
texto_integral = self.texto_integral
result = super().delete(using=using, keep_parents=keep_parents)
return models.Model.delete(
self, using=using, keep_parents=keep_parents)
if texto_integral:
texto_integral.delete(save=False)
return result
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
@ -334,8 +344,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()
@ -352,10 +362,12 @@ class AnexoNormaJuridica(models.Model):
max_length=250
)
anexo_arquivo = models.FileField(
max_length=300,
blank=True,
null=True,
upload_to=norma_upload_path,
verbose_name=_('Arquivo Anexo'),
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'),
choices=RANGE_ANOS)
@ -383,4 +395,13 @@ class AnexoNormaJuridica(models.Model):
return models.Model.save(self, force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
update_fields=update_fields)
def delete(self, using=None, keep_parents=False):
anexo_arquivo = self.anexo_arquivo
result = super().delete(using=using, keep_parents=keep_parents)
if anexo_arquivo:
anexo_arquivo.delete(save=False)
return result

38
sapl/norma/views.py

@ -1,3 +1,4 @@
from datetime import datetime
import logging
import re
@ -71,7 +72,7 @@ class NormaRelacionadaCrud(MasterDetailCrud):
class NormaPesquisaView(FilterView):
model = NormaJuridica
filterset_class = NormaFilterSet
paginate_by = 10
paginate_by = 50
def get_queryset(self):
qs = super().get_queryset()
@ -222,6 +223,9 @@ class NormaCrud(Crud):
initial['user'] = self.request.user
initial['ip'] = get_client_ip(self.request)
tz = timezone.get_current_timezone()
initial['ultima_edicao'] = tz.localize(datetime.now())
username = self.request.user.username
try:
self.logger.debug(
@ -267,7 +271,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__
@ -275,24 +280,33 @@ class NormaCrud(Crud):
dict_objeto_novo = self.object.__dict__
atributos = ['tipo_id', 'numero', 'ano', 'data', 'esfera_federacao',
'complemento', 'materia_id', 'numero',
'data_publicacao', 'data_vigencia',
'veiculo_publicacao', 'pagina_inicio_publicacao',
'pagina_fim_publicacao', 'ementa', 'indexacao',
'observacao', 'texto_integral']
'complemento', 'materia_id', 'numero',
'data_publicacao', 'data_vigencia',
'veiculo_publicacao', 'pagina_inicio_publicacao',
'pagina_fim_publicacao', 'ementa', 'indexacao',
'observacao', 'texto_integral']
for atributo in atributos:
if dict_objeto_antigo[atributo] != dict_objeto_novo[atributo]:
self.object.user = self.request.user
self.object.ip = get_client_ip(self.request)
tz = timezone.get_current_timezone()
self.object.ultima_edicao = tz.localize(datetime.now())
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
self.object.ip = get_client_ip(self.request)
tz = timezone.get_current_timezone()
self.object.ultima_edicao = tz.localize(datetime.now())
self.object.save()
return super().form_valid(form)
@ -384,7 +398,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"'
@ -411,7 +426,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

94
sapl/painel/views.py

@ -21,7 +21,7 @@ from sapl.parlamentares.models import Legislatura, Parlamentar, Votante
from sapl.sessao.models import (ExpedienteMateria, OradorExpediente, OrdemDia,
PresencaOrdemDia, RegistroVotacao,
SessaoPlenaria, SessaoPlenariaPresenca,
VotoParlamentar)
VotoParlamentar, RegistroLeitura)
from sapl.utils import filiacao_data, get_client_ip, sort_lista_chave
from .models import Cronometro
@ -290,7 +290,10 @@ def votante_view(request):
@user_passes_test(check_permission)
def painel_view(request, pk):
context = {'head_title': str(_('Painel Plenário')), 'sessao_id': pk}
now = timezone.localtime(timezone.now())
utc_offset = now.utcoffset().total_seconds() / 60
context = {'head_title': str(_('Painel Plenário')), 'sessao_id': pk, 'utc_offset': utc_offset }
return render(request, 'painel/index.html', context)
@ -361,7 +364,7 @@ def get_presentes(pk, response, materia):
else:
presentes = SessaoPlenariaPresenca.objects.filter(
sessao_plenaria_id=pk)
sessao = SessaoPlenaria.objects.get(id=pk)
num_presentes = len(presentes)
data_sessao = sessao.data_inicio
@ -408,6 +411,8 @@ def get_presentes(pk, response, materia):
tipo_votacao = 'Nominal'
elif materia.tipo_votacao == 3:
tipo_votacao = 'Secreta'
elif materia.tipo_votacao == 4:
tipo_votacao = 'Leitura'
response.update({
'tipo_resultado': materia.resultado,
@ -442,15 +447,27 @@ def response_nenhuma_materia(response):
def get_votos(response, materia):
logger = logging.getLogger(__name__)
if type(materia) == OrdemDia:
registro = RegistroVotacao.objects.filter(
ordem=materia, materia=materia.materia).last()
if materia.tipo_votacao != 4:
registro = RegistroVotacao.objects.filter(
ordem=materia, materia=materia.materia).order_by('data_hora').last()
leitura = None
else:
leitura = RegistroLeitura.objects.filter(
ordem=materia, materia=materia.materia).order_by('data_hora').last()
registro = None
tipo = 'ordem'
elif type(materia) == ExpedienteMateria:
registro = RegistroVotacao.objects.filter(
expediente=materia, materia=materia.materia).last()
if materia.tipo_votacao != 4:
registro = RegistroVotacao.objects.filter(
expediente=materia, materia=materia.materia).order_by('data_hora').last()
leitura = None
else:
leitura = RegistroLeitura.objects.filter(
expediente=materia, materia=materia.materia).order_by('data_hora').last()
registro = None
tipo = 'expediente'
if not registro:
if not registro and not leitura:
response.update({
'numero_votos_sim': 0,
'numero_votos_nao': 0,
@ -479,7 +496,15 @@ def get_votos(response, materia):
logger.error("Votos do parlamentar (id={}) não encontrados. Retornado vazio."
.format(p['parlamentar_id']))
response['presentes'][i]['voto'] = ''
elif leitura:
response.update({
'numero_votos_sim': 0,
'numero_votos_nao': 0,
'numero_abstencoes': 0,
'registro': True,
'total_votos': 0,
'tipo_resultado': 'Matéria lida.',
})
else:
total = (registro.numero_votos_sim +
registro.numero_votos_nao +
@ -556,33 +581,38 @@ def get_dados_painel(request, pk):
# Caso não tenha nenhuma aberta,
# a matéria a ser mostrada no Painel deve ser a última votada
last_ordem_voto = RegistroVotacao.objects.filter(
ordem__sessao_plenaria=sessao).last()
ordem__sessao_plenaria=sessao).order_by('data_hora').last()
last_expediente_voto = RegistroVotacao.objects.filter(
expediente__sessao_plenaria=sessao).last()
if last_ordem_voto:
ultima_ordem_votada = last_ordem_voto.ordem
if last_expediente_voto:
ultimo_expediente_votado = last_expediente_voto.expediente
expediente__sessao_plenaria=sessao).order_by('data_hora').last()
if last_ordem_voto or last_expediente_voto:
# Se alguma ordem E algum expediente já tiver sido votado...
if last_ordem_voto and last_expediente_voto:
materia = ultima_ordem_votada\
if last_ordem_voto.pk >= last_expediente_voto.pk\
else ultimo_expediente_votado
# Caso somente um deles tenha resultado, prioriza a Ordem do Dia
elif last_ordem_voto:
materia = ultima_ordem_votada
# Caso a Ordem do dia não tenha resultado, mostra o último expediente
elif last_expediente_voto:
materia = ultimo_expediente_votado
last_ordem_leitura = RegistroLeitura.objects.filter(
ordem__sessao_plenaria=sessao).order_by('data_hora').last()
last_expediente_leitura = RegistroLeitura.objects.filter(
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 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 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 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
if ordem_expediente:
return JsonResponse(get_votos(
get_presentes(pk, response, materia),
materia))
get_presentes(pk, response, ordem_expediente),
ordem_expediente))
# Retorna que não há nenhuma matéria já votada ou aberta
return response_nenhuma_materia(get_presentes(pk, response, None))

12
sapl/parlamentares/forms.py

@ -78,6 +78,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',
@ -138,8 +140,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.'))
@ -236,6 +242,8 @@ class ParlamentarFilterSet(django_filters.FilterSet):
class ParlamentarCreateForm(ParlamentarForm):
logger = logging.getLogger(__name__)
class Meta(ParlamentarForm.Meta):
widgets = {
'fotografia': forms.ClearableFileInput(),

24
sapl/parlamentares/tests/test_parlamentares.py

@ -115,14 +115,18 @@ def test_mandato_submit(admin_client):
admin_client.post(reverse('sapl.parlamentares:mandato_create',
kwargs={'pk': 14}),
{'parlamentar': 14, # hidden field
'legislatura': 5,
'data_inicio_mandato': \
Legislatura.objects.get(id=5).data_inicio,
'data_expedicao_diploma': '2016-03-22',
'observacao': 'Observação do mandato',
'salvar': 'salvar'},
follow=True)
{
'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'
},
follow=True)
mandato = Mandato.objects.first()
assert str(_('Observação do mandato')) == str(_(mandato.observacao))
@ -160,13 +164,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,
})

89
sapl/parlamentares/views.py

@ -427,6 +427,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
@ -514,8 +517,7 @@ class ParlamentarCrud(Crud):
list_field_names = [
'nome_parlamentar',
'filiacao_atual',
'ativo',
'mandato_titular']
'ativo']
class DetailView(Crud.DetailView):
@ -591,8 +593,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(
@ -608,75 +609,12 @@ 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'),
_('Ativo?'), _('Titular?')]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
username = self.request.user.username
# Adiciona legislatura para filtrar parlamentares
legislaturas = Legislatura.objects.all().order_by('-numero')
context['legislaturas'] = legislaturas
context['legislatura_id'] = self.take_legislatura_id()
for row in context['rows']:
# Pega o Parlamentar por meio da pk
parlamentar = Parlamentar.objects.get(
id=(row[0][1].split('/')[-1]))
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
# da legislatura e data de desfiliação deve nula, ou maior,
# ou igual a data de fim da legislatura
try:
self.logger.debug("user=" + username + ". Tentando obter filiação do parlamentar com (data<={} e data_desfiliacao>={}) "
"ou (data<={} e data_desfiliacao=Null))."
.format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim))
filiacao = parlamentar.filiacao_set.get(Q(
data__lte=legislatura.data_fim,
data_desfiliacao__gte=legislatura.data_fim) | Q(
data__lte=legislatura.data_fim,
data_desfiliacao__isnull=True))
# Caso não exista filiação com essas condições
except ObjectDoesNotExist:
self.logger.error("user=" + username + ". Parlamentar com (data<={} e data_desfiliacao>={}) "
"ou (data<={} e data_desfiliacao=Null)) não possui filiação."
.format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim))
row[1] = ('Não possui filiação', None, None)
# Caso exista mais de uma filiação nesse intervalo
# Entretanto, NÃO DEVE OCORRER
except MultipleObjectsReturned:
self.logger.error("user=" + username + ". O Parlamentar com (data<={} e data_desfiliacao>={}) "
"ou (data<={} e data_desfiliacao=Null)) possui duas filiações conflitantes"
.format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim))
row[1] = (
'O Parlamentar possui duas filiações conflitantes',
None, None)
# Caso encontre UMA filiação nessas condições
else:
self.logger.debug("user=" + username +
". Filiação encontrada com sucesso.")
row[1] = (filiacao.partido.sigla, None, None)
return context
class ParlamentarMateriasView(FormView):
template_name = "parlamentares/materias.html"
@ -870,14 +808,13 @@ def altera_field_mesa(request):
# atual deve ser a primeira daquela legislatura
else:
year = timezone.now().year
try:
logger.debug(
"user=" + username + ". Tentando obter id de sessoes com data_inicio.ano={}.".format(year))
sessao_selecionada = sessoes.get(data_inicio__year=year).id
except ObjectDoesNotExist:
logger.debug(
"user={}. Tentando obter id de sessoes com data_inicio.ano={}.".format(username, year))
sessao_selecionada = sessoes.filter(data_inicio__year=year).first()
if not sessao_selecionada:
logger.error("user=" + username + ". Id de sessoes com data_inicio.ano={} não encontrado. "
"Selecionado o ID da primeira sessão.".format(year))
sessao_selecionada = sessoes.first().id
"Selecionado o ID da primeira sessão.".format(year))
sessao_selecionada = sessoes.first()
# Atualiza os componentes da view após a mudança
composicao_mesa = ComposicaoMesa.objects.filter(
@ -908,7 +845,7 @@ def altera_field_mesa(request):
'lista_composicao': lista_composicao,
'lista_parlamentares': lista_parlamentares,
'lista_cargos': lista_cargos,
'sessao_selecionada': sessao_selecionada,
'sessao_selecionada': sessao_selecionada.id,
'msg': ('', 1)})

101
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'),
@ -302,9 +306,7 @@ class AnularProtocoloAdmForm(ModelForm):
class Meta:
model = Protocolo
fields = ['numero',
'ano',
'justificativa_anulacao',
fields = ['justificativa_anulacao',
'anulado',
'user_anulacao',
'ip_anulacao',
@ -666,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
@ -688,9 +689,12 @@ class TramitacaoAdmForm(ModelForm):
'data_fim_prazo',
'texto',
'user',
'ip']
'ip',
'ultima_edicao']
widgets = {'user': forms.HiddenInput(),
'ip': forms.HiddenInput()}
'ip': forms.HiddenInput(),
'ultima_edicao': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
@ -805,7 +809,8 @@ class TramitacaoAdmForm(ModelForm):
texto=tramitacao.texto,
data_fim_prazo=tramitacao.data_fim_prazo,
user=tramitacao.user,
ip=tramitacao.ip
ip=tramitacao.ip,
ultima_edicao=tramitacao.ultima_edicao
))
TramitacaoAdministrativo.objects.bulk_create(lista_tramitacao)
@ -819,7 +824,7 @@ def compara_tramitacoes_doc(tramitacao1, tramitacao2):
if not tramitacao1 or not tramitacao2:
return False
lst_items = ['id', 'documento_id', 'timestamp']
lst_items = ['id', 'documento_id', 'timestamp', 'ultima_edicao']
values = [(k,v) for k,v in tramitacao1.__dict__.items() if ((k not in lst_items) and (k[0] != '_'))]
other_values = [(k,v) for k,v in tramitacao2.__dict__.items() if (k not in lst_items and k[0] != '_')]
return values == other_values
@ -846,9 +851,12 @@ class TramitacaoAdmEditForm(TramitacaoAdmForm):
'data_fim_prazo',
'texto',
'user',
'ip']
'ip',
'ultima_edicao']
widgets = {'user': forms.HiddenInput(),
'ip': forms.HiddenInput()}
'ip': forms.HiddenInput(),
'ultima_edicao': forms.HiddenInput()}
def clean(self):
super(TramitacaoAdmEditForm, self).clean()
@ -885,6 +893,7 @@ class TramitacaoAdmEditForm(TramitacaoAdmForm):
cd['data_fim_prazo'] != obj.data_fim_prazo):
cd['user'] = obj.user
cd['ip'] = obj.ip
cd['ultima_edicao'] = obj.ultima_edicao
cd['data_tramitacao'] = obj.data_tramitacao
cd['unidade_tramitacao_local'] = obj.unidade_tramitacao_local
@ -916,6 +925,7 @@ class TramitacaoAdmEditForm(TramitacaoAdmForm):
tram_anexada.data_fim_prazo = nova_tram_principal.data_fim_prazo
tram_anexada.user = nova_tram_principal.user
tram_anexada.ip = nova_tram_principal.ip
tram_anexada.ultima_edicao = nova_tram_principal.ultima_edicao
tram_anexada.save()
da.tramitacao = False if nova_tram_principal.status.indicador == "F" else True
@ -1083,10 +1093,17 @@ class DocumentoAdministrativoForm(FileFieldCheckMixin, ModelForm):
'observacao',
'texto_integral',
'protocolo',
'restrito'
'restrito',
'user',
'ip',
'ultima_edicao'
]
widgets = {'protocolo': forms.HiddenInput()}
widgets = {'protocolo': forms.HiddenInput(),
'user': forms.HiddenInput(),
'ip': forms.HiddenInput(),
'ultima_edicao': forms.HiddenInput()
}
def clean(self):
super(DocumentoAdministrativoForm, self).clean()
@ -1159,9 +1176,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
@ -1489,9 +1505,12 @@ class TramitacaoEmLoteAdmForm(ModelForm):
'data_fim_prazo',
'texto',
'user',
'ip']
'ip',
'ultima_edicao']
widgets = {'user': forms.HiddenInput(),
'ip': forms.HiddenInput()}
'ip': forms.HiddenInput(),
'ultima_edicao': forms.HiddenInput()}
def __init__(self, *args, **kwargs):
@ -1565,7 +1584,6 @@ class TramitacaoEmLoteAdmForm(ModelForm):
)
)
def clean(self):
cleaned_data = super(TramitacaoEmLoteAdmForm, self).clean()
@ -1613,9 +1631,12 @@ class TramitacaoEmLoteAdmForm(ModelForm):
@transaction.atomic
def save(self, commit=True):
cd = self.cleaned_data
documentos = self.initial['documentos']
user = self.initial['user'] if 'user' in self.initial else None
ip = self.initial['ip'] if 'ip' in self.initial else ''
ultima_edicao = self.initial['ultima_edicao'] if 'ultima_edicao' in self.initial else ''
tramitar_anexados = AppConfig.attr('tramitacao_documento')
for doc_id in documentos:
doc = DocumentoAdministrativo.objects.get(id=doc_id)
@ -1630,7 +1651,8 @@ class TramitacaoEmLoteAdmForm(ModelForm):
texto=cd['texto'],
data_fim_prazo=cd['data_fim_prazo'],
user=user,
ip=ip
ip=ip,
ultima_edicao=ultima_edicao
)
doc.tramitacao = False if tramitacao.status.indicador == "F" else True
doc.save()
@ -1655,9 +1677,10 @@ class TramitacaoEmLoteAdmForm(ModelForm):
texto=tramitacao.texto,
data_fim_prazo=tramitacao.data_fim_prazo,
user=tramitacao.user,
ip=tramitacao.ip
ip=tramitacao.ip,
ultima_edicao=tramitacao.ultima_edicao
))
TramitacaoAdministrativo.objects.bulk_create(lista_tramitacao)
TramitacaoAdministrativo.objects.bulk_create(lista_tramitacao)
return tramitacao

26
sapl/protocoloadm/migrations/0022_auto_20190712_1053.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-12 13:53
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0021_merge_20190429_1531'),
]
operations = [
migrations.AlterField(
model_name='documentoacessorioadministrativo',
name='arquivo',
field=models.FileField(blank=True, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.utils.texto_upload_path, verbose_name='Arquivo'),
),
migrations.AlterField(
model_name='documentoadministrativo',
name='texto_integral',
field=models.FileField(blank=True, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.utils.texto_upload_path, verbose_name='Texto Integral'),
),
]

32
sapl/protocoloadm/migrations/0022_deduplica_protocolos.py

@ -0,0 +1,32 @@
from __future__ import unicode_literals
from django.db import migrations
def deduplica_protocolos(apps, schema_editor):
from sapl.base.views import protocolos_duplicados
Protocolo = apps.get_model('protocoloadm', 'Protocolo')
DocumentoAdministrativo = apps.get_model('protocoloadm', 'DocumentoAdministrativo')
protocolos = protocolos_duplicados()
for p in protocolos:
principal = Protocolo.objects.filter(numero=p['numero'], ano=p['ano']).order_by('-id').first()
replicas = Protocolo.objects.filter(numero=p['numero'], ano=p['ano']).exclude(id=principal.id)
for r in replicas:
documentos = DocumentoAdministrativo.objects.filter(protocolo_id=r.id)
for d in documentos:
d.protocolo = principal
d.save()
replicas.delete()
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0021_merge_20190429_1531'),
]
operations = [
migrations.RunPython(deduplica_protocolos)
]

19
sapl/protocoloadm/migrations/0023_auto_20190711_1755.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-07-11 20:55
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0022_deduplica_protocolos'),
]
operations = [
migrations.AlterUniqueTogether(
name='protocolo',
unique_together=set([('numero', 'ano')]),
),
]

16
sapl/protocoloadm/migrations/0023_merge_20190802_1112.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-08-02 14:12
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0022_auto_20190712_1053'),
('protocoloadm', '0022_auto_20190715_1101'),
]
operations = [
]

16
sapl/protocoloadm/migrations/0024_merge_20190821_1418.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-08-21 17:18
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0023_auto_20190711_1755'),
('protocoloadm', '0023_merge_20190802_1112'),
]
operations = [
]

20
sapl/protocoloadm/migrations/0024_tramitacaoadministrativo_ultima_edicao.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-08-08 21:14
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0023_merge_20190802_1112'),
]
operations = [
migrations.AddField(
model_name='tramitacaoadministrativo',
name='ultima_edicao',
field=models.DateField(blank=True, null=True, verbose_name='Data e Hora da Edição'),
),
]

20
sapl/protocoloadm/migrations/0025_auto_20190815_1539.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.23 on 2019-08-15 18:39
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0024_tramitacaoadministrativo_ultima_edicao'),
]
operations = [
migrations.AlterField(
model_name='tramitacaoadministrativo',
name='ultima_edicao',
field=models.DateTimeField(blank=True, null=True, verbose_name='Data e Hora da Edição'),
),
]

26
sapl/protocoloadm/migrations/0025_auto_20191001_1115.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-01 14:15
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0024_merge_20190821_1418'),
]
operations = [
migrations.AlterField(
model_name='documentoacessorioadministrativo',
name='arquivo',
field=models.FileField(blank=True, max_length=200, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.utils.texto_upload_path, verbose_name='Arquivo'),
),
migrations.AlterField(
model_name='documentoadministrativo',
name='texto_integral',
field=models.FileField(blank=True, max_length=200, null=True, storage=sapl.utils.OverwriteStorage(), upload_to=sapl.utils.texto_upload_path, verbose_name='Texto Integral'),
),
]

33
sapl/protocoloadm/migrations/0026_auto_20190815_1737.py

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.23 on 2019-08-15 20:37
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('protocoloadm', '0025_auto_20190815_1539'),
]
operations = [
migrations.AddField(
model_name='documentoadministrativo',
name='ip',
field=models.CharField(blank=True, default='', max_length=30, verbose_name='IP'),
),
migrations.AddField(
model_name='documentoadministrativo',
name='ultima_edicao',
field=models.DateTimeField(blank=True, null=True, verbose_name='Data e Hora da Edição'),
),
migrations.AddField(
model_name='documentoadministrativo',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Usuário'),
),
]

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'),
),
]

16
sapl/protocoloadm/migrations/0027_merge_20190926_1250.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-09-26 15:50
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0026_auto_20190815_1737'),
('protocoloadm', '0024_merge_20190821_1418'),
]
operations = [
]

16
sapl/protocoloadm/migrations/0028_merge_20191009_1814.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-09 21:14
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0027_merge_20190926_1250'),
('protocoloadm', '0025_auto_20191001_1115'),
]
operations = [
]

16
sapl/protocoloadm/migrations/0029_merge_20191209_1531.py

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-12-09 18:31
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0028_merge_20191009_1814'),
('protocoloadm', '0026_auto_20191120_1440'),
]
operations = [
]

25
sapl/protocoloadm/migrations/0030_auto_20200114_1121.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2020-01-14 14:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0029_merge_20191209_1531'),
]
operations = [
migrations.AlterField(
model_name='documentoadministrativo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano'),
),
migrations.AlterField(
model_name='protocolo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(2021, 2021), (2020, 2020), (2019, 2019), (2018, 2018), (2017, 2017), (2016, 2016), (2015, 2015), (2014, 2014), (2013, 2013), (2012, 2012), (2011, 2011), (2010, 2010), (2009, 2009), (2008, 2008), (2007, 2007), (2006, 2006), (2005, 2005), (2004, 2004), (2003, 2003), (2002, 2002), (2001, 2001), (2000, 2000), (1999, 1999), (1998, 1998), (1997, 1997), (1996, 1996), (1995, 1995), (1994, 1994), (1993, 1993), (1992, 1992), (1991, 1991), (1990, 1990), (1989, 1989), (1988, 1988), (1987, 1987), (1986, 1986), (1985, 1985), (1984, 1984), (1983, 1983), (1982, 1982), (1981, 1981), (1980, 1980), (1979, 1979), (1978, 1978), (1977, 1977), (1976, 1976), (1975, 1975), (1974, 1974), (1973, 1973), (1972, 1972), (1971, 1971), (1970, 1970), (1969, 1969), (1968, 1968), (1967, 1967), (1966, 1966), (1965, 1965), (1964, 1964), (1963, 1963), (1962, 1962), (1961, 1961), (1960, 1960), (1959, 1959), (1958, 1958), (1957, 1957), (1956, 1956), (1955, 1955), (1954, 1954), (1953, 1953), (1952, 1952), (1951, 1951), (1950, 1950), (1949, 1949), (1948, 1948), (1947, 1947), (1946, 1946), (1945, 1945), (1944, 1944), (1943, 1943), (1942, 1942), (1941, 1941), (1940, 1940), (1939, 1939), (1938, 1938), (1937, 1937), (1936, 1936), (1935, 1935), (1934, 1934), (1933, 1933), (1932, 1932), (1931, 1931), (1930, 1930), (1929, 1929), (1928, 1928), (1927, 1927), (1926, 1926), (1925, 1925), (1924, 1924), (1923, 1923), (1922, 1922), (1921, 1921), (1920, 1920), (1919, 1919), (1918, 1918), (1917, 1917), (1916, 1916), (1915, 1915), (1914, 1914), (1913, 1913), (1912, 1912), (1911, 1911), (1910, 1910), (1909, 1909), (1908, 1908), (1907, 1907), (1906, 1906), (1905, 1905), (1904, 1904), (1903, 1903), (1902, 1902), (1901, 1901), (1900, 1900), (1899, 1899), (1898, 1898), (1897, 1897), (1896, 1896), (1895, 1895), (1894, 1894), (1893, 1893), (1892, 1892), (1891, 1891), (1890, 1890)], verbose_name='Ano do Protocolo'),
),
]

52
sapl/protocoloadm/models.py

@ -7,7 +7,8 @@ import reversion
from sapl.base.models import Autor
from sapl.materia.models import TipoMateriaLegislativa, UnidadeTramitacao
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, texto_upload_path,
get_settings_auth_user_model)
get_settings_auth_user_model,
OverwriteStorage)
@reversion.register()
@ -119,6 +120,7 @@ class Protocolo(models.Model):
permissions = (
('action_anular_protocolo', _('Permissão para Anular Protocolo')),
)
unique_together = ('numero', 'ano',)
def __str__(self):
return _('%(numero)s/%(ano)s') % {
@ -162,8 +164,10 @@ class DocumentoAdministrativo(models.Model):
observacao = models.TextField(
blank=True, verbose_name=_('Observação'))
texto_integral = models.FileField(
max_length=300,
blank=True,
null=True,
storage=OverwriteStorage(),
upload_to=texto_upload_path,
verbose_name=_('Texto Integral'))
restrito = models.BooleanField(default=False,
@ -182,6 +186,24 @@ class DocumentoAdministrativo(models.Model):
)
)
user = models.ForeignKey(
get_settings_auth_user_model(),
verbose_name=_('Usuário'),
on_delete=models.PROTECT,
null=True,
blank=True
)
ip = models.CharField(
verbose_name=_('IP'),
max_length=30,
blank=True,
default=''
)
ultima_edicao = models.DateTimeField(
verbose_name=_('Data e Hora da Edição'),
blank=True, null=True
)
class Meta:
verbose_name = _('Documento Administrativo')
verbose_name_plural = _('Documentos Administrativos')
@ -192,11 +214,13 @@ class DocumentoAdministrativo(models.Model):
}
def delete(self, using=None, keep_parents=False):
if self.texto_integral:
self.texto_integral.delete()
texto_integral = self.texto_integral
result = super().delete(using=using, keep_parents=keep_parents)
if texto_integral:
texto_integral.delete(save=False)
return models.Model.delete(
self, using=using, keep_parents=keep_parents)
return result
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
@ -226,13 +250,15 @@ class DocumentoAcessorioAdministrativo(models.Model):
verbose_name=_('Tipo'))
nome = models.CharField(max_length=30, verbose_name=_('Nome'))
arquivo = models.FileField(
max_length=300,
blank=True,
null=True,
upload_to=texto_upload_path,
storage=OverwriteStorage(),
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)
@ -245,11 +271,13 @@ class DocumentoAcessorioAdministrativo(models.Model):
return self.nome
def delete(self, using=None, keep_parents=False):
if self.arquivo:
self.arquivo.delete()
arquivo = self.arquivo
result = super().delete(using=using, keep_parents=keep_parents)
return models.Model.delete(
self, using=using, keep_parents=keep_parents)
if arquivo:
arquivo.delete(save=False)
return result
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
@ -330,6 +358,10 @@ class TramitacaoAdministrativo(models.Model):
max_length=30,
blank=True,
default='')
ultima_edicao = models.DateTimeField(
verbose_name=_('Data e Hora da Edição'),
blank=True, null=True
)
class Meta:
verbose_name = _('Tramitação de Documento Administrativo')

274
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)
@ -34,10 +34,10 @@ from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa, Unid
from sapl.materia.views import gerar_pdf_impressos
from sapl.parlamentares.models import Legislatura, Parlamentar
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)
from sapl.relatorios.views import relatorio_doc_administrativos
show_results_filter_set, mail_service_configured, from_date_to_datetime_utc)
from .forms import (AcompanhamentoDocumentoForm, AnularProtocoloAdmForm,
DocumentoAcessorioAdministrativoForm,
@ -290,7 +290,7 @@ class AcompanhamentoDocumentoView(CreateView):
de mensagens e clique no link que nós enviamos para \
confirmar o acompanhamento deste documento.')
messages.add_message(request, messages.SUCCESS, msg)
# Caso esse Acompanhamento já exista
# avisa ao usuário que esse documento já está sendo acompanhado
else:
@ -302,7 +302,7 @@ class AcompanhamentoDocumentoView(CreateView):
return self.render_to_response(
{'form': form,
'documento': documento,
})
})
return HttpResponseRedirect(self.get_success_url())
else:
return self.render_to_response(
@ -350,6 +350,17 @@ class DocumentoAdministrativoCrud(Crud):
form_class = DocumentoAdministrativoForm
layout_key = None
def get_initial(self):
initial = super().get_initial()
initial['user'] = self.request.user
initial['ip'] = get_client_ip(self.request)
tz = timezone.get_current_timezone()
initial['ultima_edicao'] = tz.localize(datetime.now())
return initial
@property
def cancel_url(self):
return self.search_url
@ -358,6 +369,33 @@ class DocumentoAdministrativoCrud(Crud):
form_class = DocumentoAdministrativoForm
layout_key = None
def form_valid(self, form):
dict_objeto_antigo = DocumentoAdministrativo.objects.get(
pk=self.kwargs['pk']
).__dict__
self.object = form.save()
dict_objeto_novo = self.object.__dict__
atributos = [
'tipo_id', 'ano', 'numero', 'data', 'protocolo_id', 'assunto',
'interessado', 'tramitacao', 'restrito', 'texto_integral','numero_externo',
'dias_prazo', 'data_fim_prazo', 'observacao'
]
for atributo in atributos:
if dict_objeto_antigo[atributo] != dict_objeto_novo[atributo]:
self.object.user = self.request.user
self.object.ip = get_client_ip(self.request)
tz = timezone.get_current_timezone()
self.object.ultima_edicao = tz.localize(datetime.now())
self.object.save()
break
return super().form_valid(form)
def get_initial(self):
if self.object.protocolo:
p = self.object.protocolo
@ -372,14 +410,24 @@ class DocumentoAdministrativoCrud(Crud):
if documento.restrito and self.request.user.is_anonymous():
return redirect('/')
return super(Crud.DetailView, self).get(args, kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
self.layout_display[0]['rows'][-1][0]['text'] = (
'<a href="%s"></a>' % reverse(
'sapl.protocoloadm:doc_texto_integral',
kwargs={'pk': self.object.pk}))
context['user'] = self.request.user
context['documentoadministrativo'] = DocumentoAdministrativo.objects.get(
pk=self.kwargs['pk']
)
return context
def urlize(self, obj, fieldname):
a = '<a href="%s">%s</a>' % (
reverse(
'sapl.protocoloadm:doc_texto_integral',
kwargs={'pk': obj.pk}),
obj.texto_integral.name.split('/')[-1])
return obj.texto_integral.field.verbose_name, a
class DeleteView(Crud.DeleteView):
@ -551,7 +599,7 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
).sequencia_numeracao_protocolo
if not numeracao:
self.logger.error("user=" + username + ". É preciso definir a sequencia de "
"numeração na tabelas auxiliares! " + str(e))
"numeração na tabelas auxiliares! ")
msg = _('É preciso definir a sequencia de ' +
'numeração na tabelas auxiliares!')
messages.add_message(self.request, messages.ERROR, msg)
@ -564,12 +612,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(
data__gte=data_inicio,
data__lte=data_fim).aggregate(
Max('numero'))
Q(data__isnull=False,
data__gte=data_inicio,
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'))
@ -633,6 +693,12 @@ class CriarDocumentoProtocolo(PermissionRequiredMixin, CreateView):
doc['assunto'] = protocolo.assunto_ementa
doc['interessado'] = protocolo.interessado
doc['numero'] = numero_max + 1 if numero_max else 1
doc['user'] = self.request.user
doc['ip'] = get_client_ip(self.request)
tz = timezone.get_current_timezone()
doc['ultima_edicao'] = tz.localize(datetime.now())
return doc
@ -700,6 +766,13 @@ class ComprovanteProtocoloView(PermissionRequiredMixin, TemplateView):
autenticacao = str(protocolo.tipo_processo) + \
data + str(protocolo.numero).zfill(6)
if protocolo.tipo_materia:
materia = MateriaLegislativa.objects.filter(
numero_protocolo=protocolo.numero,
ano=protocolo.ano).first()
if materia:
context['materia'] = materia.numero
context.update({"protocolo": protocolo,
"barcode": barcode,
"autenticacao": autenticacao})
@ -753,10 +826,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(
data__gte=data_inicio,
data__lte=data_fim).aggregate(
Max('numero'))
Q(data__isnull=False,
data__gte=data_inicio,
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'))
@ -798,12 +883,22 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
protocolo.user_data_hora_manual = ''
protocolo.ip_data_hora_manual = ''
protocolo.save()
data = form.cleaned_data
if data['vincular_materia'] == 'True':
materia = MateriaLegislativa.objects.get(ano=data['ano_materia'],
numero=data['numero_materia'],
tipo=data['tipo_materia'])
materia = MateriaLegislativa.objects.get(
ano=data['ano_materia'],
numero=data['numero_materia'],
tipo=data['tipo_materia']
)
materia.numero_protocolo = protocolo.numero
materia.user = self.request.user
materia.ip = get_client_ip(self.request)
tz = timezone.get_current_timezone()
materia.ultima_edicao = tz.localize(datetime.now())
materia.save()
return redirect(self.get_success_url(protocolo))
@ -910,7 +1005,7 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
return context
def get(self, request, *args, **kwargs):
@ -935,21 +1030,22 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
length = self.object_list.filter(restrito=False).count()
else:
length = self.object_list.count()
is_relatorio = url!='' and request.GET.get('relatorio',None)
self.paginate_by = None if is_relatorio else self.paginate_by
is_relatorio = url != '' and request.GET.get('relatorio', None)
self.paginate_by = None if is_relatorio else self.paginate_by
context = self.get_context_data(filter=self.filterset,
filter_url=url,
numero_res=length
)
context['show_results'] = show_results_filter_set(
self.request.GET.copy())
if is_relatorio:
return relatorio_doc_administrativos(request,context)
else:
return relatorio_doc_administrativos(request, context)
else:
return self.render_to_response(context)
class AnexadoCrud(MasterDetailCrud):
model = Anexado
parent_field = 'documento_principal'
@ -987,7 +1083,7 @@ class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView):
def get_context_data(self, **kwargs):
context = super(
DocumentoAnexadoEmLoteView, self
).get_context_data(**kwargs)
).get_context_data(**kwargs)
context['root_pk'] = self.kwargs['pk']
@ -997,17 +1093,17 @@ class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView):
# Verifica se os campos foram preenchidos
if not self.request.GET.get('tipo', " "):
msg =_('Por favor, selecione um tipo de documento.')
msg = _('Por favor, selecione um tipo de documento.')
messages.add_message(self.request, messages.ERROR, msg)
if not self.request.GET.get('data_0', " ") or not self.request.GET.get('data_1', " "):
msg =_('Por favor, preencha as datas.')
msg = _('Por favor, preencha as datas.')
messages.add_message(self.request, messages.ERROR, msg)
return context
if not self.request.GET.get('data_0', " ") or not self.request.GET.get('data_1', " "):
msg =_('Por favor, preencha as datas.')
msg = _('Por favor, preencha as datas.')
messages.add_message(self.request, messages.ERROR, msg)
return context
@ -1019,17 +1115,19 @@ class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView):
context['object_list'] = []
for obj in context['temp_object_list']:
if not obj.pk == int(context['root_pk']):
documento_principal = DocumentoAdministrativo.objects.get(id=context['root_pk'])
documento_principal = DocumentoAdministrativo.objects.get(
id=context['root_pk'])
documento_anexado = obj
is_anexado = Anexado.objects.filter(documento_principal=documento_principal,
documento_anexado=documento_anexado).exists()
if not is_anexado:
ciclico = False
anexados_anexado = Anexado.objects.filter(documento_principal=documento_anexado)
anexados_anexado = Anexado.objects.filter(
documento_principal=documento_anexado)
while anexados_anexado and not ciclico:
anexados = []
for anexo in anexados_anexado:
if documento_principal == anexo.documento_anexado:
@ -1042,18 +1140,18 @@ class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView):
if not ciclico:
context['object_list'].append(obj)
context['numero_res'] = len(context['object_list'])
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr)
return context
def post(self, request, *args, **kwargs):
marcados = request.POST.getlist('documento_id')
data_anexacao = datetime.strptime(
request.POST['data_anexacao'], "%d/%m/%Y"
).date()
@ -1068,22 +1166,22 @@ class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView):
v_data_desanexacao = data_desanexacao
if len(marcados) == 0:
msg =_('Nenhum documento foi selecionado')
msg = _('Nenhum documento foi selecionado')
messages.add_message(request, messages.ERROR, msg)
if data_anexacao > v_data_desanexacao:
msg=_('Data de anexação posterior à data de desanexação.')
msg = _('Data de anexação posterior à data de desanexação.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
if data_anexacao > v_data_desanexacao:
msg =_('Data de anexação posterior à data de desanexação.')
msg = _('Data de anexação posterior à data de desanexação.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, messages.ERROR, msg)
principal = DocumentoAdministrativo.objects.get(pk = kwargs['pk'])
for documento in DocumentoAdministrativo.objects.filter(id__in = marcados):
principal = DocumentoAdministrativo.objects.get(pk=kwargs['pk'])
for documento in DocumentoAdministrativo.objects.filter(id__in=marcados):
anexado = Anexado()
anexado.documento_principal = principal
anexado.documento_anexado = documento
@ -1094,7 +1192,8 @@ class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView):
msg = _('Documento(s) anexado(s).')
messages.add_message(request, messages.SUCCESS, msg)
success_url = reverse('sapl.protocoloadm:anexado_list', kwargs={'pk': kwargs['pk']})
success_url = reverse('sapl.protocoloadm:anexado_list', kwargs={
'pk': kwargs['pk']})
return HttpResponseRedirect(success_url)
@ -1130,6 +1229,10 @@ class TramitacaoAdmCrud(MasterDetailCrud):
initial['data_tramitacao'] = timezone.now().date()
initial['ip'] = get_client_ip(self.request)
initial['user'] = self.request.user
tz = timezone.get_current_timezone()
initial['ultima_edicao'] = tz.localize(datetime.now())
return initial
def get_context_data(self, **kwargs):
@ -1142,7 +1245,8 @@ class TramitacaoAdmCrud(MasterDetailCrud):
'-timestamp',
'-id').first()
#TODO: Esta checagem foi inserida na issue #2027, mas é mesmo necessária?
# TODO: Esta checagem foi inserida na issue #2027, mas é mesmo
# necessária?
if ultima_tramitacao:
if ultima_tramitacao.unidade_tramitacao_destino:
context['form'].fields[
@ -1192,6 +1296,10 @@ class TramitacaoAdmCrud(MasterDetailCrud):
initial = super(UpdateView, self).get_initial()
initial['ip'] = get_client_ip(self.request)
initial['user'] = self.request.user
tz = timezone.get_current_timezone()
initial['ultima_edicao'] = tz.localize(datetime.now())
return initial
def form_valid(self, form):
@ -1224,13 +1332,12 @@ class TramitacaoAdmCrud(MasterDetailCrud):
MasterDetailCrud.DetailView):
template_name = 'protocoloadm/tramitacaoadministrativo_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user'] = self.request.user
return context
class DeleteView(MasterDetailCrud.DeleteView):
logger = logging.getLogger(__name__)
@ -1257,7 +1364,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()
@ -1267,11 +1374,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()
TramitacaoAdministrativo.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)
@ -1338,6 +1453,13 @@ class DesvincularDocumentoView(PermissionRequiredMixin, CreateView):
ano=form.cleaned_data['ano'],
tipo=form.cleaned_data['tipo'])
documento.protocolo = None
documento.user = self.request.user
documento.ip = get_client_ip(self.request)
tz = timezone.get_current_timezone()
documento.ultima_edicao = tz.localize(datetime.now())
documento.save()
return redirect(self.get_success_url())
@ -1352,10 +1474,20 @@ class DesvincularMateriaView(PermissionRequiredMixin, FormView):
return reverse('sapl.protocoloadm:protocolo')
def form_valid(self, form):
materia = MateriaLegislativa.objects.get(numero=form.cleaned_data['numero'],
ano=form.cleaned_data['ano'],
tipo=form.cleaned_data['tipo'])
materia = MateriaLegislativa.objects.get(
numero=form.cleaned_data['numero'],
ano=form.cleaned_data['ano'],
tipo=form.cleaned_data['tipo']
)
materia.numero_protocolo = None
materia.user = self.request.user
materia.ip = get_client_ip(self.request)
tz = timezone.get_current_timezone()
materia.ultima_edicao = tz.localize(datetime.now())
materia.save()
return redirect(self.get_success_url())
@ -1464,7 +1596,6 @@ class PrimeiraTramitacaoEmLoteAdmView(PermissionRequiredMixin, FilterView):
logger = logging.getLogger(__name__)
def get_context_data(self, **kwargs):
context = super(PrimeiraTramitacaoEmLoteAdmView,
self).get_context_data(**kwargs)
@ -1486,8 +1617,8 @@ class PrimeiraTramitacaoEmLoteAdmView(PermissionRequiredMixin, FilterView):
if self.primeira_tramitacao:
context['title'] = _('Primeira Tramitação em Lote')
# Pega somente documentos que não possuem tramitação
context['object_list'] = [obj for obj in context['object_list']
if obj.tramitacaoadministrativo_set.all().count() == 0]
context['object_list'] = [obj for obj in context['object_list']
if obj.tramitacaoadministrativo_set.all().count() == 0]
else:
context['title'] = _('Tramitação em Lote')
context['form'].fields['unidade_tramitacao_local'].initial = UnidadeTramitacao.objects.get(
@ -1503,6 +1634,9 @@ class PrimeiraTramitacaoEmLoteAdmView(PermissionRequiredMixin, FilterView):
user = request.user
ip = get_client_ip(request)
tz = timezone.get_current_timezone()
ultima_edicao = tz.localize(datetime.now())
documentos_ids = request.POST.getlist('documentos')
if not documentos_ids:
msg = _("Escolha algum Documento para ser tramitado.")
@ -1511,30 +1645,32 @@ class PrimeiraTramitacaoEmLoteAdmView(PermissionRequiredMixin, FilterView):
form = TramitacaoEmLoteAdmForm(request.POST,
initial= {'documentos': documentos_ids,
'user': user, 'ip':ip})
'user': user, 'ip':ip,
'ultima_edicao': ultima_edicao})
if form.is_valid():
form.save()
msg = _('Tramitação completa.')
self.logger.info('user=' + user.username + '. Tramitação completa.')
self.logger.info('user=' + user.username +
'. Tramitação completa.')
messages.add_message(request, messages.SUCCESS, msg)
return self.get_success_url()
return self.form_invalid(form)
def get_success_url(self):
return HttpResponseRedirect(reverse('sapl.protocoloadm:primeira_tramitacao_em_lote_docadm'))
def form_invalid(self, form, *args, **kwargs):
for key, erros in form.errors.items():
if not key=='__all__':
[messages.add_message(self.request, messages.ERROR, form.fields[key].label + ": " + e) for e in erros]
if not key == '__all__':
[messages.add_message(
self.request, messages.ERROR, form.fields[key].label + ": " + e) for e in erros]
else:
[messages.add_message(self.request, messages.ERROR, e) for e in erros]
return self.get(self.request, kwargs, {'form':form})
[messages.add_message(self.request, messages.ERROR, e)
for e in erros]
return self.get(self.request, kwargs, {'form': form})
class TramitacaoEmLoteAdmView(PrimeiraTramitacaoEmLoteAdmView):
@ -1562,21 +1698,18 @@ class TramitacaoEmLoteAdmView(PrimeiraTramitacaoEmLoteAdmView):
return context
def pega_ultima_tramitacao(self):
return TramitacaoAdministrativo.objects.values(
'documento_id').annotate(data_encaminhamento=Max(
'data_encaminhamento'),
id=Max('id')).values_list('id', flat=True)
def filtra_tramitacao_status(self, status):
lista = self.pega_ultima_tramitacao()
return TramitacaoAdministrativo.objects.filter(
id__in=lista,
status=status).distinct().values_list('documento_id', flat=True)
def filtra_tramitacao_destino(self, destino):
lista = self.pega_ultima_tramitacao()
return TramitacaoAdministrativo.objects.filter(
@ -1584,7 +1717,6 @@ class TramitacaoEmLoteAdmView(PrimeiraTramitacaoEmLoteAdmView):
unidade_tramitacao_destino=destino).distinct().values_list(
'documento_id', flat=True)
def filtra_tramitacao_destino_and_status(self, status, destino):
lista = self.pega_ultima_tramitacao()
return TramitacaoAdministrativo.objects.filter(

14
sapl/relatorios/templates/pdf_pauta_sessao_gerar.py

@ -112,6 +112,17 @@ def inf_basicas(inf_basicas_dic):
return tmp
def build_expedientes(expedientes):
"""
"""
tmp = ""
tmp += '\t\t<para style="P1">Expedientes</para>\n'
for e in expedientes:
tmp += '\t\t\t<para style="P2" spaceAfter="5"><b>{}:</b></para>'.format(e['tipo'])
tmp += '\t\t\t <para style="P2" spaceAfter="5"><p>{}</p></para>'.format(e['conteudo'])
return tmp
def expediente_materia(lst_expediente_materia):
"""
@ -168,7 +179,7 @@ def votacao(lst_votacao):
return tmp
def principal(rodape_dic, imagem, inf_basicas_dic, lst_expediente_materia, lst_votacao):
def principal(rodape_dic, imagem, inf_basicas_dic, lst_expediente_materia, lst_votacao, expedientes):
"""
"""
@ -190,6 +201,7 @@ def principal(rodape_dic, imagem, inf_basicas_dic, lst_expediente_materia, lst_v
tmp += paraStyle()
tmp += '\t<story>\n'
tmp += inf_basicas(inf_basicas_dic)
tmp += build_expedientes(expedientes)
tmp += expediente_materia(lst_expediente_materia)
tmp += votacao(lst_votacao)
tmp += '\t</story>\n'

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

Loading…
Cancel
Save