Browse Source

Merge

pull/1109/head
Eduardo Calil 8 years ago
parent
commit
43a788ef81
  1. 2
      Dockerfile
  2. 2
      docs/instacao31.rst
  3. 64
      sapl/api/forms.py
  4. 1
      sapl/api/serializers.py
  5. 58
      sapl/api/views.py
  6. 16
      sapl/base/search_indexes.py
  7. 12
      sapl/materia/forms.py
  8. 32
      sapl/materia/migrations/0005_auto_20170522_1904.py
  9. 22
      sapl/materia/views.py
  10. 12
      sapl/parlamentares/models.py
  11. 6
      sapl/templates/base.html
  12. 2
      sapl/templates/materia/documentoacessorio_form.html
  13. 17
      sapl/templates/materia/em_lote/acessorio.html
  14. 4
      sapl/templates/protocoloadm/protocolo_list.html
  15. 9
      sapl/templates/sessao/votacao/nominal_detail.html
  16. 11
      sapl/templates/sessao/votacao/nominal_edit.html
  17. 110
      sapl/utils.py

2
Dockerfile

@ -2,7 +2,7 @@ FROM alpine:3.5
ENV BUILD_PACKAGES postgresql-dev graphviz-dev graphviz build-base git pkgconfig \
python3-dev libxml2-dev jpeg-dev libressl-dev libffi-dev libxslt-dev nodejs py3-lxml \
py3-magic postgresql-client vim
py3-magic postgresql-client poppler-utils vim
RUN apk add --no-cache python3 nginx && \
python3 -m ensurepip && \

2
docs/instacao31.rst

@ -28,7 +28,7 @@ Instalar as seguintes dependências do sistema::
pkg-config postgresql postgresql-contrib pgadmin3 python-psycopg2 \
software-properties-common build-essential libxml2-dev libjpeg-dev \
libmysqlclient-dev libssl-dev libffi-dev libxslt1-dev python3-setuptools \
python3-pip curl
python3-pip curl poppler-utils
sudo -i
curl -sL https://deb.nodesource.com/setup_5.x | bash -

64
sapl/api/forms.py

@ -1,5 +1,8 @@
from django.db.models import Q
from django.forms.fields import MultiValueField, CharField
from django.forms.widgets import TextInput, MultiWidget
from django_filters.filters import MethodFilter, ModelChoiceFilter
from rest_framework.compat import django_filters
from rest_framework.filters import FilterSet
from sapl.base.models import Autor, TipoAutor
@ -35,10 +38,11 @@ class SaplGenericRelationSearchFilterSet(FilterSet):
item.related_query_name(),
field[0])
)
q_fs = q_fs | Q(**{'%s__%s%s' % (
item.related_query_name(),
field[0],
field[1]): qtext})
if len(field) == 3 and field[2](qtext) is not None:
q_fs = q_fs | Q(**{'%s__%s%s' % (
item.related_query_name(),
field[0],
field[1]): qtext if len(field) == 2 else field[2](qtext)})
q = q & q_fs
@ -48,6 +52,37 @@ class SaplGenericRelationSearchFilterSet(FilterSet):
return queryset
class SearchForFieldWidget(MultiWidget):
def decompress(self, value):
if value is None:
return [None, None]
return value
def __init__(self, attrs=None):
widgets = (TextInput, TextInput)
MultiWidget.__init__(self, widgets, attrs)
class SearchForFieldField(MultiValueField):
widget = SearchForFieldWidget
def __init__(self, *args, **kwargs):
fields = (
CharField(),
CharField())
super(SearchForFieldField, self).__init__(fields, *args, **kwargs)
def compress(self, parameters):
if parameters:
return parameters
return None
class SearchForFieldFilter(django_filters.filters.MethodFilter):
field_class = SearchForFieldField
class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet):
q = MethodFilter()
tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all())
@ -60,4 +95,23 @@ class AutorChoiceFilterSet(SaplGenericRelationSearchFilterSet):
def filter_q(self, queryset, value):
return SaplGenericRelationSearchFilterSet.filter_q(
self, queryset, value).order_by('nome')
self, queryset, value).distinct('nome').order_by('nome')
class AutorSearchForFieldFilterSet(AutorChoiceFilterSet):
q = SearchForFieldFilter()
class Meta(AutorChoiceFilterSet.Meta):
pass
def filter_q(self, queryset, value):
value[0] = value[0].split(',')
value[1] = value[1].split(',')
params = {}
for key, v in list(zip(value[0], value[1])):
if v in ['True', 'False']:
v = '1' if v == 'True' else '0'
params[key] = v
return queryset.filter(**params).distinct('nome').order_by('nome')

