From dc5666155f0b90fd55cc64ff942f7d6b5de0df86 Mon Sep 17 00:00:00 2001 From: Leandro Roberto da Silva Date: Tue, 14 Nov 2017 10:50:17 -0200 Subject: [PATCH] adiciona Textos Articulados na pesquisa textual (#1594) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * adiciona Textos Articulados na pesquisa textual * separa extrações em funções individuais * apl sugestões de rev e outras ideias surgidas a partir daí --- sapl/base/search_indexes.py | 187 ++++++++++++++++++------------ sapl/compilacao/views.py | 16 ++- sapl/materia/views.py | 5 +- sapl/templates/search/search.html | 8 +- 4 files changed, 136 insertions(+), 80 deletions(-) diff --git a/sapl/base/search_indexes.py b/sapl/base/search_indexes.py index cee126dd2..e925de8c8 100644 --- a/sapl/base/search_indexes.py +++ b/sapl/base/search_indexes.py @@ -3,33 +3,36 @@ import os.path import re import string -import textract +from django.db.models import Q, F, Value +from django.db.models.fields import TextField +from django.db.models.fields.files import FieldFile +from django.db.models.functions import Concat from django.template import loader -from haystack import indexes +from haystack.constants import Indexable +from haystack.fields import CharField +from haystack.indexes import SearchIndex +from haystack.utils import get_model_ct_tuple from textract.exceptions import ExtensionNotSupported +import textract +from sapl.compilacao.models import TextoArticulado, Dispositivo,\ + STATUS_TA_PUBLIC, STATUS_TA_IMMUTABLE_PUBLIC from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa from sapl.norma.models import NormaJuridica from sapl.settings import BASE_DIR, SOLR_URL -logger = logging.getLogger(BASE_DIR.name) +logger = logging.getLogger(BASE_DIR.name) -class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable): - text = indexes.CharField(document=True, use_template=True) - filename = 'arquivo' - model = DocumentoAcessorio - template_name = 'materia/documentoacessorio_text.txt' +class TextExtractField(CharField): - def get_model(self): - return self.model - - def index_queryset(self, using=None): - return self.get_model().objects.all() + def __init__(self, **kwargs): + super().__init__(**kwargs) + assert self.model_attr - def get_updated_field(self): - return 'data_ultima_atualizacao' + if not isinstance(self.model_attr, (list, tuple)): + self.model_attr = (self.model_attr, ) def solr_extraction(self, arquivo): extracted_data = self._get_backend(None).extract_file_contents( @@ -59,71 +62,109 @@ class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable): print(msg) logger.error(msg) - def prepare(self, obj): - if not self.filename or not self.model or not self.template_name: - raise Exception - - data = super(DocumentoAcessorioIndex, self).prepare(obj) - - arquivo = getattr(obj, self.filename) - - if arquivo: - if not os.path.exists(arquivo.path): - return self.prepared_data - - if not os.path.splitext(arquivo.path)[1][:1]: - return self.prepared_data - - # Em ambiente de produção utiliza-se o SOLR - if SOLR_URL: - try: - extracted_data = self.solr_extraction(arquivo) - except Exception: - self.print_error(arquivo) - return self.prepared_data - - # Em ambiente de DEV utiliza-se o Whoosh - # Como ele não possui extração, faz-se uso do textract - else: - try: - extracted_data = self.whoosh_extraction(arquivo) - except ExtensionNotSupported as e: - print(str(e)) - logger.error(str(e)) - return self.prepared_data - except Exception: - self.print_error(arquivo) - return self.prepared_data - - # Now we'll finally perform the template processing to render the - # text field with *all* of our metadata visible for templating: - t = loader.select_template(( - 'search/indexes/' + self.template_name, )) - data['text'] = t.render({'object': obj, - 'extracted': extracted_data}) - - return data - - return self.prepared_data - + def file_extractor(self, arquivo): + if not os.path.exists(arquivo.path) or \ + not os.path.splitext(arquivo.path)[1][:1]: + return '' + + # Em ambiente de produção utiliza-se o SOLR + if SOLR_URL: + try: + return self.solr_extraction(arquivo) + except Exception: + self.print_error(arquivo) + + # Em ambiente de DEV utiliza-se o Whoosh + # Como ele não possui extração, faz-se uso do textract + else: + try: + return self.whoosh_extraction(arquivo) + except ExtensionNotSupported as e: + print(str(e)) + logger.error(str(e)) + except Exception: + self.print_error(arquivo) + return '' + + def ta_extractor(self, value): + r = [] + for ta in value.filter(privacidade__in=[ + STATUS_TA_PUBLIC, + STATUS_TA_IMMUTABLE_PUBLIC]): + dispositivos = Dispositivo.objects.filter( + Q(ta=ta) | Q(ta_publicado=ta) + ).order_by( + 'ordem' + ).annotate( + rotulo_texto=Concat( + F('rotulo'), Value(' '), F('texto'), + output_field=TextField(), + ) + ).values_list( + 'rotulo_texto', flat=True) + r += list(filter(lambda x: x.strip(), dispositivos)) + return ' '.join(r) + + def extract_data(self, obj): + + data = '' + + for attr, func in self.model_attr: + if not hasattr(obj, attr) or not hasattr(self, func): + raise Exception + + value = getattr(obj, attr) + if not value: + continue + data += getattr(self, func)(value) + + return data + + def prepare_template(self, obj): + app_label, model_name = get_model_ct_tuple(obj) + template_names = ['search/indexes/%s/%s_%s.txt' % + (app_label, model_name, self.instance_name)] + + t = loader.select_template(template_names) + + return t.render({'object': obj, + 'extracted': self.extract_data(obj)}) + + +class DocumentoAcessorioIndex(SearchIndex, Indexable): + model = DocumentoAcessorio + text = TextExtractField( + document=True, use_template=True, + model_attr=(('arquivo', 'file_extractor'), ) + ) -class MateriaLegislativaIndex(DocumentoAcessorioIndex): - text = indexes.CharField(document=True, use_template=True) + def get_model(self): + return self.model - filename = 'texto_original' - model = MateriaLegislativa - template_name = 'materia/materialegislativa_text.txt' + def index_queryset(self, using=None): + return self.get_model().objects.all() def get_updated_field(self): return 'data_ultima_atualizacao' class NormaJuridicaIndex(DocumentoAcessorioIndex): - text = indexes.CharField(document=True, use_template=True) - - filename = 'texto_integral' model = NormaJuridica - template_name = 'norma/normajuridica_text.txt' + text = TextExtractField( + document=True, use_template=True, + model_attr=( + ('texto_integral', 'file_extractor'), + ('texto_articulado', 'ta_extractor') + ) + ) - def get_updated_field(self): - return 'data_ultima_atualizacao' + +class MateriaLegislativaIndex(DocumentoAcessorioIndex): + model = MateriaLegislativa + text = TextExtractField( + document=True, use_template=True, + model_attr=( + ('texto_original', 'file_extractor'), + ('texto_articulado', 'ta_extractor') + ) + ) diff --git a/sapl/compilacao/views.py b/sapl/compilacao/views.py index 4dfad8e99..9a763f3b2 100644 --- a/sapl/compilacao/views.py +++ b/sapl/compilacao/views.py @@ -1,7 +1,7 @@ -import logging -import sys from collections import OrderedDict from datetime import timedelta +import logging +import sys from braces.views import FormMessagesMixin from django import forms @@ -19,8 +19,8 @@ from django.http.response import (HttpResponse, HttpResponseRedirect, from django.shortcuts import get_object_or_404, redirect from django.utils.dateparse import parse_date from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat +from django.utils.translation import ugettext_lazy as _ from django.views.generic.base import TemplateView from django.views.generic.detail import DetailView from django.views.generic.edit import (CreateView, DeleteView, FormView, @@ -50,6 +50,7 @@ from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED, from sapl.crud.base import Crud, CrudListView, make_pagination from sapl.settings import BASE_DIR + TipoNotaCrud = Crud.build(TipoNota, 'tipo_nota') TipoVideCrud = Crud.build(TipoVide, 'tipo_vide') TipoPublicacaoCrud = Crud.build(TipoPublicacao, 'tipo_publicacao') @@ -1157,10 +1158,14 @@ class TextEditView(CompMixin, TemplateView): self.object.save() messages.success(request, _( 'Texto Articulado desbloqueado com sucesso.')) + + if self.object.content_object: + self.object.content_object.save() + else: if 'lock' in request.GET: - # TODO - implementar logging de ação de usuário + # TODO - implementar logging de ação de usuário notificacoes = self.get_notificacoes( object_list=self.object.dispositivos_set.all(), type_notificacoes=['danger', ]) @@ -1183,6 +1188,9 @@ class TextEditView(CompMixin, TemplateView): messages.success(request, _( 'Texto Articulado bloqueado com sucesso.')) + if self.object.content_object: + self.object.content_object.save() + return redirect(to=reverse_lazy( 'sapl.compilacao:ta_text', kwargs={ 'ta_id': self.object.id})) diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 05120f616..d478a8589 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -2,7 +2,6 @@ from datetime import datetime from random import choice from string import ascii_letters, digits -import weasyprint from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML from django.contrib import messages @@ -20,8 +19,8 @@ from django.views.generic import CreateView, ListView, TemplateView, UpdateView from django.views.generic.base import RedirectView from django.views.generic.edit import FormView from django_filters.views import FilterView +import weasyprint -import sapl from sapl.base.models import Autor, CasaLegislativa from sapl.comissoes.models import Comissao, Participacao from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_RESTRICT, @@ -42,6 +41,7 @@ from sapl.protocoloadm.models import Protocolo from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label, autor_modal, gerar_hash_arquivo, get_base_url, montar_row_autor, show_results_filter_set) +import sapl from .email_utils import do_envia_email_confirmacao from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, @@ -62,6 +62,7 @@ from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria, TipoProposicao, Tramitacao, UnidadeTramitacao) from .signals import tramitacao_signal + AssuntoMateriaCrud = Crud.build(AssuntoMateria, 'assunto_materia') OrigemCrud = Crud.build(Origem, '') diff --git a/sapl/templates/search/search.html b/sapl/templates/search/search.html index 3ea67cc86..e3b430f40 100644 --- a/sapl/templates/search/search.html +++ b/sapl/templates/search/search.html @@ -54,11 +54,14 @@ {% if result.object.texto_original %} Texto Original: Clique aqui
+ {% endif %} + {% if result.object.texto_articulado.first %} + Texto Articulado: Clique aqui
{% else %} O texto desta matéria foi removido recentemente. Em breve ela sairá desta listagem.
{% endif %}

- + {% elif result.object|search_get_model == 'd' %}

Documento Acessório: {{ result.object }}
@@ -74,6 +77,9 @@ Norma Jurídica: {{ result.object }}
{% if result.object.texto_integral %} Texto Original: Clique aqui
+ {% endif %} + {% if result.object.texto_articulado.first %} + Texto Articulado: Clique aqui
{% else %} O texto desta norma foi removido recentemente. Em breve ela sairá desta listagem.
{% endif %}