From 2a4141bdb2598340596dba425ecb3bbd521e5550 Mon Sep 17 00:00:00 2001 From: LeandroRoberto Date: Tue, 11 Oct 2016 16:36:43 -0300 Subject: [PATCH] =?UTF-8?q?Conc=20refatora=C3=A7=C3=A3o=20no=20Cada=20de?= =?UTF-8?q?=20Autor=20e=20Tipos=20de=20Autor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/api/serializers.py | 16 - sapl/api/urls.py | 4 +- sapl/api/views.py | 10 +- sapl/base/forms.py | 305 ++++++++++++------ .../migrations/0025_tipoautor_cria_usuario.py | 20 ++ .../0026_remove_tipoautor_cria_usuario.py | 19 ++ .../migrations/0027_auto_20161011_1624.py | 22 ++ sapl/base/models.py | 7 +- sapl/base/urls.py | 15 +- sapl/base/views.py | 169 +++++----- sapl/comissoes/models.py | 1 - sapl/crispy_layout_mixin.py | 2 +- sapl/crud/base.py | 13 +- sapl/materia/forms.py | 9 +- sapl/materia/migrations/0056_merge.py | 16 + sapl/materia/models.py | 2 +- sapl/materia/urls.py | 10 +- sapl/materia/views.py | 30 +- sapl/parlamentares/models.py | 5 +- sapl/protocoloadm/forms.py | 1 + sapl/sessao/forms.py | 8 +- sapl/sessao/urls.py | 7 +- sapl/settings.py | 10 +- sapl/templates/base.html | 1 - sapl/templates/base/autor_form.html | 28 +- sapl/templates/base/layouts.yaml | 4 +- .../layout/checkboxselectmultiple.html | 14 + .../bootstrap3/layout/radioselect.html | 19 ++ sapl/test_urls.py | 6 +- sapl/urls.py | 2 +- sapl/utils.py | 41 ++- 31 files changed, 528 insertions(+), 288 deletions(-) create mode 100644 sapl/base/migrations/0025_tipoautor_cria_usuario.py create mode 100644 sapl/base/migrations/0026_remove_tipoautor_cria_usuario.py create mode 100644 sapl/base/migrations/0027_auto_20161011_1624.py create mode 100644 sapl/materia/migrations/0056_merge.py create mode 100644 sapl/templates/bootstrap3/layout/checkboxselectmultiple.html create mode 100644 sapl/templates/bootstrap3/layout/radioselect.html diff --git a/sapl/api/serializers.py b/sapl/api/serializers.py index d61643d21..0cbade069 100644 --- a/sapl/api/serializers.py +++ b/sapl/api/serializers.py @@ -1,8 +1,5 @@ from rest_framework import serializers -from sapl.comissoes.models import Comissao -from sapl.parlamentares.models import Parlamentar - class ChoiceSerializer(serializers.Serializer): pk = serializers.IntegerField() @@ -10,16 +7,3 @@ class ChoiceSerializer(serializers.Serializer): def get_display(self, obj): return str(obj) -""" - -class ModelChoiceParlamentarSerializer(ModelChoiceSerializer): - - class Meta: - model = Parlamentar - - -class ModelChoiceComissaoSerializer(ModelChoiceSerializer): - - class Meta: - model = Comissao -""" \ No newline at end of file diff --git a/sapl/api/urls.py b/sapl/api/urls.py index a3a130da9..2d6753308 100644 --- a/sapl/api/urls.py +++ b/sapl/api/urls.py @@ -1,11 +1,9 @@ -from django.conf.urls import include, url -from rest_framework.routers import DefaultRouter +from django.conf.urls import url from sapl.api.views import TipoAutorContentOfModelContentTypeView from .apps import AppConfig - app_name = AppConfig.name diff --git a/sapl/api/views.py b/sapl/api/views.py index a8058e5c6..def65a217 100644 --- a/sapl/api/views.py +++ b/sapl/api/views.py @@ -1,10 +1,7 @@ from django.db.models import Q from django.http import Http404 -from rest_framework import mixins, viewsets -from rest_framework.generics import ListAPIView, GenericAPIView,\ - get_object_or_404 -from rest_framework.permissions import IsAuthenticated, AllowAny -from rest_framework.views import APIView +from rest_framework.generics import ListAPIView, get_object_or_404 +from rest_framework.permissions import IsAuthenticated from rest_framework.viewsets import ModelViewSet from sapl.api.serializers import ChoiceSerializer @@ -14,7 +11,8 @@ from sapl.utils import SaplGenericRelation class TipoAutorContentOfModelContentTypeView(ListAPIView): serializer_class = ChoiceSerializer - permission_classes = (AllowAny,) + # FIXME aplicar permissão correta de usuário + permission_classes = (IsAuthenticated,) queryset = TipoAutor.objects.all() model = TipoAutor pagination_class = None diff --git a/sapl/base/forms.py b/sapl/base/forms.py index bfc85b7b6..baaa3059d 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -1,23 +1,23 @@ -from crispy_forms.bootstrap import FieldWithButtons, StrictButton +import django_filters +from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton from crispy_forms.helper import FormHelper -from crispy_forms.layout import HTML, Button, Fieldset, Layout, Field, Div, Row -from crispy_forms.templatetags.crispy_forms_field import css_class +from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row from django import forms +from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.models import Group from django.contrib.auth.password_validation import validate_password from django.contrib.contenttypes.fields import GenericRel from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import ValidationError, ObjectDoesNotExist +from django.core.exceptions import ValidationError from django.db import models, transaction -from django.forms import ModelForm, widgets +from django.forms import ModelForm from django.utils.translation import ugettext_lazy as _ -import django_filters from sapl.base.models import Autor, TipoAutor -from sapl.crispy_layout_mixin import form_actions, to_row, SaplFormLayout,\ - to_column +from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column, + to_row) from sapl.materia.models import MateriaLegislativa from sapl.sessao.models import SessaoPlenaria from sapl.settings import MAX_IMAGE_UPLOAD_SIZE @@ -26,6 +26,12 @@ from sapl.utils import (RANGE_ANOS, ImageThumbnailFileInput, from .models import AppConfig, CasaLegislativa +ACTION_CREATE_USERS_AUTOR_CHOICE = [ + ('C', _('Criar novo Usuário')), + ('A', _('Associar um usuário existente')), + ('N', _('Autor sem Usuário de Acesso ao Sapl')), +] + class TipoAutorForm(ModelForm): @@ -37,7 +43,7 @@ class TipoAutorForm(ModelForm): class Meta: model = TipoAutor fields = ['descricao', - 'content_type', ] + 'content_type'] def __init__(self, *args, **kwargs): @@ -61,47 +67,65 @@ class TipoAutorForm(ModelForm): (ct.pk, ct) for key, ct in content_types.items()] +class ChoiceWithoutValidationField(forms.ChoiceField): + + def validate(self, value): + if self.required and not value: + raise ValidationError( + self.error_messages['required'], code='required') + + class AutorForm(ModelForm): - """senha = forms.CharField( + senha = forms.CharField( max_length=20, label=_('Senha'), - required=True, + required=False, widget=forms.PasswordInput()) senha_confirma = forms.CharField( max_length=20, label=_('Confirmar Senha'), - required=True, + required=False, widget=forms.PasswordInput()) + email = forms.EmailField( + required=False, + label=_('Email')) + confirma_email = forms.EmailField( - required=True, + required=False, label=_('Confirmar Email')) username = forms.CharField( - required=True, - max_length=50 - )""" + required=False, + max_length=50) q = forms.CharField( max_length=50, required=False, label='Pesquise o nome do Autor com o ' 'tipo Selecionado e marque o escolhido.') - autor_related = forms.ChoiceField(label='', - required=False, - widget=forms.RadioSelect()) + + autor_related = ChoiceWithoutValidationField(label='', + required=False, + widget=forms.RadioSelect()) + + action_user = forms.ChoiceField( + label=_('Usuário de acesso ao Sistema para este Autor'), + choices=ACTION_CREATE_USERS_AUTOR_CHOICE, + widget=forms.RadioSelect()) class Meta: model = Autor fields = ['tipo', 'nome', 'autor_related', - 'q'] + 'q', + 'action_user', + 'username'] def __init__(self, *args, **kwargs): autor_related = Div( - FieldWithButtons( Field('q', placeholder=_('Pesquisar por possíveis autores para ' @@ -109,110 +133,195 @@ class AutorForm(ModelForm): StrictButton( _('Filtrar'), css_class='btn-default btn-filtrar-autor', type='button')), + + Field('autor_related'), css_class='hidden', data_action='create', data_application='AutorSearch', data_field='autor_related') - row1 = to_row([ - ('tipo', 4), - ('nome', 8), - (autor_related, 8), - - ]) + autor_select = Row(to_column(('tipo', 4)), + to_column(('nome', 8)), + to_column((autor_related, 8))) + + row2 = Row(to_column((InlineRadios('action_user'), 8)), + to_column(('username', 4))) + row3 = Row(to_column(('senha', 3)), + to_column(('senha_confirma', 3)), + to_column(('email', 3)), + to_column(('confirma_email', 3)), + css_class='new_user_fields hidden') + + controle_acesso = Fieldset( + _('Controle de Acesso do Autor'), + row2, row3 + ) self.helper = FormHelper() - self.helper.layout = SaplFormLayout(row1) + self.helper.layout = SaplFormLayout(autor_select, controle_acesso) super(AutorForm, self).__init__(*args, **kwargs) - self.fields['autor_related'].choices = [] - if self.instance and self.instance.autor_related: - self.fields['autor_related'].choices = [ - (self.instance.autor_related.pk, - self.instance.autor_related)] + self.fields['action_user'].initial = 'N' + self.fields['action_user'].inline_class = True + + if self.instance.pk: + if self.instance.autor_related: + self.fields['autor_related'].choices = [ + (self.instance.autor_related.pk, + self.instance.autor_related)] + self.fields['q'].initial = self.instance.nome + + if self.instance.user: + self.fields['username'].initial = self.instance.user.username + self.fields['action_user'].initial = 'A' def valida_igualdade(self, texto1, texto2, msg): if texto1 != texto2: raise ValidationError(msg) return True - def valida_email_existente(self): - return get_user_model().objects.filter( - email=self.cleaned_data['email']).exists() - - def clea(self): - - if 'username' not in self.cleaned_data: - raise ValidationError(_('Favor informar o username')) - - if ('senha' not in self.cleaned_data or - 'senha_confirma' not in self.cleaned_data): - raise ValidationError(_('Favor informar as senhas')) - - msg = _('As senhas não conferem.') - self.valida_igualdade( - self.cleaned_data['senha'], - self.cleaned_data['senha_confirma'], - msg) - - if ('email' not in self.cleaned_data or - 'confirma_email' not in self.cleaned_data): - raise ValidationError(_('Favor informar endereços de email')) - - msg = _('Os emails não conferem.') - self.valida_igualdade( - self.cleaned_data['email'], - self.cleaned_data['confirma_email'], - msg) - - email_existente = self.valida_email_existente() - - if (Autor.objects.filter( - username=self.cleaned_data['username']).exists()): - raise ValidationError(_('Já existe um autor para este usuário')) - - if email_existente: - msg = _('Este email já foi cadastrado.') - raise ValidationError(msg) - - try: - validate_password(self.cleaned_data['senha']) - except ValidationError as error: - raise ValidationError(error) - - try: - get_user_model().objects.get( - username=self.cleaned_data['username'], - email=self.cleaned_data['email']) - except ObjectDoesNotExist: - msg = _('Este nome de usuario não está cadastrado. ' + - 'Por favor, cadastre-o no Administrador do ' + - 'Sistema antes de adicioná-lo como Autor') - raise ValidationError(msg) + def clean(self): + User = get_user_model() + cd = self.cleaned_data + + if 'username' not in cd: + raise ValidationError(_('O username deve ser informado.')) + + if 'action_user' not in cd: + raise ValidationError(_('Informe se o Autor terá usuário ' + 'para acesso ao Sistema.')) + + qs_user = User.objects.all() + qs_autor = Autor.objects.all() + + if self.instance.pk: + qs_autor = qs_autor.exclude(pk=self.instance.pk) + if self.instance.user: + qs_user = qs_user.exclude(pk=self.instance.user.pk) + + if cd['action_user'] == 'C': + if User.objects.filter(username=cd['username']).exists(): + raise ValidationError( + _('Já existe usuário com o username "%s". ' + 'Para usá-lo você deve selecionar ' + '"Associar um usuário existente".') % cd['username']) + + if ('senha' not in cd or 'senha_confirma' not in cd or + not cd['senha'] or not cd['senha_confirma']): + raise ValidationError(_( + 'A senha e sua confirmação devem ser informadas.')) + msg = _('As senhas não conferem.') + self.valida_igualdade(cd['senha'], cd['senha_confirma'], msg) + + try: + validate_password(self.cleaned_data['senha']) + except ValidationError as error: + raise ValidationError(error) + + if ('email' not in cd or 'confirma_email' not in cd or + not cd['email'] or not cd['confirma_email']): + raise ValidationError(_( + 'O email e sua confirmação devem ser informados.')) + msg = _('Os emails não conferem.') + self.valida_igualdade(cd['email'], cd['confirma_email'], msg) + + if qs_user.filter(email=cd['email']).exists(): + raise ValidationError(_('Este email já foi cadastrado.')) + + if qs_autor.filter(user__email=cd['email']).exists(): + raise ValidationError( + _('Já existe um Autor com este email.')) + + elif cd['action_user'] == 'A': + if not User.objects.filter(username=cd['username']).exists(): + raise ValidationError( + _('Não existe usuário com username "%s". ' + 'Para usá-lo você deve selecionar ' + '"Criar novo Usuário".') % cd['username']) + + if cd['action_user'] != 'N': + if qs_autor.filter(user__username=cd['username']).exists(): + raise ValidationError( + _('Já existe um Autor para este usuário.')) + + """ + 'if' não é necessário por ser campo obrigatório e o framework já + mostrar a mensagem de obrigatório junto ao campo. mas foi colocado + ainda assim para renderizar um message.danger no topo do form. + """ + if 'tipo' not in cd or not cd['tipo']: + raise ValidationError( + _('O Tipo do Autor deve ser selecionado.')) + + tipo = cd['tipo'] + + if not tipo.content_type: + if 'nome' not in cd or not cd['nome']: + raise ValidationError( + _('O Nome do Autor deve ser informado.')) + else: + if 'autor_related' not in cd or not cd['autor_related']: + raise ValidationError( + _('Um registro de %s deve ser escolhido para ser ' + 'vinculado ao cadastro de Autor') % tipo.descricao) + + if not tipo.content_type.model_class().objects.filter( + pk=cd['autor_related']).exists(): + raise ValidationError( + _('O Registro definido (%s-%s) não está na base de %s.' + ) % (cd['autor_related'], cd['q'], tipo.descricao)) + + if qs_autor.filter(object_id=cd['autor_related']).exists(): + autor = qs_autor.filter(object_id=cd['autor_related']).first() + raise ValidationError( + _('Já existe um autor Cadastrado para %s' + ) % autor.autor_related) return self.cleaned_data @transaction.atomic - def sav(self, commit=False): - + def save(self, commit=False): + print('aqui') autor = super(AutorForm, self).save(commit) - u = get_user_model().objects.get( - username=autor.username, - email=autor.email) + user_old = autor.user if autor.user_id else None - u.set_password(self.cleaned_data['senha']) - u.is_active = False - u.save() - - autor.user = u + if self.cleaned_data['action_user'] == 'A': + u = get_user_model().objects.get( + username=self.cleaned_data['username']) + autor.user = u + elif self.cleaned_data['action_user'] == 'C': + u = get_user_model().objects.create( + username=self.cleaned_data['username'], + email=self.cleaned_data['email']) + u.set_password(self.cleaned_data['senha']) + # Define usuário como ativo em ambiente de desenvolvimento + # pode logar sem a necessidade de passar pela validação de email + u.is_active = settings.DEBUG + u.save() + autor.user = u + + if not autor.tipo.content_type: + autor.content_type = None + autor.object_id = None + autor.autor_related = None + else: + autor.autor_related = autor.tipo.content_type.model_class( + ).objects.get(pk=self.cleaned_data['autor_related']) + autor.nome = str(autor.autor_related) autor.save() - grupo = Group.objects.filter(name='Autor')[0] - u.groups.add(grupo) + if self.cleaned_data['action_user'] != 'N': + # FIXME melhorar captura de grupo de Autor, levando em conta + # tradução + grupo = Group.objects.filter(name='Autor')[0] + autor.user.groups.add(grupo) + + if user_old and user_old != autor.user: + user_old.groups.remove(grupo) return autor diff --git a/sapl/base/migrations/0025_tipoautor_cria_usuario.py b/sapl/base/migrations/0025_tipoautor_cria_usuario.py new file mode 100644 index 000000000..ba36e092c --- /dev/null +++ b/sapl/base/migrations/0025_tipoautor_cria_usuario.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-10-11 14:38 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0024_auto_20161010_1002'), + ] + + operations = [ + migrations.AddField( + model_name='tipoautor', + name='cria_usuario', + field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, help_text='Criação de Usuários víncula e libera o acesso de Autores ao sistema. Vincular um Autor a um tipo que esta opção está marcada como "Não", o Autor não terá username associado.', verbose_name='Criação de Usuários'), + ), + ] diff --git a/sapl/base/migrations/0026_remove_tipoautor_cria_usuario.py b/sapl/base/migrations/0026_remove_tipoautor_cria_usuario.py new file mode 100644 index 000000000..948427836 --- /dev/null +++ b/sapl/base/migrations/0026_remove_tipoautor_cria_usuario.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-10-11 18:08 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0025_tipoautor_cria_usuario'), + ] + + operations = [ + migrations.RemoveField( + model_name='tipoautor', + name='cria_usuario', + ), + ] diff --git a/sapl/base/migrations/0027_auto_20161011_1624.py b/sapl/base/migrations/0027_auto_20161011_1624.py new file mode 100644 index 000000000..ff6eae09d --- /dev/null +++ b/sapl/base/migrations/0027_auto_20161011_1624.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-10-11 19:24 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0026_remove_tipoautor_cria_usuario'), + ] + + operations = [ + migrations.AlterField( + model_name='autor', + name='user', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/sapl/base/models.py b/sapl/base/models.py index fecf7f3d2..b1db9e0a6 100644 --- a/sapl/base/models.py +++ b/sapl/base/models.py @@ -7,12 +7,11 @@ from django.contrib.contenttypes.models import ContentType from django.core import exceptions from django.db import models, router from django.db.utils import DEFAULT_DB_ALIAS -from django.utils.translation import string_concat from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import string_concat from sapl.utils import UF, YES_NO_CHOICES, get_settings_auth_user_model - TIPO_DOCUMENTO_ADMINISTRATIVO = (('O', _('Ostensivo')), ('R', _('Restritivo'))) @@ -151,7 +150,9 @@ class TipoAutor(models.Model): class Autor(models.Model): - user = models.OneToOneField(get_settings_auth_user_model()) + user = models.OneToOneField(get_settings_auth_user_model(), + on_delete=models.SET_NULL, + null=True) tipo = models.ForeignKey(TipoAutor, verbose_name=_('Tipo do Autor')) diff --git a/sapl/base/urls.py b/sapl/base/urls.py index 1e822dcb0..e06580cfa 100644 --- a/sapl/base/urls.py +++ b/sapl/base/urls.py @@ -14,21 +14,18 @@ from .views import (AppConfigCrud, CasaLegislativaCrud, HelpView, RelatorioMateriasTramitacaoView, RelatorioPresencaSessaoView) - app_name = AppConfig.name urlpatterns = [ -<<<<<<< 6123d2617726dd220c02c2bb3b3c27ed4b136df1 -======= url(r'^sistema/autor/tipo/', include(TipoAutorCrud.get_urls())), url(r'^sistema/autor/', include(AutorCrud.get_urls())), ->>>>>>> Ref Autor, TipoAutor, cria app api DRF url(r'^sistema/ajuda/', TemplateView.as_view(template_name='ajuda.html')), url(r'^sistema/ajuda/(?P\w+)$', HelpView.as_view(), name='help_topic'), - url(r'^sistema/ajuda/', TemplateView.as_view(template_name='ajuda/index.html'), + url(r'^sistema/ajuda/', + TemplateView.as_view(template_name='ajuda/index.html'), name='help_base'), url(r'^sistema/casa-legislativa/', include(CasaLegislativaCrud.get_urls()), name="casa_legislativa"), @@ -55,21 +52,13 @@ urlpatterns = [ RelatorioAtasView.as_view(), name='atas'), -<<<<<<< 6123d2617726dd220c02c2bb3b3c27ed4b136df1 -======= - # todos os sublink s de sistema devem vir acima deste ->>>>>>> Ref Autor, TipoAutor, cria app api DRF url(r'^sistema/', permission_required('base.view_tabelas_auxiliares') (TemplateView.as_view(template_name='sistema.html'))), - -<<<<<<< 6123d2617726dd220c02c2bb3b3c27ed4b136df1 -======= url(r'^login/$', views.login, { 'template_name': 'base/login.html', 'authentication_form': LoginForm}, name='login'), url(r'^logout/$', views.logout, {'next_page': '/login'}, name='logout'), ->>>>>>> Ref Autor, TipoAutor, cria app api DRF ] diff --git a/sapl/base/views.py b/sapl/base/views.py index ac3e3ef56..650c2d055 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -1,9 +1,7 @@ -from crispy_forms.helper import FormHelper -from crispy_forms.layout import HTML, Button from django.conf import settings -from django.contrib.auth import get_user_model from django.contrib.auth.mixins import PermissionRequiredMixin +from django.contrib.auth.models import Group from django.contrib.auth.tokens import default_token_generator from django.core.mail import send_mail from django.core.urlresolvers import reverse @@ -16,13 +14,11 @@ from django.views.generic.base import TemplateView from django_filters.views import FilterView from sapl.base.forms import AutorForm, TipoAutorForm -from sapl.base.models import TipoAutor, Autor -from sapl.crispy_layout_mixin import to_row, SaplFormLayout, form_actions +from sapl.base.models import Autor, TipoAutor from sapl.crud.base import CrudAux from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.parlamentares.models import Parlamentar from sapl.sessao.models import OrdemDia, SessaoPlenaria -from sapl.utils import autor_label, autor_modal from .forms import (CasaLegislativaForm, ConfiguracoesAppForm, RelatorioAtasFilterSet, @@ -38,41 +34,6 @@ def get_casalegislativa(): return CasaLegislativa.objects.first() -def montar_row_autor(name): - autor_row = to_row( - [(name, 0), - (Button('pesquisar', - 'Pesquisar Autor', - css_class='btn btn-primary btn-sm'), 2), - (Button('limpar', - 'Limpar Autor', - css_class='btn btn-primary btn-sm'), 10)]) - - return autor_row - - -def montar_helper_autor(self): - autor_row = montar_row_autor('nome') - self.helper = FormHelper() - self.helper.layout = SaplFormLayout(*self.get_layout()) - - # Adiciona o novo campo 'autor' e mecanismo de busca - self.helper.layout[0][0].append(HTML(autor_label)) - self.helper.layout[0][0].append(HTML(autor_modal)) - self.helper.layout[0][1] = autor_row - - # Adiciona espaço entre o novo campo e os botões - # self.helper.layout[0][4][1].append(HTML('

