Browse Source

Merge branch '3.1.x' into ata_sessao

pull/2684/head
Edward 7 years ago
committed by GitHub
parent
commit
b040a492bd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 58
      docs/solr.rst
  2. 5
      sapl/api/views.py
  3. 52
      sapl/materia/forms.py
  4. 53
      sapl/materia/views.py
  5. 3
      sapl/norma/forms.py
  6. 17
      sapl/norma/models.py
  7. 3
      sapl/templates/menu_tabelas_auxiliares.yaml
  8. 2
      solr/docker-compose.yml

58
docs/solr.rst

@ -1,27 +1,55 @@
**ESTAS INSTRUÇÕES ESTÃO DEFASADAS. EM BREVE IREMOS DISPONIBILIZAR UM TUTORIAL MAIS ATUALIZADO DE COMO INTEGRAR O SOLR AO SAPL**
================================
Instruções para instalar o Solr
================================
Solr é a ferramenta utilizada pelo SAPL 3.1 para indexar documentos para que possa ser feita
a Pesquisa Textual.
Solr é uma plataforma open source de indexação e busca textual utilizada pelo SAPL 3.1 para indexar documentos (normas jurídicas, matérias legislativas e documentos acessórios).
Observação: Se a execução do SAPL for mediante containers Docker então use o arquivo *docker-compose.yml* disponível em
*https://github.com/interlegis/sapl/blob/3.1.x/solr/docker-compose.yml* (verifique os mapeamentos de volume estão corretos, a verso do SAPL referenciada no arquivo docker-compose.yml, e realize o backup de seu BD **antes** de qualquer tentativa de substituição do arquivo *docker-compose.yml* em uso corrente);
1) Faça o download da distribuição *binária* do Apache Solr do site oficial do projeto **http://lucene.apache.org/solr**
As instalações Solr suportadas até o momento vão da 7.4 à 8;
2) Descompacte o arquivo em uma pasta do diretório (referenciada neste tutorial como $SOLR_HOME)
3) Inicie o Solr com o comando:
**$SOLR_HOME/bin/solr start -c**
4) Por meio do browser, acesse a URL **http://localhost:8983** (ou informe o endereço da máquina onde o Solr foi instalado)
5) Pare o servidor do SAPL;
6) Edite o arquivo .env adicionando as seguintes linhas:
USE_SOLR = True
SOLR_COLLECTION = sapl
SOLR_URL = http://localhost:8983
Adicione ao arquivo ``.env`` o seguinte atributo:
(o valor do campo SOLR_URL deve corresponder à URL acessada no item 3)
``SOLR_URL = 'http://127.0.0.1:8983/solr'``
7) Entre no diretório raiz do SAPL e digite o comando: **python3 solr_api.py -c sapl -u http://localhost:8983`**
Dentro do diretório principal siga os seguintes passos::
(a URL informada acima deve ser a mesma dos itens 3 e 6)
curl -LO https://archive.apache.org/dist/lucene/solr/4.10.2/solr-4.10.2.tgz
tar xvzf solr-4.10.2.tgz
cd solr-4.10.2
cd example
java -jar start.jar
./manage.py build_solr_schema --filename solr-4.10.2/example/solr/collection1/conf/schema.xml
8) Enquanto o Solr realiza a indexação da base de dados do SAPL, inicie em uma outra tela o SAPL;
9) Após realizados os passos com sucesso, nas telas de busca de Matéria Legislativa e Normas deverá aparecer um botão
de 'Busca Textual' próximo ao botão de busca tradicional.
Após isso, deve-se parar o servidor do Solr e restartar com ``java -jar start.jar``
**Observações:**
* Para parar o Solr execute o comando **$SOLR_HOME/bin/solr stop**
**OBS: Toda vez que o código da pesquisa textual for modificado, os comandos de build_solr_schema e start.jar devem ser rodados, nessa mesma ordem.**
* Para reindexar os dados do SAPL execute o comando **python3 manage.py rebuild_index** (isso irá apagar todos os dados
do Solr e indexar tudo novamente).

5
sapl/api/views.py

