Browse Source

Merge branch '3.1.x' into fix-relatorios

pull/2433/head
Cesar Augusto de Carvalho 7 years ago
committed by GitHub
parent
commit
42ddf96c16
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      Dockerfile
  2. 18
      check_solr.sh
  3. 4
      docker-compose.yml
  4. 9
      requirements/requirements.txt
  5. 8
      sapl/api/forms.py
  6. 140
      sapl/base/forms.py
  7. 72
      sapl/base/search_indexes.py
  8. 7
      sapl/base/views.py
  9. 221
      sapl/materia/forms.py
  10. 35
      sapl/materia/migrations/0034_auto_20190101_1618.py
  11. 35
      sapl/materia/migrations/0035_auto_20190104_1021.py
  12. 41
      sapl/materia/migrations/0036_auto_20190106_0330.py
  13. 15
      sapl/materia/models.py
  14. 11
      sapl/materia/tests/test_materia.py
  15. 49
      sapl/materia/views.py
  16. 70
      sapl/norma/forms.py
  17. 25
      sapl/norma/migrations/0018_auto_20190101_1618.py
  18. 25
      sapl/norma/migrations/0019_auto_20190104_1021.py
  19. 31
      sapl/norma/migrations/0020_auto_20190106_0454.py
  20. 11
      sapl/norma/models.py
  21. 16
      sapl/norma/tests/test_norma.py
  22. 2
      sapl/norma/views.py
  23. 3
      sapl/parlamentares/models.py
  24. 165
      sapl/protocoloadm/forms.py
  25. 25
      sapl/protocoloadm/migrations/0011_auto_20190101_1618.py
  26. 25
      sapl/protocoloadm/migrations/0012_auto_20190104_1021.py
  27. 26
      sapl/protocoloadm/migrations/0013_auto_20190106_1336.py
  28. 15
      sapl/protocoloadm/models.py
  29. 10
      sapl/protocoloadm/tests/test_protocoloadm.py
  30. 70
      sapl/sessao/forms.py
  31. 14
      sapl/sessao/views.py
  32. 22
      sapl/settings.py
  33. 12
      sapl/static/js/app.js
  34. 503
      sapl/static/styles/app.css
  35. 47
      sapl/static/styles/app.scss
  36. 1136
      sapl/static/styles/compilacao.css
  37. 2
      sapl/templates/base.html
  38. 2
      sapl/templates/base/RelatorioPresencaSessao_filter.html
  39. 2
      sapl/templates/base/relatorios_list.html
  40. 16
      sapl/templates/materia/materialegislativa_filter.html
  41. 4
      sapl/templates/norma/normajuridica_filter.html
  42. 86
      sapl/utils.py
  43. 13
      setup.py
  44. 61
      solr/docker-compose.yml
  45. 54
      solr/sapl_configset/conf/lang/stopwords_en.txt
  46. 253
      solr/sapl_configset/conf/lang/stopwords_pt.txt
  47. 573
      solr/sapl_configset/conf/managed-schema
  48. 20
      solr/sapl_configset/conf/params.json
  49. 21
      solr/sapl_configset/conf/protwords.txt
  50. BIN
      solr/sapl_configset/conf/saplconfigset.zip
  51. 165
      solr/sapl_configset/conf/schema.xml
  52. 1367
      solr/sapl_configset/conf/solrconfig.xml
  53. 14
      solr/sapl_configset/conf/stopwords.txt
  54. 29
      solr/sapl_configset/conf/synonyms.txt
  55. 155
      solr_api.py
  56. 37
      start.sh

3
Dockerfile

@ -2,7 +2,8 @@ FROM alpine:3.8
ENV BUILD_PACKAGES postgresql-dev graphviz-dev graphviz build-base git pkgconfig \ ENV BUILD_PACKAGES postgresql-dev graphviz-dev graphviz build-base git pkgconfig \
python3-dev libxml2-dev jpeg-dev libressl-dev libffi-dev libxslt-dev \ python3-dev libxml2-dev jpeg-dev libressl-dev libffi-dev libxslt-dev \
nodejs npm py3-lxml py3-magic postgresql-client poppler-utils antiword vim openssh-client nodejs npm py3-lxml py3-magic postgresql-client poppler-utils antiword \
curl jq openssh-client vim openssh-client bash
RUN apk update --update-cache && apk upgrade RUN apk update --update-cache && apk upgrade

18
check_solr.sh

@ -0,0 +1,18 @@
#!/bin/bash
# Pass the base SOLR URL as parameter, i.e., bash check_solr http://localhost:8983
SOLR_URL=$1
echo "Waiting for solr connection at $SOLR_URL ..."
while true; do
echo "$SOLR_URL/solr/admin/collections?action=LIST"
RESULT=$(curl -s -o /dev/null -I "$SOLR_URL/solr/admin/collections?action=LIST" -w '%{http_code}')
echo $RESULT
if [ "$RESULT" -eq '200' ]; then
echo "Solr server is up!"
break
else
sleep 3
fi
done

4
docker-compose.yml

@ -11,14 +11,14 @@ sapldb:
ports: ports:
- "5432:5432" - "5432:5432"
sapl: sapl:
image: interlegis/sapl:3.1.138 image: interlegis/sapl:3.1.140
restart: always restart: always
environment: environment:
ADMIN_PASSWORD: interlegis ADMIN_PASSWORD: interlegis
ADMIN_EMAIL: email@dominio.net ADMIN_EMAIL: email@dominio.net
DEBUG: 'False' DEBUG: 'False'
USE_TLS: 'False'
EMAIL_PORT: 587 EMAIL_PORT: 587
EMAIL_USE_TLS: 'False'
EMAIL_HOST: smtp.dominio.net EMAIL_HOST: smtp.dominio.net
EMAIL_HOST_USER: usuariosmtp EMAIL_HOST_USER: usuariosmtp
EMAIL_HOST_PASSWORD: senhasmtp EMAIL_HOST_PASSWORD: senhasmtp

9
requirements/requirements.txt

@ -1,7 +1,6 @@
dj-database-url==0.4.1 dj-database-url==0.4.1
django-haystack==2.6.0 django-haystack==2.6.0
django>=1.10,<1.11 django>=1.10,<1.11
git+git://github.com/rubgombar1/django-admin-bootstrapped.git
django-bootstrap3==7.0.1 django-bootstrap3==7.0.1
django-bower==5.2.0 django-bower==5.2.0
django-braces==1.9.0 django-braces==1.9.0
@ -14,15 +13,13 @@ django-floppyforms==1.6.2
django-model-utils==3.1.1 django-model-utils==3.1.1
django-sass-processor==0.5.8 django-sass-processor==0.5.8
djangorestframework==3.4.0 djangorestframework==3.4.0
git+git://github.com/jasperlittle/django-rest-framework-docs
easy-thumbnails==2.5 easy-thumbnails==2.5
django-image-cropping==1.2 django-image-cropping==1.2
git+git://github.com/interlegis/trml2pdf.git
libsass==0.11.1 libsass==0.11.1
psycopg2-binary==2.7.4 psycopg2-binary==2.7.4
python-decouple==3.0 python-decouple==3.0
pytz==2016.4 pytz==2016.4
pyyaml==3.11 pyyaml==4.2b1
rtyaml==0.0.3 rtyaml==0.0.3
textract==1.5.0 textract==1.5.0
unipath==1.1 unipath==1.1
@ -34,3 +31,7 @@ WeasyPrint==0.42
whoosh==2.7.4 whoosh==2.7.4
django-speedinfo==1.3.5 django-speedinfo==1.3.5
django-reversion-compare==0.8.4 django-reversion-compare==0.8.4
git+git://github.com/interlegis/trml2pdf.git
git+git://github.com/jasperlittle/django-rest-framework-docs
git+git://github.com/rubgombar1/django-admin-bootstrapped.git

8
sapl/api/forms.py

@ -101,8 +101,8 @@ class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet):
'nome', ] 'nome', ]
def filter_q(self, queryset, name, value): def filter_q(self, queryset, name, value):
return SaplGenericRelationSearchFilterSet.filter_q( return super().filter_q(
self, queryset, value).distinct('nome').order_by('nome') queryset, name, value).distinct('nome').order_by('nome')
class AutorSearchForFieldFilterSet(AutorChoiceFilterSet): class AutorSearchForFieldFilterSet(AutorChoiceFilterSet):
@ -160,11 +160,9 @@ class AutoresPossiveisFilterSet(FilterSet):
tipo = self.form.cleaned_data['tipo'] \ tipo = self.form.cleaned_data['tipo'] \
if 'tipo' in self.form.cleaned_data else None if 'tipo' in self.form.cleaned_data else None
if not tipo and not data_relativa: if not tipo:
return qs return qs
if tipo:
# não precisa de try except, já foi validado em filter_tipo
tipo = TipoAutor.objects.get(pk=tipo) tipo = TipoAutor.objects.get(pk=tipo)
if not tipo.content_type: if not tipo.content_type:
return qs return qs

140
sapl/base/forms.py

@ -1,4 +1,3 @@
import django_filters
import logging import logging
from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton
@ -14,10 +13,13 @@ from django.core.exceptions import ValidationError
from django.db import models, transaction from django.db import models, transaction
from django.db.models import Q from django.db.models import Q
from django.forms import Form, ModelForm from django.forms import Form, ModelForm
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica
from sapl.base.models import Autor, TipoAutor from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Reuniao, Comissao
from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column, from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
to_row) to_row)
from sapl.audiencia.models import AudienciaPublica,TipoAudienciaPublica from sapl.audiencia.models import AudienciaPublica,TipoAudienciaPublica
@ -30,10 +32,13 @@ from sapl.settings import MAX_IMAGE_UPLOAD_SIZE
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
ChoiceWithoutValidationField, ImageThumbnailFileInput, ChoiceWithoutValidationField, ImageThumbnailFileInput,
RangeWidgetOverride, autor_label, autor_modal, RangeWidgetOverride, autor_label, autor_modal,
models_with_gr_for_model, qs_override_django_filter) models_with_gr_for_model, qs_override_django_filter,
choice_anos_com_normas, choice_anos_com_materias,
FilterOverridesMetaMixin)
from .models import AppConfig, CasaLegislativa from .models import AppConfig, CasaLegislativa
ACTION_CREATE_USERS_AUTOR_CHOICE = [ ACTION_CREATE_USERS_AUTOR_CHOICE = [
('A', _('Associar um usuário existente')), ('A', _('Associar um usuário existente')),
('N', _('Autor sem Usuário de Acesso ao Sapl')), ('N', _('Autor sem Usuário de Acesso ao Sapl')),
@ -84,7 +89,8 @@ class UsuarioCreateForm(ModelForm):
data = self.cleaned_data data = self.cleaned_data
if data['password1'] != data['password2']: if data['password1'] != data['password2']:
self.logger.error('Erro de validação. Senhas informadas ({}, {}) são diferentes.'.format(data['password1'], data['password2'])) self.logger.error('Erro de validação. Senhas informadas ({}, {}) são diferentes.'.format(
data['password1'], data['password2']))
raise ValidationError('Senhas informadas são diferentes') raise ValidationError('Senhas informadas são diferentes')
return data return data
@ -121,8 +127,10 @@ class UsuarioEditForm(ModelForm):
# ROLES = [(g.id, g.name) for g in Group.objects.all().order_by('name')] # ROLES = [(g.id, g.name) for g in Group.objects.all().order_by('name')]
ROLES = [] ROLES = []
password1 = forms.CharField(required=False, widget=forms.PasswordInput, label='Senha') password1 = forms.CharField(
password2 = forms.CharField(required=False, widget=forms.PasswordInput, label='Confirmar senha') required=False, widget=forms.PasswordInput, label='Senha')
password2 = forms.CharField(
required=False, widget=forms.PasswordInput, label='Confirmar senha')
user_active = forms.ChoiceField(choices=YES_NO_CHOICES, required=True, user_active = forms.ChoiceField(choices=YES_NO_CHOICES, required=True,
label="Usuário ativo?", initial='True') label="Usuário ativo?", initial='True')
roles = forms.MultipleChoiceField( roles = forms.MultipleChoiceField(
@ -159,13 +167,16 @@ class UsuarioEditForm(ModelForm):
data = self.cleaned_data data = self.cleaned_data
if data['password1'] and data['password1'] != data['password2']: if data['password1'] and data['password1'] != data['password2']:
self.logger.error('Erro de validação. Senhas informadas ({}, {}) são diferentes.'.format(data['password1'], data['password2'])) self.logger.error('Erro de validação. Senhas informadas ({}, {}) são diferentes.'.format(
data['password1'], data['password2']))
raise ValidationError('Senhas informadas são diferentes') raise ValidationError('Senhas informadas são diferentes')
return data return data
class SessaoLegislativaForm(ModelForm): class SessaoLegislativaForm(ModelForm):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Meta: class Meta:
model = SessaoLegislativa model = SessaoLegislativa
exclude = [] exclude = []
@ -186,12 +197,17 @@ class SessaoLegislativaForm(ModelForm):
data_fim_leg = legislatura.data_fim data_fim_leg = legislatura.data_fim
pk = self.initial['id'] if self.initial else None pk = self.initial['id'] if self.initial else None
# Queries para verificar se existem Sessões Legislativas no período selecionado no form # Queries para verificar se existem Sessões Legislativas no período selecionado no form
# Caso onde a data_inicio e data_fim são iguais a de alguma sessão já criada # Caso onde a data_inicio e data_fim são iguais a de alguma sessão já
# criada
primeiro_caso = Q(data_inicio=data_inicio, data_fim=data_fim) primeiro_caso = Q(data_inicio=data_inicio, data_fim=data_fim)
# Caso onde a data_inicio está entre o início e o fim de uma Sessão já existente # Caso onde a data_inicio está entre o início e o fim de uma Sessão já
segundo_caso = Q(data_inicio__lt=data_inicio, data_fim__range=(data_inicio, data_fim)) # existente
# Caso onde a data_fim está entre o início e o fim de uma Sessão já existente segundo_caso = Q(data_inicio__lt=data_inicio,
terceiro_caso = Q(data_inicio__range=(data_inicio, data_fim), data_fim__gt=data_fim) data_fim__range=(data_inicio, data_fim))
# Caso onde a data_fim está entre o início e o fim de uma Sessão já
# existente
terceiro_caso = Q(data_inicio__range=(
data_inicio, data_fim), data_fim__gt=data_fim)
sessoes_existentes = SessaoLegislativa.objects.filter(primeiro_caso | segundo_caso | terceiro_caso).\ sessoes_existentes = SessaoLegislativa.objects.filter(primeiro_caso | segundo_caso | terceiro_caso).\
exclude(pk=pk) exclude(pk=pk)
@ -237,8 +253,10 @@ class SessaoLegislativaForm(ModelForm):
'entre a data início e fim da Legislatura selecionada') 'entre a data início e fim da Legislatura selecionada')
if data_inicio > data_fim: if data_inicio > data_fim:
self.logger.error('Data início ({}) superior à data fim ({}).'.format(data_inicio, data_fim)) self.logger.error(
raise ValidationError('Data início não pode ser superior à data fim') 'Data início ({}) superior à data fim ({}).'.format(data_inicio, data_fim))
raise ValidationError(
'Data início não pode ser superior à data fim')
data_inicio_intervalo = cleaned_data['data_inicio_intervalo'] data_inicio_intervalo = cleaned_data['data_inicio_intervalo']
data_fim_intervalo = cleaned_data['data_fim_intervalo'] data_fim_intervalo = cleaned_data['data_fim_intervalo']
@ -464,7 +482,8 @@ class AutorForm(ModelForm):
def valida_igualdade(self, texto1, texto2, msg): def valida_igualdade(self, texto1, texto2, msg):
if texto1 != texto2: if texto1 != texto2:
self.logger.error('Textos diferentes. ("{}" e "{}")'.format(texto1, texto2)) self.logger.error(
'Textos diferentes. ("{}" e "{}")'.format(texto1, texto2))
raise ValidationError(msg) raise ValidationError(msg)
return True return True
@ -509,7 +528,8 @@ class AutorForm(ModelForm):
if cd['action_user'] == 'A': if cd['action_user'] == 'A':
param_username = {get_user_model().USERNAME_FIELD: cd['username']} param_username = {get_user_model().USERNAME_FIELD: cd['username']}
if not User.objects.filter(**param_username).exists(): if not User.objects.filter(**param_username).exists():
self.logger.error('Não existe usuário com username "%s". ' % cd['username']) self.logger.error(
'Não existe usuário com username "%s". ' % cd['username'])
raise ValidationError( raise ValidationError(
_('Não existe usuário com username "%s". ' _('Não existe usuário com username "%s". '
'Para utilizar esse username você deve selecionar ' 'Para utilizar esse username você deve selecionar '
@ -524,7 +544,8 @@ class AutorForm(ModelForm):
param_username = { param_username = {
'user__' + get_user_model().USERNAME_FIELD: cd['username']} 'user__' + get_user_model().USERNAME_FIELD: cd['username']}
if qs_autor.filter(**param_username).exists(): if qs_autor.filter(**param_username).exists():
self.logger.error('Já existe um Autor para este usuário ({}).'.format(cd['username'])) self.logger.error(
'Já existe um Autor para este usuário ({}).'.format(cd['username']))
raise ValidationError( raise ValidationError(
_('Já existe um Autor para este usuário.')) _('Já existe um Autor para este usuário.'))
@ -655,13 +676,7 @@ class AutorFormForAdmin(AutorForm):
class RelatorioAtasFilterSet(django_filters.FilterSet): class RelatorioAtasFilterSet(django_filters.FilterSet):
class Meta: class Meta(FilterOverridesMetaMixin):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
model = SessaoPlenaria model = SessaoPlenaria
fields = ['data_inicio'] fields = ['data_inicio']
@ -692,15 +707,7 @@ class RelatorioNormasMesFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True, ano = django_filters.ChoiceFilter(required=True,
label='Ano da Norma', label='Ano da Norma',
choices=RANGE_ANOS) choices=choice_anos_com_normas)
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Ano')),
'widget': RangeWidgetOverride}
}}
class Meta: class Meta:
model = NormaJuridica model = NormaJuridica
@ -760,7 +767,7 @@ class RelatorioNormasVigenciaFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True, ano = django_filters.ChoiceFilter(required=True,
label='Ano da Norma', label='Ano da Norma',
choices=RANGE_ANOS) choices=choice_anos_com_normas)
vigencia = forms.ChoiceField( vigencia = forms.ChoiceField(
label=_('Vigência'), label=_('Vigência'),
@ -769,7 +776,6 @@ class RelatorioNormasVigenciaFilterSet(django_filters.FilterSet):
required=True, required=True,
initial=True) initial=True)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(RelatorioNormasVigenciaFilterSet, self).__init__( super(RelatorioNormasVigenciaFilterSet, self).__init__(
*args, **kwargs) *args, **kwargs)
@ -796,13 +802,7 @@ class RelatorioNormasVigenciaFilterSet(django_filters.FilterSet):
class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet): class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet):
class Meta: class Meta(FilterOverridesMetaMixin):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
model = SessaoPlenaria model = SessaoPlenaria
fields = ['data_inicio'] fields = ['data_inicio']
@ -834,13 +834,7 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
parent = super(RelatorioHistoricoTramitacaoFilterSet, self).qs parent = super(RelatorioHistoricoTramitacaoFilterSet, self).qs
return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero') return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero')
class Meta: class Meta(FilterOverridesMetaMixin):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
model = MateriaLegislativa model = MateriaLegislativa
fields = ['tipo', 'tramitacao__unidade_tramitacao_local', fields = ['tipo', 'tramitacao__unidade_tramitacao_local',
'tramitacao__status', 'tramitacao__data_tramitacao'] 'tramitacao__status', 'tramitacao__data_tramitacao']
@ -873,13 +867,7 @@ class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet):
parent = super(RelatorioDataFimPrazoTramitacaoFilterSet, self).qs parent = super(RelatorioDataFimPrazoTramitacaoFilterSet, self).qs
return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero') return parent.distinct().prefetch_related('tipo').order_by('-ano', 'tipo', 'numero')
class Meta: class Meta(FilterOverridesMetaMixin):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
model = MateriaLegislativa model = MateriaLegislativa
fields = ['tipo', 'tramitacao__unidade_tramitacao_local', fields = ['tipo', 'tramitacao__unidade_tramitacao_local',
'tramitacao__status', 'tramitacao__data_fim_prazo'] 'tramitacao__status', 'tramitacao__data_fim_prazo']
@ -935,6 +923,7 @@ class RelatorioReuniaoFilterSet(django_filters.FilterSet):
form_actions(label='Pesquisar')) form_actions(label='Pesquisar'))
) )
class RelatorioAudienciaFilterSet(django_filters.FilterSet): class RelatorioAudienciaFilterSet(django_filters.FilterSet):
@property @property
@ -965,12 +954,11 @@ class RelatorioAudienciaFilterSet(django_filters.FilterSet):
) )
class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet): class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True, ano = django_filters.ChoiceFilter(required=True,
label='Ano da Matéria', label='Ano da Matéria',
choices=RANGE_ANOS) choices=choice_anos_com_materias)
tramitacao__unidade_tramitacao_destino = django_filters.ModelChoiceFilter( tramitacao__unidade_tramitacao_destino = django_filters.ModelChoiceFilter(
queryset=UnidadeTramitacao.objects.all(), queryset=UnidadeTramitacao.objects.all(),
@ -985,7 +973,6 @@ class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
parent = super(RelatorioMateriasTramitacaoilterSet, self).qs parent = super(RelatorioMateriasTramitacaoilterSet, self).qs
return parent.distinct().order_by('-ano', 'tipo', '-numero') return parent.distinct().order_by('-ano', 'tipo', '-numero')
class Meta: class Meta:
model = MateriaLegislativa model = MateriaLegislativa
fields = ['ano', 'tipo', 'tramitacao__unidade_tramitacao_destino', fields = ['ano', 'tipo', 'tramitacao__unidade_tramitacao_destino',
@ -1015,7 +1002,7 @@ class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=True, ano = django_filters.ChoiceFilter(required=True,
label='Ano da Matéria', label='Ano da Matéria',
choices=RANGE_ANOS) choices=choice_anos_com_materias)
class Meta: class Meta:
model = MateriaLegislativa model = MateriaLegislativa
@ -1047,13 +1034,7 @@ class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet):
return parent.distinct().filter(autoria__primeiro_autor=True)\ return parent.distinct().filter(autoria__primeiro_autor=True)\
.order_by('autoria__autor', '-autoria__primeiro_autor', 'tipo', '-ano', '-numero') .order_by('autoria__autor', '-autoria__primeiro_autor', 'tipo', '-ano', '-numero')
class Meta: class Meta(FilterOverridesMetaMixin):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
model = MateriaLegislativa model = MateriaLegislativa
fields = ['tipo', 'data_apresentacao'] fields = ['tipo', 'data_apresentacao']
@ -1173,7 +1154,6 @@ class ConfiguracoesAppForm(ModelForm):
self.fields['cronometro_ordem'].widget.attrs['class'] = 'cronometro' self.fields['cronometro_ordem'].widget.attrs['class'] = 'cronometro'
self.fields['cronometro_consideracoes'].widget.attrs['class'] = 'cronometro' self.fields['cronometro_consideracoes'].widget.attrs['class'] = 'cronometro'
def clean_mostrar_brasao_painel(self): def clean_mostrar_brasao_painel(self):
mostrar_brasao_painel = self.cleaned_data.get( mostrar_brasao_painel = self.cleaned_data.get(
'mostrar_brasao_painel', False) 'mostrar_brasao_painel', False)
@ -1287,24 +1267,30 @@ class AlterarSenhaForm(Form):
new_password2 = data['new_password2'] new_password2 = data['new_password2']
if new_password1 != new_password2: if new_password1 != new_password2:
self.logger.error("'Nova Senha' ({}) diferente de 'Confirmar Senha' ({})".format(new_password1, new_password2)) self.logger.error("'Nova Senha' ({}) diferente de 'Confirmar Senha' ({})".format(
raise ValidationError("'Nova Senha' diferente de 'Confirmar Senha'") new_password1, new_password2))
raise ValidationError(
"'Nova Senha' diferente de 'Confirmar Senha'")
# TODO: colocar mais regras como: tamanho mínimo, # TODO: colocar mais regras como: tamanho mínimo,
# TODO: caracteres alfanuméricos, maiúsculas (?), # TODO: caracteres alfanuméricos, maiúsculas (?),
# TODO: senha atual igual a senha anterior, etc # TODO: senha atual igual a senha anterior, etc
if len(new_password1) < 6: if len(new_password1) < 6:
self.logger.error('A senha informada ({}) não tem o mínimo de 6 caracteres.'.format(new_password1)) self.logger.error(
raise ValidationError("A senha informada deve ter no mínimo 6 caracteres") 'A senha informada ({}) não tem o mínimo de 6 caracteres.'.format(new_password1))
raise ValidationError(
"A senha informada deve ter no mínimo 6 caracteres")
username = data['username'] username = data['username']
old_password = data['old_password'] old_password = data['old_password']
user = User.objects.get(username=username) user = User.objects.get(username=username)
if user.is_anonymous(): if user.is_anonymous():
self.logger.error('Não é possível alterar senha de usuário anônimo ({}).'.format(username)) self.logger.error(
raise ValidationError("Não é possível alterar senha de usuário anônimo") 'Não é possível alterar senha de usuário anônimo ({}).'.format(username))
raise ValidationError(
"Não é possível alterar senha de usuário anônimo")
if not user.check_password(old_password): if not user.check_password(old_password):
self.logger.error('Senha atual informada ({}) não confere ' self.logger.error('Senha atual informada ({}) não confere '
@ -1313,7 +1299,9 @@ class AlterarSenhaForm(Form):
"com a senha armazenada") "com a senha armazenada")
if user.check_password(new_password1): if user.check_password(new_password1):
self.logger.error('Nova senha ({}) igual à senha anterior.'.format(new_password1)) self.logger.error(
raise ValidationError("Nova senha não pode ser igual à senha anterior") 'Nova senha ({}) igual à senha anterior.'.format(new_password1))
raise ValidationError(
"Nova senha não pode ser igual à senha anterior")
return self.cleaned_data return self.cleaned_data

72
sapl/base/search_indexes.py

@ -1,6 +1,4 @@
import os.path import os.path
import re
import string
import textract import textract
import logging import logging
@ -8,6 +6,7 @@ from django.db.models import F, Q, Value
from django.db.models.fields import TextField from django.db.models.fields import TextField
from django.db.models.functions import Concat from django.db.models.functions import Concat
from django.template import loader from django.template import loader
from haystack import connections
from haystack.constants import Indexable from haystack.constants import Indexable
from haystack.fields import CharField from haystack.fields import CharField
from haystack.indexes import SearchIndex from haystack.indexes import SearchIndex
@ -24,6 +23,7 @@ from sapl.utils import RemoveTag
class TextExtractField(CharField): class TextExtractField(CharField):
backend = None
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -34,24 +34,20 @@ class TextExtractField(CharField):
self.model_attr = (self.model_attr, ) self.model_attr = (self.model_attr, )
def solr_extraction(self, arquivo): def solr_extraction(self, arquivo):
extracted_data = self._get_backend(None).extract_file_contents( if not self.backend:
arquivo)['contents'] self.backend = connections['default'].get_backend()
# Remove as tags xml try:
self.logger.debug("Removendo as tags xml.") with open(arquivo.path, 'rb') as f:
extracted_data = re.sub('<[^>]*>', '', extracted_data) content = self.backend.extract_file_contents(f)
# Remove tags \t e \n if not content or not content['contents']:
self.logger.debug("Removendo as \t e \n.") return ''
extracted_data = extracted_data.replace( data = content['contents']
'\n', ' ').replace('\t', ' ') except Exception as e:
# Remove sinais de pontuação print('erro processando arquivo: ' % arquivo.path)
self.logger.debug("Removendo sinais de pontuação.") self.logger.error(arquivo.path)
extracted_data = re.sub('[' + string.punctuation + ']', self.logger.error('erro processando arquivo: ' % arquivo.path)
' ', extracted_data) data = ''
# Remove espaços múltiplos return data
self.logger.debugger("Removendo espaços múltiplos.")
extracted_data = " ".join(extracted_data.split())
return extracted_data
def whoosh_extraction(self, arquivo): def whoosh_extraction(self, arquivo):
@ -66,11 +62,11 @@ class TextExtractField(CharField):
language='pt-br').decode('utf-8').replace('\n', ' ').replace( language='pt-br').decode('utf-8').replace('\n', ' ').replace(
'\t', ' ') '\t', ' ')
def print_error(self, arquivo): def print_error(self, arquivo, error):
self.logger.error("Erro inesperado processando arquivo: {}".format(arquivo.path)) msg = 'Erro inesperado processando arquivo %s erro: %s' % (
msg = 'Erro inesperado processando arquivo: %s' % ( arquivo.path, error)
arquivo.path) print(msg, error)
print(msg) self.logger.error(msg, error)
def file_extractor(self, arquivo): def file_extractor(self, arquivo):
if not os.path.exists(arquivo.path) or \ if not os.path.exists(arquivo.path) or \
@ -81,9 +77,9 @@ class TextExtractField(CharField):
if SOLR_URL: if SOLR_URL:
try: try:
return self.solr_extraction(arquivo) return self.solr_extraction(arquivo)
except Exception as e: except Exception as err:
self.logger.error("Erro no arquivo {}. ".format(arquivo.path) + str(e)) print(str(err))
self.print_error(arquivo) self.print_error(arquivo, err)
# Em ambiente de DEV utiliza-se o Whoosh # Em ambiente de DEV utiliza-se o Whoosh
# Como ele não possui extração, faz-se uso do textract # Como ele não possui extração, faz-se uso do textract
@ -91,13 +87,13 @@ class TextExtractField(CharField):
try: try:
self.logger.debug("Tentando whoosh_extraction no arquivo {}".format(arquivo.path)) self.logger.debug("Tentando whoosh_extraction no arquivo {}".format(arquivo.path))
return self.whoosh_extraction(arquivo) return self.whoosh_extraction(arquivo)
except ExtensionNotSupported as e:
self.logger.error("Erro no arquivo {}".format(arquivo.path) + str(e))
print(str(e))
except Exception as e2:
self.logger.error(str(e))
print(str(e2))
self.print_error(arquivo) self.print_error(arquivo)
except ExtensionNotSupported as err:
print(str(err))
self.logger.error(str(err))
except Exception as err:
print(str(err))
self.print_error(arquivo, str(err))
return '' return ''
def ta_extractor(self, value): def ta_extractor(self, value):
@ -133,7 +129,9 @@ class TextExtractField(CharField):
value = getattr(obj, attr) value = getattr(obj, attr)
if not value: if not value:
continue continue
data += getattr(self, func)(value) data += getattr(self, func)(value) + ' '
data = data.replace('\n', ' ')
return data return data
@ -159,6 +157,10 @@ class DocumentoAcessorioIndex(SearchIndex, Indexable):
) )
) )
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.text.search_index = self
def get_model(self): def get_model(self):
return self.model return self.model

7
sapl/base/views.py

@ -11,7 +11,7 @@ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.core.mail import send_mail from django.core.mail import send_mail
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import connection from django.db import connection
from django.db.models import Count from django.db.models import Count, Q
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.template import TemplateDoesNotExist from django.template import TemplateDoesNotExist
from django.template.loader import get_template from django.template.loader import get_template
@ -375,8 +375,13 @@ class RelatorioPresencaSessaoView(FilterView):
# Completa o dicionario as informacoes parlamentar/sessao/ordem # Completa o dicionario as informacoes parlamentar/sessao/ordem
parlamentares_presencas = [] parlamentares_presencas = []
for i, p in enumerate(parlamentares_qs): for i, p in enumerate(parlamentares_qs):
m = p.mandato_set.filter(Q(data_inicio_mandato__lte=_range[0], data_fim_mandato__gte=_range[1]) |
Q(data_inicio_mandato__lte=_range[0], data_fim_mandato__isnull=True) |
Q(data_inicio_mandato__gte=_range[0], data_fim_mandato__lte=_range[1]))
m = m.last()
parlamentares_presencas.append({ parlamentares_presencas.append({
'parlamentar': p, 'parlamentar': p,
'titular': m.titular,
'sessao_porc': 0, 'sessao_porc': 0,
'ordemdia_porc': 0 'ordemdia_porc': 0
}) })

221
sapl/materia/forms.py

@ -1,18 +1,18 @@
import os
import logging import logging
import django_filters import os
from crispy_forms.bootstrap import Alert, FormActions, InlineRadios
from crispy_forms.bootstrap import Alert, InlineRadios
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import (HTML, Button, Column, Div, Field, Fieldset, from crispy_forms.layout import (HTML, Button, Column, Div, Field, Fieldset,
Layout, Submit) Layout)
from django import forms from django import forms
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.files.base import File from django.core.files.base import File
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import models, transaction from django.db import models, transaction
from django.db.models import Max from django.db.models import Max, Q, F
from django.forms import ModelChoiceField, ModelForm, widgets from django.forms import ModelChoiceField, ModelForm, widgets
from django.forms.forms import Form from django.forms.forms import Form
from django.forms.models import ModelMultipleChoiceField from django.forms.models import ModelMultipleChoiceField
@ -22,6 +22,7 @@ from django.utils.encoding import force_text
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import django_filters
import sapl import sapl
from sapl.base.models import AppConfig, Autor, TipoAutor from sapl.base.models import AppConfig, Autor, TipoAutor
@ -36,30 +37,34 @@ from sapl.materia.models import (AssuntoMateria, Autoria, MateriaAssunto,
UnidadeTramitacao) UnidadeTramitacao)
from sapl.norma.models import (LegislacaoCitada, NormaJuridica, from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
TipoNormaJuridica) TipoNormaJuridica)
from sapl.parlamentares.models import Legislatura from sapl.parlamentares.models import Legislatura, Partido
from sapl.protocoloadm.models import Protocolo, DocumentoAdministrativo from sapl.protocoloadm.models import Protocolo, DocumentoAdministrativo
from sapl.settings import MAX_DOC_UPLOAD_SIZE from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SEPARADOR_HASH_PROPOSICAO, from sapl.utils import (YES_NO_CHOICES, SEPARADOR_HASH_PROPOSICAO,
ChoiceWithoutValidationField, ChoiceWithoutValidationField,
MateriaPesquisaOrderingFilter, RangeWidgetOverride, MateriaPesquisaOrderingFilter, RangeWidgetOverride,
autor_label, autor_modal, gerar_hash_arquivo, autor_label, autor_modal, gerar_hash_arquivo,
models_with_gr_for_model, qs_override_django_filter) models_with_gr_for_model, qs_override_django_filter,
choice_anos_com_materias, FilterOverridesMetaMixin)
from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial, from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial,
DocumentoAcessorio, Numeracao, Proposicao, Relatoria, DocumentoAcessorio, Numeracao, Proposicao, Relatoria,
TipoMateriaLegislativa, Tramitacao, UnidadeTramitacao) TipoMateriaLegislativa, Tramitacao, UnidadeTramitacao)
def ANO_CHOICES(): def CHOICE_TRAMITACAO():
return [('', '---------')] + RANGE_ANOS
def em_tramitacao():
return [('', 'Tanto Faz'), return [('', 'Tanto Faz'),
(1, 'Sim'), (1, 'Sim'),
(0, 'Não')] (0, 'Não')]
def CHOICE_TIPO_LISTAGEM():
return [
(1, _('Detalhada')),
(2, _('Simplificada')),
]
class AdicionarVariasAutoriasFilterSet(django_filters.FilterSet): class AdicionarVariasAutoriasFilterSet(django_filters.FilterSet):
class Meta: class Meta:
@ -135,7 +140,8 @@ class MateriaSimplificadaForm(ModelForm):
row1 = to_row([('tipo', 6), ('numero', 3), ('ano', 3)]) row1 = to_row([('tipo', 6), ('numero', 3), ('ano', 3)])
row2 = to_row([('data_apresentacao', 6), ('numero_protocolo', 6)]) row2 = to_row([('data_apresentacao', 6), ('numero_protocolo', 6)])
row3 = to_row([('regime_tramitacao', 6), ('em_tramitacao', 3), ('tipo_apresentacao', 3)]) row3 = to_row([('regime_tramitacao', 6),
('em_tramitacao', 3), ('tipo_apresentacao', 3)])
row4 = to_row([('ementa', 12)]) row4 = to_row([('ementa', 12)])
row5 = to_row([('texto_original', 12)]) row5 = to_row([('texto_original', 12)])
@ -239,7 +245,8 @@ class MateriaLegislativaForm(ModelForm):
if p.tipo_materia != cleaned_data['tipo']: if p.tipo_materia != cleaned_data['tipo']:
self.logger.error("Tipo do Protocolo ({}) deve ser o mesmo do Tipo Matéria ({})." self.logger.error("Tipo do Protocolo ({}) deve ser o mesmo do Tipo Matéria ({})."
.format(cleaned_data['tipo'], p.tipo_materia)) .format(cleaned_data['tipo'], p.tipo_materia))
raise ValidationError(_('Tipo do Protocolo deve ser o mesmo do Tipo Matéria')) raise ValidationError(
_('Tipo do Protocolo deve ser o mesmo do Tipo Matéria'))
if data_apresentacao.year != ano: if data_apresentacao.year != ano:
self.logger.error("O ano da matéria ({}) é diferente " self.logger.error("O ano da matéria ({}) é diferente "
@ -280,6 +287,7 @@ class MateriaLegislativaForm(ModelForm):
return materia return materia
class UnidadeTramitacaoForm(ModelForm): class UnidadeTramitacaoForm(ModelForm):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -643,6 +651,7 @@ class LegislacaoCitadaForm(ModelForm):
class NumeracaoForm(ModelForm): class NumeracaoForm(ModelForm):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Meta: class Meta:
model = Numeracao model = Numeracao
fields = ['tipo_materia', fields = ['tipo_materia',
@ -759,40 +768,45 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=False, ano = django_filters.ChoiceFilter(required=False,
label='Ano da Matéria', label='Ano da Matéria',
choices=ANO_CHOICES) choices=choice_anos_com_materias)
autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput()) autoria__autor = django_filters.CharFilter(widget=forms.HiddenInput())
autoria__primeiro_autor = django_filters.BooleanFilter( autoria__primeiro_autor = django_filters.BooleanFilter(
required=False, required=False,
label='Primeiro Autor', label=_('Primeiro Autor'))
widget=forms.HiddenInput())
autoria__autor__parlamentar_set__filiacao__partido = django_filters.ModelChoiceFilter(
queryset=Partido.objects.all(),
label=_('Matérias por Partido'))
ementa = django_filters.CharFilter(lookup_expr='icontains') ementa = django_filters.CharFilter(
label=_('Pesquisar expressões na ementa'),
method='filter_ementa'
)
indexacao = django_filters.CharFilter(lookup_expr='icontains', indexacao = django_filters.CharFilter(lookup_expr='icontains',
label=_('Indexação')) label=_('Indexação'))
em_tramitacao = django_filters.ChoiceFilter(required=False, em_tramitacao = django_filters.ChoiceFilter(required=False,
label='Em tramitação', label='Em tramitação',
choices=em_tramitacao) choices=CHOICE_TRAMITACAO)
materiaassunto__assunto = django_filters.ModelChoiceFilter( materiaassunto__assunto = django_filters.ModelChoiceFilter(
queryset=AssuntoMateria.objects.all(), queryset=AssuntoMateria.objects.all(),
label=_('Assunto da Matéria')) label=_('Assunto'))
numeracao__numero_materia = django_filters.NumberFilter( numeracao__numero_materia = django_filters.NumberFilter(
required=False, required=False,
label=_('Número do Processo')) label=_('Número do processo'))
o = MateriaPesquisaOrderingFilter() o = MateriaPesquisaOrderingFilter(help_text='')
class Meta: tipo_listagem = forms.ChoiceField(
filter_overrides = {models.DateField: { required=True,
'filter_class': django_filters.DateFromToRangeFilter, choices=CHOICE_TIPO_LISTAGEM,
'extra': lambda f: { label=_('Tipo da Listagem do Resultado da Pesquisa'))
'label': '%s (%s)' % (f.verbose_name, _('Inicial Final')),
'widget': RangeWidgetOverride} class Meta(FilterOverridesMetaMixin):
}}
model = MateriaLegislativa model = MateriaLegislativa
fields = ['numero', fields = ['numero',
'numero_protocolo', 'numero_protocolo',
@ -803,7 +817,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
'data_publicacao', 'data_publicacao',
'autoria__autor__tipo', 'autoria__autor__tipo',
'autoria__primeiro_autor', 'autoria__primeiro_autor',
# FIXME 'autoria__autor__partido', 'autoria__autor__parlamentar_set__filiacao__partido',
'relatoria__parlamentar_id', 'relatoria__parlamentar_id',
'local_origem_externa', 'local_origem_externa',
'tramitacao__unidade_tramitacao_destino', 'tramitacao__unidade_tramitacao_destino',
@ -812,16 +826,36 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
'em_tramitacao', 'em_tramitacao',
] ]
def filter_ementa(self, queryset, name, value):
texto = value.split()
q = Q()
for t in texto:
q &= Q(ementa__icontains=t)
return queryset.filter(q)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(MateriaLegislativaFilterSet, self).__init__(*args, **kwargs) super(MateriaLegislativaFilterSet, self).__init__(*args, **kwargs)
self.filters['tipo'].label = 'Tipo de Matéria' # self.filters['tipo'].label = 'Tipo de Matéria'
self.filters['autoria__autor__tipo'].label = 'Tipo de Autor' self.filters[
# self.filters['autoria__autor__partido'].label = 'Partido do Autor' 'autoria__autor__parlamentar_set__filiacao__partido'
self.filters['relatoria__parlamentar_id'].label = 'Relatoria' ].label = 'Partido do Autor'
self.filters['autoria__autor__tipo'].label = _('Tipo de Autor')
self.filters['relatoria__parlamentar_id'].label = _('Relatoria')
self.filters['tramitacao__unidade_tramitacao_destino'].label = _(
'Unidade de tramitação atual')
self.filters['tramitacao__status'].label = _(
'Status da tramitação atual')
self.filters['tramitacao__status'].label = _(
'Status da tramitação atual')
self.filters['o'].label = _('Ordenação')
self.form.fields['tipo_listagem'] = self.tipo_listagem
row1 = to_row( row1 = to_row(
[('tipo', 12)]) [('tipo', 5), ('ementa', 7)])
row2 = to_row( row2 = to_row(
[('numero', 3), [('numero', 3),
('numeracao__numero_materia', 3), ('numeracao__numero_materia', 3),
@ -830,47 +864,78 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
row3 = to_row( row3 = to_row(
[('data_apresentacao', 6), [('data_apresentacao', 6),
('data_publicacao', 6)]) ('data_publicacao', 6)])
row4 = to_row( row4 = to_row([
[('autoria__autor', 0), ('autoria__autor', 0),
('autoria__primeiro_autor', 0),
(Button('pesquisar', (Button('pesquisar',
'Pesquisar Autor', 'Pesquisar Autor',
css_class='btn btn-primary btn-sm'), 2), css_class='btn btn-primary btn-sm'), 2),
(Button('limpar', (Button('limpar',
'limpar Autor', 'limpar Autor',
css_class='btn btn-primary btn-sm'), 10)]) css_class='btn btn-primary btn-sm'), 2),
row5 = to_row( ('autoria__primeiro_autor', 2),
[('autoria__autor__tipo', 12), ('autoria__autor__tipo', 3),
# ('autoria__autor__partido', 6) ('autoria__autor__parlamentar_set__filiacao__partido', 3)
]) ])
row6 = to_row( row6 = to_row(
[('relatoria__parlamentar_id', 6), [('relatoria__parlamentar_id', 6),
('local_origem_externa', 6)]) ('local_origem_externa', 6)])
row7 = to_row( row7 = to_row(
[('tramitacao__unidade_tramitacao_destino', 6), [('tramitacao__unidade_tramitacao_destino', 5),
('tramitacao__status', 6)]) ('tramitacao__status', 5),
row8 = to_row( ('em_tramitacao', 2)
[('em_tramitacao', 6), ])
('o', 6)])
row9 = to_row( row9 = to_row(
[('materiaassunto__assunto', 6), ('indexacao', 6)]) [('materiaassunto__assunto', 6), ('indexacao', 6)])
row10 = to_row(
[('ementa', 12)]) row8 = to_row(
[
('o', 8),
('tipo_listagem', 4)
])
self.form.helper = FormHelper() self.form.helper = FormHelper()
self.form.helper.form_method = 'GET' self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout( self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Matéria'), Fieldset(_('Pesquisa Básica'),
row1, row2, row3, row1, row2),
Fieldset(_('Como listar os resultados da pesquisa'),
row8
),
Fieldset(_('Pesquisa Avançada'),
row3,
HTML(autor_label), HTML(autor_label),
HTML(autor_modal), HTML(autor_modal),
row4, row5, row6, row7, row8, row9, row10, row4, row6, row7, row9,
form_actions(label='Pesquisar')) form_actions(label=_('Pesquisar')))
) )
@property @property
def qs(self): def qs(self):
return qs_override_django_filter(self) qs = qs_override_django_filter(self)
if hasattr(self.form, 'cleaned_data') and self.form.cleaned_data[
'autoria__autor__parlamentar_set__filiacao__partido']:
q_data_inicio_e_fim = Q(data_apresentacao__gte=F(
'autoria__autor__parlamentar_set__filiacao__data'),
data_apresentacao__lte=F(
'autoria__autor__parlamentar_set__filiacao__data_desfiliacao'))
q_data_inicio = Q(
data_apresentacao__gte=F(
'autoria__autor__parlamentar_set__filiacao__data'),
autoria__autor__parlamentar_set__filiacao__data_desfiliacao__isnull=True
)
qs = qs.filter(
q_data_inicio_e_fim | q_data_inicio
)
return qs
def pega_ultima_tramitacao(): def pega_ultima_tramitacao():
@ -971,7 +1036,8 @@ class AutoriaForm(ModelForm):
if ((not pk and autorias.exists()) or if ((not pk and autorias.exists()) or
(pk and autorias.exclude(pk=pk).exists())): (pk and autorias.exclude(pk=pk).exists())):
self.logger.error("Esse Autor (pk={}) já foi cadastrado.".format(pk)) self.logger.error(
"Esse Autor (pk={}) já foi cadastrado.".format(pk))
raise ValidationError(_('Esse Autor já foi cadastrado.')) raise ValidationError(_('Esse Autor já foi cadastrado.'))
return cd return cd
@ -1022,7 +1088,8 @@ class AutoriaMultiCreateForm(Form):
del self.errors['autores'] del self.errors['autores']
if 'autor' not in cd or not cd['autor'].exists(): if 'autor' not in cd or not cd['autor'].exists():
self.logger.error("Ao menos um autor deve ser selecionado para inclusão") self.logger.error(
"Ao menos um autor deve ser selecionado para inclusão")
raise ValidationError( raise ValidationError(
_('Ao menos um autor deve ser selecionado para inclusão')) _('Ao menos um autor deve ser selecionado para inclusão'))
@ -1031,13 +1098,7 @@ class AutoriaMultiCreateForm(Form):
class AcessorioEmLoteFilterSet(django_filters.FilterSet): class AcessorioEmLoteFilterSet(django_filters.FilterSet):
class Meta: class Meta(FilterOverridesMetaMixin):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
model = MateriaLegislativa model = MateriaLegislativa
fields = ['tipo', 'data_apresentacao'] fields = ['tipo', 'data_apresentacao']
@ -1061,13 +1122,7 @@ class AcessorioEmLoteFilterSet(django_filters.FilterSet):
class PrimeiraTramitacaoEmLoteFilterSet(django_filters.FilterSet): class PrimeiraTramitacaoEmLoteFilterSet(django_filters.FilterSet):
class Meta: class Meta(FilterOverridesMetaMixin):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
model = MateriaLegislativa model = MateriaLegislativa
fields = ['tipo', 'data_apresentacao'] fields = ['tipo', 'data_apresentacao']
@ -1092,13 +1147,7 @@ class PrimeiraTramitacaoEmLoteFilterSet(django_filters.FilterSet):
class TramitacaoEmLoteFilterSet(django_filters.FilterSet): class TramitacaoEmLoteFilterSet(django_filters.FilterSet):
class Meta: class Meta(FilterOverridesMetaMixin):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
model = MateriaLegislativa model = MateriaLegislativa
fields = ['tipo', 'data_apresentacao', 'tramitacao__status', fields = ['tipo', 'data_apresentacao', 'tramitacao__status',
'tramitacao__unidade_tramitacao_destino'] 'tramitacao__unidade_tramitacao_destino']
@ -1410,7 +1459,8 @@ class ProposicaoForm(forms.ModelForm):
texto_original = self.cleaned_data.get('texto_original', False) texto_original = self.cleaned_data.get('texto_original', False)
if texto_original and texto_original.size > MAX_DOC_UPLOAD_SIZE: if texto_original and texto_original.size > MAX_DOC_UPLOAD_SIZE:
max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024)) max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
self.logger.error("- Arquivo muito grande. ( > {0}MB )".format(max_size)) self.logger.error(
"- Arquivo muito grande. ( > {0}MB )".format(max_size))
raise ValidationError( raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size)) "Arquivo muito grande. ( > {0}MB )".format(max_size))
return texto_original return texto_original
@ -1479,7 +1529,6 @@ class ProposicaoForm(forms.ModelForm):
inst.texto_original.delete() inst.texto_original.delete()
self.gerar_hash(inst, receber_recibo) self.gerar_hash(inst, receber_recibo)
return super().save(commit) return super().save(commit)
inst.ano = timezone.now().year inst.ano = timezone.now().year
@ -1803,7 +1852,8 @@ class ConfirmarProposicaoForm(ProposicaoForm):
numeracao = None numeracao = None
try: try:
self.logger.debug("Tentando obter modelo de sequência de numeração.") self.logger.debug(
"Tentando obter modelo de sequência de numeração.")
numeracao = sapl.base.models.AppConfig.objects.last( numeracao = sapl.base.models.AppConfig.objects.last(
).sequencia_numeracao ).sequencia_numeracao
except AttributeError as e: except AttributeError as e:
@ -1829,12 +1879,14 @@ class ConfirmarProposicaoForm(ProposicaoForm):
tipo=tipo).aggregate( tipo=tipo).aggregate(
Max('numero')) Max('numero'))
elif numeracao == 'U': elif numeracao == 'U':
numero = MateriaLegislativa.objects.filter(tipo=tipo).aggregate(Max('numero')) numero = MateriaLegislativa.objects.filter(
tipo=tipo).aggregate(Max('numero'))
if numeracao is None: if numeracao is None:
numero['numero__max'] = 0 numero['numero__max'] = 0
max_numero = numero['numero__max'] + 1 if numero['numero__max'] else 1 max_numero = numero['numero__max'] + \
1 if numero['numero__max'] else 1
# dados básicos # dados básicos
materia = MateriaLegislativa() materia = MateriaLegislativa()
@ -1974,7 +2026,8 @@ class ConfirmarProposicaoForm(ProposicaoForm):
protocolo.tipo_protocolo = '1' protocolo.tipo_protocolo = '1'
protocolo.interessado = str(proposicao.autor)[:200] # tamanho máximo 200 protocolo.interessado = str(proposicao.autor)[
:200] # tamanho máximo 200
protocolo.autor = proposicao.autor protocolo.autor = proposicao.autor
protocolo.assunto_ementa = proposicao.descricao protocolo.assunto_ementa = proposicao.descricao
protocolo.numero_paginas = cd['numero_de_paginas'] protocolo.numero_paginas = cd['numero_de_paginas']

35
sapl/materia/migrations/0034_auto_20190101_1618.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-01 18:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0033_auto_20181030_1039'),
]
operations = [
migrations.AlterField(
model_name='materialegislativa',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(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=[(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=[(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=[(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'),
),
]

35
sapl/materia/migrations/0035_auto_20190104_1021.py

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-04 12:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('materia', '0034_auto_20190101_1618'),
]
operations = [
migrations.AlterField(
model_name='materialegislativa',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(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=[(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=[(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=[(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'),
),
]

41
sapl/materia/migrations/0036_auto_20190106_0330.py

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-06 05:30
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('materia', '0035_auto_20190104_1021'),
]
operations = [
migrations.AlterField(
model_name='materialegislativa',
name='data_apresentacao',
field=models.DateField(verbose_name='Data de Apresentação'),
),
migrations.AlterField(
model_name='materialegislativa',
name='data_publicacao',
field=models.DateField(blank=True, null=True, verbose_name='Data de Publicação'),
),
migrations.AlterField(
model_name='materialegislativa',
name='local_origem_externa',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='materia.Origem', verbose_name='Local de Origem'),
),
migrations.AlterField(
model_name='materialegislativa',
name='numero_protocolo',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Número do Protocolo'),
),
migrations.AlterField(
model_name='materialegislativa',
name='tipo',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='materia.TipoMateriaLegislativa', verbose_name='Tipo de Matéria Legislativa'),
),
]

15
sapl/materia/models.py

@ -2,7 +2,6 @@
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist,MultipleObjectsReturned
from django.db import models from django.db import models
from django.db.models.functions import Concat from django.db.models.functions import Concat
from django.template import defaultfilters from django.template import defaultfilters
@ -143,15 +142,17 @@ def anexo_upload_path(instance, filename):
@reversion.register() @reversion.register()
class MateriaLegislativa(models.Model): class MateriaLegislativa(models.Model):
tipo = models.ForeignKey(TipoMateriaLegislativa, tipo = models.ForeignKey(
TipoMateriaLegislativa,
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Tipo')) verbose_name=TipoMateriaLegislativa._meta.verbose_name)
numero = models.PositiveIntegerField(verbose_name=_('Número')) numero = models.PositiveIntegerField(verbose_name=_('Número'))
ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'), ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'),
choices=RANGE_ANOS) choices=RANGE_ANOS)
numero_protocolo = models.PositiveIntegerField( numero_protocolo = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Núm. Protocolo')) blank=True, null=True, verbose_name=_('Número do Protocolo'))
data_apresentacao = models.DateField(verbose_name=_('Data Apresentação')) data_apresentacao = models.DateField(
verbose_name=_('Data de Apresentação'))
tipo_apresentacao = models.CharField( tipo_apresentacao = models.CharField(
max_length=1, blank=True, max_length=1, blank=True,
verbose_name=_('Tipo de Apresentação'), verbose_name=_('Tipo de Apresentação'),
@ -161,7 +162,7 @@ class MateriaLegislativa(models.Model):
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Regime Tramitação')) verbose_name=_('Regime Tramitação'))
data_publicacao = models.DateField( data_publicacao = models.DateField(
blank=True, null=True, verbose_name=_('Data Publicação')) blank=True, null=True, verbose_name=_('Data de Publicação'))
tipo_origem_externa = models.ForeignKey( tipo_origem_externa = models.ForeignKey(
TipoMateriaLegislativa, TipoMateriaLegislativa,
blank=True, blank=True,
@ -177,7 +178,7 @@ class MateriaLegislativa(models.Model):
blank=True, null=True, verbose_name=_('Data')) blank=True, null=True, verbose_name=_('Data'))
local_origem_externa = models.ForeignKey( local_origem_externa = models.ForeignKey(
Origem, blank=True, null=True, Origem, blank=True, null=True,
on_delete=models.PROTECT, verbose_name=_('Local Origem')) on_delete=models.PROTECT, verbose_name=_('Local de Origem'))
apelido = models.CharField( apelido = models.CharField(
max_length=50, blank=True, verbose_name=_('Apelido')) max_length=50, blank=True, verbose_name=_('Apelido'))
dias_prazo = models.PositiveIntegerField( dias_prazo = models.PositiveIntegerField(

11
sapl/materia/tests/test_materia.py

@ -1,10 +1,10 @@
import pytest
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db.models import Max from django.db.models import Max
from model_mommy import mommy from model_mommy import mommy
import pytest
from sapl.base.models import Autor, TipoAutor from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Comissao, TipoComissao from sapl.comissoes.models import Comissao, TipoComissao
@ -528,11 +528,13 @@ def test_numeracao_materia_legislativa_por_legislatura(admin_client):
) )
# Cria uma materia na legislatura1 # Cria uma materia na legislatura1
tipo_materia = mommy.make(TipoMateriaLegislativa, id=1, sequencia_numeracao='L') tipo_materia = mommy.make(TipoMateriaLegislativa,
id=1, sequencia_numeracao='L')
materia = mommy.make(MateriaLegislativa, materia = mommy.make(MateriaLegislativa,
tipo=tipo_materia, tipo=tipo_materia,
ano=2017, ano=2017,
numero=1 numero=1,
data_apresentacao='2017-03-05'
) )
url = reverse('sapl.materia:recuperar_materia') url = reverse('sapl.materia:recuperar_materia')
@ -556,7 +558,8 @@ def test_numeracao_materia_legislativa_por_legislatura(admin_client):
def test_numeracao_materia_legislativa_por_ano(admin_client): def test_numeracao_materia_legislativa_por_ano(admin_client):
# Cria uma materia # Cria uma materia
tipo_materia = mommy.make(TipoMateriaLegislativa, id=1, sequencia_numeracao='A') tipo_materia = mommy.make(TipoMateriaLegislativa,
id=1, sequencia_numeracao='A')
materia = mommy.make(MateriaLegislativa, materia = mommy.make(MateriaLegislativa,
tipo=tipo_materia, tipo=tipo_materia,
ano=2017, ano=2017,

49
sapl/materia/views.py

@ -1747,12 +1747,16 @@ class MateriaLegislativaPesquisaView(FilterView):
kwargs = {'data': self.request.GET or None} kwargs = {'data': self.request.GET or None}
tipo_listagem = self.request.GET.get('tipo_listagem', '1')
tipo_listagem = '1' if not tipo_listagem else tipo_listagem
qs = self.get_queryset().distinct()
if tipo_listagem == '1':
status_tramitacao = self.request.GET.get('tramitacao__status') status_tramitacao = self.request.GET.get('tramitacao__status')
unidade_destino = self.request.GET.get( unidade_destino = self.request.GET.get(
'tramitacao__unidade_tramitacao_destino') 'tramitacao__unidade_tramitacao_destino')
qs = self.get_queryset().distinct()
if status_tramitacao and unidade_destino: if status_tramitacao and unidade_destino:
lista = filtra_tramitacao_destino_and_status(status_tramitacao, lista = filtra_tramitacao_destino_and_status(status_tramitacao,
unidade_destino) unidade_destino)
@ -1766,9 +1770,6 @@ class MateriaLegislativaPesquisaView(FilterView):
lista = filtra_tramitacao_destino(unidade_destino) lista = filtra_tramitacao_destino(unidade_destino)
qs = qs.filter(id__in=lista).distinct() qs = qs.filter(id__in=lista).distinct()
if 'o' in self.request.GET and not self.request.GET['o']:
qs = qs.order_by('-ano', 'tipo__sigla', '-numero')
qs = qs.prefetch_related("autoria_set", qs = qs.prefetch_related("autoria_set",
"autoria_set__autor", "autoria_set__autor",
"numeracao_set", "numeracao_set",
@ -1782,6 +1783,15 @@ class MateriaLegislativaPesquisaView(FilterView):
"normajuridica_set", "normajuridica_set",
"registrovotacao_set", "registrovotacao_set",
"documentoacessorio_set") "documentoacessorio_set")
else:
qs = qs.prefetch_related("autoria_set",
"numeracao_set",
"autoria_set__autor",
"tipo",)
if 'o' in self.request.GET and not self.request.GET['o']:
qs = qs.order_by('-ano', 'tipo__sigla', '-numero')
kwargs.update({ kwargs.update({
'queryset': qs, 'queryset': qs,
@ -1794,7 +1804,10 @@ class MateriaLegislativaPesquisaView(FilterView):
context['title'] = _('Pesquisar Matéria Legislativa') context['title'] = _('Pesquisar Matéria Legislativa')
self.filterset.form.fields['o'].label = _('Ordenação') tipo_listagem = self.request.GET.get('tipo_listagem', '1')
tipo_listagem = '1' if not tipo_listagem else tipo_listagem
context['tipo_listagem'] = tipo_listagem
qr = self.request.GET.copy() qr = self.request.GET.copy()
if 'page' in qr: if 'page' in qr:
@ -1810,6 +1823,9 @@ class MateriaLegislativaPesquisaView(FilterView):
context['show_results'] = show_results_filter_set(qr) context['show_results'] = show_results_filter_set(qr)
context['USE_SOLR'] = settings.USE_SOLR if hasattr(
settings, 'USE_SOLR') else False
return context return context
@ -2039,24 +2055,41 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
if not request.POST['data_encaminhamento']: if not request.POST['data_encaminhamento']:
data_encaminhamento = None data_encaminhamento = None
else: else:
try:
data_encaminhamento = tz.localize(datetime.strptime( data_encaminhamento = tz.localize(datetime.strptime(
request.POST['data_encaminhamento'], "%d/%m/%Y")) request.POST['data_encaminhamento'], "%d/%m/%Y"))
except ValueError:
msg = _('Formato da data de encaminhamento incorreto.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
if request.POST['data_fim_prazo'] == '': if request.POST['data_fim_prazo'] == '':
data_fim_prazo = None data_fim_prazo = None
else: else:
try:
data_fim_prazo = tz.localize(datetime.strptime( data_fim_prazo = tz.localize(datetime.strptime(
request.POST['data_fim_prazo'], "%d/%m/%Y")) request.POST['data_fim_prazo'], "%d/%m/%Y"))
except ValueError:
msg = _('Formato da data fim do prazo incorreto.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
# issue https://github.com/interlegis/sapl/issues/1123 # issue https://github.com/interlegis/sapl/issues/1123
# TODO: usar Form # TODO: usar Form
urgente = request.POST['urgente'] == 'True' urgente = request.POST['urgente'] == 'True'
flag_error = False flag_error = False
for materia_id in marcadas: for materia_id in marcadas:
try:
data_tramitacao = tz.localize(datetime.strptime(
request.POST['data_tramitacao'], "%d/%m/%Y"))
except ValueError:
msg = _('Formato da data da tramitação incorreto.')
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
t = Tramitacao( t = Tramitacao(
materia_id=materia_id, materia_id=materia_id,
data_tramitacao=tz.localize(datetime.strptime( data_tramitacao=data_tramitacao,
request.POST['data_tramitacao'], "%d/%m/%Y")),
data_encaminhamento=data_encaminhamento, data_encaminhamento=data_encaminhamento,
data_fim_prazo=data_fim_prazo, data_fim_prazo=data_fim_prazo,
unidade_tramitacao_local_id=request.POST[ unidade_tramitacao_local_id=request.POST[

70
sapl/norma/forms.py

@ -1,29 +1,29 @@
import django_filters
import logging import logging
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Fieldset, Layout from crispy_forms.layout import Fieldset, Layout
from django import forms from django import forms
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import models from django.db import models
from django.db.models import Q
from django.forms import ModelForm, widgets, ModelChoiceField from django.forms import ModelForm, widgets, ModelChoiceField
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.base.models import Autor, TipoAutor 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, to_row
from sapl.materia.forms import choice_anos_com_materias
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.settings import MAX_DOC_UPLOAD_SIZE from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import NormaPesquisaOrderingFilter, RANGE_ANOS, RangeWidgetOverride from sapl.utils import NormaPesquisaOrderingFilter, RangeWidgetOverride,\
choice_anos_com_normas
from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada, from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica, AutoriaNorma) TipoNormaJuridica, AutoriaNorma)
def ANO_CHOICES():
return [('', '---------')] + RANGE_ANOS
def get_esferas(): def get_esferas():
return [('E', 'Estadual'), return [('E', 'Estadual'),
('F', 'Federal'), ('F', 'Federal'),
@ -43,9 +43,11 @@ class NormaFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=False, ano = django_filters.ChoiceFilter(required=False,
label='Ano', label='Ano',
choices=ANO_CHOICES) choices=choice_anos_com_normas)
ementa = django_filters.CharFilter(lookup_expr='icontains') ementa = django_filters.CharFilter(
method='filter_ementa',
label=_('Pesquisar expressões na ementa da norma'))
indexacao = django_filters.CharFilter(lookup_expr='icontains', indexacao = django_filters.CharFilter(lookup_expr='icontains',
label=_('Indexação')) label=_('Indexação'))
@ -53,15 +55,9 @@ class NormaFilterSet(django_filters.FilterSet):
assuntos = django_filters.ModelChoiceFilter( assuntos = django_filters.ModelChoiceFilter(
queryset=AssuntoNorma.objects.all()) queryset=AssuntoNorma.objects.all())
o = NormaPesquisaOrderingFilter() o = NormaPesquisaOrderingFilter(help_text='')
class Meta: class Meta(FilterOverridesMetaMixin):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
model = NormaJuridica model = NormaJuridica
fields = ['tipo', 'numero', 'ano', 'data', 'data_vigencia', fields = ['tipo', 'numero', 'ano', 'data', 'data_vigencia',
'data_publicacao', 'ementa', 'assuntos'] 'data_publicacao', 'ementa', 'assuntos']
@ -83,6 +79,14 @@ class NormaFilterSet(django_filters.FilterSet):
form_actions(label='Pesquisar')) form_actions(label='Pesquisar'))
) )
def filter_ementa(self, queryset, name, value):
texto = value.split()
q = Q()
for t in texto:
q &= Q(ementa__icontains=t)
return queryset.filter(q)
class NormaJuridicaForm(ModelForm): class NormaJuridicaForm(ModelForm):
@ -102,7 +106,7 @@ class NormaJuridicaForm(ModelForm):
ano_materia = forms.ChoiceField( ano_materia = forms.ChoiceField(
label='Ano Matéria', label='Ano Matéria',
required=False, required=False,
choices=ANO_CHOICES, choices=choice_anos_com_materias,
widget=forms.Select(attrs={'autocomplete': 'off'}) widget=forms.Select(attrs={'autocomplete': 'off'})
) )
@ -131,7 +135,6 @@ class NormaJuridicaForm(ModelForm):
'assuntos'] 'assuntos']
widgets = {'assuntos': widgets.CheckboxSelectMultiple} widgets = {'assuntos': widgets.CheckboxSelectMultiple}
def clean(self): def clean(self):
cleaned_data = super(NormaJuridicaForm, self).clean() cleaned_data = super(NormaJuridicaForm, self).clean()
@ -142,8 +145,10 @@ class NormaJuridicaForm(ModelForm):
import re import re
has_digits = re.sub('[^0-9]', '', cleaned_data['numero']) has_digits = re.sub('[^0-9]', '', cleaned_data['numero'])
if not has_digits: if not has_digits:
self.logger.error("Número de norma ({}) não pode conter somente letras.".format(cleaned_data['numero'])) self.logger.error("Número de norma ({}) não pode conter somente letras.".format(
raise ValidationError('Número de norma não pode conter somente letras') cleaned_data['numero']))
raise ValidationError(
'Número de norma não pode conter somente letras')
if self.instance.numero != cleaned_data['numero']: if self.instance.numero != cleaned_data['numero']:
norma = NormaJuridica.objects.filter(ano=cleaned_data['ano'], norma = NormaJuridica.objects.filter(ano=cleaned_data['ano'],
@ -199,7 +204,8 @@ class NormaJuridicaForm(ModelForm):
if texto_integral and texto_integral.size > MAX_DOC_UPLOAD_SIZE: if texto_integral and texto_integral.size > MAX_DOC_UPLOAD_SIZE:
max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024)) max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
tam_fornecido = str(texto_integral.size / (1024 * 1024)) tam_fornecido = str(texto_integral.size / (1024 * 1024))
self.logger.error("Arquivo muito grande ({}MB). ( Tamanho máximo permitido: {}MB )".format(tam_fornecido, max_size)) self.logger.error("Arquivo muito grande ({}MB). ( Tamanho máximo permitido: {}MB )".format(
tam_fornecido, max_size))
raise ValidationError( raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size)) "Arquivo muito grande. ( > {0}MB )".format(max_size))
return texto_integral return texto_integral
@ -256,11 +262,13 @@ class AutoriaNormaForm(ModelForm):
if ((not pk and autorias.exists()) or if ((not pk and autorias.exists()) or
(pk and autorias.exclude(pk=pk).exists())): (pk and autorias.exclude(pk=pk).exists())):
self.logger.error("Autor ({}) já foi cadastrado.".format(cd['autor'])) self.logger.error(
"Autor ({}) já foi cadastrado.".format(cd['autor']))
raise ValidationError(_('Esse Autor já foi cadastrado.')) raise ValidationError(_('Esse Autor já foi cadastrado.'))
return cd return cd
class AnexoNormaJuridicaForm(ModelForm): class AnexoNormaJuridicaForm(ModelForm):
class Meta: class Meta:
model = AnexoNormaJuridica model = AnexoNormaJuridica
@ -270,6 +278,7 @@ class AnexoNormaJuridicaForm(ModelForm):
} }
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def clean(self): def clean(self):
cleaned_data = super(AnexoNormaJuridicaForm, self).clean() cleaned_data = super(AnexoNormaJuridicaForm, self).clean()
if not self.is_valid(): if not self.is_valid():
@ -278,7 +287,8 @@ class AnexoNormaJuridicaForm(ModelForm):
if anexo_arquivo and anexo_arquivo.size > MAX_DOC_UPLOAD_SIZE: if anexo_arquivo and anexo_arquivo.size > MAX_DOC_UPLOAD_SIZE:
max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024)) max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024))
tam_fornecido = str(anexo_arquivo.size / (1024 * 1024)) tam_fornecido = str(anexo_arquivo.size / (1024 * 1024))
self.logger.error("Arquivo muito grande ({}MB). ( Tamanho máximo permitido: {}MB )".format(tam_fornecido, max_size)) self.logger.error("Arquivo muito grande ({}MB). ( Tamanho máximo permitido: {}MB )".format(
tam_fornecido, max_size))
raise ValidationError( raise ValidationError(
"Arquivo muito grande. ( > {0}MB )".format(max_size)) "Arquivo muito grande. ( > {0}MB )".format(max_size))
return cleaned_data return cleaned_data
@ -294,7 +304,6 @@ class AnexoNormaJuridicaForm(ModelForm):
return anexo return anexo
class NormaRelacionadaForm(ModelForm): class NormaRelacionadaForm(ModelForm):
tipo = forms.ModelChoiceField( tipo = forms.ModelChoiceField(
@ -310,6 +319,7 @@ class NormaRelacionadaForm(ModelForm):
widget=forms.Textarea(attrs={'disabled': 'disabled'})) widget=forms.Textarea(attrs={'disabled': 'disabled'}))
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Meta: class Meta:
model = NormaRelacionada model = NormaRelacionada
fields = ['tipo', 'numero', 'ano', 'ementa', 'tipo_vinculo'] fields = ['tipo', 'numero', 'ano', 'ementa', 'tipo_vinculo']
@ -325,17 +335,20 @@ class NormaRelacionadaForm(ModelForm):
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
try: try:
self.logger.debug("Tentando obter objeto NormaJuridica com numero={}, ano={}, tipo={}.".format(cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo'])) self.logger.debug("Tentando obter objeto NormaJuridica com numero={}, ano={}, tipo={}.".format(
cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo']))
norma_relacionada = NormaJuridica.objects.get( norma_relacionada = NormaJuridica.objects.get(
numero=cleaned_data['numero'], numero=cleaned_data['numero'],
ano=cleaned_data['ano'], ano=cleaned_data['ano'],
tipo=cleaned_data['tipo']) tipo=cleaned_data['tipo'])
except ObjectDoesNotExist: except ObjectDoesNotExist:
self.logger.info("NormaJuridica com numero={}, ano={}, tipo={} não existe.".format(cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo'])) self.logger.info("NormaJuridica com numero={}, ano={}, tipo={} não existe.".format(
cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo']))
msg = _('A norma a ser relacionada não existe.') msg = _('A norma a ser relacionada não existe.')
raise ValidationError(msg) raise ValidationError(msg)
else: else:
self.logger.info("NormaJuridica com numero={}, ano={}, tipo={} obtida com sucesso.".format(cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo'])) self.logger.info("NormaJuridica com numero={}, ano={}, tipo={} obtida com sucesso.".format(
cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo']))
cleaned_data['norma_relacionada'] = norma_relacionada cleaned_data['norma_relacionada'] = norma_relacionada
return cleaned_data return cleaned_data
@ -408,7 +421,8 @@ class NormaPesquisaSimplesForm(forms.Form):
if (data_inicial and data_final and if (data_inicial and data_final and
data_inicial > data_final): data_inicial > data_final):
self.logger.error("Data Final ({}) menor que a Data Inicial ({}).".format(data_final, data_inicial)) self.logger.error("Data Final ({}) menor que a Data Inicial ({}).".format(
data_final, data_inicial))
raise ValidationError(_( raise ValidationError(_(
'A Data Final não pode ser menor que a Data Inicial')) 'A Data Final não pode ser menor que a Data Inicial'))
else: else:

25
sapl/norma/migrations/0018_auto_20190101_1618.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-01 18:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0017_normaestatisticas'),
]
operations = [
migrations.AlterField(
model_name='anexonormajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(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='normajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(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'),
),
]

25
sapl/norma/migrations/0019_auto_20190104_1021.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-04 12:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0018_auto_20190101_1618'),
]
operations = [
migrations.AlterField(
model_name='anexonormajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(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='normajuridica',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(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'),
),
]

31
sapl/norma/migrations/0020_auto_20190106_0454.py

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-06 06:54
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('norma', '0019_auto_20190104_1021'),
]
operations = [
migrations.AlterField(
model_name='normajuridica',
name='data_publicacao',
field=models.DateField(blank=True, null=True, verbose_name='Data de Publicação'),
),
migrations.AlterField(
model_name='normajuridica',
name='tipo',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='norma.TipoNormaJuridica', verbose_name='Tipo da Norma Jurídica'),
),
migrations.AlterField(
model_name='normajuridica',
name='veiculo_publicacao',
field=models.CharField(blank=True, max_length=30, verbose_name='Veículo de Publicação'),
),
]

11
sapl/norma/models.py

@ -85,7 +85,7 @@ class NormaJuridica(models.Model):
tipo = models.ForeignKey( tipo = models.ForeignKey(
TipoNormaJuridica, TipoNormaJuridica,
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Tipo da Norma Juridica')) verbose_name=_('Tipo da Norma Jurídica'))
materia = models.ForeignKey( materia = models.ForeignKey(
MateriaLegislativa, blank=True, null=True, MateriaLegislativa, blank=True, null=True,
on_delete=models.PROTECT, verbose_name=_('Matéria')) on_delete=models.PROTECT, verbose_name=_('Matéria'))
@ -100,11 +100,11 @@ class NormaJuridica(models.Model):
choices=ESFERA_FEDERACAO_CHOICES) choices=ESFERA_FEDERACAO_CHOICES)
data = models.DateField(blank=False, null=True, verbose_name=_('Data')) data = models.DateField(blank=False, null=True, verbose_name=_('Data'))
data_publicacao = models.DateField( data_publicacao = models.DateField(
blank=True, null=True, verbose_name=_('Data Publicação')) blank=True, null=True, verbose_name=_('Data de Publicação'))
veiculo_publicacao = models.CharField( veiculo_publicacao = models.CharField(
max_length=30, max_length=30,
blank=True, blank=True,
verbose_name=_('Veículo Publicação')) verbose_name=_('Veículo de Publicação'))
pagina_inicio_publicacao = models.PositiveIntegerField( pagina_inicio_publicacao = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Pg. Início')) blank=True, null=True, verbose_name=_('Pg. Início'))
pagina_fim_publicacao = models.PositiveIntegerField( pagina_fim_publicacao = models.PositiveIntegerField(
@ -121,7 +121,8 @@ class NormaJuridica(models.Model):
assuntos = models.ManyToManyField( assuntos = models.ManyToManyField(
AssuntoNorma, blank=True, AssuntoNorma, blank=True,
verbose_name=_('Assuntos')) verbose_name=_('Assuntos'))
data_vigencia = models.DateField(blank=True, null=True, verbose_name=_('Data Fim Vigência')) data_vigencia = models.DateField(
blank=True, null=True, verbose_name=_('Data Fim Vigência'))
timestamp = models.DateTimeField(null=True) timestamp = models.DateTimeField(null=True)
texto_articulado = GenericRelation( texto_articulado = GenericRelation(
@ -205,6 +206,7 @@ class NormaEstatisticas(models.Model):
choices=RANGE_ANOS, default=get_ano_atual) choices=RANGE_ANOS, default=get_ano_atual)
norma = models.ForeignKey(NormaJuridica, norma = models.ForeignKey(NormaJuridica,
on_delete=models.CASCADE) on_delete=models.CASCADE)
def __str__(self): def __str__(self):
return _('Usuário: %(usuario)s, Norma: %(norma)s') % { return _('Usuário: %(usuario)s, Norma: %(norma)s') % {
'usuario': self.usuario, 'norma': self.norma} 'usuario': self.usuario, 'norma': self.norma}
@ -232,6 +234,7 @@ class AutoriaNorma(models.Model):
return _('Autoria: %(autor)s - %(norma)s') % { return _('Autoria: %(autor)s - %(norma)s') % {
'autor': self.autor, 'norma': self.norma} 'autor': self.autor, 'norma': self.norma}
@reversion.register() @reversion.register()
class LegislacaoCitada(models.Model): class LegislacaoCitada(models.Model):
materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE) materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE)

16
sapl/norma/tests/test_norma.py

@ -1,13 +1,13 @@
import pytest
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from model_mommy import mommy from model_mommy import mommy
import pytest
from sapl.base.models import AppConfig
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.norma.forms import (NormaJuridicaForm, NormaPesquisaSimplesForm, from sapl.norma.forms import (NormaJuridicaForm, NormaPesquisaSimplesForm,
NormaRelacionadaForm) NormaRelacionadaForm)
from sapl.norma.models import NormaJuridica, TipoNormaJuridica from sapl.norma.models import NormaJuridica, TipoNormaJuridica
from sapl.base.models import AppConfig
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
@ -81,6 +81,15 @@ def test_norma_juridica_materia_inexistente():
tipo = mommy.make(TipoNormaJuridica) tipo = mommy.make(TipoNormaJuridica)
tipo_materia = mommy.make(TipoMateriaLegislativa, descricao='VETO') tipo_materia = mommy.make(TipoMateriaLegislativa, descricao='VETO')
# cria uma matéria qualquer em 2017 pois, no teste, o campo ano_materia
# está vazio
materia = mommy.make(MateriaLegislativa,
tipo=tipo_materia,
ano=2017,
numero=1,
data_apresentacao='2017-03-05'
)
form = NormaJuridicaForm(data={'tipo': str(tipo.pk), form = NormaJuridicaForm(data={'tipo': str(tipo.pk),
'numero': '1', 'numero': '1',
'ano': '2017', 'ano': '2017',
@ -94,7 +103,8 @@ def test_norma_juridica_materia_inexistente():
assert not form.is_valid() assert not form.is_valid()
assert form.errors['__all__'] == [_("Matéria Legislativa 2/2017 (VETO) é inexistente.")] assert form.errors['__all__'] == [
_("Matéria Legislativa 2/2017 (VETO) é inexistente.")]
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)

2
sapl/norma/views.py

@ -15,6 +15,7 @@ from django.views.generic import TemplateView, UpdateView
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django_filters.views import FilterView from django_filters.views import FilterView
from sapl import settings
from sapl.base.models import AppConfig from sapl.base.models import AppConfig
from sapl.compilacao.views import IntegracaoTaView from sapl.compilacao.views import IntegracaoTaView
from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
@ -107,6 +108,7 @@ class NormaPesquisaView(FilterView):
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else ''
context['show_results'] = show_results_filter_set(qr) context['show_results'] = show_results_filter_set(qr)
context['USE_SOLR'] = settings.USE_SOLR if hasattr(settings, 'USE_SOLR') else False
return context return context

3
sapl/parlamentares/models.py

@ -1,10 +1,10 @@
import reversion
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from image_cropping.fields import ImageCropField, ImageRatioField from image_cropping.fields import ImageCropField, ImageRatioField
from model_utils import Choices from model_utils import Choices
import reversion
from sapl.base.models import Autor from sapl.base.models import Autor
from sapl.decorators import vigencia_atual from sapl.decorators import vigencia_atual
@ -281,6 +281,7 @@ class Parlamentar(models.Model):
('nome_completo', '__icontains'), ('nome_completo', '__icontains'),
('nome_parlamentar', '__icontains'), ('nome_parlamentar', '__icontains'),
('filiacao__partido__sigla', '__icontains'), ('filiacao__partido__sigla', '__icontains'),
('filiacao__partido', '__exact'),
)) ))
class Meta: class Meta:

165
sapl/protocoloadm/forms.py

@ -1,6 +1,6 @@
import django_filters
import logging import logging
from crispy_forms.bootstrap import InlineRadios from crispy_forms.bootstrap import InlineRadios
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout
@ -12,34 +12,34 @@ from django.db.models import Max
from django.forms import ModelForm from django.forms import ModelForm
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
import django_filters
from sapl.base.models import Autor, TipoAutor from sapl.base.models import Autor, TipoAutor
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row
from sapl.materia.models import (MateriaLegislativa, TipoMateriaLegislativa, from sapl.materia.models import (MateriaLegislativa, TipoMateriaLegislativa,
UnidadeTramitacao) UnidadeTramitacao)
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter, from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter,
RangeWidgetOverride, autor_label, autor_modal) RangeWidgetOverride, autor_label, autor_modal,
choice_anos_com_protocolo, choice_force_optional,
choice_anos_com_documentoadministrativo)
from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo, from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo, DocumentoAdministrativo,
Protocolo, TipoDocumentoAdministrativo, Protocolo, TipoDocumentoAdministrativo,
TramitacaoAdministrativo) TramitacaoAdministrativo)
TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'), ('2', 'Interno'), ('', '---------')]
TIPOS_PROTOCOLO_CREATE = [('0', 'Recebido'), ('1', 'Enviado'), ('2', 'Interno')]
NATUREZA_PROCESSO = [('', '---------'), TIPOS_PROTOCOLO = [('0', 'Recebido'), ('1', 'Enviado'),
('0', 'Administrativo'), ('2', 'Interno')]
('1', 'Legislativo')] TIPOS_PROTOCOLO_CREATE = [
('0', 'Recebido'), ('1', 'Enviado'), ('2', 'Interno')]
NATUREZA_PROCESSO = [('0', 'Administrativo'),
('1', 'Legislativo')]
def ANO_CHOICES():
return [('', '---------')] + RANGE_ANOS
EM_TRAMITACAO = [(0, 'Sim'), (1, 'Não')]
EM_TRAMITACAO = [('', '---------'),
(0, 'Sim'),
(1, 'Não')]
class AcompanhamentoDocumentoForm(ModelForm): class AcompanhamentoDocumentoForm(ModelForm):
@ -66,13 +66,18 @@ class AcompanhamentoDocumentoForm(ModelForm):
class ProtocoloFilterSet(django_filters.FilterSet): class ProtocoloFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=False, ano = django_filters.ChoiceFilter(
required=False,
label='Ano', label='Ano',
choices=ANO_CHOICES) choices=choice_anos_com_protocolo)
assunto_ementa = django_filters.CharFilter(lookup_expr='icontains') assunto_ementa = django_filters.CharFilter(
label=_('Assunto'),
lookup_expr='icontains')
interessado = django_filters.CharFilter(lookup_expr='icontains') interessado = django_filters.CharFilter(
label=_('Interessado'),
lookup_expr='icontains')
autor = django_filters.CharFilter(widget=forms.HiddenInput()) autor = django_filters.CharFilter(widget=forms.HiddenInput())
@ -89,15 +94,9 @@ class ProtocoloFilterSet(django_filters.FilterSet):
widget=forms.Select( widget=forms.Select(
attrs={'class': 'selector'})) attrs={'class': 'selector'}))
o = AnoNumeroOrderingFilter() o = AnoNumeroOrderingFilter(help_text='')
class Meta: class Meta(FilterOverridesMetaMixin):
filter_overrides = {models.DateTimeField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': 'Data (%s)' % (_('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
model = Protocolo model = Protocolo
fields = ['numero', fields = ['numero',
'tipo_documento', 'tipo_documento',
@ -108,8 +107,7 @@ class ProtocoloFilterSet(django_filters.FilterSet):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ProtocoloFilterSet, self).__init__(*args, **kwargs) super(ProtocoloFilterSet, self).__init__(*args, **kwargs)
self.filters['autor'].label = 'Tipo de Matéria' self.filters['timestamp'].label = 'Data (Inicial - Final)'
self.filters['assunto_ementa'].label = 'Assunto'
row1 = to_row( row1 = to_row(
[('numero', 4), [('numero', 4),
@ -134,9 +132,7 @@ class ProtocoloFilterSet(django_filters.FilterSet):
'Limpar Autor', 'Limpar Autor',
css_class='btn btn-primary btn-sm'), 10)]) css_class='btn btn-primary btn-sm'), 10)])
row5 = to_row( row5 = to_row(
[('tipo_processo', 12)]) [('tipo_processo', 6), ('o', 6)])
row6 = to_row(
[('o', 12)])
self.form.helper = FormHelper() self.form.helper = FormHelper()
self.form.helper.form_method = 'GET' self.form.helper.form_method = 'GET'
@ -144,36 +140,36 @@ class ProtocoloFilterSet(django_filters.FilterSet):
Fieldset(_('Pesquisar Protocolo'), Fieldset(_('Pesquisar Protocolo'),
row1, row2, row1, row2,
row3, row3,
row5,
HTML(autor_label), HTML(autor_label),
HTML(autor_modal), HTML(autor_modal),
row4, row5, row6, row4,
form_actions(label='Pesquisar')) form_actions(label='Pesquisar'))
) )
class DocumentoAdministrativoFilterSet(django_filters.FilterSet): class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(required=False, ano = django_filters.ChoiceFilter(
required=False,
label='Ano', label='Ano',
choices=ANO_CHOICES) choices=choice_anos_com_documentoadministrativo)
tramitacao = django_filters.ChoiceFilter(required=False, tramitacao = django_filters.ChoiceFilter(required=False,
label='Em Tramitação?', label='Em Tramitação?',
choices=EM_TRAMITACAO) choices=YES_NO_CHOICES)
assunto = django_filters.CharFilter(lookup_expr='icontains') assunto = django_filters.CharFilter(
label=_('Assunto'),
lookup_expr='icontains')
interessado = django_filters.CharFilter(lookup_expr='icontains') interessado = django_filters.CharFilter(
label=_('Interessado'),
lookup_expr='icontains')
o = AnoNumeroOrderingFilter() o = AnoNumeroOrderingFilter(help_text='')
class Meta: class Meta(FilterOverridesMetaMixin):
filter_overrides = {models.DateField: {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': 'Data (%s)' % (_('Inicial - Final')),
'widget': RangeWidgetOverride}
}}
model = DocumentoAdministrativo model = DocumentoAdministrativo
fields = ['tipo', fields = ['tipo',
'numero', 'numero',
@ -188,37 +184,38 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
local_atual = 'tramitacaoadministrativo__unidade_tramitacao_destino' local_atual = 'tramitacaoadministrativo__unidade_tramitacao_destino'
self.filters['tipo'].label = 'Tipo de Documento' self.filters['tipo'].label = 'Tipo de Documento'
self.filters['protocolo__numero'].label = 'Núm. Protocolo'
self.filters['tramitacaoadministrativo__status'].label = 'Situação' self.filters['tramitacaoadministrativo__status'].label = 'Situação'
self.filters[local_atual].label = 'Localização Atual' self.filters[local_atual].label = 'Localização Atual'
row1 = to_row( row1 = to_row(
[('tipo', 6), [('tipo', 8),
('numero', 6)]) ('o', 4), ])
row2 = to_row( row2 = to_row(
[('ano', 4), [('numero', 2),
('ano', 2),
('protocolo__numero', 2), ('protocolo__numero', 2),
('numero_externo', 2), ('numero_externo', 2),
('data', 4)]) ('data', 4)])
row3 = to_row( row3 = to_row(
[('interessado', 4), [('interessado', 6),
('assunto', 4), ('assunto', 6)])
('tramitacao', 4)])
row4 = to_row( row4 = to_row(
[('tramitacaoadministrativo__unidade_tramitacao_destino', 6), [
('tramitacaoadministrativo__status', 6)]) ('tramitacao', 2),
('tramitacaoadministrativo__status', 5),
row5 = to_row( ('tramitacaoadministrativo__unidade_tramitacao_destino', 5),
[('o', 12)]) ])
self.form.helper = FormHelper() self.form.helper = FormHelper()
self.form.helper.form_method = 'GET' self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout( self.form.helper.layout = Layout(
Fieldset(_('Pesquisar Documento'), Fieldset(_('Pesquisar Documento'),
row1, row2, row1, row2,
row3, row4, row5, row3, row4,
form_actions(label='Pesquisar')) form_actions(label='Pesquisar'))
) )
@ -253,10 +250,12 @@ class AnularProcoloAdmForm(ModelForm):
ano = cleaned_data['ano'] ano = cleaned_data['ano']
try: try:
self.logger.debug("Tentando obter Protocolo com numero={} e ano={}.".format(numero, ano)) self.logger.debug(
"Tentando obter Protocolo com numero={} e ano={}.".format(numero, ano))
protocolo = Protocolo.objects.get(numero=numero, ano=ano) protocolo = Protocolo.objects.get(numero=numero, ano=ano)
if protocolo.anulado: if protocolo.anulado:
self.logger.error("Protocolo %s/%s já encontra-se anulado" % (numero, ano)) self.logger.error(
"Protocolo %s/%s já encontra-se anulado" % (numero, ano))
raise forms.ValidationError( raise forms.ValidationError(
_("Protocolo %s/%s já encontra-se anulado") _("Protocolo %s/%s já encontra-se anulado")
% (numero, ano)) % (numero, ano))
@ -341,7 +340,8 @@ class ProtocoloDocumentForm(ModelForm):
observacao = forms.CharField(required=False, observacao = forms.CharField(required=False,
widget=forms.Textarea, label=_('Observação')) widget=forms.Textarea, label=_('Observação'))
numero = forms.IntegerField(required=False, label=_('Número de Protocolo (opcional)')) numero = forms.IntegerField(
required=False, label=_('Número de Protocolo (opcional)'))
class Meta: class Meta:
model = Protocolo model = Protocolo
@ -431,7 +431,8 @@ class ProtocoloMateriaForm(ModelForm):
assunto_ementa = forms.CharField(required=True, assunto_ementa = forms.CharField(required=True,
widget=forms.Textarea, label=_('Ementa')) widget=forms.Textarea, label=_('Ementa'))
numero = forms.IntegerField(required=False, label=_('Número de Protocolo (opcional)')) numero = forms.IntegerField(
required=False, label=_('Número de Protocolo (opcional)'))
class Meta: class Meta:
model = Protocolo model = Protocolo
@ -450,13 +451,16 @@ class ProtocoloMateriaForm(ModelForm):
def clean_autor(self): def clean_autor(self):
autor_field = self.cleaned_data['autor'] autor_field = self.cleaned_data['autor']
try: try:
self.logger.debug("Tentando obter Autor com id={}.".format(autor_field.id)) self.logger.debug(
"Tentando obter Autor com id={}.".format(autor_field.id))
autor = Autor.objects.get(id=autor_field.id) autor = Autor.objects.get(id=autor_field.id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
self.logger.error("Autor com id={} não encontrado. Definido como None.".format(autor_field.id)) self.logger.error(
"Autor com id={} não encontrado. Definido como None.".format(autor_field.id))
autor_field = None autor_field = None
else: else:
self.logger.info("Autor com id={} encontrado com sucesso.".format(autor_field.id)) self.logger.info(
"Autor com id={} encontrado com sucesso.".format(autor_field.id))
autor_field = autor autor_field = autor
return autor_field return autor_field
@ -471,7 +475,8 @@ class ProtocoloMateriaForm(ModelForm):
if data['vincular_materia'] == 'True': if data['vincular_materia'] == 'True':
try: try:
if not data['ano_materia'] or not data['numero_materia']: if not data['ano_materia'] or not data['numero_materia']:
self.logger.error("Não foram informados o número ou ano da matéria a ser vinculada") self.logger.error(
"Não foram informados o número ou ano da matéria a ser vinculada")
raise ValidationError( raise ValidationError(
'Favor informar o número e ano da matéria a ser vinculada') 'Favor informar o número e ano da matéria a ser vinculada')
self.logger.debug("Tentando obter MateriaLegislativa com ano={}, numero={} e data={}." self.logger.debug("Tentando obter MateriaLegislativa com ano={}, numero={} e data={}."
@ -487,7 +492,8 @@ class ProtocoloMateriaForm(ModelForm):
except ObjectDoesNotExist: except ObjectDoesNotExist:
self.logger.error("MateriaLegislativa informada (ano={}, numero={} e data={}) não existente." self.logger.error("MateriaLegislativa informada (ano={}, numero={} e data={}) não existente."
.format(data['ano_materia'], data['numero_materia'], data['tipo_materia'])) .format(data['ano_materia'], data['numero_materia'], data['tipo_materia']))
raise ValidationError(_('Matéria Legislativa informada não existente.')) raise ValidationError(
_('Matéria Legislativa informada não existente.'))
return data return data
@ -691,10 +697,11 @@ class DocumentoAdministrativoForm(ModelForm):
data = forms.DateField(initial=timezone.now) data = forms.DateField(initial=timezone.now)
ano_protocolo = forms.ChoiceField(required=False, ano_protocolo = forms.ChoiceField(
required=False,
label=Protocolo._meta. label=Protocolo._meta.
get_field('ano').verbose_name, get_field('ano').verbose_name,
choices=RANGE_ANOS, choices=choice_force_optional(choice_anos_com_protocolo),
widget=forms.Select( widget=forms.Select(
attrs={'class': 'selector'})) attrs={'class': 'selector'}))
@ -773,7 +780,8 @@ class DocumentoAdministrativoForm(ModelForm):
numero_protocolo, ano_protocolo)) numero_protocolo, ano_protocolo))
raise ValidationError(msg) raise ValidationError(msg)
except MultipleObjectsReturned: except MultipleObjectsReturned:
self.logger.error("Existe mais de um Protocolo com este ano ({}) e número ({}).".format(ano_protocolo,numero_protocolo)) self.logger.error("Existe mais de um Protocolo com este ano ({}) e número ({}).".format(
ano_protocolo, numero_protocolo))
msg = _( msg = _(
'Existe mais de um Protocolo com este ano e número.' % ( 'Existe mais de um Protocolo com este ano e número.' % (
numero_protocolo, ano_protocolo)) numero_protocolo, ano_protocolo))
@ -812,7 +820,7 @@ class DocumentoAdministrativoForm(ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
row1 = to_row( row1 = to_row(
[('tipo', 4), ('numero', 4), ('ano', 4)]) [('tipo', 6), ('numero', 3), ('ano', 3)])
row2 = to_row( row2 = to_row(
[('data', 4), ('numero_protocolo', 4), ('ano_protocolo', 4)]) [('data', 4), ('numero_protocolo', 4), ('ano_protocolo', 4)])
@ -821,7 +829,7 @@ class DocumentoAdministrativoForm(ModelForm):
[('assunto', 12)]) [('assunto', 12)])
row4 = to_row( row4 = to_row(
[('interessado', 8), ('tramitacao', 2), (InlineRadios('restrito'), 2)]) [('interessado', 7), ('tramitacao', 2), (InlineRadios('restrito'), 3)])
row5 = to_row( row5 = to_row(
[('texto_integral', 12)]) [('texto_integral', 12)])
@ -871,13 +879,16 @@ class DesvincularDocumentoForm(ModelForm):
try: try:
self.logger.debug("Tentando obter DocumentoAdministrativo com numero={}, ano={} e tipo={}." self.logger.debug("Tentando obter DocumentoAdministrativo com numero={}, ano={} e tipo={}."
.format(numero, ano, tipo)) .format(numero, ano, tipo))
documento = DocumentoAdministrativo.objects.get(numero=numero, ano=ano, tipo=tipo) documento = DocumentoAdministrativo.objects.get(
numero=numero, ano=ano, tipo=tipo)
if not documento.protocolo: if not documento.protocolo:
self.logger.error("DocumentoAdministrativo %s %s/%s não se encontra vinculado a nenhum protocolo." % (tipo, numero, ano)) self.logger.error(
"DocumentoAdministrativo %s %s/%s não se encontra vinculado a nenhum protocolo." % (tipo, numero, ano))
raise forms.ValidationError( raise forms.ValidationError(
_("%s %s/%s não se encontra vinculado a nenhum protocolo" % (tipo, numero, ano))) _("%s %s/%s não se encontra vinculado a nenhum protocolo" % (tipo, numero, ano)))
except ObjectDoesNotExist: except ObjectDoesNotExist:
self.logger.error("DocumentoAdministrativo %s %s/%s não existe" % (tipo, numero, ano)) self.logger.error(
"DocumentoAdministrativo %s %s/%s não existe" % (tipo, numero, ano))
raise forms.ValidationError( raise forms.ValidationError(
_("%s %s/%s não existe" % (tipo, numero, ano))) _("%s %s/%s não existe" % (tipo, numero, ano)))
@ -939,13 +950,16 @@ class DesvincularMateriaForm(forms.Form):
try: try:
self.logger.info("Tentando obter MateriaLegislativa com numero={}, ano={} e tipo={}." self.logger.info("Tentando obter MateriaLegislativa com numero={}, ano={} e tipo={}."
.format(numero, ano, tipo)) .format(numero, ano, tipo))
materia = MateriaLegislativa.objects.get(numero=numero, ano=ano, tipo=tipo) materia = MateriaLegislativa.objects.get(
numero=numero, ano=ano, tipo=tipo)
if not materia.numero_protocolo: if not materia.numero_protocolo:
self.logger.error("MateriaLegislativa %s %s/%s não se encontra vinculada a nenhum protocolo" % (tipo, numero, ano)) self.logger.error(
"MateriaLegislativa %s %s/%s não se encontra vinculada a nenhum protocolo" % (tipo, numero, ano))
raise forms.ValidationError( raise forms.ValidationError(
_("%s %s/%s não se encontra vinculada a nenhum protocolo" % (tipo, numero, ano))) _("%s %s/%s não se encontra vinculada a nenhum protocolo" % (tipo, numero, ano)))
except ObjectDoesNotExist: except ObjectDoesNotExist:
self.logger.error("MateriaLegislativa %s %s/%s não existe" % (tipo, numero, ano)) self.logger.error(
"MateriaLegislativa %s %s/%s não existe" % (tipo, numero, ano))
raise forms.ValidationError( raise forms.ValidationError(
_("%s %s/%s não existe" % (tipo, numero, ano))) _("%s %s/%s não existe" % (tipo, numero, ano)))
@ -999,6 +1013,7 @@ def filtra_tramitacao_adm_destino_and_status(status, destino):
unidade_tramitacao_destino=destino).distinct().values_list( unidade_tramitacao_destino=destino).distinct().values_list(
'documento_id', flat=True) 'documento_id', flat=True)
class FichaPesquisaAdmForm(forms.Form): class FichaPesquisaAdmForm(forms.Form):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