')) - - # Remove botões que estão fora do form - self.helper.layout[1].pop() - - # Adiciona novos botões dentro do form - self.helper.layout[0][4][0].insert(2, form_actions(more=[ - HTML('Cancelar')])) - - class TipoAutorCrud(CrudAux): model = TipoAutor help_path = 'tipo-autor' @@ -89,61 +50,97 @@ class AutorCrud(CrudAux): class BaseMixin(CrudAux.BaseMixin): list_field_names = ['tipo', 'nome', 'user__username'] + class DeleteView(CrudAux.DeleteView): + + def delete(self, *args, **kwargs): + self.object = self.get_object() + + # FIXME melhorar captura de grupo de Autor, levando em conta trad + grupo = Group.objects.filter(name='Autor')[0] + self.object.user.groups.remove(grupo) + + return CrudAux.DeleteView.delete(self, *args, **kwargs) + class UpdateView(CrudAux.UpdateView): layout_key = None form_class = AutorForm - def __init__(self, *args, **kwargs): - # montar_helper_autor(self) - super(CrudAux.UpdateView, self).__init__(*args, **kwargs) - - def get_context_data(self, **kwargs): - context = super( - CrudAux.UpdateView, self).get_context_data(**kwargs) - #context['helper'] = self.helper - return context + def form_valid(self, form): + # devido a implement do form o form_valid do Crud deve ser pulado + return super(CrudAux.UpdateView, self).form_valid(form) + + def get_success_url(self): + + # FIXME try except - testar envio de emails + + pk_autor = self.object.id + try: + kwargs = {} + user = self.object.user + kwargs['token'] = default_token_generator.make_token(user) + kwargs['uidb64'] = urlsafe_base64_encode(force_bytes(user.pk)) + assunto = "SAPL - Confirmação de Conta" + full_url = self.request.get_raw_uri() + url_base = full_url[:full_url.find('sistema') - 1] + + mensagem = ( + "Este e-mail foi utilizado para fazer cadastro no " + + "SAPL com o perfil de Autor. Agora você pode " + + "criar/editar/enviar Proposições.\n" + + "Seu nome de usuário é: " + + self.request.POST['username'] + "\n" + "Caso você não tenha feito este cadastro, por favor " + + "ignore esta mensagem. Caso tenha, clique " + + "no link abaixo\n" + url_base + + reverse('sapl.materia:confirmar_email', kwargs=kwargs)) + remetente = [settings.EMAIL_SEND_USER] + destinatario = [user.email] + send_mail(assunto, mensagem, remetente, destinatario, + fail_silently=False) + except: + pass + return reverse('sapl.base:autor_detail', + kwargs={'pk': pk_autor}) class CreateView(CrudAux.CreateView): form_class = AutorForm layout_key = None - """def __init__(self, *args, **kwargs): - montar_helper_autor(self) - super(CrudAux.CreateView, self).__init__(*args, **kwargs)""" - - """def get_context_data(self, **kwargs): - context = super( - CrudAux.CreateView, self).get_context_data(**kwargs) - context['helper'] = self.helper - return context""" - - """def get_success_url(self): - pk_autor = Autor.objects.get( - email=self.request.POST.get('email')).id - kwargs = {} - user = get_user_model().objects.get( - email=self.request.POST.get('email')) - kwargs['token'] = default_token_generator.make_token(user) - kwargs['uidb64'] = urlsafe_base64_encode(force_bytes(user.pk)) - assunto = "SAPL - Confirmação de Conta" - full_url = self.request.get_raw_uri() - url_base = full_url[:full_url.find('sistema') - 1] - - mensagem = ("Este e-mail foi utilizado para fazer cadastro no " + - "SAPL com o perfil de Autor. Agora você pode " + - "criar/editar/enviar Proposições.\n" + - "Seu nome de usuário é: " + - self.request.POST['username'] + "\n" - "Caso você não tenha feito este cadastro, por favor " + - "ignore esta mensagem. Caso tenha, clique " + - "no link abaixo\n" + url_base + - reverse('sapl.materia:confirmar_email', kwargs=kwargs)) - remetente = settings.EMAIL_SEND_USER - destinatario = [self.request.POST.get('email')] - send_mail(assunto, mensagem, remetente, destinatario, - fail_silently=False) + def form_valid(self, form): + # devido a implement do form o form_valid do Crud deve ser pulado + return super(CrudAux.CreateView, self).form_valid(form) + + def get_success_url(self): + pk_autor = self.object.id + try: + # FIXME try except - testar envio de emails + kwargs = {} + user = self.object.user + kwargs['token'] = default_token_generator.make_token(user) + kwargs['uidb64'] = urlsafe_base64_encode(force_bytes(user.pk)) + assunto = "SAPL - Confirmação de Conta" + full_url = self.request.get_raw_uri() + url_base = full_url[:full_url.find('sistema') - 1] + + mensagem = ( + "Este e-mail foi utilizado para fazer cadastro no " + + "SAPL com o perfil de Autor. Agora você pode " + + "criar/editar/enviar Proposições.\n" + + "Seu nome de usuário é: " + + self.request.POST['username'] + "\n" + "Caso você não tenha feito este cadastro, por favor " + + "ignore esta mensagem. Caso tenha, clique " + + "no link abaixo\n" + url_base + + reverse('sapl.materia:confirmar_email', kwargs=kwargs)) + remetente = settings.EMAIL_SEND_USER + destinatario = [user.email] + send_mail(assunto, mensagem, remetente, destinatario, + fail_silently=False) + except: + pass + return reverse('sapl.base:autor_detail', - kwargs={'pk': pk_autor})""" + kwargs={'pk': pk_autor}) class RelatorioAtasView(FilterView): diff --git a/sapl/comissoes/models.py b/sapl/comissoes/models.py index c0c3ec4d3..47321fde6 100644 --- a/sapl/comissoes/models.py +++ b/sapl/comissoes/models.py @@ -1,5 +1,4 @@ -from django.contrib.contenttypes.fields import GenericRelation from django.db import models from django.utils.translation import ugettext_lazy as _ from model_utils import Choices diff --git a/sapl/crispy_layout_mixin.py b/sapl/crispy_layout_mixin.py index 33e6d2608..7f7aca408 100644 --- a/sapl/crispy_layout_mixin.py +++ b/sapl/crispy_layout_mixin.py @@ -1,12 +1,12 @@ from math import ceil +import rtyaml from crispy_forms.bootstrap import FormActions from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Div, Fieldset, Layout, Submit from django import template from django.utils import formats from django.utils.translation import ugettext as _ -import rtyaml def heads_and_tails(list_of_lists): diff --git a/sapl/crud/base.py b/sapl/crud/base.py index 1077c55a3..f479d3ea1 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -13,8 +13,8 @@ from django.db import models from django.http.response import Http404 from django.utils.decorators import classonlymethod from django.utils.encoding import force_text -from django.utils.translation import string_concat from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import string_concat from django.views.generic import (CreateView, DeleteView, DetailView, ListView, UpdateView) from django.views.generic.base import ContextMixin @@ -23,7 +23,6 @@ from django.views.generic.list import MultipleObjectMixin from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display from sapl.utils import normalize - logger = logging.getLogger(__name__) ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \ @@ -882,9 +881,9 @@ class CrudAux(Crud): """ Checa permissão para ver qualquer dado de tabela auxiliar a permissão base.view_tabelas_auxiliares está definada class Meta - do model sapl.base.models.AppConfig que, naturalmente é um arquivo - de configuração geral e só pode ser acessado através das Tabelas - Auxiliares... Com isso o script de geração de perfis acaba que por + do model sapl.base.models.AppConfig que, naturalmente é um arquivo + de configuração geral e só pode ser acessado através das Tabelas + Auxiliares... Com isso o script de geração de perfis acaba que por criar essa permissão apenas para o perfil Operador Geral. """ permission_required = ('base.view_tabelas_auxiliares',) @@ -895,8 +894,8 @@ class CrudAux(Crud): def __init__(self, **kwargs): super().__init__(**kwargs) """ - Mantem as permissões individuais geradas pelo Crud através do - Modelo e adiciona a obrigatoriedade de permissão para view + Mantem as permissões individuais geradas pelo Crud através do + Modelo e adiciona a obrigatoriedade de permissão para view tabelas auxiliares. """ self.permission_required = self.permission_required + \ diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index 5e434007d..30663200e 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -1,17 +1,14 @@ from datetime import datetime +import django_filters from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout from django import forms -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group, User -from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ObjectDoesNotExist, ValidationError -from django.db import models, transaction +from django.db import models from django.db.models import Max from django.forms import ModelForm from django.utils.translation import ugettext_lazy as _ -import django_filters from sapl.comissoes.models import Comissao from sapl.crispy_layout_mixin import form_actions, to_row @@ -508,7 +505,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet): 'data_apresentacao', 'data_publicacao', 'autoria__autor__tipo', - #'autoria__autor__partido', + # 'autoria__autor__partido', 'relatoria__parlamentar_id', 'local_origem_externa', 'tramitacao__unidade_tramitacao_destino', diff --git a/sapl/materia/migrations/0056_merge.py b/sapl/materia/migrations/0056_merge.py new file mode 100644 index 000000000..895977296 --- /dev/null +++ b/sapl/materia/migrations/0056_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-10-11 19:45 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0055_auto_20161009_1418'), + ('materia', '0054_auto_20161011_0904'), + ] + + operations = [ + ] diff --git a/sapl/materia/models.py b/sapl/materia/models.py index 5a14e7ced..e326b7698 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -5,7 +5,7 @@ from model_utils import Choices from sapl.base.models import Autor from sapl.comissoes.models import Comissao -from sapl.parlamentares.models import Parlamentar, Partido +from sapl.parlamentares.models import Parlamentar from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, get_settings_auth_user_model, restringe_tipos_de_arquivo_txt) diff --git a/sapl/materia/urls.py b/sapl/materia/urls.py index dc18e47ef..a6f85016a 100644 --- a/sapl/materia/urls.py +++ b/sapl/materia/urls.py @@ -15,11 +15,11 @@ from sapl.materia.views import (AcompanhamentoConfirmarView, ProposicaoRecebida, ProposicaoTaView, ReceberProposicao, ReciboProposicaoView, RegimeTramitacaoCrud, RelatoriaCrud, - StatusTramitacaoCrud, - TipoDocumentoCrud, TipoFimRelatoriaCrud, - TipoMateriaCrud, TipoProposicaoCrud, - TramitacaoCrud, TramitacaoEmLoteView, - UnidadeTramitacaoCrud, recuperar_materia) + StatusTramitacaoCrud, TipoDocumentoCrud, + TipoFimRelatoriaCrud, TipoMateriaCrud, + TipoProposicaoCrud, TramitacaoCrud, + TramitacaoEmLoteView, UnidadeTramitacaoCrud, + recuperar_materia) from .apps import AppConfig diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 71b166c12..329b964be 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -3,12 +3,10 @@ from random import choice from string import ascii_letters, digits from crispy_forms.helper import FormHelper -from crispy_forms.layout import HTML, Button -from django.conf import settings +from crispy_forms.layout import HTML from django.contrib import messages from django.contrib.auth import get_user_model from django.contrib.auth.mixins import PermissionRequiredMixin -from django.contrib.auth.tokens import default_token_generator from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist from django.core.mail import send_mail from django.core.urlresolvers import reverse @@ -17,17 +15,15 @@ from django.http import JsonResponse from django.http.response import HttpResponseRedirect from django.shortcuts import redirect from django.template import Context, loader -from django.utils.encoding import force_bytes -from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode +from django.utils.http import urlsafe_base64_decode from django.utils.translation import ugettext_lazy as _ from django.views.generic import CreateView, ListView, TemplateView, UpdateView from django.views.generic.base import RedirectView from django_filters.views import FilterView -from sapl.base.models import AppConfig, CasaLegislativa, Autor, TipoAutor -from sapl.base.views import montar_row_autor +from sapl.base.models import AppConfig, Autor, CasaLegislativa, TipoAutor from sapl.compilacao.views import IntegracaoTaView -from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row +from sapl.crispy_layout_mixin import SaplFormLayout, form_actions from sapl.crud.base import (ACTION_CREATE, ACTION_DELETE, ACTION_DETAIL, ACTION_LIST, ACTION_UPDATE, RP_DETAIL, RP_LIST, Crud, CrudAux, CrudDetailView, MasterDetailCrud, @@ -37,8 +33,13 @@ from sapl.materia.forms import AnexadaForm, LegislacaoCitadaForm from sapl.norma.models import LegislacaoCitada from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label, autor_modal, gerar_hash_arquivo, get_base_url, +<<<<<<< 3276eb12726b741df770d5a6ed2a9a1a83c15849 permissoes_autor, permissoes_materia, permissoes_protocoloadm, permission_required_for_app) +======= + montar_row_autor, permissoes_autor, permissoes_materia, + permissoes_protocoloadm) +>>>>>>> Conc refatoração no Cada de Autor e Tipos de Autor from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, ConfirmarProposicaoForm, DocumentoAcessorioForm, @@ -48,13 +49,12 @@ from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, filtra_tramitacao_destino, filtra_tramitacao_destino_and_status, filtra_tramitacao_status) -from .models import (AcompanhamentoMateria, Anexada, Autoria, - DespachoInicial, DocumentoAcessorio, MateriaLegislativa, - Numeracao, Orgao, Origem, Proposicao, RegimeTramitacao, - Relatoria, StatusTramitacao, TipoDocumento, - TipoFimRelatoria, TipoMateriaLegislativa, TipoProposicao, - Tramitacao, UnidadeTramitacao) - +from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial, + DocumentoAcessorio, MateriaLegislativa, Numeracao, Orgao, + Origem, Proposicao, RegimeTramitacao, Relatoria, + StatusTramitacao, TipoDocumento, TipoFimRelatoria, + TipoMateriaLegislativa, TipoProposicao, Tramitacao, + UnidadeTramitacao) OrigemCrud = Crud.build(Origem, '') diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index ef7afeb7c..9a6954d14 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -1,14 +1,13 @@ from datetime import datetime -from django.contrib.contenttypes.fields import GenericRelation from django.db import models from django.utils.translation import ugettext_lazy as _ from model_utils import Choices from sapl.base.models import Autor from sapl.utils import (INDICADOR_AFASTAMENTO, UF, YES_NO_CHOICES, - intervalos_tem_intersecao, - restringe_tipos_de_arquivo_img, SaplGenericRelation) + SaplGenericRelation, intervalos_tem_intersecao, + restringe_tipos_de_arquivo_img) class Legislatura(models.Model): diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py index 20e77b782..8e9ddd595 100644 --- a/sapl/protocoloadm/forms.py +++ b/sapl/protocoloadm/forms.py @@ -421,6 +421,7 @@ class ProtocoloMateriaForm(ModelForm): super(ProtocoloMateriaForm, self).__init__( *args, **kwargs) + self.fields['tipo_protocolo'].inline_class = True class DocumentoAcessorioAdministrativoForm(ModelForm): diff --git a/sapl/sessao/forms.py b/sapl/sessao/forms.py index d629c608c..f213e1b94 100644 --- a/sapl/sessao/forms.py +++ b/sapl/sessao/forms.py @@ -1,12 +1,12 @@ from datetime import datetime +import django_filters from crispy_forms.helper import FormHelper from crispy_forms.layout import HTML, Button, Fieldset, Layout from django import forms from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.forms import ModelForm from django.utils.translation import ugettext_lazy as _ -import django_filters from sapl.crispy_layout_mixin import form_actions, to_row from sapl.materia.forms import MateriaLegislativaFilterSet @@ -212,7 +212,7 @@ class AdicionarVariasMateriasFilterSet(MateriaLegislativaFilterSet): 'data_apresentacao', 'data_publicacao', 'autoria__autor__tipo', - #'autoria__autor__partido', + # 'autoria__autor__partido', 'relatoria__parlamentar_id', 'local_origem_externa', 'em_tramitacao', @@ -231,7 +231,7 @@ class AdicionarVariasMateriasFilterSet(MateriaLegislativaFilterSet): self.filters['tipo'].label = 'Tipo de Matéria' self.filters['autoria__autor__tipo'].label = 'Tipo de Autor' - #self.filters['autoria__autor__partido'].label = 'Partido do Autor' + # self.filters['autoria__autor__partido'].label = 'Partido do Autor' self.filters['relatoria__parlamentar_id'].label = 'Relatoria' row1 = to_row( @@ -253,7 +253,7 @@ class AdicionarVariasMateriasFilterSet(MateriaLegislativaFilterSet): css_class='btn btn-primary btn-sm'), 10)]) row5 = to_row( [('autoria__autor__tipo', 6), - #('autoria__autor__partido', 6) + # ('autoria__autor__partido', 6) ]) row6 = to_row( [('relatoria__parlamentar_id', 6), diff --git a/sapl/sessao/urls.py b/sapl/sessao/urls.py index 672a95f0c..fd06ceb7f 100644 --- a/sapl/sessao/urls.py +++ b/sapl/sessao/urls.py @@ -11,10 +11,9 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente, PesquisarPautaSessaoView, PesquisarSessaoPlenariaView, PresencaOrdemDiaView, PresencaView, ResumoView, - SessaoCrud, - TipoExpedienteCrud, TipoResultadoVotacaoCrud, - TipoSessaoCrud, VotacaoEditView, - VotacaoExpedienteEditView, + SessaoCrud, TipoExpedienteCrud, + TipoResultadoVotacaoCrud, TipoSessaoCrud, + VotacaoEditView, VotacaoExpedienteEditView, VotacaoExpedienteView, VotacaoNominalEditView, VotacaoNominalExpedienteEditView, VotacaoNominalExpedienteView, diff --git a/sapl/settings.py b/sapl/settings.py index b9f28d6e3..c58befa4f 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -26,7 +26,6 @@ PROJECT_DIR = Path(__file__).ancestor(2) # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = config('SECRET_KEY', default='') - # SECURITY WARNING: don't run with debug turned on in production! DEBUG = config('DEBUG', default=False, cast=bool) @@ -35,6 +34,9 @@ ALLOWED_HOSTS = ['*'] LOGIN_REDIRECT_URL = '/' LOGIN_URL = '/login/?next=' +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + + # SAPL business apps in dependency order SAPL_APPS = ( 'sapl.base', @@ -48,7 +50,7 @@ SAPL_APPS = ( 'sapl.painel', 'sapl.protocoloadm', 'sapl.compilacao', - 'sapl.api' + 'sapl.api' ) INSTALLED_APPS = ( @@ -72,8 +74,8 @@ INSTALLED_APPS = ( ) + SAPL_APPS -if DEBUG: - INSTALLED_APPS += ('debug_toolbar',) +# if DEBUG: +# INSTALLED_APPS += ('debug_toolbar',) MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', diff --git a/sapl/templates/base.html b/sapl/templates/base.html index 5aee60a89..a4b3db6e6 100644 --- a/sapl/templates/base.html +++ b/sapl/templates/base.html @@ -287,7 +287,6 @@ - diff --git a/sapl/templates/base/autor_form.html b/sapl/templates/base/autor_form.html index 710747cc8..823d64f03 100644 --- a/sapl/templates/base/autor_form.html +++ b/sapl/templates/base/autor_form.html @@ -32,17 +32,20 @@ $(document).ready(function(){ if (atualizar) { var radios = $("#div_id_autor_related .controls").html(''); data.forEach(function (val, index) { - var html_radio = '
'; + var html_radio = ''; radios.append(html_radio); }); + if (data.length > 1) { $('input[name=autor_related]').change(function(event){ - $('#id_q').val(event.target.parentElement.textContent); + if (this.checked) + $('#id_q').val(event.target.parentElement.textContent); //$('input[name=autor_related]:not(:checked)').closest('.radio').remove(); }); } else { - $('input[name=autor_related]').prop('checked', 'checked') + $('input[name=autor_related]').prop('checked', 'checked'); + $('input[name=autor_related]').closest('.radio').addClass('checked'); } } else{ @@ -52,7 +55,6 @@ $(document).ready(function(){ active('nome'); }); } - $('#id_tipo').change(function(event) { if (event.target.selectedIndex == 0) { $('#id_nome, #id_q').val(''); @@ -68,6 +70,24 @@ $(document).ready(function(){ var pk = $('#id_tipo').val(); update_search(pk); }); + $('input[name=action_user]').change(function(event) { + if (event.target.value == 'C') + $('.new_user_fields, #div_id_username').removeClass('hidden'); + else if (event.target.value == 'N') + $('.new_user_fields, #div_id_username').addClass('hidden'); + else { + $('#div_id_username').removeClass('hidden'); + $('.new_user_fields').addClass('hidden'); + } + }); + + $('input[name=action_user]:checked').trigger('change'); + $('input[name=autor_related]').closest('.radio').remove(); + + var pk = $('#id_tipo').val(); + if (pk) + update_search(pk, $('#id_q').val().length > 0) + }); diff --git a/sapl/templates/base/layouts.yaml b/sapl/templates/base/layouts.yaml index c63135ee6..85be02341 100644 --- a/sapl/templates/base/layouts.yaml +++ b/sapl/templates/base/layouts.yaml @@ -18,12 +18,12 @@ AppConfig: TipoAutor: {% trans 'Tipo Autor' %}: - - content_type descricao:7 + - content_type:4 descricao Autor: {% trans 'Autor' %}: - tipo:3 nome - - username:6 cargo + - user:6 cargo AutorCreate: {% trans 'Cadastro de Usuários Autores' %}: diff --git a/sapl/templates/bootstrap3/layout/checkboxselectmultiple.html b/sapl/templates/bootstrap3/layout/checkboxselectmultiple.html new file mode 100644 index 000000000..a7398ab11 --- /dev/null +++ b/sapl/templates/bootstrap3/layout/checkboxselectmultiple.html @@ -0,0 +1,14 @@ +{% load crispy_forms_filters %} +{% load l10n %} + +
+ {% include 'bootstrap3/layout/field_errors_block.html' %} + {% for choice in field.field.choices %} + + {% endfor %} + {% include 'bootstrap3/layout/help_text.html' %} +
diff --git a/sapl/templates/bootstrap3/layout/radioselect.html b/sapl/templates/bootstrap3/layout/radioselect.html new file mode 100644 index 000000000..b3e6b589e --- /dev/null +++ b/sapl/templates/bootstrap3/layout/radioselect.html @@ -0,0 +1,19 @@ +{% load crispy_forms_filters %} +{% load l10n %} + +
+ {% include 'bootstrap3/layout/field_errors_block.html' %} + + {% for choice in field.field.choices %} + + {% endfor %} + + {% include 'bootstrap3/layout/help_text.html' %} +
diff --git a/sapl/test_urls.py b/sapl/test_urls.py index 4d6f35e6c..4f6f0587b 100644 --- a/sapl/test_urls.py +++ b/sapl/test_urls.py @@ -1,3 +1,4 @@ +import pytest from django.apps import apps from django.contrib.auth import get_user_model from django.contrib.auth.management import _get_all_permissions @@ -6,7 +7,7 @@ from django.contrib.contenttypes.models import ContentType from django.db import transaction from django.utils.translation import string_concat from django.utils.translation import ugettext_lazy as _ -import pytest +from django.utils.translation import string_concat from sapl.crud.base import PermissionRequiredForAppCrudMixin from sapl.materia.views import recuperar_materia @@ -15,7 +16,6 @@ from scripts.lista_urls import lista_urls from .settings import SAPL_APPS - pytestmark = pytest.mark.django_db sapl_appconfs = [apps.get_app_config(n[5:]) for n in SAPL_APPS] @@ -335,7 +335,7 @@ urls_publicas_excecoes = { """ # gerar uma instancia de teste para cada usuário não foi possível. São 500 -urls para cada operador. Isso fez com que o Travis estourasse o tempo de +urls para cada operador. Isso fez com que o Travis estourasse o tempo de processamento do teste... passando de 2hs... até outro modo, a estratégia de encapsular apenas as urls e testar em loop os operadores será mantida. diff --git a/sapl/urls.py b/sapl/urls.py index 84f92363d..b987fc208 100644 --- a/sapl/urls.py +++ b/sapl/urls.py @@ -20,6 +20,7 @@ from django.contrib import admin from django.views.generic.base import TemplateView from django.views.static import serve as view_static_server +import sapl.api.urls import sapl.base.urls import sapl.comissoes.urls import sapl.compilacao.urls @@ -31,7 +32,6 @@ import sapl.parlamentares.urls import sapl.protocoloadm.urls import sapl.relatorios.urls import sapl.sessao.urls -import sapl.api.urls urlpatterns = [ url(r'^$', TemplateView.as_view(template_name='index.html')), diff --git a/sapl/utils.py b/sapl/utils.py index 3b5cbd36f..b4c8b24fa 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -4,6 +4,9 @@ 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 from django.apps import apps from django.conf import settings @@ -15,12 +18,13 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied, ValidationError from django.utils.translation import ugettext_lazy as _ from floppyforms import ClearableFileInput -import magic from sapl.settings import BASE_DIR sapl_logger = logging.getLogger(BASE_DIR.name) +from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row + def normalize(txt): return unicodedata_normalize( @@ -56,6 +60,41 @@ autor_modal = ''' ''' +def montar_row_autor(name): + autor_row = to_row( + [(name, 0), + (Button('pesquisar', + 'Pesquisar Autor', + css_class='btn btn-primary btn-sm'), 2), + (Button('limpar', + 'Limpar Autor', + css_class='btn btn-primary btn-sm'), 10)]) + + return autor_row + + +def montar_helper_autor(self): + autor_row = montar_row_autor('nome') + self.helper = FormHelper() + self.helper.layout = SaplFormLayout(*self.get_layout()) + + # Adiciona o novo campo 'autor' e mecanismo de busca + self.helper.layout[0][0].append(HTML(autor_label)) + self.helper.layout[0][0].append(HTML(autor_modal)) + self.helper.layout[0][1] = autor_row + + # Adiciona espaço entre o novo campo e os botões + # self.helper.layout[0][4][1].append(HTML('

')) + + # Remove botões que estão fora do form + self.helper.layout[1].pop() + + # Adiciona novos botões dentro do form + self.helper.layout[0][4][0].insert(2, form_actions(more=[ + HTML('Cancelar')])) + + class SaplGenericRelation(GenericRelation): def __init__(self, to, fields_search=(), **kwargs):