@ -401,6 +401,11 @@ class _MateriaLegislativaViewSet:
return Response(serializer_class.data)
@action(detail=True, methods=['GET'])
def anexadas(self, request, *args, **kwargs):
self.queryset = self.get_object().anexadas.all()
return self.list(request, *args, **kwargs)
@customize(TipoMateriaLegislativa)
class _TipoMateriaLegislativaViewSet:

52
sapl/materia/forms.py

@ -3,7 +3,6 @@ import logging
import os
from crispy_forms.bootstrap import Alert, InlineRadios
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import (HTML, Button, Column, Div, Field, Fieldset,
Layout, Row)
from django import forms
@ -31,6 +30,7 @@ from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC,
STATUS_TA_PRIVATE)
from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
to_row)
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.materia.models import (AssuntoMateria, Autoria, MateriaAssunto,
MateriaLegislativa, Orgao, RegimeTramitacao,
TipoDocumento, TipoProposicao, StatusTramitacao,
@ -402,7 +402,17 @@ class RelatoriaForm(ModelForm):
self.fields['composicao'].choices = [('', '---------')] + \
[(c.pk, c) for c in composicoes]
self.fields['parlamentar'].choices = [('', '---------')]
# UPDATE
if self.initial.get('composicao') and self.initial.get('parlamentar'):
parlamentares = [(p.parlamentar.id, p.parlamentar) for p in
Participacao.objects.filter(composicao__comissao_id=comissao_pk,
composicao_id=self.initial['composicao'])]
self.fields['parlamentar'].choices = [
('', '---------')] + parlamentares
# INSERT
else:
self.fields['parlamentar'].choices = [('', '---------')]
def clean(self):
super().clean()
@ -424,6 +434,11 @@ class RelatoriaForm(ModelForm):
else:
cleaned_data['comissao'] = comissao
if cleaned_data['data_designacao_relator'] < cleaned_data['composicao'].periodo.data_inicio \
or cleaned_data['data_designacao_relator'] > cleaned_data['composicao'].periodo.data_fim:
raise ValidationError(
_('Data de designação deve estar dentro do período da composição.'))
return cleaned_data
@ -452,9 +467,12 @@ class TramitacaoForm(ModelForm):
super(TramitacaoForm, self).__init__(*args, **kwargs)
self.fields['data_tramitacao'].initial = timezone.now().date()
ust = UnidadeTramitacao.objects.select_related().all()
unidade_tramitacao_destino = [('', '---------')]+[(ut.pk, ut) for ut in ust if ut.comissao and ut.comissao.ativa]
unidade_tramitacao_destino.extend([(ut.pk, ut) for ut in ust if ut.orgao])
unidade_tramitacao_destino.extend([(ut.pk, ut) for ut in ust if ut.parlamentar])
unidade_tramitacao_destino = [('', '---------')] + [(ut.pk, ut)
for ut in ust if ut.comissao and ut.comissao.ativa]
unidade_tramitacao_destino.extend(
[(ut.pk, ut) for ut in ust if ut.orgao])
unidade_tramitacao_destino.extend(
[(ut.pk, ut) for ut in ust if ut.parlamentar])
self.fields['unidade_tramitacao_destino'].choices = unidade_tramitacao_destino
def clean(self):
@ -791,9 +809,11 @@ class AnexadaForm(ModelForm):
self.logger.error("Matéria não pode ser anexada a si mesma.")
raise ValidationError(_('Matéria não pode ser anexada a si mesma'))
is_anexada = Anexada.objects.filter(materia_principal=materia_principal,
materia_anexada=materia_anexada
).exists()
is_anexada = Anexada.objects.filter(
materia_principal=materia_principal,
materia_anexada=materia_anexada
).exclude(pk=self.instance.pk).exists()
if is_anexada:
self.logger.error("Matéria já se encontra anexada.")
raise ValidationError(_('Matéria já se encontra anexada'))
@ -1169,6 +1189,7 @@ class AcessorioEmLoteFilterSet(django_filters.FilterSet):
Fieldset(_('Documentos Acessórios em Lote'),
row1, row2, form_actions(label='Pesquisar')))
class AnexadaEmLoteFilterSet(django_filters.FilterSet):
class Meta(FilterOverridesMetaMixin):
@ -1570,8 +1591,8 @@ class ProposicaoForm(FileFieldCheckMixin, forms.ModelForm):
if cd['numero_materia_futuro'] and \
'tipo' in cd and \
MateriaLegislativa.objects.filter(tipo=cd['tipo'].tipo_conteudo_related,
ano=timezone.now().year,
numero=cd['numero_materia_futuro']):
ano=timezone.now().year,
numero=cd['numero_materia_futuro']):
raise ValidationError(_("A matéria {} {}/{} já existe.".format(cd['tipo'].tipo_conteudo_related.descricao,
cd['numero_materia_futuro'],
timezone.now().year)))
@ -1990,14 +2011,13 @@ class ConfirmarProposicaoForm(ProposicaoForm):
if numeracao is None:
numero['numero__max'] = 0
if cd['numero_materia_futuro'] and not MateriaLegislativa.objects.filter(tipo=tipo,
ano=ano,
numero=cd['numero_materia_futuro']):
max_numero = cd['numero_materia_futuro']
else:
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
materia = MateriaLegislativa()
@ -2488,7 +2508,9 @@ class MateriaPesquisaSimplesForm(forms.Form):
raise ValidationError(_('Caso pesquise por data, os campos de Data Inicial e '
'Data Final devem ser preenchidos obrigatoriamente'))
elif data_inicial > data_final:
self.logger.error("Data Final ({}) menor que a Data Inicial ({}).".format(data_final, data_inicial))
raise ValidationError(_('A Data Final não pode ser menor que a Data Inicial'))
self.logger.error("Data Final ({}) menor que a Data Inicial ({}).".format(
data_final, data_inicial))
raise ValidationError(
_('A Data Final não pode ser menor que a Data Inicial'))
return cleaned_data

