diff --git a/sapl/base/forms.py b/sapl/base/forms.py index 25b8dda55..8d3839b6f 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -36,7 +36,8 @@ from sapl.utils import (autor_label, autor_modal, ChoiceWithoutValidationField, FilterOverridesMetaMixin, FileFieldCheckMixin, AnoNumeroOrderingFilter, ImageThumbnailFileInput, models_with_gr_for_model, qs_override_django_filter, - RangeWidgetOverride, RANGE_ANOS, YES_NO_CHOICES) + RangeWidgetOverride, RANGE_ANOS, YES_NO_CHOICES, + GoogleRecapthaMixin) from .models import AppConfig, CasaLegislativa @@ -116,7 +117,8 @@ class UsuarioCreateForm(ModelForm): data = self.cleaned_data if data['password1'] != data['password2']: - self.logger.warning('Erro de validação. Senhas informadas são diferentes.') + self.logger.warning( + 'Erro de validação. Senhas informadas são diferentes.') raise ValidationError('Senhas informadas são diferentes') return data @@ -235,7 +237,8 @@ class UsuarioEditForm(ModelForm): self.helper = SaplFormHelper() self.helper.layout = Layout( 'username', - FieldWithButtons('token', StrictButton('Renovar', id="renovar-token", css_class="btn-outline-primary")), + FieldWithButtons('token', StrictButton( + 'Renovar', id="renovar-token", css_class="btn-outline-primary")), rows, form_actions( more=[ @@ -250,7 +253,8 @@ class UsuarioEditForm(ModelForm): data = self.cleaned_data if data['password1'] and data['password1'] != data['password2']: - self.logger.warning("Erro de validação. Senhas informadas são diferentes.") + self.logger.warning( + "Erro de validação. Senhas informadas são diferentes.") raise ValidationError('Senhas informadas são diferentes') return data @@ -315,7 +319,8 @@ class SessaoLegislativaForm(FileFieldCheckMixin, ModelForm): if numero <= ult and flag_edit: self.logger.warning( 'O número da SessaoLegislativa ({}) é menor ou igual ' - 'que o de Sessões Legislativas passadas ({})'.format(numero, ult) + 'que o de Sessões Legislativas passadas ({})'.format( + numero, ult) ) raise ValidationError('O número da Sessão Legislativa não pode ser menor ou igual ' 'que o de Sessões Legislativas passadas') @@ -325,7 +330,8 @@ class SessaoLegislativaForm(FileFieldCheckMixin, ModelForm): self.logger.warning( 'A data de início ({}) da SessaoLegislativa está compreendida ' 'fora da data início ({}) e fim ({}) da Legislatura ' - 'selecionada'.format(data_inicio, data_inicio_leg, data_fim_leg) + 'selecionada'.format( + data_inicio, data_inicio_leg, data_fim_leg) ) raise ValidationError('A data de início da Sessão Legislativa deve estar compreendida ' 'entre a data início e fim da Legislatura selecionada') @@ -342,13 +348,15 @@ class SessaoLegislativaForm(FileFieldCheckMixin, ModelForm): if data_inicio > data_fim: self.logger.warning( - 'Data início ({}) superior à data fim ({}).'.format(data_inicio, data_fim) + 'Data início ({}) superior à data fim ({}).'.format( + data_inicio, data_fim) ) raise ValidationError( 'Data início não pode ser superior à data fim') if data_fim.year > data_inicio.year + 1: - raise ValidationError('A Sessão Legislativa só pode ter, no máximo, dois anos de período.') + raise ValidationError( + 'A Sessão Legislativa só pode ter, no máximo, dois anos de período.') data_inicio_intervalo = cleaned_data['data_inicio_intervalo'] data_fim_intervalo = cleaned_data['data_fim_intervalo'] @@ -357,7 +365,8 @@ class SessaoLegislativaForm(FileFieldCheckMixin, ModelForm): data_inicio_intervalo > data_fim_intervalo: self.logger.warning( 'Data início de intervalo ({}) superior à ' - 'data fim de intervalo ({}).'.format(data_inicio_intervalo, data_fim_intervalo) + 'data fim de intervalo ({}).'.format( + data_inicio_intervalo, data_fim_intervalo) ) raise ValidationError('Data início de intervalo não pode ser ' 'superior à data fim de intervalo') @@ -766,7 +775,8 @@ class AutorForm(ModelForm): class AutorFilterSet(django_filters.FilterSet): - nome = django_filters.CharFilter(label=_('Nome do Autor'), lookup_expr='icontains') + nome = django_filters.CharFilter( + label=_('Nome do Autor'), lookup_expr='icontains') class Meta: model = Autor @@ -1063,20 +1073,24 @@ class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.form.fields['exibir_ordem_dia'] = forms.BooleanField(required=False, label='Exibir presença das Ordens do Dia') + self.form.fields['exibir_ordem_dia'] = forms.BooleanField( + required=False, label='Exibir presença das Ordens do Dia') self.form.initial['exibir_ordem_dia'] = True - self.form.fields['exibir_somente_titular'] = forms.BooleanField(required=False, label='Exibir somente parlamentares titulares') + self.form.fields['exibir_somente_titular'] = forms.BooleanField( + required=False, label='Exibir somente parlamentares titulares') self.form.initial['exibir_somente_titular'] = False - - self.form.fields['exibir_somente_ativo'] = forms.BooleanField(required=False, label='Exibir somente parlamentares ativos') + + self.form.fields['exibir_somente_ativo'] = forms.BooleanField( + required=False, label='Exibir somente parlamentares ativos') self.form.initial['exibir_somente_ativo'] = False - + self.form.fields['legislatura'].required = True self.filters['data_inicio'].label = 'Período (Inicial - Final)' - tipo_sessao_ordinaria = self.filters['tipo'].queryset.filter(nome='Ordinária') + tipo_sessao_ordinaria = self.filters['tipo'].queryset.filter( + nome='Ordinária') if tipo_sessao_ordinaria: self.form.initial['tipo'] = tipo_sessao_ordinaria.first() @@ -1341,7 +1355,6 @@ class RelatorioMateriasTramitacaoFilterSet(django_filters.FilterSet): label='Autor da Matéria', queryset=Autor.objects.all()) - @property def qs(self): parent = super(RelatorioMateriasTramitacaoFilterSet, self).qs @@ -1353,7 +1366,7 @@ class RelatorioMateriasTramitacaoFilterSet(django_filters.FilterSet): model = MateriaEmTramitacao fields = ['materia__ano', 'materia__tipo', 'tramitacao__unidade_tramitacao_destino', - 'tramitacao__status','materia__autores'] + 'tramitacao__status', 'materia__autores'] def __init__(self, *args, **kwargs): super(RelatorioMateriasTramitacaoFilterSet, self).__init__( @@ -1385,7 +1398,7 @@ class RelatorioMateriasTramitacaoFilterSet(django_filters.FilterSet): self.form.helper.form_method = 'GET' self.form.helper.layout = Layout( Fieldset(_('Pesquisa de Matéria em Tramitação'), - row1, row2, row3, row4,row5, + row1, row2, row3, row4, row5, buttons,) ) @@ -1552,6 +1565,18 @@ class ConfiguracoesAppForm(ModelForm): label=_('Mostrar brasão da Casa no painel?'), required=False) + google_recaptcha_site_key = forms.CharField( + label=AppConfig._meta.get_field( + 'google_recaptcha_site_key').verbose_name, + max_length=256, + required=False) + + google_recaptcha_secret_key = forms.CharField( + label=AppConfig._meta.get_field( + 'google_recaptcha_secret_key').verbose_name, + max_length=256, + required=False) + class Meta: model = AppConfig fields = ['documentos_administrativos', @@ -1575,7 +1600,9 @@ class ConfiguracoesAppForm(ModelForm): 'estatisticas_acesso_normas', 'escolher_numero_materia_proposicao', 'tramitacao_materia', - 'tramitacao_documento'] + 'tramitacao_documento', + 'google_recaptcha_site_key', + 'google_recaptcha_secret_key'] def __init__(self, *args, **kwargs): super(ConfiguracoesAppForm, self).__init__(*args, **kwargs) @@ -1609,35 +1636,29 @@ class ConfiguracoesAppForm(ModelForm): return cleaned_data -class RecuperarSenhaForm(PasswordResetForm): +class RecuperarSenhaForm(GoogleRecapthaMixin, PasswordResetForm): logger = logging.getLogger(__name__) def __init__(self, *args, **kwargs): - row1 = to_row( - [('email', 12)]) - self.helper = SaplFormHelper() - self.helper.layout = Layout( - Fieldset(_('Insira o e-mail cadastrado com a sua conta'), - row1, - form_actions(label='Enviar')) - ) - super(RecuperarSenhaForm, self).__init__(*args, **kwargs) + kwargs['title_label'] = _('Insira o e-mail cadastrado com a sua conta') + kwargs['action_label'] = _('Enviar') + + super().__init__(*args, **kwargs) def clean(self): - super(RecuperarSenhaForm, self).clean() - if not self.is_valid(): - return self.cleaned_data + super(RecuperarSenhaForm, self).clean() - email_existente = User.objects.filter( + email_existente = get_user_model().objects.filter( email=self.data['email']).exists() if not email_existente: msg = 'Não existe nenhum usuário cadastrado com este e-mail.' self.logger.warning( - 'Não existe nenhum usuário cadastrado com este e-mail ({}).'.format(self.data['email']) + 'Não existe nenhum usuário cadastrado com este e-mail ({}).'.format( + self.data['email']) ) raise ValidationError(msg) @@ -1726,7 +1747,8 @@ class AlterarSenhaForm(Form): if user.is_anonymous: self.logger.warning( - 'Não é possível alterar senha de usuário anônimo ({}).'.format(username) + 'Não é possível alterar senha de usuário anônimo ({}).'.format( + username) ) raise ValidationError( "Não é possível alterar senha de usuário anônimo") diff --git a/sapl/base/migrations/0043_auto_20210203_1442.py b/sapl/base/migrations/0043_auto_20210203_1442.py new file mode 100644 index 000000000..05ac2cbb4 --- /dev/null +++ b/sapl/base/migrations/0043_auto_20210203_1442.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.13 on 2021-02-03 17:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0042_auto_20201013_1151'), + ] + + operations = [ + migrations.AddField( + model_name='appconfig', + name='google_recaptcha_secret_key', + field=models.CharField(default='', max_length=256, verbose_name='Chave privada gerada pelo Google Recaptcha'), + ), + migrations.AddField( + model_name='appconfig', + name='google_recaptcha_site_key', + field=models.CharField(default='', max_length=256, verbose_name='Chave pública gerada pelo Google Recaptcha'), + ), + ] diff --git a/sapl/base/models.py b/sapl/base/models.py index 625c93d07..aabdd88f3 100644 --- a/sapl/base/models.py +++ b/sapl/base/models.py @@ -19,11 +19,11 @@ RELATORIO_ATOS_ACESSADOS = (('S', _('Sim')), ('N', _('Não'))) SEQUENCIA_NUMERACAO_PROTOCOLO = (('A', _('Sequencial por ano')), - ('L', _('Sequencial por legislatura')), - ('U', _('Sequencial único'))) + ('L', _('Sequencial por legislatura')), + ('U', _('Sequencial único'))) SEQUENCIA_NUMERACAO_PROPOSICAO = (('A', _('Sequencial por ano para cada autor')), - ('B', _('Sequencial por ano indepententemente do autor'))) + ('B', _('Sequencial por ano indepententemente do autor'))) ESFERA_FEDERACAO_CHOICES = (('M', _('Municipal')), ('E', _('Estadual')), @@ -108,7 +108,7 @@ class AppConfig(models.Model): max_length=1, verbose_name=_('Sequência de numeração de protocolos'), choices=SEQUENCIA_NUMERACAO_PROTOCOLO, default='A') - + inicio_numeracao_protocolo = models.PositiveIntegerField( verbose_name=_('Início da numeração de protocolo'), default=1 @@ -179,17 +179,27 @@ class AppConfig(models.Model): choices=YES_NO_CHOICES, default=False) escolher_numero_materia_proposicao = models.BooleanField( - verbose_name=_('Indicar número da matéria a ser gerada na proposição?'), + verbose_name=_( + 'Indicar número da matéria a ser gerada na proposição?'), choices=YES_NO_CHOICES, default=False) tramitacao_materia = models.BooleanField( - verbose_name=_('Tramitar matérias anexadas junto com as matérias principais?'), + verbose_name=_( + 'Tramitar matérias anexadas junto com as matérias principais?'), choices=YES_NO_CHOICES, default=True) - + tramitacao_documento = models.BooleanField( - verbose_name=_('Tramitar documentos anexados junto com os documentos principais?'), + verbose_name=_( + 'Tramitar documentos anexados junto com os documentos principais?'), choices=YES_NO_CHOICES, default=True) + google_recaptcha_site_key = models.CharField( + verbose_name=_('Chave pública gerada pelo Google Recaptcha'), + max_length=256, default='') + google_recaptcha_secret_key = models.CharField( + verbose_name=_('Chave privada gerada pelo Google Recaptcha'), + max_length=256, default='') + class Meta: verbose_name = _('Configurações da Aplicação') verbose_name_plural = _('Configurações da Aplicação') @@ -219,7 +229,8 @@ class TipoAutor(models.Model): descricao = models.CharField( max_length=50, verbose_name=_('Descrição'), - help_text=_('Obs: Não crie tipos de autores semelhante aos tipos fixos. ') + help_text=_( + 'Obs: Não crie tipos de autores semelhante aos tipos fixos. ') ) content_type = models.OneToOneField( @@ -325,4 +336,3 @@ class AuditLog(models.Model): self.model_name, self.username, ) - diff --git a/sapl/base/views.py b/sapl/base/views.py index eac16c54d..2608ae0b9 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -1,62 +1,67 @@ +from collections import OrderedDict import collections -import itertools import datetime +import itertools import logging import os -from collections import OrderedDict from django.contrib import messages from django.contrib.auth import get_user_model from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.models import Group, User from django.contrib.auth.tokens import default_token_generator -from django.contrib.auth.views import (PasswordResetView,PasswordResetConfirmView, PasswordResetCompleteView, +from django.contrib.auth.views import (PasswordResetView, PasswordResetConfirmView, PasswordResetCompleteView, PasswordResetDoneView) from django.core.exceptions import ObjectDoesNotExist, PermissionDenied, ValidationError from django.core.mail import send_mail -from django.urls import reverse, reverse_lazy from django.db import connection from django.db.models import Count, Q, ProtectedError, Max -from django.shortcuts import render from django.http import Http404, HttpResponseRedirect, JsonResponse +from django.shortcuts import render, redirect from django.template import TemplateDoesNotExist from django.template.loader import get_template +from django.urls import reverse, reverse_lazy from django.utils import timezone from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode from django.utils.translation import ugettext_lazy as _ -from django.views.generic import (CreateView, DetailView, DeleteView, FormView, ListView, UpdateView) +from django.views.generic import ( + CreateView, DetailView, DeleteView, FormView, ListView, UpdateView) from django.views.generic.base import RedirectView, TemplateView from django_filters.views import FilterView -from haystack.views import SearchView from haystack.query import SearchQuerySet - -from sapl.relatorios.views import (relatorio_materia_em_tramitacao, relatorio_materia_por_autor, - relatorio_materia_por_ano_autor, relatorio_presenca_sessao, - relatorio_historico_tramitacao, relatorio_fim_prazo_tramitacao, relatorio_atas, - relatorio_audiencia, relatorio_normas_mes, relatorio_normas_vigencia, - relatorio_historico_tramitacao_adm, relatorio_reuniao, - relatorio_estatisticas_acesso_normas, relatorio_normas_por_autor, - relatorio_documento_acessorio) +from haystack.views import SearchView +from rest_framework.authtoken.models import Token from sapl import settings from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica -from sapl.base.models import Autor, TipoAutor from sapl.base.forms import (AutorForm, AutorFormForAdmin, TipoAutorForm, AutorFilterSet, RecuperarSenhaForm, NovaSenhaForm) +from sapl.base.models import Autor, TipoAutor from sapl.comissoes.models import Comissao, Reuniao from sapl.crud.base import CrudAux, make_pagination from sapl.materia.models import (Anexada, Autoria, DocumentoAcessorio, MateriaEmTramitacao, MateriaLegislativa, Proposicao, StatusTramitacao, TipoDocumento, TipoMateriaLegislativa, UnidadeTramitacao, Tramitacao) from sapl.norma.models import NormaJuridica, TipoNormaJuridica -from sapl.parlamentares.models import (Filiacao, Legislatura, Mandato, Parlamentar, SessaoLegislativa) +from sapl.parlamentares.models import ( + Filiacao, Legislatura, Mandato, Parlamentar, SessaoLegislativa) from sapl.protocoloadm.models import (Anexado, DocumentoAdministrativo, Protocolo, StatusTramitacaoAdministrativo, TipoDocumentoAdministrativo) -from sapl.sessao.models import (Bancada, PresencaOrdemDia, SessaoPlenaria, SessaoPlenariaPresenca, TipoSessaoPlenaria) +from sapl.relatorios.views import (relatorio_materia_em_tramitacao, relatorio_materia_por_autor, + relatorio_materia_por_ano_autor, relatorio_presenca_sessao, + relatorio_historico_tramitacao, relatorio_fim_prazo_tramitacao, relatorio_atas, + relatorio_audiencia, relatorio_normas_mes, relatorio_normas_vigencia, + relatorio_historico_tramitacao_adm, relatorio_reuniao, + relatorio_estatisticas_acesso_normas, relatorio_normas_por_autor, + relatorio_documento_acessorio) +from sapl.sessao.models import ( + Bancada, PresencaOrdemDia, SessaoPlenaria, SessaoPlenariaPresenca, TipoSessaoPlenaria) from sapl.settings import EMAIL_SEND_USER from sapl.utils import (gerar_hash_arquivo, intervalos_tem_intersecao, mail_service_configured, parlamentares_ativos, - SEPARADOR_HASH_PROPOSICAO, show_results_filter_set, num_materias_por_tipo) + SEPARADOR_HASH_PROPOSICAO, show_results_filter_set, num_materias_por_tipo, + google_recaptcha_configured) + from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, RelatorioAtasFilterSet, RelatorioAudienciaFilterSet, RelatorioDataFimPrazoTramitacaoFilterSet, RelatorioHistoricoTramitacaoFilterSet, RelatorioMateriasPorAnoAutorTipoFilterSet, @@ -67,8 +72,6 @@ from .forms import (AlterarSenhaForm, CasaLegislativaForm, ConfiguracoesAppForm, RelatorioNormasPorAutorFilterSet) from .models import AppConfig, CasaLegislativa -from rest_framework.authtoken.models import Token - def get_casalegislativa(): return CasaLegislativa.objects.first() @@ -87,6 +90,8 @@ class ConfirmarEmailView(TemplateView): class RecuperarSenhaEmailView(PasswordResetView): + logger = logging.getLogger(__name__) + success_url = reverse_lazy('sapl.base:recuperar_senha_finalizado') email_template_name = 'base/recuperar_senha_email.html' html_email_template_name = 'base/recuperar_senha_email.html' @@ -94,6 +99,24 @@ class RecuperarSenhaEmailView(PasswordResetView): from_email = EMAIL_SEND_USER form_class = RecuperarSenhaForm + def get(self, request, *args, **kwargs): + + if not google_recaptcha_configured(): + self.logger.warning(_('Google Recaptcha não configurado!')) + messages.error(request, _('Google Recaptcha não configurado!')) + return redirect(request.META.get('HTTP_REFERER', '/')) + + return PasswordResetView.get(self, request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + + if not google_recaptcha_configured(): + self.logger.warning(_('Google Recaptcha não configurado!')) + messages.error(request, _('Google Recaptcha não configurado!')) + return redirect(request.META.get('HTTP_REFERER', '/')) + + return PasswordResetView.post(self, request, *args, **kwargs) + class RecuperarSenhaFinalizadoView(PasswordResetDoneView): template_name = 'base/recupera_senha_email_enviado.html' @@ -330,7 +353,8 @@ class PesquisarAutorView(FilterView): paginator = context['paginator'] page_obj = context['page_obj'] - context['page_range'] = make_pagination(page_obj.number, paginator.num_pages) + context['page_range'] = make_pagination( + page_obj.number, paginator.num_pages) context['NO_ENTRIES_MSG'] = 'Nenhum Autor encontrado!' @@ -354,19 +378,20 @@ class PesquisarAutorView(FilterView): filter_url=url, numero_res=len(self.object_list)) - context['show_results'] = show_results_filter_set(self.request.GET.copy()) + context['show_results'] = show_results_filter_set( + self.request.GET.copy()) return self.render_to_response(context) class RelatoriosListView(TemplateView): - template_name='base/relatorios_list.html' + template_name = 'base/relatorios_list.html' def get_context_data(self, **kwargs): context = super(TemplateView, self).get_context_data(**kwargs) estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas context['estatisticas_acesso_normas'] = True if estatisticas_acesso_normas == 'S' else False - + return context @@ -398,10 +423,10 @@ class RelatorioDocumentosAcessoriosView(RelatorioMixin, FilterView): if not self.filterset.form.is_valid(): return context - + query_dict = self.request.GET.copy() context['show_results'] = show_results_filter_set(query_dict) - + context['tipo_documento'] = str( TipoDocumento.objects.get(pk=self.request.GET['tipo']) ) @@ -467,15 +492,17 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView): # Verifica se os campos foram preenchidos if not self.filterset.form.is_valid(): return context - + cd = self.filterset.form.cleaned_data if not cd['data_inicio'] and not cd['sessao_legislativa'] \ - and not cd['legislatura']: - msg = _("Formulário inválido! Preencha pelo menos algum dos campos Período, Legislatura ou Sessão Legislativa.") + and not cd['legislatura']: + msg = _( + "Formulário inválido! Preencha pelo menos algum dos campos Período, Legislatura ou Sessão Legislativa.") messages.error(self.request, msg) return context - # Caso a data tenha sido preenchida, verifica se foi preenchida corretamente + # Caso a data tenha sido preenchida, verifica se foi preenchida + # corretamente if self.request.GET.get('data_inicio_0') and not self.request.GET.get('data_inicio_1'): msg = _("Formulário inválido! Preencha a data do Período Final.") messages.error(self.request, msg) @@ -497,49 +524,57 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView): sessao_legislativa_pk = self.request.GET.get('sessao_legislativa') if sessao_legislativa_pk: param0['sessao_plenaria__sessao_legislativa_id'] = sessao_legislativa_pk - sessao_legislativa = SessaoLegislativa.objects.get(id=sessao_legislativa_pk) + sessao_legislativa = SessaoLegislativa.objects.get( + id=sessao_legislativa_pk) context['sessao_legislativa'] = sessao_legislativa tipo_sessao_plenaria_pk = self.request.GET.get('tipo') context['tipo'] = '' if tipo_sessao_plenaria_pk: param0['sessao_plenaria__tipo_id'] = tipo_sessao_plenaria_pk - context['tipo'] = TipoSessaoPlenaria.objects.get(id=tipo_sessao_plenaria_pk) + context['tipo'] = TipoSessaoPlenaria.objects.get( + id=tipo_sessao_plenaria_pk) _range = [] if ('data_inicio_0' in self.request.GET) and self.request.GET['data_inicio_0'] and \ - ('data_inicio_1' in self.request.GET) and self.request.GET['data_inicio_1']: + ('data_inicio_1' in self.request.GET) and self.request.GET['data_inicio_1']: where = context['object_list'].query.where _range = where.children[0].rhs elif legislatura_pk and not sessao_legislativa_pk: _range = [legislatura.data_inicio, legislatura.data_fim] - + elif sessao_legislativa_pk: - _range = [sessao_legislativa.data_inicio, sessao_legislativa.data_fim] + _range = [sessao_legislativa.data_inicio, + sessao_legislativa.data_fim] param0.update({'sessao_plenaria__data_inicio__range': _range}) - # Parlamentares com Mandato no intervalo de tempo (Ativos) - parlamentares_qs = parlamentares_ativos(_range[0], _range[1]).order_by('nome_parlamentar') + parlamentares_qs = parlamentares_ativos( + _range[0], _range[1]).order_by('nome_parlamentar') parlamentares_id = parlamentares_qs.values_list('id', flat=True) # Presenças de cada Parlamentar em Sessões - presenca_sessao = SessaoPlenariaPresenca.objects.filter(**param0).values_list('parlamentar_id').annotate(sessao_count=Count('id')) + presenca_sessao = SessaoPlenariaPresenca.objects.filter( + **param0).values_list('parlamentar_id').annotate(sessao_count=Count('id')) # Presenças de cada Ordem do Dia - presenca_ordem = PresencaOrdemDia.objects.filter(**param0).values_list('parlamentar_id').annotate(sessao_count=Count('id')) + presenca_ordem = PresencaOrdemDia.objects.filter( + **param0).values_list('parlamentar_id').annotate(sessao_count=Count('id')) - total_ordemdia = PresencaOrdemDia.objects.filter(**param0).distinct('sessao_plenaria__id').order_by('sessao_plenaria__id').count() + total_ordemdia = PresencaOrdemDia.objects.filter( + **param0).distinct('sessao_plenaria__id').order_by('sessao_plenaria__id').count() total_sessao = context['object_list'].count() username = self.request.user.username - context['exibir_somente_titular'] = self.request.GET.get('exibir_somente_titular') == 'on' - context['exibir_somente_ativo'] = self.request.GET.get('exibir_somente_ativo') == 'on' + context['exibir_somente_titular'] = self.request.GET.get( + 'exibir_somente_titular') == 'on' + context['exibir_somente_ativo'] = self.request.GET.get( + 'exibir_somente_ativo') == 'on' # Completa o dicionario as informacoes parlamentar/sessao/ordem parlamentares_presencas = [] @@ -594,17 +629,21 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView): continue try: - self.logger.debug(F'user={username}. Tentando obter presença do parlamentar (pk={p.id}).') + self.logger.debug( + F'user={username}. Tentando obter presença do parlamentar (pk={p.id}).') sessao_count = presenca_sessao.get(parlamentar_id=p.id)[1] except ObjectDoesNotExist as e: - self.logger.error(F'user={username}. Erro ao obter presença do parlamentar (pk={p.id}). Definido como 0. {str(e)}') + self.logger.error( + F'user={username}. Erro ao obter presença do parlamentar (pk={p.id}). Definido como 0. {str(e)}') sessao_count = 0 try: # Presenças de cada Ordem do Dia - self.logger.info(F'user={username}. Tentando obter PresencaOrdemDia para o parlamentar pk={p.id}.') + self.logger.info( + F'user={username}. Tentando obter PresencaOrdemDia para o parlamentar pk={p.id}.') ordemdia_count = presenca_ordem.get(parlamentar_id=p.id)[1] except ObjectDoesNotExist: - self.logger.error(F'user={username}. Erro ao obter PresencaOrdemDia para o parlamentar pk={p.id}. Definido como 0.') + self.logger.error( + F'user={username}. Erro ao obter PresencaOrdemDia para o parlamentar pk={p.id}. Definido como 0.') ordemdia_count = 0 parlamentar.update({ @@ -613,9 +652,11 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView): }) if total_sessao != 0: - parlamentar.update({'sessao_porc': round(sessao_count * 100 / total_sessao, 2)}) + parlamentar.update({'sessao_porc': round( + sessao_count * 100 / total_sessao, 2)}) if total_ordemdia != 0: - parlamentar.update({'ordemdia_porc': round(ordemdia_count * 100 / total_ordemdia, 2)}) + parlamentar.update({'ordemdia_porc': round( + ordemdia_count * 100 / total_ordemdia, 2)}) parlamentares_presencas.append(parlamentar) @@ -626,10 +667,12 @@ class RelatorioPresencaSessaoView(RelatorioMixin, FilterView): context['periodo'] = f"{self.request.GET['data_inicio_0']} - {self.request.GET['data_inicio_1']}" context['sessao_legislativa'] = '' context['legislatura'] = '' - context['exibir_ordem'] = self.request.GET.get('exibir_ordem_dia') == 'on' + context['exibir_ordem'] = self.request.GET.get( + 'exibir_ordem_dia') == 'on' if sessao_legislativa_pk: - context['sessao_legislativa'] = SessaoLegislativa.objects.get(id=sessao_legislativa_pk) + context['sessao_legislativa'] = SessaoLegislativa.objects.get( + id=sessao_legislativa_pk) if legislatura_pk: context['legislatura'] = Legislatura.objects.get(id=legislatura_pk) # ===================================================================== @@ -650,7 +693,8 @@ class RelatorioHistoricoTramitacaoView(RelatorioMixin, FilterView): def get_context_data(self, **kwargs): context = super(RelatorioHistoricoTramitacaoView, self).get_context_data(**kwargs) - context['title'] = _('Histórico de Tramitações de Matérias Legislativas') + context['title'] = _( + 'Histórico de Tramitações de Matérias Legislativas') if not self.filterset.form.is_valid(): return context qr = self.request.GET.copy() @@ -848,21 +892,22 @@ class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView): kwargs['tramitacao__status'] = status_tramitacao if autor: kwargs['materia__autores'] = autor - + qs = qs.filter(**kwargs) data['queryset'] = qs - - self.total_resultados_tipos = num_materias_por_tipo(qs, "materia__tipo") + + self.total_resultados_tipos = num_materias_por_tipo( + qs, "materia__tipo") return data def get_queryset(self): qs = super().get_queryset() qs = qs.select_related('materia__tipo').filter( - materia__em_tramitacao=True - ).exclude( - tramitacao__status__indicador='F' - ).order_by('-materia__ano', '-materia__numero') + materia__em_tramitacao=True + ).exclude( + tramitacao__status__indicador='F' + ).order_by('-materia__ano', '-materia__numero') return qs def get_context_data(self, **kwargs): @@ -871,7 +916,7 @@ class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView): ).get_context_data(**kwargs) context['title'] = _('Matérias em Tramitação') - + if not self.filterset.form.is_valid(): return context @@ -887,7 +932,7 @@ class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView): ) else: context['tipo'] = '' - + if self.request.GET['tramitacao__status']: tramitacao_status = self.request.GET['tramitacao__status'] context['tramitacao__status'] = ( @@ -895,7 +940,7 @@ class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView): ) else: context['tramitacao__status'] = '' - + if self.request.GET['tramitacao__unidade_tramitacao_destino']: context['tramitacao__unidade_tramitacao_destino'] = ( str(UnidadeTramitacao.objects.get( @@ -912,7 +957,7 @@ class RelatorioMateriasTramitacaoView(RelatorioMixin, FilterView): ) else: context['materia__autor'] = '' - + context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context['show_results'] = show_results_filter_set(qr) @@ -1044,8 +1089,8 @@ class RelatorioMateriasPorAutorView(RelatorioMixin, FilterView): else: context['autor'] = '' context['periodo'] = ( - self.request.GET['data_apresentacao_0'] + - ' - ' + self.request.GET['data_apresentacao_1']) + self.request.GET['data_apresentacao_0'] + + ' - ' + self.request.GET['data_apresentacao_1']) return context @@ -1072,15 +1117,15 @@ class RelatorioNormasPublicadasMesView(RelatorioMixin, FilterView): context['ano'] = self.request.GET['ano'] normas_mes = collections.OrderedDict() - meses = {1: 'Janeiro', 2: 'Fevereiro', 3:'Março', 4: 'Abril', 5: 'Maio', 6:'Junho', - 7: 'Julho', 8: 'Agosto', 9:'Setembro', 10:'Outubro', 11:'Novembro', 12:'Dezembro'} + meses = {1: 'Janeiro', 2: 'Fevereiro', 3: 'Março', 4: 'Abril', 5: 'Maio', 6: 'Junho', + 7: 'Julho', 8: 'Agosto', 9: 'Setembro', 10: 'Outubro', 11: 'Novembro', 12: 'Dezembro'} for norma in context['object_list']: if not meses[norma.data.month] in normas_mes: normas_mes[meses[norma.data.month]] = [] normas_mes[meses[norma.data.month]].append(norma) - + context['normas_mes'] = normas_mes - + quant_normas_mes = {} for key in normas_mes.keys(): quant_normas_mes[key] = len(normas_mes[key]) @@ -1107,19 +1152,20 @@ class RelatorioNormasVigenciaView(RelatorioMixin, FilterView): vigencia = kwargs['data']['vigencia'] if ano: qs = qs.filter(ano=ano) - + if vigencia == 'True': qs_dt_not_null = qs.filter(data_vigencia__isnull=True) - qs = (qs_dt_not_null | qs.filter(data_vigencia__gte=datetime.datetime.now().date())).distinct() + qs = (qs_dt_not_null | qs.filter( + data_vigencia__gte=datetime.datetime.now().date())).distinct() else: - qs = qs.filter(data_vigencia__lt=datetime.datetime.now().date()) + qs = qs.filter( + data_vigencia__lt=datetime.datetime.now().date()) kwargs.update({ 'queryset': qs }) return kwargs - def get_context_data(self, **kwargs): context = super(RelatorioNormasVigenciaView, self).get_context_data(**kwargs) @@ -1129,17 +1175,20 @@ class RelatorioNormasVigenciaView(RelatorioMixin, FilterView): if not self.filterset.form.is_valid(): return context - normas_totais = NormaJuridica.objects.filter(ano=self.request.GET['ano']) - + normas_totais = NormaJuridica.objects.filter( + ano=self.request.GET['ano']) + context['quant_total'] = len(normas_totais) if self.request.GET['vigencia'] == 'True': context['vigencia'] = 'Vigente' context['quant_vigente'] = len(context['object_list']) - context['quant_nao_vigente'] = context['quant_total'] - context['quant_vigente'] + context['quant_nao_vigente'] = context['quant_total'] - \ + context['quant_vigente'] else: context['vigencia'] = 'Não vigente' context['quant_nao_vigente'] = len(context['object_list']) - context['quant_vigente'] = context['quant_total'] - context['quant_nao_vigente'] + context['quant_vigente'] = context['quant_total'] - \ + context['quant_nao_vigente'] qr = self.request.GET.copy() context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' @@ -1165,7 +1214,7 @@ class EstatisticasAcessoNormas(TemplateView): return self.render_to_response(context) context['ano'] = self.request.GET['ano'] - + query = ''' select norma_id, ano, extract(month from horario_acesso) as mes, count(*) from norma_normaestatisticas @@ -1178,20 +1227,21 @@ class EstatisticasAcessoNormas(TemplateView): rows = cursor.fetchall() normas_mes = collections.OrderedDict() - meses = {1: 'Janeiro', 2: 'Fevereiro', 3:'Março', 4: 'Abril', 5: 'Maio', 6:'Junho', - 7: 'Julho', 8: 'Agosto', 9:'Setembro', 10:'Outubro', 11:'Novembro', 12:'Dezembro'} - + meses = {1: 'Janeiro', 2: 'Fevereiro', 3: 'Março', 4: 'Abril', 5: 'Maio', 6: 'Junho', + 7: 'Julho', 8: 'Agosto', 9: 'Setembro', 10: 'Outubro', 11: 'Novembro', 12: 'Dezembro'} + for row in rows: if not meses[int(row[2])] in normas_mes: normas_mes[meses[int(row[2])]] = [] norma_est = [NormaJuridica.objects.get(id=row[0]), row[3]] normas_mes[meses[int(row[2])]].append(norma_est) - + # Ordena por acesso e limita em 5 for n in normas_mes: - sorted_by_value = sorted(normas_mes[n], key=lambda kv: kv[1], reverse=True) + sorted_by_value = sorted( + normas_mes[n], key=lambda kv: kv[1], reverse=True) normas_mes[n] = sorted_by_value[0:5] - + context['normas_mes'] = normas_mes is_relatorio = request.GET.get('relatorio') @@ -1235,19 +1285,19 @@ class ListarInconsistenciasView(PermissionRequiredMixin, ListView): ('filiacoes_sem_data_filiacao', 'Filiações sem data filiação', len(filiacoes_sem_data_filiacao()) - ) + ) ) tabela.append( ('mandato_sem_data_inicio', 'Mandatos sem data inicial', - len(mandato_sem_data_inicio()) - ) + len(mandato_sem_data_inicio()) + ) ) tabela.append( ('parlamentares_duplicados', 'Parlamentares duplicados', len(parlamentares_duplicados()) - ) + ) ) tabela.append( ('parlamentares_mandatos_intersecao', @@ -1258,8 +1308,8 @@ class ListarInconsistenciasView(PermissionRequiredMixin, ListView): tabela.append( ('parlamentares_filiacoes_intersecao', 'Parlamentares com filiações em interseção', - len(parlamentares_filiacoes_intersecao()) - ) + len(parlamentares_filiacoes_intersecao()) + ) ) tabela.append( ('autores_duplicados', @@ -1277,7 +1327,7 @@ class ListarInconsistenciasView(PermissionRequiredMixin, ListView): ('legislatura_infindavel', 'Legislaturas sem data fim', len(legislatura_infindavel()) - ) + ) ) tabela.append( ('anexadas_ciclicas', @@ -1293,6 +1343,7 @@ class ListarInconsistenciasView(PermissionRequiredMixin, ListView): ) return tabela + def materias_anexadas_ciclicas(): ciclos = [] @@ -1306,7 +1357,8 @@ def materias_anexadas_ciclicas(): ma = anexadas.pop() if ma not in visitados: visitados.append(ma) - anexadas.extend([a.materia_anexada for a in Anexada.objects.filter(materia_principal=ma)]) + anexadas.extend( + [a.materia_anexada for a in Anexada.objects.filter(materia_principal=ma)]) else: ciclo_list = visitados + [ma] ciclos.append(ciclo_list) @@ -1320,12 +1372,14 @@ def materias_anexadas_ciclicas(): return ciclos_unique + def is_ciclo_unique(ciclo, ciclos_set): - if set(ciclo) not in ciclos_set: - ciclos_set.append(set(ciclo)) - return True - else: - return False + if set(ciclo) not in ciclos_set: + ciclos_set.append(set(ciclo)) + return True + else: + return False + def anexados_ciclicos(ofMateriaLegislativa): ciclicos = [] @@ -1367,7 +1421,8 @@ def anexados_ciclicos(ofMateriaLegislativa): ) anexados_temp.extend(anexados_anexado) else: - ciclicos.append((anexado.data_anexacao, anexado.materia_principal, anexado.materia_anexada)) + ciclicos.append( + (anexado.data_anexacao, anexado.materia_principal, anexado.materia_anexada)) else: if anexado.documento_anexado not in anexados_total: if not principal['documento_principal'] == anexado.documento_anexado.pk: @@ -1377,7 +1432,8 @@ def anexados_ciclicos(ofMateriaLegislativa): ) anexados_temp.extend(anexados_anexado) else: - ciclicos.append((anexado.data_anexacao, anexado.documento_principal, anexado.documento_anexado)) + ciclicos.append( + (anexado.data_anexacao, anexado.documento_principal, anexado.documento_anexado)) return ciclicos @@ -1425,7 +1481,7 @@ class ListarAnexadasCiclicasView(PermissionRequiredMixin, ListView): paginator = context['paginator'] page_obj = context['page_obj'] - + context['page_range'] = make_pagination( page_obj.number, paginator.num_pages ) @@ -1451,14 +1507,14 @@ class ListarLegislaturaInfindavelView(PermissionRequiredMixin, ListView): def get_context_data(self, **kwargs): context = super( ListarLegislaturaInfindavelView, self - ).get_context_data(**kwargs) + ).get_context_data(**kwargs) paginator = context['paginator'] page_obj = context['page_obj'] context['page_range'] = make_pagination( page_obj.number, paginator.num_pages) context[ 'NO_ENTRIES_MSG' - ] = 'Nenhuma encontrada.' + ] = 'Nenhuma encontrada.' return context @@ -1501,14 +1557,14 @@ class ListarBancadaComissaoAutorExternoView(PermissionRequiredMixin, ListView): def get_context_data(self, **kwargs): context = super( ListarBancadaComissaoAutorExternoView, self - ).get_context_data(**kwargs) + ).get_context_data(**kwargs) paginator = context['paginator'] page_obj = context['page_obj'] context['page_range'] = make_pagination( page_obj.number, paginator.num_pages) context[ 'NO_ENTRIES_MSG' - ] = 'Nenhuma encontrada.' + ] = 'Nenhuma encontrada.' return context @@ -1534,7 +1590,7 @@ class ListarAutoresDuplicadosView(PermissionRequiredMixin, ListView): page_obj.number, paginator.num_pages) context[ 'NO_ENTRIES_MSG' - ] = 'Nenhum encontrado.' + ] = 'Nenhum encontrado.' return context @@ -1547,10 +1603,12 @@ def parlamentares_filiacoes_intersecao(): for c in combinacoes: data_filiacao1 = c[0].data - data_desfiliacao1 = c[0].data_desfiliacao if c[0].data_desfiliacao else timezone.now().date() + data_desfiliacao1 = c[0].data_desfiliacao if c[0].data_desfiliacao else timezone.now( + ).date() data_filiacao2 = c[1].data - data_desfiliacao2 = c[1].data_desfiliacao if c[1].data_desfiliacao else timezone.now().date() + data_desfiliacao2 = c[1].data_desfiliacao if c[1].data_desfiliacao else timezone.now( + ).date() if data_filiacao1 and data_filiacao2: exists = intervalos_tem_intersecao( @@ -1580,8 +1638,8 @@ class ListarParlFiliacoesIntersecaoView(PermissionRequiredMixin, ListView): page_obj.number, paginator.num_pages) context[ 'NO_ENTRIES_MSG' - ] = 'Nenhum encontrado.' - return context + ] = 'Nenhum encontrado.' + return context def parlamentares_mandatos_intersecao(): @@ -1593,10 +1651,12 @@ def parlamentares_mandatos_intersecao(): for c in combinacoes: data_inicio_mandato1 = c[0].data_inicio_mandato - data_fim_mandato1 = c[0].data_fim_mandato if c[0].data_fim_mandato else timezone.now().date() + data_fim_mandato1 = c[0].data_fim_mandato if c[0].data_fim_mandato else timezone.now( + ).date() data_inicio_mandato2 = c[1].data_inicio_mandato - data_fim_mandato2 = c[1].data_fim_mandato if c[1].data_fim_mandato else timezone.now().date() + data_fim_mandato2 = c[1].data_fim_mandato if c[1].data_fim_mandato else timezone.now( + ).date() if data_inicio_mandato1 and data_inicio_mandato2: exists = intervalos_tem_intersecao( @@ -1627,7 +1687,7 @@ class ListarParlMandatosIntersecaoView(PermissionRequiredMixin, ListView): page_obj.number, paginator.num_pages) context[ 'NO_ENTRIES_MSG' - ] = 'Nenhum encontrado.' + ] = 'Nenhum encontrado.' return context @@ -1646,7 +1706,7 @@ class ListarParlamentaresDuplicadosView(PermissionRequiredMixin, ListView): def get_queryset(self): return parlamentares_duplicados() - + def get_context_data(self, **kwargs): context = super( ListarParlamentaresDuplicadosView, self).get_context_data(**kwargs) @@ -1656,9 +1716,9 @@ class ListarParlamentaresDuplicadosView(PermissionRequiredMixin, ListView): page_obj.number, paginator.num_pages) context[ 'NO_ENTRIES_MSG' - ] = 'Nenhum encontrado.' + ] = 'Nenhum encontrado.' return context - + def mandato_sem_data_inicio(): return Mandato.objects.filter(data_inicio_mandato__isnull=True).order_by('parlamentar') @@ -1669,13 +1729,16 @@ def get_estatistica(request): normas = NormaJuridica.objects.all() datas = [ - materias.order_by('-data_ultima_atualizacao').values_list('data_ultima_atualizacao', flat=True) - .exclude(data_ultima_atualizacao__isnull=True).first(), - normas.order_by('-data_ultima_atualizacao').values_list('data_ultima_atualizacao', flat=True) - .exclude(data_ultima_atualizacao__isnull=True).first() + materias.order_by( + '-data_ultima_atualizacao').values_list('data_ultima_atualizacao', flat=True) + .exclude(data_ultima_atualizacao__isnull=True).first(), + normas.order_by( + '-data_ultima_atualizacao').values_list('data_ultima_atualizacao', flat=True) + .exclude(data_ultima_atualizacao__isnull=True).first() ] - max_data = max(datas) if datas[0] and datas[1] else next(iter([i for i in datas if i is not None]), '') + max_data = max(datas) if datas[0] and datas[1] else next( + iter([i for i in datas if i is not None]), '') return JsonResponse({ "data_ultima_atualizacao": max_data, @@ -1699,14 +1762,14 @@ class ListarMandatoSemDataInicioView(PermissionRequiredMixin, ListView): def get_context_data(self, **kwargs): context = super( ListarMandatoSemDataInicioView, self - ).get_context_data(**kwargs) + ).get_context_data(**kwargs) paginator = context['paginator'] page_obj = context['page_obj'] context['page_range'] = make_pagination( page_obj.number, paginator.num_pages) context[ 'NO_ENTRIES_MSG' - ] = 'Nenhum encontrado.' + ] = 'Nenhum encontrado.' return context @@ -1723,11 +1786,11 @@ class ListarFiliacoesSemDataFiliacaoView(PermissionRequiredMixin, ListView): def get_queryset(self): return filiacoes_sem_data_filiacao() - + def get_context_data(self, **kwargs): context = super( ListarFiliacoesSemDataFiliacaoView, self - ).get_context_data(**kwargs) + ).get_context_data(**kwargs) paginator = context['paginator'] page_obj = context['page_obj'] context['page_range'] = make_pagination( @@ -1762,27 +1825,27 @@ class ListarMatProtocoloInexistenteView(PermissionRequiredMixin, ListView): def get_context_data(self, **kwargs): context = super( ListarMatProtocoloInexistenteView, self - ).get_context_data(**kwargs) + ).get_context_data(**kwargs) paginator = context['paginator'] page_obj = context['page_obj'] context['page_range'] = make_pagination( page_obj.number, paginator.num_pages) context[ 'NO_ENTRIES_MSG' - ] = 'Nenhuma encontrada.' + ] = 'Nenhuma encontrada.' return context def protocolos_com_materias(): protocolos = {} - + for m in MateriaLegislativa.objects.filter(numero_protocolo__isnull=False).order_by('-ano', 'numero_protocolo'): if Protocolo.objects.filter(numero=m.numero_protocolo, ano=m.ano).exists(): key = "{}/{}".format(m.numero_protocolo, m.ano) val = protocolos.get(key, list()) val.append(m) protocolos[key] = val - + return [(v[0], len(v)) for (k, v) in protocolos.items() if len(v) > 1] @@ -1805,7 +1868,7 @@ class ListarProtocolosComMateriasView(PermissionRequiredMixin, ListView): page_obj.number, paginator.num_pages) context[ 'NO_ENTRIES_MSG' - ] = 'Nenhum encontrado.' + ] = 'Nenhum encontrado.' return context @@ -1835,7 +1898,7 @@ class ListarProtocolosDuplicadosView(PermissionRequiredMixin, ListView): page_obj.number, paginator.num_pages) context[ 'NO_ENTRIES_MSG' - ] = 'Nenhum encontrado.' + ] = 'Nenhum encontrado.' return context @@ -1958,13 +2021,14 @@ class DeleteUsuarioView(PermissionRequiredMixin, DeleteView): template_name = "crud/confirm_delete.html" permission_required = ('base.delete_appconfig',) success_url = reverse_lazy('sapl.base:usuario') - success_message = "Usuário removido com sucesso!" + success_message = "Usuário removido com sucesso!" - def delete(self, request, *args, **kwargs): + def delete(self, request, *args, **kwargs): try: super(DeleteUsuarioView, self).delete(request, *args, **kwargs) except ProtectedError as exception: - error_url = reverse_lazy('sapl.base:user_delete', kwargs={'pk': self.kwargs['pk']}) + error_url = reverse_lazy('sapl.base:user_delete', kwargs={ + 'pk': self.kwargs['pk']}) error_message = "O usuário não pode ser removido, pois é referenciado por: