diff --git a/sapl/api/forms.py b/sapl/api/forms.py index b6593d6a0..445444b0a 100644 --- a/sapl/api/forms.py +++ b/sapl/api/forms.py @@ -1,7 +1,9 @@ + from django.db.models import Q from django_filters.filters import MethodFilter, ModelChoiceFilter from rest_framework.filters import FilterSet + from sapl.base.models import Autor, TipoAutor from sapl.utils import generic_relations_for_model @@ -10,6 +12,7 @@ class SaplGenericRelationSearchFilterSet(FilterSet): q = MethodFilter() def filter_q(self, queryset, value): + query = value.split(' ') if query: q = Q() @@ -25,6 +28,7 @@ class SaplGenericRelationSearchFilterSet(FilterSet): sgr = gr[1] for item in sgr: if item.related_model != self._meta.model: + continue flag_order_by = True for field in item.fields_search: diff --git a/sapl/api/pagination.py b/sapl/api/pagination.py index 38cd219c5..47d35ee65 100644 --- a/sapl/api/pagination.py +++ b/sapl/api/pagination.py @@ -1,5 +1,4 @@ from django.core.paginator import EmptyPage -from django.utils.encoding import force_text from rest_framework import pagination from rest_framework.response import Response @@ -31,4 +30,5 @@ class StandardPagination(pagination.PageNumberPagination): 'page': self.page.number, }, 'results': data, + }) diff --git a/sapl/api/permissions.py b/sapl/api/permissions.py index 73cdba329..5e17d1fe1 100644 --- a/sapl/api/permissions.py +++ b/sapl/api/permissions.py @@ -1,4 +1,4 @@ -from rest_framework.permissions import DjangoModelPermissions, BasePermission +from rest_framework.permissions import DjangoModelPermissions class DjangoModelPermissions(DjangoModelPermissions): @@ -14,4 +14,5 @@ class DjangoModelPermissions(DjangoModelPermissions): 'PUT': ['%(app_label)s.change_%(model_name)s'], 'PATCH': ['%(app_label)s.change_%(model_name)s'], 'DELETE': ['%(app_label)s.delete_%(model_name)s'], - } \ No newline at end of file + + } diff --git a/sapl/api/serializers.py b/sapl/api/serializers.py index d68dafdeb..0791ab22f 100644 --- a/sapl/api/serializers.py +++ b/sapl/api/serializers.py @@ -1,6 +1,4 @@ -from django.contrib.contenttypes.fields import GenericRel from rest_framework import serializers - from sapl.base.models import Autor from sapl.materia.models import MateriaLegislativa @@ -51,4 +49,4 @@ class AutorSerializer(serializers.ModelSerializer): class MateriaLegislativaSerializer(serializers.ModelSerializer): class Meta: - model = MateriaLegislativa \ No newline at end of file + model = MateriaLegislativa diff --git a/sapl/api/urls.py b/sapl/api/urls.py index d65446011..c1ec1bd9a 100644 --- a/sapl/api/urls.py +++ b/sapl/api/urls.py @@ -1,13 +1,13 @@ - from django.conf import settings from django.conf.urls import url, include from rest_framework.routers import DefaultRouter -from sapl.api.views import AutorListView, ModelChoiceView,\ - MateriaLegislativaViewSet +from sapl.api.views import MateriaLegislativaViewSet, AutorListView,\ + ModelChoiceView from .apps import AppConfig + app_name = AppConfig.name @@ -17,11 +17,12 @@ urlpatterns_router = router.urls urlpatterns_api = [ - # url(r'^$', api_root), + url(r'^autor', AutorListView.as_view(), name='autor_list'), url(r'^model/(?P\d+)/(?P\d*)$', ModelChoiceView.as_view(), name='model_list'), + ] if settings.DEBUG: diff --git a/sapl/api/views.py b/sapl/api/views.py index 04d952131..3b16ce37a 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -1,10 +1,13 @@ + from django.contrib.contenttypes.models import ContentType + from django.db.models import Q from django.http import Http404 from django.utils.translation import ugettext_lazy as _ from rest_framework.filters import DjangoFilterBackend from rest_framework.generics import ListAPIView + from rest_framework.mixins import ListModelMixin, RetrieveModelMixin from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.viewsets import GenericViewSet @@ -58,6 +61,7 @@ class AutorListView(ListAPIView): de Autores mas feito para Possíveis Autores armazenados segundo o ContentType associado ao Tipo de Autor via relacionamento genérico. + Busca feita sem django-filter processada no get_queryset -> processo no cadastro de autores para seleção e busca dos possíveis autores @@ -123,7 +127,6 @@ class AutorListView(ListAPIView): return ListAPIView.get(self, request, *args, **kwargs) - def get_queryset(self): queryset = ListAPIView.get_queryset(self) diff --git a/sapl/base/forms.py b/sapl/base/forms.py index 73bf92e71..451b4b353 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -27,9 +27,11 @@ from sapl.sessao.models import SessaoPlenaria from sapl.settings import MAX_IMAGE_UPLOAD_SIZE from sapl.utils import (RANGE_ANOS, ImageThumbnailFileInput, RangeWidgetOverride, autor_label, autor_modal, + SaplGenericRelation, models_with_gr_for_model, ChoiceWithoutValidationField) + from .models import AppConfig, CasaLegislativa @@ -120,16 +122,6 @@ class AutorForm(ModelForm): choices=ACTION_CREATE_USERS_AUTOR_CHOICE, widget=forms.RadioSelect()) - status_user = forms.ChoiceField( - label=_('Bloqueio do Usuário Existente'), - choices=STATUS_USER_CHOICE, - widget=forms.RadioSelect(), - required=False, - help_text=_('Se vc está trocando ou removendo o usuário deste Autor, ' - 'como o Sistema deve proceder com o usuário que está sendo' - ' desvinculado?')) - - class Meta: model = Autor fields = ['tipo', @@ -150,7 +142,6 @@ class AutorForm(ModelForm): StrictButton( _('Filtrar'), css_class='btn-default btn-filtrar-autor', type='button')), - css_class='hidden', data_action='create', data_application='AutorSearch', @@ -160,7 +151,6 @@ class AutorForm(ModelForm): Div(to_column(('nome', 5)), to_column(('cargo', 4)), css_class="div_nome_cargo"), to_column((autor_related, 9)), - to_column((Div( Field('autor_related'), css_class='radiogroup-autor-related hidden'), @@ -175,13 +165,17 @@ class AutorForm(ModelForm): to_column(('confirma_email', 3)), css_class='new_user_fields hidden') - row4 = Row(to_column((Div(InlineRadios('status_user'), - css_class='radiogroup-status hidden'), 12))) + row4 = Row(to_column(( + Div(InlineRadios('status_user'), + css_class='radiogroup-status hidden'), + 12))) if 'status_user' in self.Meta.fields else None - controle_acesso = Fieldset( - _('Controle de Acesso do Autor'), - row2, row3, row4 - ) + controle_acesso = [row2, row3] + + if row4: + controle_acesso.append(row4) + controle_acesso = Fieldset(_('Controle de Acesso do Autor'), + *controle_acesso) self.helper = FormHelper() self.helper.layout = SaplFormLayout(autor_select, controle_acesso) @@ -195,26 +189,33 @@ class AutorForm(ModelForm): self.fields['autor_related'].choices = [ (self.instance.autor_related.pk, self.instance.autor_related)] - self.fields['autor_related'].initial = self.instance.object_id + self.fields['q'].initial = '' + self.fields['autor_related'].initial = self.instance.autor_related + if self.instance.user: self.fields['username'].initial = self.instance.user.username self.fields['action_user'].initial = 'A' - self.fields['status_user'].initial = 'R' + self.fields['username'].label = string_concat( self.fields['username'].label, ' (', self.instance.user.username, ')') - self.fields['status_user'].label = string_concat( - self.fields['status_user'].label, - ' (', self.instance.user.username, ')') + + if 'status_user' in self.Meta.fields: + self.fields['status_user'].initial = 'R' + self.fields['status_user'].label = string_concat( + self.fields['status_user'].label, + ' (', self.instance.user.username, ')') + self.fields['username'].widget.attrs.update({ 'data': self.instance.user.username if self.instance.user else ''}) - self.fields['status_user'].widget.attrs.update({ - 'data': self.instance.user.username - if self.instance.user else ''}) + if 'status_user' in self.Meta.fields: + self.fields['status_user'].widget.attrs.update({ + 'data': self.instance.user.username + if self.instance.user else ''}) def valida_igualdade(self, texto1, texto2, msg): if texto1 != texto2: @@ -229,13 +230,14 @@ class AutorForm(ModelForm): raise ValidationError(_('Informe se o Autor terá usuário ' 'vinculado para acesso ao Sistema.')) - if self.instance.pk and self.instance.user_id: - if self.instance.user.username != cd['username']: - if 'status_user' not in cd or not cd['status_user']: - raise ValidationError( - _('Foi trocado ou removido o usuário deste Autor, ' - 'mas não foi informado como se deve proceder com o ' - 'usuário que está sendo desvinculado?')) + if 'status_user' in self.Meta.fields: + if self.instance.pk and self.instance.user_id: + if self.instance.user.username != cd['username']: + if 'status_user' not in cd or not cd['status_user']: + raise ValidationError( + _('Foi trocado ou removido o usuário deste Autor, ' + 'mas não foi informado como se deve proceder ' + 'com o usuário que está sendo desvinculado?')) qs_user = User.objects.all() qs_autor = Autor.objects.all() @@ -374,23 +376,49 @@ class AutorForm(ModelForm): user_old.groups.remove(grupo) else: - if 'status_user' in self.cleaned_data and user_old: - if self.cleaned_data['status_user'] == 'X': - user_old.delete() - - elif self.cleaned_data['status_user'] == 'D': + if 'status_user' in self.Meta.fields: + if 'status_user' in self.cleaned_data and user_old: + if self.cleaned_data['status_user'] == 'X': + user_old.delete() + + elif self.cleaned_data['status_user'] == 'D': + user_old.groups.remove(grupo) + user_old.is_active = False + user_old.save() + + elif self.cleaned_data['status_user'] == 'R': + user_old.groups.remove(grupo) + elif user_old: user_old.groups.remove(grupo) - user_old.is_active = False - user_old.save() + else: - elif self.cleaned_data['status_user'] == 'R': - user_old.groups.remove(grupo) - elif user_old: user_old.groups.remove(grupo) return autor +class AutorFormForAdmin(AutorForm): + status_user = forms.ChoiceField( + label=_('Bloqueio do Usuário Existente'), + choices=STATUS_USER_CHOICE, + widget=forms.RadioSelect(), + required=False, + help_text=_('Se vc está trocando ou removendo o usuário deste Autor, ' + 'como o Sistema deve proceder com o usuário que está sendo' + ' desvinculado?')) + + class Meta: + model = Autor + fields = ['tipo', + 'nome', + 'cargo', + 'autor_related', + 'q', + 'action_user', + 'username', + 'status_user'] + + class RelatorioAtasFilterSet(django_filters.FilterSet): filter_overrides = {models.DateField: { diff --git a/sapl/base/views.py b/sapl/base/views.py index 45cb5ed8a..db6b9a1f7 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -13,7 +13,9 @@ from django.utils.translation import ugettext_lazy as _ from django.views.generic.base import TemplateView from django_filters.views import FilterView -from sapl.base.forms import AutorForm, TipoAutorForm + +from sapl.base.forms import AutorForm, TipoAutorForm, AutorFormForAdmin + from sapl.base.models import Autor, TipoAutor from sapl.crud.base import CrudAux from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa @@ -49,7 +51,6 @@ class AutorCrud(CrudAux): class BaseMixin(CrudAux.BaseMixin): list_field_names = ['tipo', 'nome', 'user__username'] - ordering = ('tipo__descricao', ) class DeleteView(CrudAux.DeleteView): @@ -66,6 +67,11 @@ class AutorCrud(CrudAux): layout_key = None form_class = AutorForm + def get(self, request, *args, **kwargs): + if request.user.is_superuser: + self.form_class = AutorFormForAdmin + return CrudAux.UpdateView.get(self, request, *args, **kwargs) + def get_success_url(self): # FIXME try except - testar envio de emails @@ -74,7 +80,7 @@ class AutorCrud(CrudAux): try: kwargs = {} user = self.object.user - + if user.is_active: return reverse('sapl.base:autor_detail', kwargs={'pk': pk_autor}) @@ -108,6 +114,11 @@ class AutorCrud(CrudAux): form_class = AutorForm layout_key = None + def get(self, request, *args, **kwargs): + if request.user.is_superuser: + self.form_class = AutorFormForAdmin + return CrudAux.CreateView.get(self, request, *args, **kwargs) + def get_success_url(self): pk_autor = self.object.id try: diff --git a/sapl/crud/base.py b/sapl/crud/base.py index a707c6ba7..03c02a7c6 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -9,7 +9,6 @@ from django import forms from django.conf.urls import url from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin -from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.db import models from django.db.models.fields.related import ForeignKey @@ -17,8 +16,8 @@ from django.http.response import Http404 from django.shortcuts import redirect from django.utils.decorators import classonlymethod from django.utils.encoding import force_text -from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat +from django.utils.translation import ugettext_lazy as _ from django.views.generic import (CreateView, DeleteView, DetailView, ListView, UpdateView) from django.views.generic.base import ContextMixin @@ -28,6 +27,7 @@ from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display from sapl.settings import BASE_DIR from sapl.utils import normalize + logger = logging.getLogger(BASE_DIR.name) ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \ diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index b61706add..c37e73e8e 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -1,20 +1,18 @@ -from cProfile import label + from datetime import datetime -import django_filters -from crispy_forms.bootstrap import InlineRadios, InlineField, Alert,\ - InlineCheckboxes +from crispy_forms.bootstrap import Alert, InlineCheckboxes from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout, Row,\ - Div, Field + Field from django import forms -from django.contrib.contenttypes.fields import GenericRel from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.db import models, transaction from django.db.models import Max from django.forms import ModelForm, widgets from django.utils.translation import ugettext_lazy as _ +import django_filters from sapl import base from sapl.base.models import Autor diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 4814cc101..28eebe9b2 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -38,6 +38,7 @@ from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label, permissoes_protocoloadm, permission_required_for_app, montar_row_autor) + from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, ConfirmarProposicaoForm, DocumentoAcessorioForm, MateriaLegislativaFilterSet, @@ -325,6 +326,12 @@ class ConfirmarProposicao(PermissionRequiredMixin, CreateView): class ProposicaoCrud(Crud): + """ + TODO: Entre outros comportamento gerais, mesmo que um usuário tenha + Perfil de Autor o Crud de proposição não deverá permitir acesso a + proposições. O acesso só deve ser permitido se existe um Autor registrado + e vinculado ao usuário. Essa tarefa deve ser realizada nas Tabelas Aux. + """ model = Proposicao help_path = '' container_field = 'autor__user' diff --git a/sapl/static/js/app.js b/sapl/static/js/app.js index 408a06029..c6c3f1db6 100644 --- a/sapl/static/js/app.js +++ b/sapl/static/js/app.js @@ -150,9 +150,19 @@ function autorModal() { get_nome_autor("#id_autoria__autor");*/ } + +var customsFront = function() { + $('[type=radio], [type=checkbox]').each(function() { + var $controls = $(this).closest('.controls') + $controls && !$controls.hasClass('controls-radio-checkbox') && $controls.addClass('controls-radio-checkbox') + }); + +} + $(document).ready(function(){ refreshDatePicker(); refreshMask(); autorModal(); initTinymce("texto-rico"); + customsFront(); }); diff --git a/sapl/static/styles/app.scss b/sapl/static/styles/app.scss index ac0390bc3..8db570b01 100644 --- a/sapl/static/styles/app.scss +++ b/sapl/static/styles/app.scss @@ -113,6 +113,40 @@ h6, .h6 { } } +.controls-radio-checkbox { + padding: 0px; + border: 1px solid #d6e1e5; + border-radius: 4px; + min-height: 20px; + .help-block { + margin: $grid-gutter-width / 2; + padding: $grid-gutter-width / 2; + border: 2px dashed #d6e1e5; + } + + label { + padding: 5px; + .icons { + top: 5px; + left: 8px; + } + &.checkbox-inline, &.radio-inline { + padding: 8px; + padding-left: 36px; + .icons { + top: 8px; + left: 8px; + } + } + } + .checkbox, .radio, .checkbox-inline, .radio-inline { + margin: 0; + &:hover { + background-color: #d6e1e5;; + } + } +} + // #### CRUD DETAIL ######################################## p.control-label { diff --git a/sapl/templates/base.html b/sapl/templates/base.html index 9d732de1a..40295a7f3 100644 --- a/sapl/templates/base.html +++ b/sapl/templates/base.html @@ -284,11 +284,11 @@ - - - + + + diff --git a/sapl/templates/base/autor_form.html b/sapl/templates/base/autor_form.html index 8a4fb5f30..688711d79 100644 --- a/sapl/templates/base/autor_form.html +++ b/sapl/templates/base/autor_form.html @@ -40,7 +40,6 @@ $(document).ready(function(){ if (atualizar) { var radios = $("#div_id_autor_related .controls").html(''); data.results.forEach(function (val, index) { - var html_radio = '
'; radios.append(html_radio); }); @@ -64,6 +63,11 @@ $(document).ready(function(){ } else{ $('#id_nome, #id_q').val(''); + if ($('input[name=autor_related]').length == 1 ) { + $('input[name=autor_related]').prop('checked', 'checked'); + $('input[name=autor_related]').closest('.radio').addClass('checked'); + } + } }).fail(function(data) { active('nome', atualizar); diff --git a/sapl/templates/materia/materialegislativa_filter.html b/sapl/templates/materia/materialegislativa_filter.html index 48bcaf1d6..bd69f8ab1 100644 --- a/sapl/templates/materia/materialegislativa_filter.html +++ b/sapl/templates/materia/materialegislativa_filter.html @@ -47,6 +47,19 @@
Localização Atual:  {{m.tramitacao_set.last.unidade_tramitacao_destino|default_if_none:"Não Informada"}}
Status:  {{m.tramitacao_set.last.status|default_if_none:"Não Informada"}}
+ {% if m.registrovotacao_set.exists %} + Data Votação: + {% if m.registrovotacao_set.last.ordem %} + + {{ m.registrovotacao_set.last.ordem.data_ordem }} + + {% elif m.registrovotacao_set.last.expediente %} + + {{ m.registrovotacao_set.last.expediente.data_ordem }} + + {% endif %} +
+ {% endif %} Data da última Tramitação:  {{m.tramitacao_set.last.data_tramitacao|default_if_none:"Não Informada"}}
Ementa: {{ m.ementa|safe }}

diff --git a/sapl/utils.py b/sapl/utils.py index efe3cabb8..928a252e3 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -4,7 +4,6 @@ from unicodedata import normalize as unicodedata_normalize import hashlib import logging -import magic from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Button from django import forms