53
sapl/materia/views.py

@ -14,7 +14,7 @@ from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, ValidationError
from django.core.urlresolvers import reverse
from django.db.models import Max, Q
from django.http import HttpResponse, JsonResponse
@ -33,7 +33,7 @@ import sapl
from sapl.base.email_utils import do_envia_email_confirmacao
from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig
from sapl.base.signals import tramitacao_signal
from sapl.comissoes.models import Comissao, Participacao
from sapl.comissoes.models import Comissao, Participacao, Composicao
from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_RESTRICT,
STATUS_TA_PRIVATE)
from sapl.compilacao.views import IntegracaoTaView
@ -1135,6 +1135,26 @@ class RelatoriaCrud(MasterDetailCrud):
layout_key = None
logger = logging.getLogger(__name__)
def get_initial(self):
relatoria = Relatoria.objects.get(id=self.kwargs['pk'])
parlamentar = relatoria.parlamentar
comissao = relatoria.comissao
composicoes = [p.composicao for p in
Participacao.objects.filter(
parlamentar=parlamentar,
composicao__comissao=comissao)]
data_designacao = relatoria.data_designacao_relator
composicao = ''
for c in composicoes:
data_inicial = c.periodo.data_inicio
data_fim = c.periodo.data_fim if c.periodo.data_fim else timezone.now().date()
if data_inicial <= data_designacao <= data_fim:
composicao = c.id
break
return {'comissao': relatoria.comissao.id,
'parlamentar': relatoria.parlamentar.id,
'composicao': composicao}
class TramitacaoCrud(MasterDetailCrud):
model = Tramitacao
@ -1925,6 +1945,7 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = AcessorioEmLoteFilterSet
template_name = 'materia/em_lote/acessorio.html'
permission_required = ('materia.add_documentoacessorio',)
logger = logging.getLogger(__name__)
def get_context_data(self, **kwargs):
context = super(DocumentoAcessorioEmLoteView,
@ -1946,6 +1967,7 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
return context
def post(self, request, *args, **kwargs):
username = request.user.username
marcadas = request.POST.getlist('materia_id')
if len(marcadas) == 0:
@ -1962,13 +1984,20 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs)
tmp_name = os.path.join(tempfile.gettempdir(), request.FILES['arquivo'].name)
tmp_name = os.path.join(MEDIA_ROOT, request.FILES['arquivo'].name)
with open(tmp_name, 'wb') as destination:
for chunk in request.FILES['arquivo'].chunks():
destination.write(chunk)
try:
doc_data = tz.localize(datetime.strptime(
request.POST['data'], "%d/%m/%Y"))
except Exception as e:
msg = _('Formato da data incorreto. O formato deve ser da forma dd/mm/aaaa.')
messages.add_message(request, messages.ERROR, msg)
self.logger.error("User={}. {}. Data inserida: {}".format(username, str(msg), request.POST['data']))
os.remove(tmp_name)
return self.get(request, self.kwargs)
doc_data = tz.localize(datetime.strptime(
request.POST['data'], "%d/%m/%Y"))
for materia_id in marcadas:
doc = DocumentoAcessorio()
doc.materia_id = materia_id
@ -1977,6 +2006,18 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
doc.data = doc_data
doc.autor = request.POST['autor']
doc.ementa = request.POST['ementa']
doc.arquivo.name = tmp_name
try:
doc.clean_fields()
except ValidationError as e:
for m in [ '%s: %s' % (DocumentoAcessorio()._meta.get_field(k).verbose_name, '</br>'.join(v))
for k,v in e.message_dict.items() ]:
# Insere as mensagens de erro no formato:
# 'verbose_name do nome do campo': 'mensagem de erro'
messages.add_message(request, messages.ERROR, m)
self.logger.error("User={}. {}. Nome do arquivo: {}.".format(username, str(msg), request.FILES['arquivo'].name))
os.remove(tmp_name)
return self.get(request, self.kwargs)
doc.save()
diretorio = os.path.join(MEDIA_ROOT,
'sapl/public/documentoacessorio',
@ -1987,7 +2028,7 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
file_path = os.path.join(diretorio,
request.FILES['arquivo'].name)
shutil.copy2(tmp_name, file_path)
doc.arquivo.name = file_path.split(MEDIA_ROOT)[1] # Retira MEDIA_ROOT do nome
doc.arquivo.name = file_path.split(MEDIA_ROOT + "/")[1] # Retira MEDIA_ROOT do nome
doc.save()
os.remove(tmp_name)

3
sapl/norma/forms.py

@ -298,11 +298,10 @@ class AnexoNormaJuridicaForm(FileFieldCheckMixin, ModelForm):
def save(self, commit=False):
anexo = self.instance
anexo.ano = self.cleaned_data['norma'].ano
anexo = super(AnexoNormaJuridicaForm, self).save(commit=True)
anexo.norma = self.cleaned_data['norma']
anexo.assunto_anexo = self.cleaned_data['assunto_anexo']
anexo.anexo_arquivo = self.cleaned_data['anexo_arquivo']
anexo.save()
anexo = super(AnexoNormaJuridicaForm, self).save(commit=True)
return anexo

17
sapl/norma/models.py

@ -351,3 +351,20 @@ class AnexoNormaJuridica(models.Model):
def __str__(self):
return _('Anexo: %(anexo)s da norma %(norma)s') % {
'anexo': self.anexo_arquivo, 'norma': self.norma}
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
if not self.pk and self.anexo_arquivo:
anexo_arquivo = self.anexo_arquivo
self.anexo_arquivo = None
models.Model.save(self, force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
self.anexo_arquivo = anexo_arquivo
return models.Model.save(self, force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)

3
sapl/templates/menu_tabelas_auxiliares.yaml

@ -20,6 +20,9 @@
- title: {% trans 'Pesquisar Parlamentar' %}
url: sapl.parlamentares:pesquisar_parlamentar
css_class: btn btn-link
- title: {% trans 'Adicionar Parlamentar' %}
url: sapl.parlamentares:parlamentar_create
css_class: btn btn-link
- title: {% trans 'Legislatura' %}
url: sapl.parlamentares:legislatura_list
css_class: btn btn-link

2
solr/docker-compose.yml

@ -24,7 +24,7 @@ services:
- "8983:8983"
sapl:
image: interlegis/sapl:3.1.144
image: interlegis/sapl:3.1.152
# build: .
restart: always
environment:

Loading…
Cancel
Save