1
sapl/api/serializers.py

@ -46,6 +46,7 @@ class AutorSerializer(serializers.ModelSerializer):
class Meta:
model = Autor
fields = '__all__'
class MateriaLegislativaSerializer(serializers.ModelSerializer):

58
sapl/api/views.py

@ -10,7 +10,7 @@ from rest_framework.permissions import (IsAuthenticated,
AllowAny)
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from sapl.api.forms import AutorChoiceFilterSet
from sapl.api.forms import AutorChoiceFilterSet, AutorSearchForFieldFilterSet
from sapl.api.serializers import (AutorChoiceSerializer, AutorSerializer,
ChoiceSerializer,
MateriaLegislativaSerializer,
@ -79,7 +79,57 @@ class AutorListView(ListAPIView):
o django-filter é desativado e a busca é feita
no model do ContentType associado ao tipo.
Outros campos
- q_0 / q_1 - q_0 faz o código ignorar "q"...
q_0 -> campos lookup a serem filtrados em qualquer Model
que implemente SaplGenericRelation
q_1 -> o valor que será pesquisado no lookup de q_0
q_0 e q_1 podem ser separados por ","... isso dará a
possibilidade de filtrar mais de um campo.
http://localhost:8000
/api/autor?tr=1&q_0=parlamentar_set__ativo&q_1=False
/api/autor?tr=1&q_0=parlamentar_set__ativo&q_1=True
/api/autor?tr=3&q_0=parlamentar_set__ativo&q_1=False
/api/autor?tr=3&q_0=parlamentar_set__ativo&q_1=True
http://localhost:8000
/api/autor?tr=1
&q_0=parlamentar_set__nome_completo__icontains,
parlamentar_set__ativo
&q_1=Carvalho,False
/api/autor?tr=1
&q_0=parlamentar_set__nome_completo__icontains,
parlamentar_set__ativo
&q_1=Carvalho,True
/api/autor?tr=3
&q_0=parlamentar_set__nome_completo__icontains,
parlamentar_set__ativo
&q_1=Carvalho,False
/api/autor?tr=3
&q_0=parlamentar_set__nome_completo__icontains,
parlamentar_set__ativo
&q_1=Carvalho,True
não importa o campo que vc passe de qualquer dos Models
ligados... é possível ver que models são esses,
na ocasião do commit deste texto, executando:
In [6]: from sapl.utils import models_with_gr_for_model
In [7]: models_with_gr_for_model(Autor)
Out[7]:
[sapl.parlamentares.models.Parlamentar,
sapl.parlamentares.models.Frente,
sapl.comissoes.models.Comissao,
sapl.materia.models.Orgao,
sapl.sessao.models.Bancada,
sapl.sessao.models.Bloco]
qualquer atributo destes models podem ser passados
para busca
"""
TR_AUTOR_CHOICE_SERIALIZER = 1
@ -125,6 +175,9 @@ class AutorListView(ListAPIView):
self.serializer_class = AutorSerializer
self.permission_classes = (IsAuthenticated,)
if self.filter_class and 'q_0' in request.GET:
self.filter_class = AutorSearchForFieldFilterSet
return ListAPIView.get(self, request, *args, **kwargs)
def get_queryset(self):
@ -207,6 +260,7 @@ class MateriaLegislativaViewSet(ListModelMixin,
filter_backends = (DjangoFilterBackend,)
filter_fields = ('numero', 'ano', 'tipo', )
class SessaoPlenariaViewSet(ListModelMixin,
RetrieveModelMixin,
GenericViewSet):

16
sapl/base/search_indexes.py

@ -32,10 +32,7 @@ class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable):
arquivo = getattr(obj, self.filename)
if arquivo:
try:
arquivo.open()
arquivo.close()
except OSError:
if not os.path.exists(arquivo.path):
return self.prepared_data
if not os.path.splitext(arquivo.path)[1][:1]:
@ -43,10 +40,13 @@ class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable):
try:
extracted_data = textract.process(
arquivo.path).decode(
'utf-8').replace('\n', ' ')
arquivo.path,
language='pt-br').decode('utf-8').replace('\n', ' ')
except ExtensionNotSupported:
return self.prepared_data
except Exception:
print('Erro inesperado processando arquivo: %s' % arquivo.path)
return self.prepared_data
extracted_data = extracted_data.replace('\t', ' ')
@ -54,8 +54,8 @@ class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable):
# text field with *all* of our metadata visible for templating:
t = loader.select_template((
'search/indexes/' + self.template_name, ))
data['text'] = t.render(Context({'object': obj,
'extracted': extracted_data}))
data['text'] = t.render({'object': obj,
'extracted': extracted_data})
return data

12
sapl/materia/forms.py

@ -164,18 +164,6 @@ class DocumentoAcessorioForm(ModelForm):
class Meta:
model = DocumentoAcessorio
fields = ['tipo', 'nome', 'data', 'autor', 'ementa', 'arquivo']
widgets = {'autor': forms.HiddenInput()}
def clean_autor(self):
autor_field = self.cleaned_data['autor']
try:
int(autor_field)
except ValueError:
return autor_field
else:
if autor_field:
return str(Autor.objects.get(id=autor_field))
class RelatoriaForm(ModelForm):

32
sapl/materia/migrations/0005_auto_20170522_1904.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.12 on 2017-05-22 19:04
from __future__ import unicode_literals
from django.db import migrations, models
import sapl.materia.models
import sapl.utils
class Migration(migrations.Migration):
dependencies = [
('materia', '0004_auto_20170504_1751'),
]
operations = [
migrations.AlterField(
model_name='documentoacessorio',
name='arquivo',
field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.anexo_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral'),
),
migrations.AlterField(
model_name='materialegislativa',
name='texto_original',
field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.materia_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Original'),
),
migrations.AlterField(
model_name='proposicao',
name='texto_original',
field=models.FileField(blank=True, null=True, upload_to=sapl.materia.models.materia_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Original'),
),
]

22
sapl/materia/views.py

@ -963,25 +963,21 @@ class DocumentoAcessorioCrud(MasterDetailCrud):
form_class = DocumentoAcessorioForm
def __init__(self, **kwargs):
montar_helper_documento_acessorio(self)
super(MasterDetailCrud.CreateView, self).__init__(**kwargs)
def get_context_data(self, **kwargs):
context = super(
MasterDetailCrud.CreateView, self).get_context_data(**kwargs)
context['helper'] = self.helper
return context
class UpdateView(MasterDetailCrud.UpdateView):
form_class = DocumentoAcessorioForm
def __init__(self, **kwargs):
montar_helper_documento_acessorio(self)
super(MasterDetailCrud.UpdateView, self).__init__(**kwargs)
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
context['helper'] = self.helper
return context
@ -1617,15 +1613,15 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
tipo = TipoDocumento.objects.get(descricao=request.POST['tipo'])
for materia_id in marcadas:
DocumentoAcessorio.objects.create(
materia_id=materia_id,
tipo=tipo,
arquivo=request.POST['arquivo'],
nome=request.POST['nome'],
data=datetime.strptime(request.POST['data'], "%d/%m/%Y"),
autor=Autor.objects.get(id=request.POST['autor']),
ementa=request.POST['ementa']
)
doc = DocumentoAcessorio()
doc.materia_id = materia_id
doc.tipo = tipo
doc.arquivo = request.FILES['arquivo']
doc.nome = request.POST['nome']
doc.data = datetime.strptime(request.POST['data'], "%d/%m/%Y")
doc.autor = request.POST['autor']
doc.ementa = request.POST['ementa']
doc.save()
msg = _('Documento(s) criado(s).')
messages.add_message(request, messages.SUCCESS, msg)
return self.get(request, self.kwargs)

12
sapl/parlamentares/models.py

@ -1,9 +1,9 @@
from datetime import datetime
import reversion
from django.db import models
from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
import reversion
from sapl.base.models import Autor
from sapl.utils import (INDICADOR_AFASTAMENTO, UF, YES_NO_CHOICES,
@ -206,6 +206,15 @@ def foto_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath='')
def true_false_none(x):
if x == 'True':
return True
elif x == 'False':
return False
else:
return None
@reversion.register()
class Parlamentar(models.Model):
FEMININO = 'F'
@ -303,6 +312,7 @@ class Parlamentar(models.Model):
('nome_completo', '__icontains'),
('nome_parlamentar', '__icontains'),
('filiacao__partido__sigla', '__icontains'),
('ativo', '', true_false_none),
))
class Meta:

6
sapl/templates/base.html

@ -157,11 +157,13 @@
<div class="col-md-4">
<a class="footer__logo" href="#">
<img src="{% static 'img/logo_interlegis.png' %}" alt="{% trans 'Logo do Interlegis' %} ">
<a href="http://www.interlegis.leg.br/">
<img src="{% static 'img/logo_interlegis.png' %}" alt="{% trans 'Logo do Interlegis' %} ">
</a>
</a>
<p>
<small>
Desenvolvido pelo <a href="#">Interlegis</a> em software livre e aberto.
Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto.
</small>
</p>
</div>

2
sapl/templates/materia/documentoacessorio_form.html

@ -2,5 +2,5 @@
{% load i18n crispy_forms_tags %}
{% block base_content %}
{% crispy form helper %}
{% crispy form %}
{% endblock %}

17
sapl/templates/materia/em_lote/acessorio.html

@ -14,7 +14,7 @@
{% else %}
<h3 style="text-align: right;">{% blocktrans with object_list.count as total_materias %}Foram encontradas {{total_materias}} matérias.{% endblocktrans %}</h3>
{% endif %}
<form method="POST">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset>
<legend>Documento Acessório</legend>
@ -40,22 +40,9 @@
<div class="row">
<div class="col-md-12">
<label>Autor:</label>
<span id="nome_autor" name="nome_autor"> </span>
<input type="text" name="autor" class="form-control" required="False">
</div>
<div id="modal_autor" title="Selecione o Autor" align="center">
<input id="q" type="text" /> <input id="pesquisar" type="submit" value="Pesquisar" class="btn btn-primary btn-sm"/>
<div id="div-resultado"></div>
<input type="submit" id="selecionar" value="Selecionar" hidden="true" />
</div>
<div class="row-fluid">
<div class="col-md-0"><input id="id_autor" maxlength="50" name="autor" type="hidden" /></div>
<div class="col-md-2"><input type="button" name="pesquisar" value="Pesquisar Autor" class="btn btn btn-primary btn-sm" id="button-id-pesquisar"/></div>
<div class="col-md-10"><input type="button" name="limpar" value="Limpar Autor" class="btn btn btn-primary btn-sm" id="button-id-limpar"/></div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<label>Ementa</label>

4
sapl/templates/protocoloadm/protocolo_list.html

@ -21,10 +21,10 @@
</a></br>
<strong>Assunto:</strong> {{ p.assunto_ementa }}</br>
<strong>Data Protocolo:</strong> {{ p.data|date:"d/m/Y" }} - Horário: {{ p.timestamp|date:"H:m:s" }}</br>
<strong>Interessado:</strong> {{ p.interessado }}</br>
<strong>Natureza do Processo:</strong>
{% if p.tipo_processo == 0 %}
Administrativo
Administrativo </br>
<strong>Interessado:</strong> {{ p.interessado }}</br>
{% elif p.tipo_processo == 1 %}
Matéria Legislativa
{% endif %}</br>

9
sapl/templates/sessao/votacao/nominal_detail.html

@ -18,8 +18,13 @@
<legend>Votos</legend>
<div class="row">
{% for v in votos %}
<div class="col-md-6">{{v.parlamentar}}</div>
<div class="col-md-6">{{v.voto}}</div>
<div class="col-md-6">{{v.parlamentar}} -
{% if v.voto == '-1'%}
<b>Voto não Registrado</b>
{% else %}
<b>{{v.voto}}</b>
{% endif %}
</div>
{% endfor %}
</div>
</fieldset>

11
sapl/templates/sessao/votacao/nominal_edit.html

@ -18,8 +18,15 @@
<legend>Votos</legend>
<div class="row">
{% for v in votos %}
<div class="col-md-6">{{v.parlamentar}}</div>
<div class="col-md-6">{{v.voto}}</div>
<div class="col-md-6">{{v.parlamentar}} -
{% if v.voto == '-1'%}
<b>Voto não Registrado</b>
{% else %}
<b>{{v.voto}}</b>
{% endif %}
</div>
{% endfor %}
</div>
</fieldset>

110
sapl/utils.py

@ -1,15 +1,13 @@
import hashlib
import logging
import os
import re
from datetime import date
from functools import wraps
from unicodedata import normalize as unicodedata_normalize
from subprocess import PIPE, call
from threading import Thread
from unicodedata import normalize as unicodedata_normalize
import hashlib
import logging
import os
import re
import django_filters
import magic
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button
from django import forms
@ -22,10 +20,13 @@ from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from floppyforms import ClearableFileInput
from reversion.admin import VersionAdmin
import django_filters
import magic
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row
from sapl.settings import BASE_DIR, PROJECT_DIR
sapl_logger = logging.getLogger(BASE_DIR.name)
@ -159,8 +160,8 @@ class SaplGenericRelation(GenericRelation):
assert isinstance(field, (tuple, list)), _(
'fields_search deve ser um array de tuplas ou listas.')
assert len(field) == 2, _(
'cada tupla de fields_search deve possuir duas strins')
assert len(field) <= 3, _(
'cada tupla de fields_search deve possuir até 3 strings')
# TODO implementar assert para validar campos do Model e lookups
@ -368,8 +369,8 @@ def fabrica_validador_de_tipos_de_arquivo(lista, nome):
def restringe_tipos_de_arquivo(value):
if not os.path.splitext(value.path)[1][:1]:
raise ValidationError(_(
'Não é possível fazer upload de arquivos sem extensão.'))
raise ValidationError(_(
'Não é possível fazer upload de arquivos sem extensão.'))
mime = magic.from_buffer(value.read(), mime=True)
if mime not in lista:
@ -391,92 +392,6 @@ def intervalos_tem_intersecao(a_inicio, a_fim, b_inicio, b_fim):
return maior_inicio <= menor_fim
"""
def permissoes(nome_grupo, app_label):
lista_permissoes = []
try:
perms = list(Permission.objects.filter(
group__name=nome_grupo))
for p in perms:
lista_permissoes.append('%s.%s' % (app_label, p.codename))
except:
pass
return set(lista_permissoes)
def permission_required_for_app(app_label, login_url=None,
raise_exception=False):
Decorator for views that checks whether a user has a particular permission
enabled, redirecting to the log-in page if necessary.
If the raise_exception parameter is given the PermissionDenied exception
is raised.
def check_perms(user):
if user.has_module_perms(app_label):
return True
# In case the 403 handler should be called raise the exception
if raise_exception:
raise PermissionDenied
# As the last resort, show the login form
return False
return user_passes_test(check_perms, login_url=login_url)
def permissoes_materia():
return permissoes('Operador de Matéria', 'materia')
def permissoes_comissoes():
return permissoes('Operador de Comissões', 'comissoes')
def permissoes_norma():
return permissoes('Operador de Norma Jurídica', 'norma')
def permissoes_protocoloadm():
return permissoes('Operador de Protocolo Administrativo', 'protocoloadm')
def permissoes_adm():
return permissoes('Operador Administrativo', 'protocoloadm')
def permissoes_sessao():
return permissoes('Operador de Sessão Plenária', 'sessao')
def permissoes_painel():
return permissoes('Operador de Painel Eletrônico', 'painel')
def permissoes_autor():
return permissoes('Autor', 'materia')
def permissoes_parlamentares():
lista_permissoes = []
try:
cts = ContentType.objects.filter(app_label='parlamentares')
perms_parlamentares = list(Permission.objects.filter(
content_type__in=cts))
for p in perms_parlamentares:
lista_permissoes.append('parlamentares.' + p.codename)
except:
pass
return set(lista_permissoes)
def permissao_tb_aux(self):
u = self.request.user
if u.groups.filter(name='Operador Geral').exists() or u.is_superuser:
return True
else:
return False
"""
class MateriaPesquisaOrderingFilter(django_filters.OrderingFilter):
choices = (
@ -637,6 +552,7 @@ def texto_upload_path(instance, filename, subpath=''):
class UpdateIndexCommand(Thread):
def run(self):
call([PROJECT_DIR.child('manage.py'), 'update_index'],
stdout=PIPE)

Loading…
Cancel
Save