25
sapl/protocoloadm/migrations/0011_auto_20190101_1618.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-01 18:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0010_auto_20181212_1900'),
]
operations = [
migrations.AlterField(
model_name='documentoadministrativo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(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=[(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'),
),
]

25
sapl/protocoloadm/migrations/0012_auto_20190104_1021.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-04 12:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0011_auto_20190101_1618'),
]
operations = [
migrations.AlterField(
model_name='documentoadministrativo',
name='ano',
field=models.PositiveSmallIntegerField(choices=[(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=[(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'),
),
]

26
sapl/protocoloadm/migrations/0013_auto_20190106_1336.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2019-01-06 15:36
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0012_auto_20190104_1021'),
]
operations = [
migrations.AlterField(
model_name='protocolo',
name='tipo_documento',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='protocoloadm.TipoDocumentoAdministrativo', verbose_name='Tipo de Documento'),
),
migrations.AlterField(
model_name='protocolo',
name='tipo_materia',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='materia.TipoMateriaLegislativa', verbose_name='Tipo de Matéria'),
),
]

15
sapl/protocoloadm/models.py

@ -1,8 +1,8 @@
import reversion
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from model_utils import Choices from model_utils import Choices
import reversion
from sapl.base.models import Autor from sapl.base.models import Autor
from sapl.materia.models import TipoMateriaLegislativa, UnidadeTramitacao from sapl.materia.models import TipoMateriaLegislativa, UnidadeTramitacao
@ -56,10 +56,11 @@ class Protocolo(models.Model):
null=False, null=False,
choices=RANGE_ANOS, choices=RANGE_ANOS,
verbose_name=_('Ano do Protocolo')) verbose_name=_('Ano do Protocolo'))
# TODO: Remover esses dois campos após migração,
# TODO: pois timestamp supre a necessidade # FIXME: https://github.com/interlegis/sapl/issues/2337
data = models.DateField(null=True, blank=True) data = models.DateField(null=True, blank=True)
hora = models.TimeField(null=True, blank=True) hora = models.TimeField(null=True, blank=True)
# Não foi utilizado auto_now_add=True em timestamp porque # Não foi utilizado auto_now_add=True em timestamp porque
# ele usa datetime.now que não é timezone aware. # ele usa datetime.now que não é timezone aware.
timestamp = models.DateTimeField(default=timezone.now) timestamp = models.DateTimeField(default=timezone.now)
@ -78,13 +79,13 @@ class Protocolo(models.Model):
blank=True, blank=True,
null=True, null=True,
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Tipo de documento')) verbose_name=_('Tipo de Documento'))
tipo_materia = models.ForeignKey( tipo_materia = models.ForeignKey(
TipoMateriaLegislativa, TipoMateriaLegislativa,
blank=True, blank=True,
null=True, null=True,
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Tipo Matéria')) verbose_name=_('Tipo de Matéria'))
numero_paginas = models.PositiveIntegerField( numero_paginas = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Número de Páginas')) blank=True, null=True, verbose_name=_('Número de Páginas'))
observacao = models.TextField( observacao = models.TextField(
@ -299,10 +300,12 @@ class TramitacaoAdministrativo(models.Model):
'documento': self.documento, 'status': self.status 'documento': self.documento, 'status': self.status
} }
@reversion.register() @reversion.register()
class AcompanhamentoDocumento(models.Model): class AcompanhamentoDocumento(models.Model):
usuario = models.CharField(max_length=50) usuario = models.CharField(max_length=50)
documento = models.ForeignKey(DocumentoAdministrativo, on_delete=models.CASCADE) documento = models.ForeignKey(
DocumentoAdministrativo, on_delete=models.CASCADE)
email = models.EmailField( email = models.EmailField(
max_length=100, verbose_name=_('E-mail')) max_length=100, verbose_name=_('E-mail'))
data_cadastro = models.DateField(auto_now_add=True) data_cadastro = models.DateField(auto_now_add=True)

10
sapl/protocoloadm/tests/test_protocoloadm.py

@ -1,10 +1,11 @@
from datetime import date, timedelta from datetime import date, timedelta
import pytest
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils import timezone
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from model_mommy import mommy from model_mommy import mommy
import pytest
from sapl.materia.models import UnidadeTramitacao from sapl.materia.models import UnidadeTramitacao
from sapl.protocoloadm.forms import (AnularProcoloAdmForm, from sapl.protocoloadm.forms import (AnularProcoloAdmForm,
@ -191,7 +192,7 @@ def test_create_tramitacao(admin_client):
'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk, 'unidade_tramitacao_destino': unidade_tramitacao_destino_2.pk,
'documento': documento_adm.pk, 'documento': documento_adm.pk,
'status': status.pk, 'status': status.pk,
'data_tramitacao': date.today() + timedelta( 'data_tramitacao': timezone.now().date() + timedelta(
days=1)}, days=1)},
follow=True) follow=True)
@ -368,6 +369,11 @@ def test_documento_administrativo_invalido():
def test_documento_administrativo_protocolo_inexistente(): def test_documento_administrativo_protocolo_inexistente():
tipo = mommy.make(TipoDocumentoAdministrativo) tipo = mommy.make(TipoDocumentoAdministrativo)
protocolo = mommy.make(Protocolo,
ano=2017,
numero=10,
anulado=False,
tipo_documento=tipo)
form = DocumentoAdministrativoForm(data={'ano': '2017', form = DocumentoAdministrativoForm(data={'ano': '2017',
'tipo': str(tipo.pk), 'tipo': str(tipo.pk),

70
sapl/sessao/forms.py

@ -1,9 +1,9 @@
from django.contrib import messages
from datetime import datetime from datetime import datetime
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button, Fieldset, Layout from crispy_forms.layout import HTML, Button, Fieldset, Layout
from django import forms from django import forms
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import transaction from django.db import transaction
@ -22,31 +22,16 @@ from sapl.materia.models import (MateriaLegislativa, StatusTramitacao,
from sapl.parlamentares.models import Parlamentar, Legislatura, Mandato from sapl.parlamentares.models import Parlamentar, Legislatura, Mandato
from sapl.utils import (RANGE_DIAS_MES, RANGE_MESES, from sapl.utils import (RANGE_DIAS_MES, RANGE_MESES,
MateriaPesquisaOrderingFilter, autor_label, MateriaPesquisaOrderingFilter, autor_label,
autor_modal, timezone) autor_modal, timezone, choice_anos_com_sessaoplenaria)
from .models import (Bancada, Bloco, ExpedienteMateria, JustificativaAusencia, from .models import (Bancada, Bloco, ExpedienteMateria, JustificativaAusencia,
Orador, OradorExpediente, OrdemDia, PresencaOrdemDia, SessaoPlenaria, Orador, OradorExpediente, OrdemDia, PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca, TipoJustificativa, TipoResultadoVotacao, SessaoPlenariaPresenca, TipoJustificativa, TipoResultadoVotacao,
OcorrenciaSessao, RegistroVotacao, RetiradaPauta, TipoRetiradaPauta) OcorrenciaSessao, RegistroVotacao, RetiradaPauta, TipoRetiradaPauta)
def recupera_anos(): MES_CHOICES = RANGE_MESES
try: DIA_CHOICES = RANGE_DIAS_MES
anos_list = SessaoPlenaria.objects.all().dates('data_inicio', 'year')
# a listagem deve ser em ordem descrescente, mas por algum motivo
# a adicao de .order_by acima depois do all() nao surte efeito
# apos a adicao do .dates(), por isso o reversed() abaixo
anos = [(k.year, k.year) for k in reversed(anos_list)]
return anos
except Exception:
return []
def ANO_CHOICES():
return [('', '---------')] + recupera_anos()
MES_CHOICES = [('', '---------')] + RANGE_MESES
DIA_CHOICES = [('', '---------')] + RANGE_DIAS_MES
ORDENACAO_RESUMO = [('cont_mult', 'Conteúdo Multimídia'), ORDENACAO_RESUMO = [('cont_mult', 'Conteúdo Multimídia'),
@ -232,8 +217,10 @@ class RetiradaPautaForm(ModelForm):
q = Q(sessao_plenaria=kwargs['initial']['sessao_plenaria']) q = Q(sessao_plenaria=kwargs['initial']['sessao_plenaria'])
ordens = OrdemDia.objects.filter(q) ordens = OrdemDia.objects.filter(q)
expedientes = ExpedienteMateria.objects.filter(q) expedientes = ExpedienteMateria.objects.filter(q)
retiradas_ordem = [r.ordem for r in RetiradaPauta.objects.filter(q, ordem__in=ordens)] retiradas_ordem = [
retiradas_expediente = [r.expediente for r in RetiradaPauta.objects.filter(q, expediente__in=expedientes)] r.ordem for r in RetiradaPauta.objects.filter(q, ordem__in=ordens)]
retiradas_expediente = [r.expediente for r in RetiradaPauta.objects.filter(
q, expediente__in=expedientes)]
setOrdem = set(ordens) - set(retiradas_ordem) setOrdem = set(ordens) - set(retiradas_ordem)
setExpediente = set(expedientes) - set(retiradas_expediente) setExpediente = set(expedientes) - set(retiradas_expediente)
@ -264,19 +251,23 @@ class RetiradaPautaForm(ModelForm):
sessao_plenaria = self.instance.sessao_plenaria sessao_plenaria = self.instance.sessao_plenaria
if self.cleaned_data['data'] < sessao_plenaria.data_inicio: if self.cleaned_data['data'] < sessao_plenaria.data_inicio:
raise ValidationError(_("Data de retirada de pauta anterior à abertura da Sessão.")) raise ValidationError(
_("Data de retirada de pauta anterior à abertura da Sessão."))
if sessao_plenaria.data_fim and self.cleaned_data['data'] > sessao_plenaria.data_fim: if sessao_plenaria.data_fim and self.cleaned_data['data'] > sessao_plenaria.data_fim:
raise ValidationError(_("Data de retirada de pauta posterior ao encerramento da Sessão.")) raise ValidationError(
_("Data de retirada de pauta posterior ao encerramento da Sessão."))
if self.cleaned_data['ordem'] and self.cleaned_data['ordem'].registrovotacao_set.exists(): if self.cleaned_data['ordem'] and self.cleaned_data['ordem'].registrovotacao_set.exists():
raise ValidationError(_("Essa matéria já foi votada, portanto não pode ser retirada de pauta.")) raise ValidationError(
_("Essa matéria já foi votada, portanto não pode ser retirada de pauta."))
elif self.cleaned_data['expediente'] and self.cleaned_data['expediente'].registrovotacao_set.exists(): elif self.cleaned_data['expediente'] and self.cleaned_data['expediente'].registrovotacao_set.exists():
raise ValidationError(_("Essa matéria já foi votada, portanto não pode ser retirada de pauta.")) raise ValidationError(
_("Essa matéria já foi votada, portanto não pode ser retirada de pauta."))
return self.cleaned_data return self.cleaned_data
def save(self, commit=False): def save(self, commit=False):
retirada = super(RetiradaPautaForm, self).save(commit=False) retirada = super(RetiradaPautaForm, self).save(commit=commit)
if retirada.ordem: if retirada.ordem:
retirada.materia = retirada.ordem.materia retirada.materia = retirada.ordem.materia
elif retirada.expediente: elif retirada.expediente:
@ -284,6 +275,7 @@ class RetiradaPautaForm(ModelForm):
retirada.save() retirada.save()
return retirada return retirada
class BancadaForm(ModelForm): class BancadaForm(ModelForm):
class Meta: class Meta:
@ -522,8 +514,10 @@ class VotacaoForm(forms.Form):
votos_sim = forms.IntegerField(label='Sim') votos_sim = forms.IntegerField(label='Sim')
votos_nao = forms.IntegerField(label='Não') votos_nao = forms.IntegerField(label='Não')
abstencoes = forms.IntegerField(label='Abstenções') abstencoes = forms.IntegerField(label='Abstenções')
total_presentes = forms.IntegerField(required=False, widget=forms.HiddenInput()) total_presentes = forms.IntegerField(
voto_presidente = forms.IntegerField(label='A totalização inclui o voto do Presidente?') required=False, widget=forms.HiddenInput())
voto_presidente = forms.IntegerField(
label='A totalização inclui o voto do Presidente?')
total_votos = forms.IntegerField(required=False, label='total') total_votos = forms.IntegerField(required=False, label='total')
observacao = forms.CharField(required=False, label='Observação') observacao = forms.CharField(required=False, label='Observação')
resultado_votacao = forms.CharField(label='Resultado da Votação') resultado_votacao = forms.CharField(label='Resultado da Votação')
@ -540,11 +534,12 @@ class VotacaoForm(forms.Form):
qtde_votos = votos_sim + votos_nao + abstencoes qtde_votos = votos_sim + votos_nao + abstencoes
voto_presidente = cleaned_data['voto_presidente'] voto_presidente = cleaned_data['voto_presidente']
if not voto_presidente: if qtde_presentes and not voto_presidente:
qtde_presentes -= 1 qtde_presentes -= 1
if qtde_votos != qtde_presentes: if qtde_presentes and qtde_votos != qtde_presentes:
raise ValidationError('O total de votos não corresponde com a quantidade de presentes!') raise ValidationError(
'O total de votos não corresponde com a quantidade de presentes!')
return cleaned_data return cleaned_data
@ -569,9 +564,11 @@ class VotacaoEditForm(forms.Form):
class SessaoPlenariaFilterSet(django_filters.FilterSet): class SessaoPlenariaFilterSet(django_filters.FilterSet):
data_inicio__year = django_filters.ChoiceFilter(required=False, data_inicio__year = django_filters.ChoiceFilter(
required=False,
label='Ano', label='Ano',
choices=ANO_CHOICES) choices=choice_anos_com_sessaoplenaria
)
data_inicio__month = django_filters.ChoiceFilter(required=False, data_inicio__month = django_filters.ChoiceFilter(required=False,
label='Mês', label='Mês',
choices=MES_CHOICES) choices=MES_CHOICES)
@ -875,7 +872,6 @@ class JustificativaAusenciaForm(ModelForm):
legislatura=legislatura).order_by('parlamentar__nome_parlamentar') legislatura=legislatura).order_by('parlamentar__nome_parlamentar')
parlamentares = [m.parlamentar for m in mandato] parlamentares = [m.parlamentar for m in mandato]
super(JustificativaAusenciaForm, self).__init__( super(JustificativaAusenciaForm, self).__init__(
*args, **kwargs) *args, **kwargs)
@ -900,7 +896,7 @@ class JustificativaAusenciaForm(ModelForm):
("0", "------------")] + [(p.id, p) for p in setFinal] ("0", "------------")] + [(p.id, p) for p in setFinal]
def clean(self): def clean(self):
cleaned_data = super(JustificativaAusenciaForm, self).clean() super(JustificativaAusenciaForm, self).clean()
if not self.is_valid(): if not self.is_valid():
return self.cleaned_data return self.cleaned_data
@ -913,7 +909,7 @@ class JustificativaAusenciaForm(ModelForm):
else: else:
return self.cleaned_data return self.cleaned_data
def save(self, commit=False): def save(self):
justificativa = super().save(True) justificativa = super().save(True)

14
sapl/sessao/views.py

@ -1902,7 +1902,8 @@ class VotacaoView(SessaoPermissionMixin):
qtde_presentes -= 1 qtde_presentes -= 1
if (qtde_votos > qtde_presentes or qtde_votos < qtde_presentes): if (qtde_votos > qtde_presentes or qtde_votos < qtde_presentes):
form._errors["total_votos"] = ErrorList([u""]) msg = _('O total de votos não corresponde com a quantidade de presentes!')
messages.add_message(request, messages.ERROR, msg)
return self.render_to_response(context) return self.render_to_response(context)
elif (qtde_presentes == qtde_votos): elif (qtde_presentes == qtde_votos):
try: try:
@ -3280,7 +3281,7 @@ class JustificativaAusenciaCrud(MasterDetailCrud):
pass pass
class VotacaoEmBlocoExpediente(ListView): class VotacaoEmBlocoExpediente(PermissionRequiredForAppCrudMixin, ListView):
model = ExpedienteMateria model = ExpedienteMateria
template_name = 'sessao/votacao/votacao_bloco_expediente.html' template_name = 'sessao/votacao/votacao_bloco_expediente.html'
@ -3303,7 +3304,7 @@ class VotacaoEmBlocoExpediente(ListView):
return context return context
class VotacaoEmBlocoOrdemDia(ListView): class VotacaoEmBlocoOrdemDia(PermissionRequiredForAppCrudMixin, ListView):
model = OrdemDia model = OrdemDia
template_name = 'sessao/votacao/votacao_bloco_ordem.html' template_name = 'sessao/votacao/votacao_bloco_ordem.html'
app_label = AppConfig.label app_label = AppConfig.label
@ -3325,12 +3326,12 @@ class VotacaoEmBlocoOrdemDia(ListView):
return context return context
class VotacaoEmBlocoSimbolicaView(TemplateView): class VotacaoEmBlocoSimbolicaView(PermissionRequiredForAppCrudMixin, TemplateView):
""" """
Votação Simbólica Votação Simbólica
""" """
app_label = AppConfig.label
template_name = 'sessao/votacao/votacao_simbolica_bloco.html' template_name = 'sessao/votacao/votacao_simbolica_bloco.html'
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -3502,10 +3503,11 @@ class VotacaoEmBlocoSimbolicaView(TemplateView):
return self.render_to_response(context) return self.render_to_response(context)
class VotacaoEmBlocoNominalView(TemplateView): class VotacaoEmBlocoNominalView(PermissionRequiredForAppCrudMixin, TemplateView):
""" """
Votação Nominal Votação Nominal
""" """
app_label = AppConfig.label
template_name = 'sessao/votacao/votacao_nominal_bloco.html' template_name = 'sessao/votacao/votacao_nominal_bloco.html'
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

22
sapl/settings.py

@ -100,23 +100,27 @@ INSTALLED_APPS = (
# FTS = Full Text Search # FTS = Full Text Search
# Desabilita a indexação textual até encontramos uma solução para a issue # Desabilita a indexação textual até encontramos uma solução para a issue
# https://github.com/interlegis/sapl/issues/2055 # https://github.com/interlegis/sapl/issues/2055
#HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.BaseSignalProcessor' # Disable auto index
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.BaseSignalProcessor'
SEARCH_BACKEND = 'haystack.backends.whoosh_backend.WhooshEngine' SEARCH_BACKEND = 'haystack.backends.whoosh_backend.WhooshEngine'
SEARCH_URL = ('PATH', PROJECT_DIR.child('whoosh')) SEARCH_URL = ('PATH', PROJECT_DIR.child('whoosh'))
SOLR_URL = config('SOLR_URL', cast=str, default='') # SOLR
if SOLR_URL: USE_SOLR = config('USE_SOLR', cast=bool, default=False)
SEARCH_BACKEND = 'haystack.backends.solr_backend.SolrEngine' SOLR_URL = config('SOLR_URL', cast=str, default='http://localhost:8983')
SEARCH_URL = ('URL', config('SOLR_URL', cast=str)) SOLR_COLLECTION = config('SOLR_COLLECTION', cast=str, default='sapl')
# ...or for multicore...
# 'URL': 'http://127.0.0.1:8983/solr/mysite',
if USE_SOLR:
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' #enable auto-index
SEARCH_BACKEND = 'haystack.backends.solr_backend.SolrEngine'
SEARCH_URL = ('URL', '{}/solr/{}'.format(SOLR_URL, SOLR_COLLECTION))
# BATCH_SIZE: default is 1000 if omitted, avoid Too Large Entity Body errors
HAYSTACK_CONNECTIONS = { HAYSTACK_CONNECTIONS = {
'default': { 'default': {
'ENGINE': SEARCH_BACKEND, 'ENGINE': SEARCH_BACKEND,
SEARCH_URL[0]: SEARCH_URL[1] SEARCH_URL[0]: SEARCH_URL[1],
'BATCH_SIZE': 1000,
'TIMEOUT': 60,
}, },
} }

12
sapl/static/js/app.js

@ -195,10 +195,22 @@ function OptionalCustomFrontEnd() {
if (this.type === "checkbox") { if (this.type === "checkbox") {
_label.prepend(_this); _label.prepend(_this);
var _div = _label.closest('.checkbox');
if (_div.length == 0) {
_label.addClass('checkbox-inline')
}
_this.checkbox(); _this.checkbox();
} }
else if (this.type === "radio") { else if (this.type === "radio") {
_label.prepend(_this); _label.prepend(_this);
var _div = _label.closest('.radio');
if (_div.length == 0) {
_label.addClass('radio-inline')
}
_this.radio(); _this.radio();
} }

503
sapl/static/styles/app.css

@ -1,503 +0,0 @@
.container-home {
position: relative;
padding: 2em 1.5em 1.5em 1.5em;
max-width: 1000px;
margin: 0 auto; }
.container-home a:hover {
color: #444;
-webkit-transition: 0.3s ease-in;
-moz-transition: 0.3s ease-in;
-o-transition: 0.3s ease-in; }
.container-home #homeIndex {
text-align: center; }
.container-home .homeBanner span {
color: white;
font-size: 32px;
font-weight: 600;
display: inline-block;
vertical-align: middle;
padding: 2px 45px 4px;
border: 2px solid; }
.container-home .homeBanner::after {
display: inline-block;
vertical-align: middle;
height: 100%; }
.container-home .homeBlock {
display: inline-block;
position: relative;
background-color: #F3F3F3;
width: 190px;
height: 260px;
margin: 3px;
text-align: center;
font-size: 0;
overflow: hidden; }
.container-home .homeBlock > a {
display: block;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0; }
.container-home .homeBlock::after {
content: '';
display: inline-block;
vertical-align: middle;
height: 100%;
overflow: visible;
clear: none;
visibility: initial; }
.container-home .homeContent {
position: relative;
padding: 10px;
text-align: justify;
font-size: 14px;
color: #FFF;
opacity: 0;
transition: opacity 0.5s ease;
display: inline-block;
vertical-align: middle; }
.container-home .homeContent p {
display: block;
line-height: 13px;
font-size: 80%;
color: white; }
.container-home .homeIcon {
position: relative;
display: inline-block;
width: 105px;
height: 105px;
border-radius: 50%;
background: #364347;
z-index: 1; }
.container-home .homeIcon::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background: #364347;
top: 0;
left: 0;
transform: scale(0.95);
transition: transform 0.6s ease; }
.container-home .homeIcon img {
position: absolute;
margin: auto;
top: 0;
bottom: 0;
right: 0;
left: 0;
transition: opacity 0.4s 0.4s ease; }
.container-home .homeFront {
position: absolute;
top: 46%;
width: 100%;
font-size: 0;
transform: translateY(-60%); }
.container-home .homeFront h2 {
position: absolute;
margin-top: 18px;
font-size: 22px;
font-weight: 700;
color: #595959 !important;
width: 100%;
padding: 0 6%;
z-index: 0; }
.container-home .homeTitle {
display: block;
height: 32px;
text-align: center;
width: 100%;
opacity: 0;
transition: opacity 0.4s ease; }
.container-home .homeTitle::before {
content: '';
display: inline-block;
vertical-align: middle;
height: 100%; }
.container-home .homeTitle h2 {
display: inline-block;
vertical-align: middle;
max-width: 110px;
font-size: 14px;
color: white !important;
line-height: 1em; }
.container-home .homeTitle img {
display: inline-block;
vertical-align: middle;
height: 30px;
margin-right: 5px; }
.container-home .homeBlock:hover .homeIcon::before {
transform: scale(3.6) translateY(7px); }
.container-home .homeBlock:hover .homeContent {
opacity: 1;
transition-delay: 0.2s; }
.container-home .homeBlock:hover .homeIcon img {
opacity: 0;
transition-duration: 0.2s;
transition-delay: 0s; }
.container-home .homeBlock:hover .homeTitle {
opacity: 1; }
html {
position: relative;
min-height: 100%; }
body {
margin-bottom: 160px; }
h1, h2, h3, h4, h5, h6, form, dl, dt, dd, p, div, img, a {
margin: 0;
padding: 0; }
h1, .h1 {
font-size: 30px; }
h2, .h2 {
font-size: 24px; }
h3, .h3 {
font-size: 20px; }
h4, .h4 {
font-size: 16px; }
h5, .h5 {
font-size: 14px; }
h6, .h6 {
font-size: 12px; }
p {
margin: 0.5em 0; }
p .control-label {
font-weight: bold; }
label {
margin-bottom: 0;
line-height: 1; }
fieldset fieldset {
font-size: 95%; }
fieldset fieldset legend {
font-size: 18px; }
.page-header {
margin: 20px 0px 10px; }
.caret.top {
transform: rotate(180deg); }
.btn:hover, .btn:focus {
color: inherit; }
.btn-default.btn-excluir {
color: #d9534f; }
.btn-default.btn-excluir:hover {
color: #fff;
border-color: #de6764;
background-color: #de6764; }
.btn-cancel-iframe {
position: relative;
text-align: right;
opacity: 0.5; }
.btn-cancel-iframe:hover {
opacity: 1; }
.btn-cancel-iframe a {
padding: 10px;
display: inline-block; }
.legend {
display: block;
width: 100%;
padding: 0;
margin-bottom: 20px;
font-size: 21px;
line-height: inherit;
color: #333333;
border: 0;
border-bottom: 1px solid #e5e5e5;
clear: both; }
.grid-gutter-width-right {
margin-right: 15px; }
.controls-file {
padding: 10px;
border: 1px solid #d6e1e5;
border-radius: 4px; }
.controls-file label.checkbox-inline {
margin: 0px;
display: block; }
.help-block-danger {
margin: 15px;
padding: 15px;
border: 2px dashed #f00; }
.control-label {
margin: 0; }
.form-control-static {
padding-top: 0;
min-height: auto; }
.form-control-static img {
max-width: 100%; }
.pagination {
padding-top: 25px; }
.modal .alert {
margin-bottom: 0; }
.avatar-parlamentar {
height: 128px;
width: 128px;
margin: 0 auto;
display: table; }
.masthead {
padding: 10px; }
.masthead .nav {
clear: both; }
.masthead .navbar-brand {
padding: 0px;
color: inherit;
font-size: 24px; }
.masthead .navbar-brand img.img-responsive {
height: 95px;
margin-right: 15px;
display: inline-block; }
.masthead .navbar-brand small {
color: #93A4AA;
font-size: 75%;
line-height: 25px; }
.masthead .navbar-brand .vcenter {
display: inline-block;
vertical-align: middle;
float: none;
padding: 10px; }
nav.navbar {
margin-bottom: 0;
border-radius: 0;
font-size: 15px; }
nav .navbar-nav > li > a {
padding-top: 0px;
padding-bottom: 0px;
line-height: 75px; }
nav .navbar-nav > li > a:hover {
background-color: #23527c; }
nav .navbar-nav > li:nth-child(2) > .dropdown-menu {
right: auto; }
nav .navbar-nav:last-child > li:last-child a {
padding-right: 0px; }
.controls-radio-checkbox {
padding: 0px;
border: 1px solid #d6e1e5;
border-radius: 4px;
min-height: 20px; }
.controls-radio-checkbox .checkbox, .controls-radio-checkbox .radio, .controls-radio-checkbox .checkbox-inline, .controls-radio-checkbox .radio-inline {
padding: 8px 8px 8px 36px;
margin: 0;
line-height: 1.6;
display: block; }
.controls-radio-checkbox .checkbox:hover, .controls-radio-checkbox .radio:hover, .controls-radio-checkbox .checkbox-inline:hover, .controls-radio-checkbox .radio-inline:hover {
background-color: #d6e1e5; }
.controls-radio-checkbox .checkbox .icons, .controls-radio-checkbox .radio .icons, .controls-radio-checkbox .checkbox-inline .icons, .controls-radio-checkbox .radio-inline .icons {
top: auto;
left: 8px; }
.controls-radio-checkbox .checkbox-inline, .controls-radio-checkbox .radio-inline {
display: inline-block; }
.controls-radio-checkbox .help-block {
margin: 15px;
padding: 15px;
border: 2px dashed #d6e1e5; }
.controls-radio-checkbox__old {
padding: 0px;
border: 1px solid #d6e1e5;
border-radius: 4px;
min-height: 20px; }
.controls-radio-checkbox__old label {
padding: 0;
line-height: 2.7;
padding-left: 36px; }
.controls-radio-checkbox__old label .icons {
top: 8px;
left: 8px; }
.controls-radio-checkbox__old label.checkbox-inline, .controls-radio-checkbox__old label.radio-inline {
padding-right: 8px; }
.controls-radio-checkbox__old label.checkbox-inline .icons, .controls-radio-checkbox__old label.radio-inline .icons {
top: 8px;
left: 8px; }
.controls-radio-checkbox__old .checkbox, .controls-radio-checkbox__old .radio, .controls-radio-checkbox__old .checkbox-inline, .controls-radio-checkbox__old .radio-inline {
margin: 0; }
.controls-radio-checkbox__old .checkbox:hover, .controls-radio-checkbox__old .radio:hover, .controls-radio-checkbox__old .checkbox-inline:hover, .controls-radio-checkbox__old .radio-inline:hover {
background-color: #d6e1e5; }
.manual, .manual ul {
padding-left: 1.5em;
list-style-type: none;
margin-top: 0;
font-size: 100%; }
.manual li {
display: list-item;
line-height: 1.5em;
padding-right: 0; }
.manual li a {
background-color: transparent;
border: none;
border-radius: none;
padding: 0; }
.container-tabaux .sidebar-tabaux {
background: #fafafa;
margin-top: -70px;
padding: 10px;
border: 1px solid #eee; }
.container-tabaux .sidebar-tabaux .navbar-right {
margin: 0; }
.container-tabaux .sidebar-tabaux .nav-pills > li + li {
margin-left: 0px; }
.container-tabaux .sidebar-tabaux li {
width: 100%; }
.container-tabaux .sidebar-tabaux span {
display: none; }
.container-tabaux .sidebar-tabaux .dropdown-menu {
padding: 0px;
right: 10px;
margin-top: -5px;
overflow: hidden; }
.container-tabaux .sidebar-tabaux .dropdown-menu a {
border: 0px; }
.container-tabaux ul {
list-style: none;
padding: 0; }
.container-tabaux .list {
font-family: "SourceSansProSemiBold", Helvetica, Arial, sans-serif;
font-size: 0px;
display: table;
width: 100%;
margin: 0; }
.container-tabaux .list ul {
display: table;
width: 100%;
margin: 0; }
.container-tabaux .list li {
width: calc(50%);
display: inline-block;
position: relative; }
.container-tabaux .list > li {
width: 100%;
border-bottom: 1px solid #eee;
padding-bottom: 20px;
margin-bottom: 20px; }
.container-tabaux .list .head_title {
color: #364347;
font-size: 2.4rem;
text-transform: none; }
.container-tabaux .list a span {
display: none; }
#styleparlamentar {
border: 0px solid #d6e1e5;
border-top-color: #d6e1e5;
border-right-color: #d6e1e5;
border-bottom-color: #d6e1e5;
border-left-color: #d6e1e5;
border-image-source: initial;
border-image-slice: initial;
border-image-repeat: initial;
font-size: 16px;
line-height: 1.467;
padding: 7px 12px;
height: 40px;
-webkit-appearance: none;
border-radius: 4px;
-webkit-box-shadow: none;
box-shadow: none;
margin-left: 1.0em; }
.footer {
background: #364347;
color: white;
text-align: center;
position: absolute;
width: 100%;
bottom: 0px; }
.footer p {
color: white;
margin-top: 10px; }
.footer .container {
padding-top: 25px; }
@media (max-width: 1199px) {
nav .container {
width: auto !important; }
.navbar-nav > li > a {
padding-left: 10.71429px;
padding-right: 10.71429px; } }
@media (max-width: 1091px) {
.container {
width: auto; }
.navbar-nav > li > a {
padding-left: 7.5px;
padding-right: 7.5px; }
.masthead .navbar-brand {
font-size: 22px; }
.masthead .navbar-brand img.img-responsive {
height: 60px;
margin-right: 7.5px; } }
@media (max-width: 991px) {
body {
margin: 0; }
.footer {
position: relative; }
.caret {
margin-left: 1px; }
.navbar-nav > li > a {
padding-left: 4px;
padding-right: 4px; } }
@media (max-width: 767px) {
nav .navbar-nav > li > a {
line-height: 2.5; }
nav .navbar-right {
position: absolute;
top: 0;
margin: 10px; }
nav .navbar-right > li {
vertical-align: top;
display: inline-block; }
nav .navbar-right > li a {
padding-left: 10px;
padding-right: 10px; }
nav .navbar-right .pesquisa.open ul {
position: absolute; }
nav .navbar-right .navbar-form {
margin: 8px 0; }
.table {
width: auto;
white-space: normal;
display: block;
overflow-x: auto; } }
@media (min-width: 1092px) and (max-width: 1199px) {
.container {
width: 1070px; } }
@media print {
a[href]:after {
content: none !important; } }

47
sapl/static/styles/app.scss

@ -110,16 +110,6 @@ fieldset {
margin-right: $grid-gutter-width / 2; margin-right: $grid-gutter-width / 2;
} }
.controls-file {
padding: 10px;
border: 1px solid #d6e1e5;
border-radius: 4px;
label.checkbox-inline {
margin: 0px;
display: block;
}
}
.help-block-danger { .help-block-danger {
margin: $grid-gutter-width / 2; margin: $grid-gutter-width / 2;
padding: $grid-gutter-width / 2; padding: $grid-gutter-width / 2;
@ -217,13 +207,12 @@ nav {
} }
} }
.controls-radio-checkbox { .controls-radio-checkbox, .controls-file {
padding: 0px; padding: 0px;
border: 1px solid #d6e1e5; border: 1px solid #d6e1e5;
border-radius: 4px; border-radius: 4px;
min-height: 20px; min-height: 20px;
.checkbox, .radio, .checkbox-inline, .radio-inline { .checkbox, .radio, .checkbox-inline, .radio-inline {
padding: 8px 8px 8px 36px; padding: 8px 8px 8px 36px;
margin: 0; margin: 0;
@ -247,36 +236,6 @@ nav {
padding: $grid-gutter-width / 2; padding: $grid-gutter-width / 2;
border: 2px dashed #d6e1e5; border: 2px dashed #d6e1e5;
} }
}
.controls-radio-checkbox__old {
padding: 0px;
border: 1px solid #d6e1e5;
border-radius: 4px;
min-height: 20px;
label {
padding: 0;
line-height: 2.7;
padding-left: 36px;
.icons {
top: 8px;
left: 8px;
}
&.checkbox-inline, &.radio-inline, {
padding-right: 8px;
.icons {
top: 8px;
left: 8px;
}
}
}
.checkbox, .radio, .checkbox-inline, .radio-inline {
margin: 0;
&:hover {
background-color: #d6e1e5;
}
}
} }
.manual { .manual {
@ -298,7 +257,9 @@ nav {
} }
} }
} }
.controls-file {
padding: 15px;
}
.container-tabaux { .container-tabaux {
.sidebar-tabaux { .sidebar-tabaux {
background: #fafafa; background: #fafafa;

1136
sapl/static/styles/compilacao.css

File diff suppressed because it is too large

2
sapl/templates/base.html

@ -184,7 +184,7 @@
<small> <small>
Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto. Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto.
</small> </small>
<span>Release: 3.1.138</span> <span>Release: 3.1.140</span>
</p> </p>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">

2
sapl/templates/base/RelatorioPresencaSessao_filter.html

@ -25,6 +25,7 @@
<thead class="thead-default" align="center"> <thead class="thead-default" align="center">
<tr class="active"> <tr class="active">
<th rowspan="2">Nome Parlamentar / Partido</th> <th rowspan="2">Nome Parlamentar / Partido</th>
<th rowspan="2">Titular</th>
<th colspan="2">Sessão</th> <th colspan="2">Sessão</th>
<th colspan="2">Ordem do Dia</th> <th colspan="2">Ordem do Dia</th>
</tr> </tr>
@ -39,6 +40,7 @@
{% for p in parlamentares %} {% for p in parlamentares %}
<tr> <tr>
<td><b>{{p.parlamentar}}</b> / {{p.parlamentar|filiacao_intervalo_filter:date_range|default:"Sem Partido"}}</td> <td><b>{{p.parlamentar}}</b> / {{p.parlamentar|filiacao_intervalo_filter:date_range|default:"Sem Partido"}}</td>
<td>{%if p.titular %} Sim {% else %} Não {% endif %}</td>
<td>{{p.sessao_count}}</td> <td>{{p.sessao_count}}</td>
<td>{{p.sessao_porc}}</td> <td>{{p.sessao_porc}}</td>
<td>{{p.ordemdia_count}}</td> <td>{{p.ordemdia_count}}</td>

2
sapl/templates/base/relatorios_list.html

@ -58,7 +58,7 @@
</tr> </tr>
{% if estatisticas_acesso_normas %} {% if estatisticas_acesso_normas %}
<tr> <tr>
<td><a href="{% url 'sapl.base:estatisticas_acesso' %}">Estatísticas de acesso de Normas.</a></td> <td><a href="{% url 'sapl.base:estatisticas_acesso' %}">Estatísticas de acesso de Normas</a></td>
<td> Normas por acesso. </td> <td> Normas por acesso. </td>
</tr> </tr>
{% endif %} {% endif %}

16
sapl/templates/materia/materialegislativa_filter.html

@ -3,11 +3,13 @@
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% block actions %} {% block actions %}
<div class="actions btn-group pull-right" role="group"> <div class="actions btn-group pull-right" role="group">
<!-- {% if USE_SOLR %}
<a href="{% url 'sapl.base:haystack_search' %}" class="btn btn-default"> <a href="{% url 'sapl.base:haystack_search' %}" class="btn btn-default">
Pesquisa Textual Pesquisa Textual
</a> --> </a>
{% endif %}
{% if perms.materia.add_materialegislativa %} {% if perms.materia.add_materialegislativa %}
<a href="{% url 'sapl.materia:materialegislativa_create' %}" class="btn btn-default"> <a href="{% url 'sapl.materia:materialegislativa_create' %}" class="btn btn-default">
@ -32,7 +34,11 @@
</thead> </thead>
{% if paginator.count %} {% if paginator.count %}
{% if paginator.count > 1 %} {% if paginator.count > 1 %}
<h3>{% blocktrans with paginator.count as total_materias %}Pesquisa concluída com sucesso! Foram encontradas {{total_materias}} matérias.{% endblocktrans %}</h3> {% if not tipo_listagem or tipo_listagem == '1' %}
<h3>{% blocktrans with paginator.count as total_materias %}Pesquisa detalhada concluída com sucesso! Foram encontradas {{total_materias}} matérias.{% endblocktrans %}</h3>
{% else %}
<h3>{% blocktrans with paginator.count as total_materias %}Pesquisa simplificada concluída com sucesso! Foram encontradas {{total_materias}} matérias.{% endblocktrans %}</h3>
{% endif %}
{% elif paginator.count == 1 %} {% elif paginator.count == 1 %}
<h3>{% trans 'Pesquisa concluída com sucesso! Foi encontrada 1 matéria.'%}</h3> <h3>{% trans 'Pesquisa concluída com sucesso! Foi encontrada 1 matéria.'%}</h3>
{% endif %} {% endif %}
@ -71,6 +77,9 @@
{% endfor %} {% endfor %}
</br> </br>
{% endif %} {% endif %}
{% if not tipo_listagem or tipo_listagem == '1' %}
{% if m.tramitacao_set.last.unidade_tramitacao_destino %} {% if m.tramitacao_set.last.unidade_tramitacao_destino %}
<strong>Localização Atual:</strong> &nbsp;{{m.tramitacao_set.last.unidade_tramitacao_destino}}</br> <strong>Localização Atual:</strong> &nbsp;{{m.tramitacao_set.last.unidade_tramitacao_destino}}</br>
{% endif %} {% endif %}
@ -148,6 +157,7 @@
{% if m.em_tramitacao and mail_service_configured %} {% if m.em_tramitacao and mail_service_configured %}
<a href="{% url 'sapl.materia:acompanhar_materia' m.id %}">Acompanhar Matéria</a> <a href="{% url 'sapl.materia:acompanhar_materia' m.id %}">Acompanhar Matéria</a>
{% endif %} {% endif %}
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

4
sapl/templates/norma/normajuridica_filter.html

@ -4,11 +4,11 @@
{% block actions %} {% block actions %}
<div class="actions btn-group pull-right" role="group"> <div class="actions btn-group pull-right" role="group">
<!-- {% if USE_SOLR %}
<a href="{% url 'sapl.base:haystack_search' %}" class="btn btn-default"> <a href="{% url 'sapl.base:haystack_search' %}" class="btn btn-default">
Pesquisa Textual Pesquisa Textual
</a> </a>
--> {% endif %}
{% if perms.norma.add_normajuridica %} {% if perms.norma.add_normajuridica %}

86
sapl/utils.py

@ -16,6 +16,7 @@ from django.contrib.contenttypes.fields import (GenericForeignKey, GenericRel,
GenericRelation) GenericRelation)
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.mail import get_connection from django.core.mail import get_connection
from django.db import models
from django.db.models import Q from django.db.models import Q
from django.utils import six, timezone from django.utils import six, timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -319,9 +320,10 @@ LISTA_DE_UFS = [
('EX', 'Exterior'), ('EX', 'Exterior'),
] ]
RANGE_ANOS = [(year, year) for year in range(timezone.now().year, RANGE_ANOS = [(year, year) for year in range(timezone.now().year + 1,
1889, -1)] 1889, -1)]
RANGE_MESES = [ RANGE_MESES = [
(1, 'Janeiro'), (1, 'Janeiro'),
(2, 'Fevereiro'), (2, 'Fevereiro'),
@ -339,6 +341,88 @@ RANGE_MESES = [
RANGE_DIAS_MES = [(n, n) for n in range(1, 32)] RANGE_DIAS_MES = [(n, n) for n in range(1, 32)]
def ANO_CHOICES():
return [('', '---------')] + RANGE_ANOS
def choice_anos(model):
try:
anos_list = model.objects.all().distinct(
'ano').order_by('-ano').values_list('ano', 'ano')
return list(anos_list)
except Exception:
return []
def choice_anos_com_materias():
from sapl.materia.models import MateriaLegislativa
return choice_anos(MateriaLegislativa)
def choice_anos_com_normas():
from sapl.norma.models import NormaJuridica
return choice_anos(NormaJuridica)
def choice_anos_com_protocolo():
from sapl.protocoloadm.models import Protocolo
return choice_anos(Protocolo)
def choice_anos_com_documentoadministrativo():
from sapl.protocoloadm.models import DocumentoAdministrativo
return choice_anos(DocumentoAdministrativo)
def choice_anos_com_sessaoplenaria():
try:
from sapl.sessao.models import SessaoPlenaria
anos_list = SessaoPlenaria.objects.all().dates('data_inicio', 'year')
# a listagem deve ser em ordem descrescente, mas por algum motivo
# a adicao de .order_by acima depois do all() nao surte efeito
# apos a adicao do .dates(), por isso o reversed() abaixo
anos = [(k.year, k.year) for k in reversed(anos_list)]
return anos
except Exception:
return []
def choice_force_optional(callable):
""" Django-filter faz algo que tenha o mesmo sentido em ChoiceFilter,
no entanto, as funções choice_anos_... podem ser usadas em formulários
comuns de adição e/ou edição, com a particularidade de terem
required=False.
Neste caso para ser possível contar com a otimização de apenas mostrar anos
que estejam na base de dados e ainda colocar o item opcional '---------',
é necessário encapsular então, as funções choice_anos_... com a
esta função choice_force_optional... isso ocorre e foi aplicado
inicialmente no cadastro de documentos administrativos onde tem-se
opcionalmente a possibilidade de colocar o ano do protocolo.
Em ChoiceFilter choice_force_optional não deve ser usado pois duplicaria
o item opcional '---------' que ChoiceFilter o adiciona, como dito
anteriormente.
"""
def _func():
return [('', '---------')] + callable()
return _func
FILTER_OVERRIDES_DATEFIELD = {
'filter_class': django_filters.DateFromToRangeFilter,
'extra': lambda f: {
'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')),
'widget': RangeWidgetOverride
}
}
class FilterOverridesMetaMixin:
filter_overrides = {
models.DateField: FILTER_OVERRIDES_DATEFIELD
}
TIPOS_TEXTO_PERMITIDOS = ( TIPOS_TEXTO_PERMITIDOS = (
'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.text',
'application/x-vnd.oasis.opendocument.text', 'application/x-vnd.oasis.opendocument.text',

13
setup.py

@ -12,11 +12,6 @@ install_requires = [
'dj-database-url==0.4.1', 'dj-database-url==0.4.1',
'django-haystack==2.6.0', 'django-haystack==2.6.0',
'django>=1.10,<1.11', 'django>=1.10,<1.11',
# TODO O django-admin-bootstrapped 2.5.7 não inseriu a mudança que permite
# a compatibilidade com Django 1.9+. A linha abaixo será mudada quando uma
# nova versão do django-admin-bootstrapped for lançada
# 'git+git://github.com/django-admin-bootstrapped/
# django-admin-bootstrapped.git',
'django-bootstrap3==7.0.1', 'django-bootstrap3==7.0.1',
'django-bower==5.2.0', 'django-bower==5.2.0',
'django-braces==1.9.0', 'django-braces==1.9.0',
@ -32,12 +27,11 @@ install_requires = [
'drfdocs', 'drfdocs',
'easy-thumbnails==2.5', 'easy-thumbnails==2.5',
'django-image-cropping==1.1.0', 'django-image-cropping==1.1.0',
# 'git+git://github.com/interlegis/trml2pdf.git',
'libsass==0.11.1', 'libsass==0.11.1',
'psycopg2==2.7.4', 'psycopg2==2.7.4',
'python-decouple==3.0', 'python-decouple==3.0',
'pytz==2016.4', 'pytz==2016.4',
'pyyaml==3.11', 'pyyaml==4.2b1',
'rtyaml==0.0.3', 'rtyaml==0.0.3',
'textract==1.5.0', 'textract==1.5.0',
'unipath==1.1', 'unipath==1.1',
@ -49,10 +43,13 @@ install_requires = [
'whoosh==2.7.4', 'whoosh==2.7.4',
'django-speedinfo==1.3.5', 'django-speedinfo==1.3.5',
'django-reversion-compare==0.8.4' 'django-reversion-compare==0.8.4'
# 'git+git://github.com/interlegis/trml2pdf.git',
# 'git+git://github.com/jasperlittle/django-rest-framework-docs'
# 'git+git://github.com/rubgombar1/django-admin-bootstrapped.git''
] ]
setup( setup(
name='interlegis-sapl', name='interlegis-sapl',
version='3.1.138', version='3.1.140',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007', license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007',

61
solr/docker-compose.yml

@ -0,0 +1,61 @@
version: '2'
services:
sapldb:
image: postgres:10.5-alpine
restart: always
environment:
POSTGRES_PASSWORD: sapl
POSTGRES_USER: sapl
POSTGRES_DB: sapl
PGDATA : /var/lib/postgresql/data/
volumes:
- sapldb_data:/var/lib/postgresql/data/
ports:
- "5432:5432"
saplsolr:
image: solr:7.4-alpine
restart: always
command: bin/solr start -c -f
volumes:
- solr_data:/opt/solr/server/solr
- solr_configsets:/opt/solr/server/solr/configsets
ports:
- "8983:8983"
sapl:
image: interlegis/sapl:3.1.138
# build: .
restart: always
environment:
ADMIN_PASSWORD: interlegis
ADMIN_EMAIL: email@dominio.net
DEBUG: 'False'
EMAIL_PORT: 587
EMAIL_USE_TLS: 'False'
EMAIL_HOST: smtp.dominio.net
EMAIL_HOST_USER: usuariosmtp
EMAIL_HOST_PASSWORD: senhasmtp
USE_SOLR: 'True'
#SOLR_COLLECTION: sapl
#SOLR_HOST: saplsolr
SOLR_URL: http://saplsolr:8983/solr/sapl
TZ: America/Sao_Paulo
volumes:
- sapl_data:/var/interlegis/sapl/data
- sapl_media:/var/interlegis/sapl/media
- sapl_root:/var/interlegis/sapl
volumes_from:
- saplsolr
depends_on:
- sapldb
- saplsolr
ports:
- "80:80"
volumes:
sapldb_data:
sapl_data:
sapl_media:
sapl_root:
solr_data:
solr_configsets:

54
solr/sapl_configset/conf/lang/stopwords_en.txt

@ -0,0 +1,54 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# a couple of test stopwords to test that the words are really being
# configured from this file:
stopworda
stopwordb
# Standard english stop words taken from Lucene's StopAnalyzer
a
an
and
are
as
at
be
but
by
for
if
in
into
is
it
no
not
of
on
or
such
that
the
their
then
there
these
they
this
to
was
will
with

253
solr/sapl_configset/conf/lang/stopwords_pt.txt

@ -0,0 +1,253 @@
| From svn.tartarus.org/snowball/trunk/website/algorithms/portuguese/stop.txt
| This file is distributed under the BSD License.
| See http://snowball.tartarus.org/license.php
| Also see http://www.opensource.org/licenses/bsd-license.html
| - Encoding was converted to UTF-8.
| - This notice was added.
|
| NOTE: To use this file with StopFilterFactory, you must specify format="snowball"
| A Portuguese stop word list. Comments begin with vertical bar. Each stop
| word is at the start of a line.
| The following is a ranked list (commonest to rarest) of stopwords
| deriving from a large sample of text.
| Extra words have been added at the end.
de | of, from
a | the; to, at; her
o | the; him
que | who, that
e | and
do | de + o
da | de + a
em | in
um | a
para | for
| é from SER
com | with
não | not, no
uma | a
os | the; them
no | em + o
se | himself etc
na | em + a
por | for
mais | more
as | the; them
dos | de + os
como | as, like
mas | but
| foi from SER
ao | a + o
ele | he
das | de + as
| tem from TER
à | a + a
seu | his
sua | her
ou | or
| ser from SER
quando | when
muito | much
| há from HAV
nos | em + os; us
já | already, now
| está from EST
eu | I
também | also
só | only, just
pelo | per + o
pela | per + a
até | up to
isso | that
ela | he
entre | between
| era from SER
depois | after
sem | without
mesmo | same
aos | a + os
| ter from TER
seus | his
quem | whom
nas | em + as
me | me
esse | that
eles | they
| estão from EST
você | you
| tinha from TER
| foram from SER
essa | that
num | em + um
nem | nor
suas | her
meu | my
às | a + as
minha | my
| têm from TER
numa | em + uma
pelos | per + os
elas | they
| havia from HAV
| seja from SER
qual | which
| será from SER
nós | we
| tenho from TER
lhe | to him, her
deles | of them
essas | those
esses | those
pelas | per + as
este | this
| fosse from SER
dele | of him
| other words. There are many contractions such as naquele = em+aquele,
| mo = me+o, but they are rare.
| Indefinite article plural forms are also rare.
tu | thou
te | thee
vocês | you (plural)
vos | you
lhes | to them
meus | my
minhas
teu | thy
tua
teus
tuas
nosso | our
nossa
nossos
nossas
dela | of her
delas | of them
esta | this
estes | these
estas | these
aquele | that
aquela | that
aqueles | those
aquelas | those
isto | this
aquilo | that
| forms of estar, to be (not including the infinitive):
estou
está
estamos
estão
estive
esteve
estivemos
estiveram
estava
estávamos
estavam
estivera
estivéramos
esteja
estejamos
estejam
estivesse
estivéssemos
estivessem
estiver
estivermos
estiverem
| forms of haver, to have (not including the infinitive):
hei
havemos
hão
houve
houvemos
houveram
houvera
houvéramos
haja
hajamos
hajam
houvesse
houvéssemos
houvessem
houver
houvermos
houverem
houverei
houverá
houveremos
houverão
houveria
houveríamos
houveriam
| forms of ser, to be (not including the infinitive):
sou
somos
são
era
éramos
eram
fui
foi
fomos
foram
fora
fôramos
seja
sejamos
sejam
fosse
fôssemos
fossem
for
formos
forem
serei
será
seremos
serão
seria
seríamos
seriam
| forms of ter, to have (not including the infinitive):
tenho
tem
temos
tém
tinha
tínhamos
tinham
tive
teve
tivemos
tiveram
tivera
tivéramos
tenha
tenhamos
tenham
tivesse
tivéssemos
tivessem
tiver
tivermos
tiverem
terei
terá
teremos
terão
teria
teríamos
teriam

573
solr/sapl_configset/conf/managed-schema

@ -0,0 +1,573 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!--
This example schema is the recommended starting point for users.
It should be kept correct and concise, usable out-of-the-box.
For more information, on how to customize this file, please see
http://lucene.apache.org/solr/guide/documents-fields-and-schema-design.html
PERFORMANCE NOTE: this schema includes many optional features and should not
be used for benchmarking. To improve performance one could
- set stored="false" for all fields possible (esp large fields) when you
only need to search on the field but don't need to return the original
value.
- set indexed="false" if you don't need to search on the field, but only
return the field as a result of searching on other indexed fields.
- remove all unneeded copyField statements
- for best index size and searching performance, set "index" to false
for all general text fields, use copyField to copy them to the
catchall "text" field, and use that for searching.
-->
<schema name="default-config" version="1.6">
<!-- attribute "name" is the name of this schema and is only used for display purposes.
version="x.y" is Solr's version number for the schema syntax and
semantics. It should not normally be changed by applications.
1.0: multiValued attribute did not exist, all fields are multiValued
by nature
1.1: multiValued attribute introduced, false by default
1.2: omitTermFreqAndPositions attribute introduced, true by default
except for text fields.
1.3: removed optional field compress feature
1.4: autoGeneratePhraseQueries attribute introduced to drive QueryParser
behavior when a single string produces multiple tokens. Defaults
to off for version >= 1.4
1.5: omitNorms defaults to true for primitive field types
(int, float, boolean, string...)
1.6: useDocValuesAsStored defaults to true.
-->
<!-- Valid attributes for fields:
name: mandatory - the name for the field
type: mandatory - the name of a field type from the
fieldTypes section
indexed: true if this field should be indexed (searchable or sortable)
stored: true if this field should be retrievable
docValues: true if this field should have doc values. Doc Values is
recommended (required, if you are using *Point fields) for faceting,
grouping, sorting and function queries. Doc Values will make the index
faster to load, more NRT-friendly and more memory-efficient.
They are currently only supported by StrField, UUIDField, all
*PointFields, and depending on the field type, they might require
the field to be single-valued, be required or have a default value
(check the documentation of the field type you're interested in for
more information)
multiValued: true if this field may contain multiple values per document
omitNorms: (expert) set to true to omit the norms associated with
this field (this disables length normalization and index-time
boosting for the field, and saves some memory). Only full-text
fields or fields that need an index-time boost need norms.
Norms are omitted for primitive (non-analyzed) types by default.
termVectors: [false] set to true to store the term vector for a
given field.
When using MoreLikeThis, fields used for similarity should be
stored for best performance.
termPositions: Store position information with the term vector.
This will increase storage costs.
termOffsets: Store offset information with the term vector. This
will increase storage costs.
required: The field is required. It will throw an error if the
value does not exist
default: a value that should be used if no value is specified
when adding a document.
-->
<!-- field names should consist of alphanumeric or underscore characters only and
not start with a digit. This is not currently strictly enforced,
but other field names will not have first class support from all components
and back compatibility is not guaranteed. Names with both leading and
trailing underscores (e.g. _version_) are reserved.
-->
<!-- In this _default configset, only four fields are pre-declared:
id, _version_, and _text_ and _root_. All other fields will be type guessed and added via the
"add-unknown-fields-to-the-schema" update request processor chain declared in solrconfig.xml.
Note that many dynamic fields are also defined - you can use them to specify a
field's type via field naming conventions - see below.
WARNING: The _text_ catch-all field will significantly increase your index size.
If you don't need it, consider removing it and the corresponding copyField directive.
-->
<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />
<!-- docValues are enabled by default for long type so we don't need to index the version field -->
<field name="_version_" type="plong" indexed="false" stored="false"/>
<field name="_root_" type="string" indexed="true" stored="false" docValues="false" />
<field name="_text_" type="text_general" indexed="true" stored="false" multiValued="true"/>
<!-- Django fields -->
<field name="django_ct" type="string" indexed="true" stored="true" multiValued="false"/>
<field name="django_id" type="string" indexed="true" stored="true" multiValued="false"/>
<field name="text" type="text_pt" indexed="true" stored="true" multiValued="false" />
<!-- This can be enabled, in case the client does not know what fields may be searched. It isn't enabled by default
because it's very expensive to index everything twice. -->
<!-- <copyField source="*" dest="_text_"/> -->
<!-- Dynamic field definitions allow using convention over configuration
for fields via the specification of patterns to match field names.
EXAMPLE: name="*_i" will match any field ending in _i (like myid_i, z_i)
RESTRICTION: the glob-like pattern in the name attribute must have a "*" only at the start or the end. -->
<dynamicField name="*_i" type="pint" indexed="true" stored="true"/>
<dynamicField name="*_is" type="pints" indexed="true" stored="true"/>
<dynamicField name="*_s" type="string" indexed="true" stored="true" />
<dynamicField name="*_ss" type="strings" indexed="true" stored="true"/>
<dynamicField name="*_l" type="plong" indexed="true" stored="true"/>
<dynamicField name="*_ls" type="plongs" indexed="true" stored="true"/>
<dynamicField name="*_t" type="text_general" indexed="true" stored="true" multiValued="false"/>
<dynamicField name="*_txt" type="text_general" indexed="true" stored="true"/>
<dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
<dynamicField name="*_bs" type="booleans" indexed="true" stored="true"/>
<dynamicField name="*_f" type="pfloat" indexed="true" stored="true"/>
<dynamicField name="*_fs" type="pfloats" indexed="true" stored="true"/>
<dynamicField name="*_d" type="pdouble" indexed="true" stored="true"/>
<dynamicField name="*_ds" type="pdoubles" indexed="true" stored="true"/>
<!-- Type used for data-driven schema, to add a string copy for each text field -->
<dynamicField name="*_str" type="strings" stored="false" docValues="true" indexed="false" />
<dynamicField name="*_dt" type="pdate" indexed="true" stored="true"/>
<dynamicField name="*_dts" type="pdate" indexed="true" stored="true" multiValued="true"/>
<dynamicField name="*_p" type="location" indexed="true" stored="true"/>
<dynamicField name="*_srpt" type="location_rpt" indexed="true" stored="true"/>
<!-- payloaded dynamic fields -->
<dynamicField name="*_dpf" type="delimited_payloads_float" indexed="true" stored="true"/>
<dynamicField name="*_dpi" type="delimited_payloads_int" indexed="true" stored="true"/>
<dynamicField name="*_dps" type="delimited_payloads_string" indexed="true" stored="true"/>
<dynamicField name="attr_*" type="text_general" indexed="true" stored="true" multiValued="true"/>
<!-- Field to use to determine and enforce document uniqueness.
Unless this field is marked with required="false", it will be a required field
-->
<uniqueKey>id</uniqueKey>
<!-- copyField commands copy one field to another at the time a document
is added to the index. It's used either to index the same field differently,
or to add multiple fields to the same field for easier/faster searching.
<copyField source="sourceFieldName" dest="destinationFieldName"/>
-->
<!-- field type definitions. The "name" attribute is
just a label to be used by field definitions. The "class"
attribute and any other attributes determine the real
behavior of the fieldType.
Class names starting with "solr" refer to java classes in a
standard package such as org.apache.solr.analysis
-->
<!-- sortMissingLast and sortMissingFirst attributes are optional attributes are
currently supported on types that are sorted internally as strings
and on numeric types.
This includes "string", "boolean", "pint", "pfloat", "plong", "pdate", "pdouble".
- If sortMissingLast="true", then a sort on this field will cause documents
without the field to come after documents with the field,
regardless of the requested sort order (asc or desc).
- If sortMissingFirst="true", then a sort on this field will cause documents
without the field to come before documents with the field,
regardless of the requested sort order.
- If sortMissingLast="false" and sortMissingFirst="false" (the default),
then default lucene sorting will be used which places docs without the
field first in an ascending sort and last in a descending sort.
-->
<!-- The StrField type is not analyzed, but indexed/stored verbatim. -->
<fieldType name="string" class="solr.StrField" sortMissingLast="true" docValues="true" />
<fieldType name="strings" class="solr.StrField" sortMissingLast="true" multiValued="true" docValues="true" />
<!-- boolean type: "true" or "false" -->
<fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
<fieldType name="booleans" class="solr.BoolField" sortMissingLast="true" multiValued="true"/>
<!--
Numeric field types that index values using KD-trees.
Point fields don't support FieldCache, so they must have docValues="true" if needed for sorting, faceting, functions, etc.
-->
<fieldType name="pint" class="solr.IntPointField" docValues="true"/>
<fieldType name="pfloat" class="solr.FloatPointField" docValues="true"/>
<fieldType name="plong" class="solr.LongPointField" docValues="true"/>
<fieldType name="pdouble" class="solr.DoublePointField" docValues="true"/>
<fieldType name="pints" class="solr.IntPointField" docValues="true" multiValued="true"/>
<fieldType name="pfloats" class="solr.FloatPointField" docValues="true" multiValued="true"/>
<fieldType name="plongs" class="solr.LongPointField" docValues="true" multiValued="true"/>
<fieldType name="pdoubles" class="solr.DoublePointField" docValues="true" multiValued="true"/>
<!-- The format for this date field is of the form 1995-12-31T23:59:59Z, and
is a more restricted form of the canonical representation of dateTime
http://www.w3.org/TR/xmlschema-2/#dateTime
The trailing "Z" designates UTC time and is mandatory.
Optional fractional seconds are allowed: 1995-12-31T23:59:59.999Z
All other components are mandatory.
Expressions can also be used to denote calculations that should be
performed relative to "NOW" to determine the value, ie...
NOW/HOUR
... Round to the start of the current hour
NOW-1DAY
... Exactly 1 day prior to now
NOW/DAY+6MONTHS+3DAYS
... 6 months and 3 days in the future from the start of
the current day
-->
<!-- KD-tree versions of date fields -->
<fieldType name="pdate" class="solr.DatePointField" docValues="true"/>
<fieldType name="pdates" class="solr.DatePointField" docValues="true" multiValued="true"/>
<!--Binary data type. The data should be sent/retrieved in as Base64 encoded Strings -->
<fieldType name="binary" class="solr.BinaryField"/>
<!-- solr.TextField allows the specification of custom text analyzers
specified as a tokenizer and a list of token filters. Different
analyzers may be specified for indexing and querying.
The optional positionIncrementGap puts space between multiple fields of
this type on the same document, with the purpose of preventing false phrase
matching across fields.
For more info on customizing your analyzer chain, please see
http://lucene.apache.org/solr/guide/understanding-analyzers-tokenizers-and-filters.html#understanding-analyzers-tokenizers-and-filters
-->
<!-- One can also specify an existing Analyzer class that has a
default constructor via the class attribute on the analyzer element.
Example:
<fieldType name="text_greek" class="solr.TextField">
<analyzer class="org.apache.lucene.analysis.el.GreekAnalyzer"/>
</fieldType>
-->
<!-- A text field that only splits on whitespace for exact matching of words -->
<dynamicField name="*_ws" type="text_ws" indexed="true" stored="true"/>
<fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
</analyzer>
</fieldType>
<!-- A general text field that has reasonable, generic
cross-language defaults: it tokenizes with StandardTokenizer,
removes stop words from case-insensitive "stopwords.txt"
(empty by default), and down cases. At query time only, it
also applies synonyms.
-->
<fieldType name="text_general" class="solr.TextField" positionIncrementGap="100" multiValued="true">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<!-- in this example, we will only use synonyms at query time
<filter class="solr.SynonymGraphFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
<filter class="solr.FlattenGraphFilterFactory"/>
-->
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<!-- SortableTextField generaly functions exactly like TextField,
except that it supports, and by default uses, docValues for sorting (or faceting)
on the first 1024 characters of the original field values (which is configurable).
This makes it a bit more useful then TextField in many situations, but the trade-off
is that it takes up more space on disk; which is why it's not used in place of TextField
for every fieldType in this _default schema.
-->
<dynamicField name="*_t_sort" type="text_gen_sort" indexed="true" stored="true" multiValued="false"/>
<dynamicField name="*_txt_sort" type="text_gen_sort" indexed="true" stored="true"/>
<fieldType name="text_gen_sort" class="solr.SortableTextField" positionIncrementGap="100" multiValued="true">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<!-- A text field with defaults appropriate for English: it tokenizes with StandardTokenizer,
removes English stop words (lang/stopwords_en.txt), down cases, protects words from protwords.txt, and
finally applies Porter's stemming. The query time analyzer also applies synonyms from synonyms.txt. -->
<dynamicField name="*_txt_en" type="text_en" indexed="true" stored="true"/>
<fieldType name="text_en" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<!-- in this example, we will only use synonyms at query time
<filter class="solr.SynonymGraphFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
<filter class="solr.FlattenGraphFilterFactory"/>
-->
<!-- Case insensitive stop word removal.
-->
<filter class="solr.StopFilterFactory"
ignoreCase="true"
words="lang/stopwords_en.txt"
/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.EnglishPossessiveFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
<filter class="solr.EnglishMinimalStemFilterFactory"/>
-->
<filter class="solr.PorterStemFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory"
ignoreCase="true"
words="lang/stopwords_en.txt"
/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.EnglishPossessiveFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
<filter class="solr.EnglishMinimalStemFilterFactory"/>
-->
<filter class="solr.PorterStemFilterFactory"/>
</analyzer>
</fieldType>
<!-- A text field with defaults appropriate for English, plus
aggressive word-splitting and autophrase features enabled.
This field is just like text_en, except it adds
WordDelimiterGraphFilter to enable splitting and matching of
words on case-change, alpha numeric boundaries, and
non-alphanumeric chars. This means certain compound word
cases will work, for example query "wi fi" will match
document "WiFi" or "wi-fi".
-->
<dynamicField name="*_txt_en_split" type="text_en_splitting" indexed="true" stored="true"/>
<fieldType name="text_en_splitting" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="true">
<analyzer type="index">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<!-- in this example, we will only use synonyms at query time
<filter class="solr.SynonymGraphFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
-->
<!-- Case insensitive stop word removal.
-->
<filter class="solr.StopFilterFactory"
ignoreCase="true"
words="lang/stopwords_en.txt"
/>
<filter class="solr.WordDelimiterGraphFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.PorterStemFilterFactory"/>
<filter class="solr.FlattenGraphFilterFactory" />
</analyzer>
<analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory"
ignoreCase="true"
words="lang/stopwords_en.txt"
/>
<filter class="solr.WordDelimiterGraphFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.PorterStemFilterFactory"/>
</analyzer>
</fieldType>
<!-- Less flexible matching, but less false matches. Probably not ideal for product names,
but may be good for SKUs. Can insert dashes in the wrong place and still match. -->
<dynamicField name="*_txt_en_split_tight" type="text_en_splitting_tight" indexed="true" stored="true"/>
<fieldType name="text_en_splitting_tight" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="true">
<analyzer type="index">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_en.txt"/>
<filter class="solr.WordDelimiterGraphFilterFactory" generateWordParts="0" generateNumberParts="0" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.EnglishMinimalStemFilterFactory"/>
<!-- this filter can remove any duplicate tokens that appear at the same position - sometimes
possible with WordDelimiterGraphFilter in conjuncton with stemming. -->
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
<filter class="solr.FlattenGraphFilterFactory" />
</analyzer>
<analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_en.txt"/>
<filter class="solr.WordDelimiterGraphFilterFactory" generateWordParts="0" generateNumberParts="0" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.EnglishMinimalStemFilterFactory"/>
<!-- this filter can remove any duplicate tokens that appear at the same position - sometimes
possible with WordDelimiterGraphFilter in conjuncton with stemming. -->
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
</analyzer>
</fieldType>
<!-- Just like text_general except it reverses the characters of
each token, to enable more efficient leading wildcard queries.
-->
<dynamicField name="*_txt_rev" type="text_general_rev" indexed="true" stored="true"/>
<fieldType name="text_general_rev" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.ReversedWildcardFilterFactory" withOriginal="true"
maxPosAsterisk="3" maxPosQuestion="2" maxFractionAsterisk="0.33"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.SynonymGraphFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<dynamicField name="*_phon_en" type="phonetic_en" indexed="true" stored="true"/>
<fieldType name="phonetic_en" stored="false" indexed="true" class="solr.TextField" >
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.DoubleMetaphoneFilterFactory" inject="false"/>
</analyzer>
</fieldType>
<!-- lowercases the entire field value, keeping it as a single token. -->
<dynamicField name="*_s_lower" type="lowercase" indexed="true" stored="true"/>
<fieldType name="lowercase" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.KeywordTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory" />
</analyzer>
</fieldType>
<!--
Example of using PathHierarchyTokenizerFactory at index time, so
queries for paths match documents at that path, or in descendent paths
-->
<dynamicField name="*_descendent_path" type="descendent_path" indexed="true" stored="true"/>
<fieldType name="descendent_path" class="solr.TextField">
<analyzer type="index">
<tokenizer class="solr.PathHierarchyTokenizerFactory" delimiter="/" />
</analyzer>
<analyzer type="query">
<tokenizer class="solr.KeywordTokenizerFactory" />
</analyzer>
</fieldType>
<!--
Example of using PathHierarchyTokenizerFactory at query time, so
queries for paths match documents at that path, or in ancestor paths
-->
<dynamicField name="*_ancestor_path" type="ancestor_path" indexed="true" stored="true"/>
<fieldType name="ancestor_path" class="solr.TextField">
<analyzer type="index">
<tokenizer class="solr.KeywordTokenizerFactory" />
</analyzer>
<analyzer type="query">
<tokenizer class="solr.PathHierarchyTokenizerFactory" delimiter="/" />
</analyzer>
</fieldType>
<!-- This point type indexes the coordinates as separate fields (subFields)
If subFieldType is defined, it references a type, and a dynamic field
definition is created matching *___<typename>. Alternately, if
subFieldSuffix is defined, that is used to create the subFields.
Example: if subFieldType="double", then the coordinates would be
indexed in fields myloc_0___double,myloc_1___double.
Example: if subFieldSuffix="_d" then the coordinates would be indexed
in fields myloc_0_d,myloc_1_d
The subFields are an implementation detail of the fieldType, and end
users normally should not need to know about them.
-->
<dynamicField name="*_point" type="point" indexed="true" stored="true"/>
<fieldType name="point" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
<!-- A specialized field for geospatial search filters and distance sorting. -->
<fieldType name="location" class="solr.LatLonPointSpatialField" docValues="true"/>
<!-- A geospatial field type that supports multiValued and polygon shapes.
For more information about this and other spatial fields see:
http://lucene.apache.org/solr/guide/spatial-search.html
-->
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
<!-- Payloaded field types -->
<fieldType name="delimited_payloads_float" stored="false" indexed="true" class="solr.TextField">
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.DelimitedPayloadTokenFilterFactory" encoder="float"/>
</analyzer>
</fieldType>
<fieldType name="delimited_payloads_int" stored="false" indexed="true" class="solr.TextField">
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.DelimitedPayloadTokenFilterFactory" encoder="integer"/>
</analyzer>
</fieldType>
<fieldType name="delimited_payloads_string" stored="false" indexed="true" class="solr.TextField">
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.DelimitedPayloadTokenFilterFactory" encoder="identity"/>
</analyzer>
</fieldType>
<!-- Portuguese -->
<dynamicField name="*_txt_pt" type="text_pt" indexed="true" stored="true"/>
<fieldType name="text_pt" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<charFilter class="solr.HTMLStripCharFilterFactory"/>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_pt.txt" format="snowball" />
<filter class="solr.PortugueseLightStemFilterFactory"/>
<!-- less aggressive: <filter class="solr.PortugueseMinimalStemFilterFactory"/> -->
<!-- more aggressive: <filter class="solr.SnowballPorterFilterFactory" language="Portuguese"/> -->
<!-- most aggressive: <filter class="solr.PortugueseStemFilterFactory"/> -->
</analyzer>
</fieldType>
<!-- Similarity is the scoring routine for each document vs. a query.
A custom Similarity or SimilarityFactory may be specified here, but
the default is fine for most applications.
For more info: http://lucene.apache.org/solr/guide/other-schema-elements.html#OtherSchemaElements-Similarity
-->
<!--
<similarity class="com.example.solr.CustomSimilarityFactory">
<str name="paramkey">param value</str>
</similarity>
-->
</schema>

20
solr/sapl_configset/conf/params.json

@ -0,0 +1,20 @@
{"params":{
"query":{
"defType":"edismax",
"q.alt":"*:*",
"rows":"10",
"fl":"*,score",
"":{"v":0}
},
"facets":{
"facet":"on",
"facet.mincount": "1",
"":{"v":0}
},
"velocity":{
"wt": "velocity",
"v.template":"browse",
"v.layout": "layout",
"":{"v":0}
}
}}

21
solr/sapl_configset/conf/protwords.txt

@ -0,0 +1,21 @@
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#-----------------------------------------------------------------------
# Use a protected word file to protect against the stemmer reducing two
# unrelated words to the same base word.
# Some non-words that normally won't be encountered,
# just to test that they won't be stemmed.
dontstems
zwhacky

BIN
solr/sapl_configset/conf/saplconfigset.zip

Binary file not shown.

165
solr/sapl_configset/conf/schema.xml

@ -0,0 +1,165 @@
<?xml version="1.0" ?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<schema name="default" version="1.6">
<types>
<fieldtype name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
<fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>
<fieldType name="booleans" class="solr.BoolField" sortMissingLast="true" multiValued="true"/>
<fieldtype name="binary" class="solr.BinaryField"/>
<!-- Numeric field types that manipulate the value into
a string value that isn't human-readable in its internal form,
but with a lexicographic ordering the same as the numeric ordering,
so that range queries work correctly. -->
<fieldType name="pint" class="solr.IntPointField" docValues="true" />
<fieldType name="pfloat" class="solr.FloatPointField" docValues="true" />
<fieldType name="plong" class="solr.LongPointField" docValues="true" />
<fieldType name="pdouble" class="solr.DoublePointField" docValues="true"/>
<fieldType name="pdate" class="solr.DatePointField" docValues="true" />
<!-- A Trie based date field ifor faster date range queries and date faceting. -->
<fieldType name="pints" class="solr.IntPointField" docValues="true" multiValued="true"/>
<fieldType name="pfloats" class="solr.FloatPointField" docValues="true" multiValued="true"/>
<fieldType name="plongs" class="solr.LongPointField" docValues="true" multiValued="true"/>
<fieldType name="pdoubles" class="solr.DoublePointField" docValues="true" multiValued="true"/>
<fieldType name="pdates" class="solr.DatePointField" docValues="true" multiValued="true"/>
<fieldType name="point" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
<fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
<fieldtype name="geohash" class="solr.GeoHashField"/>
<fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<!-- in this example, we will only use synonyms at query time
<filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
-->
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<!-- Portuguese -->
<dynamicField name="*_txt_pt" type="text_pt" indexed="true" stored="true"/>
<fieldType name="text_pt" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_pt.txt" format="snowball" />
<filter class="solr.PortugueseLightStemFilterFactory"/>
<!-- less aggressive: <filter class="solr.PortugueseMinimalStemFilterFactory"/> -->
<!-- more aggressive: <filter class="solr.SnowballPorterFilterFactory" language="Portuguese"/> -->
<!-- most aggressive: <filter class="solr.PortugueseStemFilterFactory"/> -->
</analyzer>
</fieldType>
<fieldType name="text_en" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.StopFilterFactory"
ignoreCase="true"
words="lang/stopwords_en.txt"
/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.EnglishPossessiveFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
<filter class="solr.EnglishMinimalStemFilterFactory"/>
-->
<filter class="solr.PorterStemFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
<filter class="solr.StopFilterFactory"
ignoreCase="true"
words="lang/stopwords_en.txt"
/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.EnglishPossessiveFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<!-- Optionally you may want to use this less aggressive stemmer instead of PorterStemFilterFactory:
<filter class="solr.EnglishMinimalStemFilterFactory"/>
-->
<filter class="solr.PorterStemFilterFactory"/>
</analyzer>
</fieldType>
<fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
</analyzer>
</fieldType>
<fieldType name="ngram" class="solr.TextField" >
<analyzer type="index">
<tokenizer class="solr.KeywordTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.NGramFilterFactory" minGramSize="3" maxGramSize="15" />
</analyzer>
<analyzer type="query">
<tokenizer class="solr.KeywordTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
<fieldType name="edge_ngram" class="solr.TextField" positionIncrementGap="1">
<analyzer type="index">
<tokenizer class="solr.WhitespaceTokenizerFactory" />
<filter class="solr.LowerCaseFilterFactory" />
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
<filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" />
</analyzer>
<analyzer type="query">
<tokenizer class="solr.WhitespaceTokenizerFactory" />
<filter class="solr.LowerCaseFilterFactory" />
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
</analyzer>
</fieldType>
</types>
<fields>
<!-- general -->
<field name="id" type="string" indexed="true" stored="true" multiValued="false" required="true"/>
<field name="django_ct" type="string" indexed="true" stored="true" multiValued="false"/>
<field name="django_id" type="string" indexed="true" stored="true" multiValued="false"/>
<field name="_version_" type="plong" indexed="true" stored ="true"/>
<field name="text" type="text_pt" indexed="true" stored="true" multiValued="false" />
</fields>
<!-- field to use to determine and enforce document uniqueness. -->
<uniqueKey>id</uniqueKey>
<!-- field for the QueryParser to use when an explicit fieldname is absent -->
<df>text</df>
<!-- SolrQueryParser configuration: defaultOperator="AND|OR" -->
<solrQueryParser q.op="AND"/>
</schema>

1367
solr/sapl_configset/conf/solrconfig.xml

File diff suppressed because it is too large

14
solr/sapl_configset/conf/stopwords.txt

@ -0,0 +1,14 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

29
solr/sapl_configset/conf/synonyms.txt

@ -0,0 +1,29 @@
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#-----------------------------------------------------------------------
#some test synonym mappings unlikely to appear in real input text
aaafoo => aaabar
bbbfoo => bbbfoo bbbbar
cccfoo => cccbar cccbaz
fooaaa,baraaa,bazaaa
# Some synonym groups specific to this example
GB,gib,gigabyte,gigabytes
MB,mib,megabyte,megabytes
Television, Televisions, TV, TVs
#notice we use "gib" instead of "GiB" so any WordDelimiterGraphFilter coming
#after us won't split it into two words.
# Synonym mappings can be used for spelling correction too
pixima => pixma

155
solr_api.py

@ -0,0 +1,155 @@
import requests
import subprocess
import sys
import argparse
class SolrClient:
LIST_CONFIGSETS = "{}/solr/admin/configs?action=LIST&omitHeader=true&wt=json"
UPLOAD_CONFIGSET = "{}/solr/admin/configs?action=UPLOAD&name={}&wt=json"
LIST_COLLECTIONS = "{}/solr/admin/collections?action=LIST&wt=json"
STATUS_COLLECTION = "{}/solr/admin/collections?action=CLUSTERSTATUS&collection={}&wt=json"
STATUS_CORE = "{}/admin/cores?action=STATUS&name={}"
EXISTS_COLLECTION = "{}/solr/{}/admin/ping?wt=json"
OPTIMIZE_COLLECTION = "{}/solr/{}/update?optimize=true&wt=json"
CREATE_COLLECTION = "{}/solr/admin/collections?action=CREATE&name={}&collection.configName={}&numShards={}&replicationFactor={}&maxShardsPerNode={}&wt=json"
DELETE_COLLECTION = "{}/solr/admin/collections?action=DELETE&name={}&wt=json"
DELETE_DATA = "{}/solr/{}/update?commitWithin=1000&overwrite=true&wt=json"
QUERY_DATA = "{}/solr/{}/select?q=*:*"
CONFIGSET_NAME = "sapl_configset"
def __init__(self, url):
self.url = url
def get_num_docs(self, collection_name):
final_url = self.QUERY_DATA.format(self.url, collection_name)
res = requests.get(final_url)
dic = res.json()
num_docs = dic["response"]["numFound"]
return num_docs
def list_collections(self):
req_url = self.LIST_COLLECTIONS.format(self.url)
res = requests.get(req_url)
dic = res.json()
return dic['collections']
def exists_collection(self, collection_name):
collections = self.list_collections()
return True if collection_name in collections else False
def maybe_upload_configset(self, force=False):
req_url = self.LIST_CONFIGSETS.format(self.url)
res = requests.get(req_url)
dic = res.json()
configsets = dic['configSets']
# UPLOAD configset
if not self.CONFIGSET_NAME in configsets or force:
files = {'file': ('saplconfigset.zip',
open('./solr/sapl_configset/conf/saplconfigset.zip',
'rb'),
'application/octet-stream',
{'Expires': '0'})}
req_url = self.UPLOAD_CONFIGSET.format(self.url, self.CONFIGSET_NAME)
resp = requests.post(req_url, files=files)
print(resp.content)
else:
print('O %s já presente no servidor, NÃO enviando.' % self.CONFIGSET_NAME)
def create_collection(self, collection_name, shards=1, replication_factor=1, max_shards_per_node=1):
self.maybe_upload_configset()
req_url = self.CREATE_COLLECTION.format(self.url,
collection_name,
self.CONFIGSET_NAME,
shards,
replication_factor,
max_shards_per_node)
res = requests.post(req_url)
if res.ok:
print("Collection '%s' created succesfully" % collection_name)
else:
print("Error creating collection '%s'" % collection_name)
as_json = res.json()
print("Error %s: %s" % (res.status_code, as_json['error']['msg']))
return False
return True
def delete_collection(self, collection_name):
if collection_name == '*':
collections = self.list_collections()
else:
collections = [collection_name]
for c in collections:
req_url = self.DELETE_COLLECTION.format(self.url, c)
res = requests.post(req_url)
if not res.ok:
print("Error deleting collection '%s'", c)
print("Code {}: {}".format(res.status_code, res.text))
else:
print("Collection '%s' deleted successfully!" % c)
def delete_index_data(self, collection_name):
req_url = self.DELETE_DATA.format(self.url, collection_name)
res = requests.post(req_url,
data='<delete><query>*:*</query></delete>',
headers={'Content-Type': 'application/xml'})
if not res.ok:
print("Error deleting index for collection '%s'", collection_name)
print("Code {}: {}".format(res.status_code, res.text))
else:
print("Collection '%s' data deleted successfully!" % collection_name)
num_docs = self.get_num_docs(collection_name)
print("Num docs: %s" % num_docs)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Cria uma collection no Solr')
# required arguments
parser.add_argument('-u', type=str, metavar='URL', nargs=1, dest='url',
required=True, help='Endereço do servidor Solr na forma http(s)://<address>[:port]')
parser.add_argument('-c', type=str, metavar='COLLECTION', dest='collection', nargs=1,
required=True, help='Collection Solr a ser criada')
# optional arguments
parser.add_argument('-s', type=int, dest='shards', nargs='?',
help='Number of shards (default=1)', default=1)
parser.add_argument('-rf', type=int, dest='replication_factor', nargs='?',
help='Replication factor (default=1)', default=1)
parser.add_argument('-ms', type=int, dest='max_shards_per_node', nargs='?',
help='Max shards per node (default=1)', default=1)
try:
args = parser.parse_args()
except IOError as msg:
parser.error(str(msg))
sys.exit(-1)
url = args.url.pop()
collection = args.collection.pop()
client = SolrClient(url=url)
if not client.exists_collection(collection):
print("Collection '%s' doesn't exists. Creating a new one..." % collection)
created = client.create_collection(collection,
shards=args.shards,
replication_factor=args.replication_factor,
max_shards_per_node=args.max_shards_per_node)
if not created:
sys.exit(-1)
else:
print("Collection '%s' exists." % collection)
num_docs = client.get_num_docs(collection)
if num_docs == 0:
print("Performing a full reindex of '%s' collection..." % collection)
p = subprocess.call(["python3", "manage.py", "rebuild_index", "--noinput"])

37
start.sh

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
create_env() { create_env() {
echo "[ENV FILE] creating .env file..." echo "[ENV FILE] creating .env file..."
@ -36,6 +36,10 @@ create_env() {
echo "EMAIL_SEND_USER = ""${EMAIL_HOST_USER-''}" >> $FILENAME echo "EMAIL_SEND_USER = ""${EMAIL_HOST_USER-''}" >> $FILENAME
echo "DEFAULT_FROM_EMAIL = ""${EMAIL_HOST_USER-''}" >> $FILENAME echo "DEFAULT_FROM_EMAIL = ""${EMAIL_HOST_USER-''}" >> $FILENAME
echo "SERVER_EMAIL = ""${EMAIL_HOST_USER-''}" >> $FILENAME echo "SERVER_EMAIL = ""${EMAIL_HOST_USER-''}" >> $FILENAME
echo "USE_SOLR = ""${USE_SOLR-False}" >> $FILENAME
echo "SOLR_COLLECTION = ""${SOLR_COLLECTION-sapl}" >> $FILENAME
echo "SOLR_URL = ""${SOLR_URL-http://localhost:8983}" >> $FILENAME
echo "[ENV FILE] done." echo "[ENV FILE] done."
} }
@ -44,12 +48,41 @@ create_env
#python3 manage.py bower install #python3 manage.py bower install
/bin/sh busy-wait.sh $DATABASE_URL /bin/bash busy-wait.sh $DATABASE_URL
# manage.py migrate --noinput nao funcionava # manage.py migrate --noinput nao funcionava
yes yes | python3 manage.py migrate yes yes | python3 manage.py migrate
# python3 manage.py collectstatic --no-input # python3 manage.py collectstatic --no-input
## SOLR
USE_SOLR="${USE_SOLR:=False}"
SOLR_URL="${SOLR_URL:=http://localhost:8983}"
SOLR_COLLECTION="${SOLR_COLLECTION:=sapl}"
NUM_SHARDS=${NUM_SHARDS:=1}
RF=${RF:=1}
MAX_SHARDS_PER_NODE=${MAX_SHARDS_PER_NODE:=1}
if [ "${USE_SOLR-False}" == "True" ]; then
echo "SOLR configurations"
echo "==================="
echo "URL: $SOLR_URL"
echo "COLLECTION: $SOLR_COLLECTION"
echo "NUM_SHARDS: $NUM_SHARDS"
echo "REPLICATION FACTOR: $RF"
echo "MAX SHARDS PER NODE: $MAX_SHARDS_PER_NODE"
echo "========================================="
/bin/bash check_solr.sh $SOLR_URL
python3 solr_api.py -u $SOLR_URL -c $SOLR_COLLECTION -s $NUM_SHARDS -rf $RF -ms $MAX_SHARDS_PER_NODE &
# python3 manage.py rebuild_index --noinput & # python3 manage.py rebuild_index --noinput &
else
echo "Suporte a SOLR não inicializado."
fi
echo "Criando usuário admin..." echo "Criando usuário admin..."

Loading…
Cancel
Save