From 7986a82d8f049ca44fc30e27898eefb25914b007 Mon Sep 17 00:00:00 2001 From: Edward Ribeiro Date: Mon, 10 Jul 2017 18:51:08 -0300 Subject: [PATCH 01/20] Fixes #1265 --- sapl/materia/forms.py | 12 +++++++++--- sapl/templates/materia/layouts.yaml | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index 123f8084c..e1af3d1cc 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -13,7 +13,7 @@ from django.core.files.base import File from django.core.urlresolvers import reverse from django.db import models, transaction from django.db.models import Max -from django.forms import ModelForm, widgets +from django.forms import ModelForm, ModelChoiceField, widgets from django.forms.forms import Form from django.forms.widgets import Select from django.utils.encoding import force_text @@ -22,7 +22,7 @@ from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ import django_filters -from sapl.base.models import Autor +from sapl.base.models import Autor, TipoAutor from sapl.comissoes.models import Comissao from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC, STATUS_TA_PRIVATE) @@ -648,9 +648,15 @@ class DespachoInicialForm(ModelForm): class AutoriaForm(ModelForm): + tipo_autor = ModelChoiceField(label=_('Tipo Autor'), + required=True, + queryset= + TipoAutor.objects.all().order_by('descricao'), + empty_label='Selecione',) + class Meta: model = Autoria - fields = ['autor', 'primeiro_autor'] + fields = ['tipo_autor', 'autor', 'primeiro_autor'] def clean(self): super(AutoriaForm, self).clean() diff --git a/sapl/templates/materia/layouts.yaml b/sapl/templates/materia/layouts.yaml index 6045d61d9..c8a69889e 100644 --- a/sapl/templates/materia/layouts.yaml +++ b/sapl/templates/materia/layouts.yaml @@ -59,7 +59,7 @@ AnexadaDetail: Autoria: {% trans 'Autoria' %}: - - autor primeiro_autor + - tipo_autor autor primeiro_autor DocumentoAcessorio: {% trans 'Documento Acessório' %}: From 36c5c2cfafb5bbbd2fae9002b200ca83e6606c5d Mon Sep 17 00:00:00 2001 From: Edward Ribeiro Date: Tue, 11 Jul 2017 15:04:56 -0300 Subject: [PATCH 02/20] WIP --- sapl/materia/forms.py | 14 +++++++++++++- sapl/materia/views.py | 4 ++++ sapl/templates/materia/layouts.yaml | 8 ++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index e1af3d1cc..b88df390f 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -649,11 +649,23 @@ class DespachoInicialForm(ModelForm): class AutoriaForm(ModelForm): tipo_autor = ModelChoiceField(label=_('Tipo Autor'), - required=True, + required=False, queryset= TipoAutor.objects.all().order_by('descricao'), empty_label='Selecione',) + def __init__(self, *args, **kwargs): + super(AutoriaForm, self).__init__(*args, **kwargs) + + row1 = to_row([('tipo_autor', 4), + ('autor', 4), + ('primeiro_autor', 4)]) + + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset(_('Autoria'), + row1, form_actions(save_label='Salvar'))) + class Meta: model = Autoria fields = ['tipo_autor', 'autor', 'primeiro_autor'] diff --git a/sapl/materia/views.py b/sapl/materia/views.py index f4b6c4e35..f12ed883e 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -1077,6 +1077,10 @@ class AutoriaCrud(MasterDetailCrud): class CreateView(MasterDetailCrud.CreateView): form_class = AutoriaForm + @property + def layout_key(self): + return 'AutoriaCreate' + def get_context_data(self, **kwargs): context = super(CreateView, self).get_context_data(**kwargs) autores_ativos = self.autores_ativos() diff --git a/sapl/templates/materia/layouts.yaml b/sapl/templates/materia/layouts.yaml index c8a69889e..aac494432 100644 --- a/sapl/templates/materia/layouts.yaml +++ b/sapl/templates/materia/layouts.yaml @@ -58,6 +58,14 @@ AnexadaDetail: - data_anexacao data_desanexacao Autoria: + {% trans 'Autoria' %}: + - autor primeiro_autor + +AutoriaCreate: + {% trans 'Autoria' %}: + - tipo_autor autor primeiro_autor + +AutoriaUpdate: {% trans 'Autoria' %}: - tipo_autor autor primeiro_autor From a94ba1363a087ad44cfe0daa0d3f40f6fa7b892b Mon Sep 17 00:00:00 2001 From: Eduardo Calil Date: Tue, 11 Jul 2017 12:34:57 -0300 Subject: [PATCH 03/20] =?UTF-8?q?Comenta=20gatilho=20de=20indexa=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20normas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/norma/apps.py | 4 ++-- sapl/norma/signals.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sapl/norma/apps.py b/sapl/norma/apps.py index f1b12064b..c4d55ade0 100644 --- a/sapl/norma/apps.py +++ b/sapl/norma/apps.py @@ -7,5 +7,5 @@ class AppConfig(apps.AppConfig): label = 'norma' verbose_name = _('Norma Jurídica') -# def ready(self): -# from . import signals + def ready(self): + from . import signals diff --git a/sapl/norma/signals.py b/sapl/norma/signals.py index c01ac927d..5743428e1 100644 --- a/sapl/norma/signals.py +++ b/sapl/norma/signals.py @@ -4,5 +4,5 @@ from sapl.utils import delete_texto, save_texto from .models import NormaJuridica -post_save.connect(save_texto, sender=NormaJuridica) -post_delete.connect(delete_texto, sender=NormaJuridica) +# post_save.connect(save_texto, sender=NormaJuridica) +# post_delete.connect(delete_texto, sender=NormaJuridica) From fa9001b3b14af2bbe6c66dcb78c7be8fbd2f702b Mon Sep 17 00:00:00 2001 From: Edward Date: Wed, 12 Jul 2017 09:58:05 -0300 Subject: [PATCH 04/20] Adiciona data_ultima_atualizacao em Norma, MateriaLegislativa e DocumentoAcessorio (#1270) Este campo permite que se se use o comando do haystack ./manage.py update_index --age=2 para indexar somente os registros que foram modificados nas ultimas duas horas. --- sapl/base/search_indexes.py | 9 +++++++ .../migrations/0009_auto_20170712_0951.py | 25 +++++++++++++++++++ sapl/materia/models.py | 10 ++++++++ ...6_normajuridica_data_ultima_atualizacao.py | 20 +++++++++++++++ sapl/norma/models.py | 7 +++++- 5 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 sapl/materia/migrations/0009_auto_20170712_0951.py create mode 100644 sapl/norma/migrations/0006_normajuridica_data_ultima_atualizacao.py diff --git a/sapl/base/search_indexes.py b/sapl/base/search_indexes.py index bee7a54e3..e614a2f79 100644 --- a/sapl/base/search_indexes.py +++ b/sapl/base/search_indexes.py @@ -28,6 +28,9 @@ class DocumentoAcessorioIndex(indexes.SearchIndex, indexes.Indexable): def index_queryset(self, using=None): return self.get_model().objects.all() + def get_updated_field(self): + return 'data_ultima_atualizacao' + def solr_extraction(self, arquivo): extracted_data = self._get_backend(None).extract_file_contents( arquivo)['contents'] @@ -111,6 +114,9 @@ class MateriaLegislativaIndex(DocumentoAcessorioIndex): model = MateriaLegislativa template_name = 'materia/materialegislativa_text.txt' + def get_updated_field(self): + return 'data_ultima_atualizacao' + class NormaJuridicaIndex(DocumentoAcessorioIndex): text = indexes.CharField(document=True, use_template=True) @@ -118,3 +124,6 @@ class NormaJuridicaIndex(DocumentoAcessorioIndex): filename = 'texto_integral' model = NormaJuridica template_name = 'norma/normajuridica_text.txt' + + def get_updated_field(self): + return 'data_ultima_atualizacao' diff --git a/sapl/materia/migrations/0009_auto_20170712_0951.py b/sapl/materia/migrations/0009_auto_20170712_0951.py new file mode 100644 index 000000000..ecec298a9 --- /dev/null +++ b/sapl/materia/migrations/0009_auto_20170712_0951.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-07-12 09:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('materia', '0008_auto_20170622_1527'), + ] + + operations = [ + migrations.AddField( + model_name='documentoacessorio', + name='data_ultima_atualizacao', + field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data'), + ), + migrations.AddField( + model_name='materialegislativa', + name='data_ultima_atualizacao', + field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data'), + ), + ] diff --git a/sapl/materia/models.py b/sapl/materia/models.py index ec17e1ded..8d9eae4f5 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -225,6 +225,11 @@ class MateriaLegislativa(models.Model): through_fields=('materia', 'autor'), symmetrical=False,) + data_ultima_atualizacao = models.DateTimeField( + blank=True, null=True, + auto_now=True, + verbose_name=_('Data')) + class Meta: verbose_name = _('Matéria Legislativa') verbose_name_plural = _('Matérias Legislativas') @@ -421,6 +426,11 @@ class DocumentoAcessorio(models.Model): proposicao = GenericRelation( 'Proposicao', related_query_name='proposicao') + data_ultima_atualizacao = models.DateTimeField( + blank=True, null=True, + auto_now=True, + verbose_name=_('Data')) + class Meta: verbose_name = _('Documento Acessório') verbose_name_plural = _('Documentos Acessórios') diff --git a/sapl/norma/migrations/0006_normajuridica_data_ultima_atualizacao.py b/sapl/norma/migrations/0006_normajuridica_data_ultima_atualizacao.py new file mode 100644 index 000000000..9f5d5faba --- /dev/null +++ b/sapl/norma/migrations/0006_normajuridica_data_ultima_atualizacao.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.11 on 2017-07-12 09:51 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('norma', '0005_merge'), + ] + + operations = [ + migrations.AddField( + model_name='normajuridica', + name='data_ultima_atualizacao', + field=models.DateTimeField(auto_now=True, null=True, verbose_name='Data'), + ), + ] diff --git a/sapl/norma/models.py b/sapl/norma/models.py index 20166e7f5..a80f5a564 100644 --- a/sapl/norma/models.py +++ b/sapl/norma/models.py @@ -69,7 +69,7 @@ class NormaJuridica(models.Model): ('F', 'federal', _('Federal')), ('M', 'municipal', _('Municipal')), ) - + texto_integral = models.FileField( blank=True, null=True, @@ -121,6 +121,11 @@ class NormaJuridica(models.Model): texto_articulado = GenericRelation( TextoArticulado, related_query_name='texto_articulado') + data_ultima_atualizacao = models.DateTimeField( + blank=True, null=True, + auto_now=True, + verbose_name=_('Data')) + class Meta: verbose_name = _('Norma Jurídica') verbose_name_plural = _('Normas Jurídicas') From dca2041bf84b30281335a60071983e2fcd574dc3 Mon Sep 17 00:00:00 2001 From: Edward Ribeiro Date: Wed, 12 Jul 2017 12:07:27 -0300 Subject: [PATCH 05/20] Introduz o RealtimeSignalProcessor --- sapl/materia/signals.py | 5 ----- sapl/norma/signals.py | 3 --- sapl/settings.py | 1 + sapl/utils.py | 17 ----------------- 4 files changed, 1 insertion(+), 25 deletions(-) diff --git a/sapl/materia/signals.py b/sapl/materia/signals.py index 9dd5e4fb9..e82f3b692 100644 --- a/sapl/materia/signals.py +++ b/sapl/materia/signals.py @@ -8,8 +8,3 @@ from .models import DocumentoAcessorio, MateriaLegislativa tramitacao_signal = django.dispatch.Signal(providing_args=['post', 'request']) - -# post_save.connect(save_texto, sender=MateriaLegislativa) -# post_save.connect(save_texto, sender=DocumentoAcessorio) -# post_delete.connect(delete_texto, sender=MateriaLegislativa) -# post_delete.connect(delete_texto, sender=DocumentoAcessorio) diff --git a/sapl/norma/signals.py b/sapl/norma/signals.py index 5743428e1..8f2e77c4b 100644 --- a/sapl/norma/signals.py +++ b/sapl/norma/signals.py @@ -3,6 +3,3 @@ from django.db.models.signals import post_delete, post_save from sapl.utils import delete_texto, save_texto from .models import NormaJuridica - -# post_save.connect(save_texto, sender=NormaJuridica) -# post_delete.connect(delete_texto, sender=NormaJuridica) diff --git a/sapl/settings.py b/sapl/settings.py index 8769115bc..7ca99b1bf 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -86,6 +86,7 @@ INSTALLED_APPS = ( ) + SAPL_APPS # FTS = Full Text Search +HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' SEARCH_BACKEND = 'haystack.backends.whoosh_backend.WhooshEngine' SEARCH_URL = ('PATH', PROJECT_DIR.child('whoosh')) diff --git a/sapl/utils.py b/sapl/utils.py index e4b3fc074..7a4146fc5 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -553,20 +553,3 @@ def texto_upload_path(instance, filename, subpath='', pk_first=False): } return path - - -class UpdateIndexCommand(Thread): - - def run(self): - call([PROJECT_DIR.child('manage.py'), 'update_index'], - stdout=PIPE) - - -def save_texto(sender, instance, **kwargs): - update_index = UpdateIndexCommand() - update_index.start() - - -def delete_texto(sender, instance, **kwargs): - update_index = UpdateIndexCommand() - update_index.start() From 5f63be3d75e1bf107ea4c8b498c3f599ab65f0a5 Mon Sep 17 00:00:00 2001 From: Edward Ribeiro Date: Wed, 12 Jul 2017 13:07:09 -0300 Subject: [PATCH 06/20] =?UTF-8?q?HOT-FIX:=20remove=20imports=20n=C3=A3o=20?= =?UTF-8?q?mais=20utilizados.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/materia/signals.py | 2 -- sapl/norma/signals.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/sapl/materia/signals.py b/sapl/materia/signals.py index e82f3b692..f1cf66e03 100644 --- a/sapl/materia/signals.py +++ b/sapl/materia/signals.py @@ -2,8 +2,6 @@ from django.db.models.signals import post_delete, post_save import django.dispatch -from sapl.utils import delete_texto, save_texto - from .models import DocumentoAcessorio, MateriaLegislativa diff --git a/sapl/norma/signals.py b/sapl/norma/signals.py index 8f2e77c4b..4a5472715 100644 --- a/sapl/norma/signals.py +++ b/sapl/norma/signals.py @@ -1,5 +1,3 @@ from django.db.models.signals import post_delete, post_save -from sapl.utils import delete_texto, save_texto - from .models import NormaJuridica From 5a0770e326c0e1d9ecb3fa48c903b6a2f8cac56a Mon Sep 17 00:00:00 2001 From: Eduardo Calil Date: Wed, 12 Jul 2017 13:20:30 -0300 Subject: [PATCH 07/20] Fix #1203 frente parlamentar (#1267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Inicia a reestruturação * Adiciona lógica para aparecer ou esconder caixa de seleção de parlamentares * Cria o campo de data de inicio do mandato * Adiciona campo de inicio de mandato nos forms e details * Cria logica para mostrar somente os ativos * Finaliza as melhorias em Frente * Tira obrigatoriedade do campo Data Inicio de Mandato * Conserta teste de mandato --- sapl/parlamentares/forms.py | 3 +- .../migrations/0003_auto_20170707_1656.py | 27 ++++ .../migrations/0004_auto_20170711_1305.py | 20 +++ sapl/parlamentares/models.py | 7 +- .../parlamentares/tests/test_parlamentares.py | 2 - sapl/parlamentares/urls.py | 10 ++ sapl/parlamentares/views.py | 110 ++++++++++++++- sapl/templates/parlamentares/frente_form.html | 128 ++++++++++++++++++ sapl/templates/parlamentares/layouts.yaml | 3 +- scripts/set_inicio_mandato.py | 10 ++ 10 files changed, 312 insertions(+), 8 deletions(-) create mode 100644 sapl/parlamentares/migrations/0003_auto_20170707_1656.py create mode 100644 sapl/parlamentares/migrations/0004_auto_20170711_1305.py create mode 100644 sapl/templates/parlamentares/frente_form.html create mode 100644 scripts/set_inicio_mandato.py diff --git a/sapl/parlamentares/forms.py b/sapl/parlamentares/forms.py index bdb8c4d00..d36b3ff73 100644 --- a/sapl/parlamentares/forms.py +++ b/sapl/parlamentares/forms.py @@ -54,7 +54,8 @@ class MandatoForm(ModelForm): class Meta: model = Mandato fields = ['legislatura', 'coligacao', 'votos_recebidos', - 'data_fim_mandato', 'data_expedicao_diploma', 'titular', + 'data_inicio_mandato', 'data_fim_mandato', + 'data_expedicao_diploma', 'titular', 'tipo_afastamento', 'observacao', 'parlamentar'] widgets = {'parlamentar': forms.HiddenInput()} diff --git a/sapl/parlamentares/migrations/0003_auto_20170707_1656.py b/sapl/parlamentares/migrations/0003_auto_20170707_1656.py new file mode 100644 index 000000000..e6212354d --- /dev/null +++ b/sapl/parlamentares/migrations/0003_auto_20170707_1656.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-07-07 16:56 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0002_auto_20170504_1751'), + ] + + operations = [ + migrations.AddField( + model_name='mandato', + name='data_inicio_mandato', + field=models.DateField(default=datetime.datetime(2017, 7, 7, 16, 56, 58, 525896), verbose_name='Início do Mandato'), + preserve_default=False, + ), + migrations.AlterField( + model_name='mandato', + name='data_fim_mandato', + field=models.DateField(blank=True, null=True, verbose_name='Fim do Mandato'), + ), + ] diff --git a/sapl/parlamentares/migrations/0004_auto_20170711_1305.py b/sapl/parlamentares/migrations/0004_auto_20170711_1305.py new file mode 100644 index 000000000..9ef1faef6 --- /dev/null +++ b/sapl/parlamentares/migrations/0004_auto_20170711_1305.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-07-11 13:05 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0003_auto_20170707_1656'), + ] + + operations = [ + migrations.AlterField( + model_name='mandato', + name='data_inicio_mandato', + field=models.DateField(blank=True, null=True, verbose_name='Início do Mandato'), + ), + ] diff --git a/sapl/parlamentares/models.py b/sapl/parlamentares/models.py index d81412a4c..5039be89e 100644 --- a/sapl/parlamentares/models.py +++ b/sapl/parlamentares/models.py @@ -449,7 +449,12 @@ class Mandato(models.Model): on_delete=models.PROTECT, verbose_name=_('Coligação')) # TODO what is this field?????? tipo_causa_fim_mandato = models.PositiveIntegerField(blank=True, null=True) - data_fim_mandato = models.DateField(verbose_name=_('Fim do Mandato')) + data_inicio_mandato = models.DateField(verbose_name=_('Início do Mandato'), + blank=True, + null=True) + data_fim_mandato = models.DateField(verbose_name=_('Fim do Mandato'), + blank=True, + null=True) votos_recebidos = models.PositiveIntegerField( blank=True, null=True, verbose_name=_('Votos Recebidos (Mandato)')) data_expedicao_diploma = models.DateField( diff --git a/sapl/parlamentares/tests/test_parlamentares.py b/sapl/parlamentares/tests/test_parlamentares.py index b71816d5b..f210a4551 100644 --- a/sapl/parlamentares/tests/test_parlamentares.py +++ b/sapl/parlamentares/tests/test_parlamentares.py @@ -147,7 +147,5 @@ def test_form_errors_mandato(admin_client): assert (response.context_data['form'].errors['legislatura'] == ['Este campo é obrigatório.']) - assert (response.context_data['form'].errors['data_fim_mandato'] == - ['Este campo é obrigatório.']) assert (response.context_data['form'].errors['data_expedicao_diploma'] == ['Este campo é obrigatório.']) diff --git a/sapl/parlamentares/urls.py b/sapl/parlamentares/urls.py index 68ddcc16b..7b0f8f031 100644 --- a/sapl/parlamentares/urls.py +++ b/sapl/parlamentares/urls.py @@ -15,7 +15,9 @@ from sapl.parlamentares.views import (CargoMesaCrud, ColigacaoCrud, TipoMilitarCrud, VotanteView, altera_field_mesa, altera_field_mesa_public_view, + frente_atualiza_lista_parlamentares, insere_parlamentar_composicao, + parlamentares_frente_selected, remove_parlamentar_composicao) from .apps import AppConfig @@ -38,8 +40,16 @@ urlpatterns = [ url(r'^sistema/coligacao/', include(ColigacaoCrud.get_urls() + ComposicaoColigacaoCrud.get_urls())), + url(r'^sistema/frente/', include(FrenteCrud.get_urls())), + url(r'^sistema/frente/atualiza-lista-parlamentares', + frente_atualiza_lista_parlamentares, + name='atualiza_lista_parlamentares'), + url(r'^sistema/frente/parlamentares-frente-selected', + parlamentares_frente_selected, + name='parlamentares_frente_selected'), + url(r'^sistema/parlamentar/legislatura/', include(LegislaturaCrud.get_urls())), url(r'^sistema/parlamentar/tipo-dependente/', diff --git a/sapl/parlamentares/views.py b/sapl/parlamentares/views.py index 77ad62cf7..309c8b3cd 100644 --- a/sapl/parlamentares/views.py +++ b/sapl/parlamentares/views.py @@ -30,6 +30,10 @@ from sapl.materia.models import Autoria from django.contrib.contenttypes.models import ContentType from django.db.models.aggregates import Count +import datetime +import json + + CargoMesaCrud = CrudAux.build(CargoMesa, 'cargo_mesa') PartidoCrud = CrudAux.build(Partido, 'partidos') SessaoLegislativaCrud = CrudAux.build(SessaoLegislativa, 'sessao_legislativa') @@ -38,9 +42,6 @@ NivelInstrucaoCrud = CrudAux.build(NivelInstrucao, 'nivel_instrucao') TipoAfastamentoCrud = CrudAux.build(TipoAfastamento, 'tipo_afastamento') TipoMilitarCrud = CrudAux.build(SituacaoMilitar, 'tipo_situa_militar') -FrenteCrud = CrudAux.build(Frente, 'tipo_situa_militar', list_field_names=[ - 'nome', 'data_criacao', 'parlamentares']) - DependenteCrud = MasterDetailCrud.build( Dependente, 'parlamentar', 'dependente') @@ -211,6 +212,109 @@ class ColigacaoCrud(CrudAux): subnav_template_name = 'parlamentares/subnav_coligacao.yaml' +def json_date_convert(date): + ''' + :param date: recebe a data de uma chamada ajax no formato de + string "dd/mm/yyyy" + :return: + ''' + dia, mes, ano = date.split('/') + return datetime.date(day=int(dia), + month=int(mes), + year=int(ano)) + + +def parlamentares_ativos(data_inicio, data_fim=None): + ''' + :param data_inicio: define a data de inicial do período desejado + :param data_fim: define a data final do período desejado + :return: queryset dos parlamentares ativos naquele período + ''' + mandatos_ativos = Mandato.objects.filter(Q( + data_inicio_mandato__lte=data_inicio, + data_fim_mandato__isnull=True) | Q( + data_inicio_mandato__lte=data_inicio, + data_fim_mandato__gte=data_inicio)) + if data_fim: + mandatos_ativos = mandatos_ativos | Mandato.objects.filter( + data_inicio_mandato__gte=data_inicio, + data_inicio_mandato__lte=data_fim) + else: + mandatos_ativos = mandatos_ativos | Mandato.objects.filter( + data_inicio_mandato__gte=data_inicio) + + parlamentares_id = mandatos_ativos.values_list( + 'parlamentar_id', + flat=True).distinct('parlamentar_id') + + return Parlamentar.objects.filter(id__in=parlamentares_id) + + +def frente_atualiza_lista_parlamentares(request): + ''' + :param request: recebe os parâmetros do GET da chamada Ajax + :return: retorna a lista atualizada dos parlamentares + ''' + ativos = json.loads(request.GET['ativos']) + + parlamentares = Parlamentar.objects.all() + + if ativos: + if 'data_criacao' in request.GET and request.GET['data_criacao']: + data_criacao = json_date_convert(request.GET['data_criacao']) + + if 'data_extincao' in request.GET and request.GET['data_extincao']: + data_extincao = json_date_convert(request.GET['data_extincao']) + parlamentares = parlamentares_ativos(data_criacao, + data_extincao) + else: + parlamentares = parlamentares_ativos(data_criacao) + + parlamentares_list = [(p.id, p.__str__()) for p in parlamentares] + + return JsonResponse({'parlamentares_list': parlamentares_list}) + + +def parlamentares_frente_selected(request): + ''' + :return: Lista com o id dos parlamentares em uma frente + ''' + try: + frente = Frente.objects.get(id=int(request.GET['frente_id'])) + except ObjectDoesNotExist: + lista_parlamentar_id = [] + else: + lista_parlamentar_id = frente.parlamentares.all().values_list( + 'id', flat=True) + return JsonResponse({'id_list': list(lista_parlamentar_id)}) + + +class FrenteCrud(CrudAux): + model = Frente + help_path = 'tabelas_auxiliares#tipo_situa_militar' + list_field_names = ['nome', 'data_criacao', 'parlamentares'] + + class CreateView(CrudAux.CreateView): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + # Update view é um indicador para o javascript + # de que esta não é uma tela de edição de frente + context['update_view'] = 0 + + return context + + class UpdateView(CrudAux.UpdateView): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + # Update view é um indicador para o javascript + # de que esta não é uma tela de edição de frente + context['update_view'] = 1 + + return context + + class MandatoCrud(MasterDetailCrud): model = Mandato parent_field = 'parlamentar' diff --git a/sapl/templates/parlamentares/frente_form.html b/sapl/templates/parlamentares/frente_form.html new file mode 100644 index 000000000..5d9ae8fc1 --- /dev/null +++ b/sapl/templates/parlamentares/frente_form.html @@ -0,0 +1,128 @@ +{% extends "crud/form.html" %} +{% load i18n %} +{% load crispy_forms_tags %} +{% load common_tags %} + +{% block base_content %} +
+ {% csrf_token %} + +
+
+ {{ form.nome|as_crispy_field }} +
+
+ +
+
+ {{ form.data_criacao|as_crispy_field }} +
+
+ {{ form.data_extincao|as_crispy_field }} +
+
+ + + +
+
+ {{ form.descricao|as_crispy_field }} +
+
+ +
+ +
+ +{% endblock base_content %} + +{% block extra_js %} + +{% endblock %} diff --git a/sapl/templates/parlamentares/layouts.yaml b/sapl/templates/parlamentares/layouts.yaml index c839c1b53..57a3b86ba 100644 --- a/sapl/templates/parlamentares/layouts.yaml +++ b/sapl/templates/parlamentares/layouts.yaml @@ -71,7 +71,8 @@ Filiacao: Mandato: {% trans 'Mandato' %}: - legislatura coligacao votos_recebidos - - data_fim_mandato data_expedicao_diploma titular + - data_inicio_mandato data_fim_mandato + - data_expedicao_diploma titular - tipo_afastamento - observacao diff --git a/scripts/set_inicio_mandato.py b/scripts/set_inicio_mandato.py new file mode 100644 index 000000000..37dfcfc0c --- /dev/null +++ b/scripts/set_inicio_mandato.py @@ -0,0 +1,10 @@ +from sapl.parlamentares.models import Mandato + + +def popula_campo_data_inicio(): + for m in Mandato.objects.all(): + m.data_inicio_mandato = m.legislatura.data_inicio + m.save() + +if __name__ == '__main__': + popula_campo_data_inicio() \ No newline at end of file From f8997e2ae73c894f1b3d89a994f124fa18e40a55 Mon Sep 17 00:00:00 2001 From: Edward Ribeiro Date: Wed, 12 Jul 2017 13:28:13 -0300 Subject: [PATCH 08/20] Remove controlador painel --- sapl/painel/urls.py | 6 ++---- sapl/painel/views.py | 21 ------------------- sapl/templates/painel/controlador.html | 28 -------------------------- 3 files changed, 2 insertions(+), 53 deletions(-) delete mode 100644 sapl/templates/painel/controlador.html diff --git a/sapl/painel/urls.py b/sapl/painel/urls.py index 86cf20277..29890f30f 100644 --- a/sapl/painel/urls.py +++ b/sapl/painel/urls.py @@ -1,8 +1,8 @@ from django.conf.urls import url from .apps import AppConfig -from .views import (controlador_painel, cronometro_painel, get_dados_painel, - painel_mensagem_view, painel_parlamentar_view, painel_view, +from .views import (cronometro_painel, get_dados_painel, painel_mensagem_view, + painel_parlamentar_view, painel_view, painel_votacao_view, votante_view) app_name = AppConfig.name @@ -11,8 +11,6 @@ urlpatterns = [ url(r'^painel-principal/(?P\d+)$', painel_view, name="painel_principal"), url(r'^painel/(?P\d+)/dados$', get_dados_painel, name='dados_painel'), - url(r'^painel/controlador$', - controlador_painel, name='painel_controlador'), url(r'^painel/mensagem$', painel_mensagem_view, name="painel_mensagem"), url(r'^painel/parlamentar$', painel_parlamentar_view, name='painel_parlamentar'), diff --git a/sapl/painel/views.py b/sapl/painel/views.py index ecf7528a3..b6abd9cc5 100644 --- a/sapl/painel/views.py +++ b/sapl/painel/views.py @@ -219,27 +219,6 @@ def votante_view(request): return render(request, 'painel/voto_nominal.html', context) -@user_passes_test(check_permission) -def controlador_painel(request): - - painel_created = Painel.objects.get_or_create(data_painel=date.today()) - painel = painel_created[0] - - if request.method == 'POST': - if 'start-painel' in request.POST: - painel.aberto = True - painel.save() - elif 'stop-painel' in request.POST: - painel.aberto = False - painel.save() - elif 'save-painel' in request.POST: - painel.mostrar = request.POST['tipo_painel'] - painel.save() - - context = {'painel': painel, 'PAINEL_TYPES': Painel.PAINEL_TYPES} - return render(request, 'painel/controlador.html', context) - - @user_passes_test(check_permission) def painel_view(request, pk): context = {'head_title': str(_('Painel Plenário')), 'sessao_id': pk} diff --git a/sapl/templates/painel/controlador.html b/sapl/templates/painel/controlador.html deleted file mode 100644 index 32ed27913..000000000 --- a/sapl/templates/painel/controlador.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block base_content %} - -STATUS: -{% if painel.aberto %} -ABERTO -{% else %} -FECHADO -{% endif %} -
-
- {% csrf_token %} - Tipo de painel: {{ painel.get_mostrar_display }}
- {% for id, value in PAINEL_TYPES %} - -
- {% endfor %} -
- - - -
-
-
-Voltar - -{% endblock %} From f9097a76d8db56b64868146749819c33d5d08a24 Mon Sep 17 00:00:00 2001 From: Edward Ribeiro Date: Wed, 12 Jul 2017 13:34:52 -0300 Subject: [PATCH 09/20] =?UTF-8?q?HOT-FIX:=20remove=20controlador=20painel?= =?UTF-8?q?=20de=20sess=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/templates/sessao/painel.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sapl/templates/sessao/painel.html b/sapl/templates/sessao/painel.html index 2857bc4bb..5cd84ec32 100644 --- a/sapl/templates/sessao/painel.html +++ b/sapl/templates/sessao/painel.html @@ -13,12 +13,7 @@ {% block detail_content %}
From fa61346965cda9ce0b9f2845110024cb391d09e1 Mon Sep 17 00:00:00 2001 From: Eduardo Calil Date: Thu, 13 Jul 2017 09:41:29 -0300 Subject: [PATCH 10/20] =?UTF-8?q?Inicia=20l=C3=B3gica=20para=20filtrar=20p?= =?UTF-8?q?or=20autores=20ativos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/materia/forms.py | 12 ---- sapl/materia/views.py | 82 +++++++++++++++++++----- sapl/templates/materia/autoria_form.html | 25 ++++++++ 3 files changed, 92 insertions(+), 27 deletions(-) create mode 100644 sapl/templates/materia/autoria_form.html diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index b88df390f..67e794907 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -654,18 +654,6 @@ class AutoriaForm(ModelForm): TipoAutor.objects.all().order_by('descricao'), empty_label='Selecione',) - def __init__(self, *args, **kwargs): - super(AutoriaForm, self).__init__(*args, **kwargs) - - row1 = to_row([('tipo_autor', 4), - ('autor', 4), - ('primeiro_autor', 4)]) - - self.helper = FormHelper() - self.helper.layout = Layout( - Fieldset(_('Autoria'), - row1, form_actions(save_label='Salvar'))) - class Meta: model = Autoria fields = ['tipo_autor', 'autor', 'primeiro_autor'] diff --git a/sapl/materia/views.py b/sapl/materia/views.py index f12ed883e..4eaef9786 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -40,7 +40,7 @@ from django.views.generic.base import RedirectView from django.views.generic.edit import FormView from django_filters.views import FilterView import sapl -from sapl.base.models import Autor, CasaLegislativa +from sapl.base.models import Autor, CasaLegislativa, TipoAutor from sapl.comissoes.models import Comissao from sapl.comissoes.models import Comissao, Participacao from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_RESTRICT, @@ -57,8 +57,9 @@ from sapl.materia.forms import (AnexadaForm, ConfirmarProposicaoForm, TramitacaoUpdateForm) from sapl.materia.models import Autor from sapl.norma.models import LegislacaoCitada -from sapl.parlamentares.models import Parlamentar +from sapl.parlamentares.models import Frente, Mandato, Parlamentar from sapl.protocoloadm.models import Protocolo +from sapl.sessao.models import Bancada, Bloco from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label, autor_modal, gerar_hash_arquivo, get_base_url, montar_row_autor) @@ -1068,6 +1069,62 @@ class DocumentoAcessorioCrud(MasterDetailCrud): return context +def filtra_ativos(content_type): + if content_type == Parlamentar: + mandatos_ativos = Mandato.objects.filter() + return + elif content_type == Comissao: + return + elif content_type == Frente: + return + elif content_type == Bancada: + return + elif content_type == Bloco: + return + elif content_type == Orgao: + return + + +def autores_ativos(tipo=None): + content_types_list = [] + for ta in TipoAutor: + if ta.content_type: + content_types_list.append(ta.content_type) + + autores_by_ct = {} + for ct in content_types_list: + autores_by_ct[str(ct.id)] = filtra_ativos(ct) + + # model_parlamentar = ContentType.objects.get_for_model( + # Parlamentar) + # model_comissao = ContentType.objects.get_for_model(Comissao) + # + # lista_parlamentares = Parlamentar.objects.filter( + # ativo=True).values_list( + # 'id', flat=True) + # autor_parlamentar = Autor.objects.filter( + # content_type=model_parlamentar, + # object_id__in=lista_parlamentares) + # + # lista_comissoes = Comissao.objects.filter( + # Q(data_extincao__isnull=True)|Q( + # data_extincao__gt=date.today())).values_list( + # 'id', flat=True) + # + # autor_comissoes = Autor.objects.filter( + # content_type=model_comissao, + # object_id__in=lista_comissoes) + # autores_outros = Autor.objects.exclude( + # content_type__in=[model_parlamentar, + # model_comissao]) + # q = autor_parlamentar | autor_comissoes | autores_outros + return q + + +def atualizar_autores(request): + pass + + class AutoriaCrud(MasterDetailCrud): model = Autoria parent_field = 'materia' @@ -1083,26 +1140,21 @@ class AutoriaCrud(MasterDetailCrud): def get_context_data(self, **kwargs): context = super(CreateView, self).get_context_data(**kwargs) - autores_ativos = self.autores_ativos() + autores_ativos_list = autores_ativos() autores = [] - for a in autores_ativos: + for a in autores_ativos_list: autores.append([a.id, a.__str__()]) context['form'].fields['autor'].choices = autores return context - def autores_ativos(self): - lista_parlamentares = Parlamentar.objects.filter(ativo=True).values_list('id', flat=True) - model_parlamentar = ContentType.objects.get_for_model(Parlamentar) - autor_parlamentar = Autor.objects.filter(content_type=model_parlamentar, object_id__in=lista_parlamentares) - - lista_comissoes = Comissao.objects.filter(Q(data_extincao__isnull=True)|Q(data_extincao__gt=date.today())).values_list('id', flat=True) - model_comissao = ContentType.objects.get_for_model(Comissao) - autor_comissoes = Autor.objects.filter(content_type=model_comissao, object_id__in=lista_comissoes) - autores_outros = Autor.objects.exclude(content_type__in=[model_parlamentar, model_comissao]) - q = autor_parlamentar | autor_comissoes | autores_outros - return q + class UpdateView(MasterDetailCrud.UpdateView): + form_class = AutoriaForm + + @property + def layout_key(self): + return 'AutoriaUpdate' class DespachoInicialCrud(MasterDetailCrud): diff --git a/sapl/templates/materia/autoria_form.html b/sapl/templates/materia/autoria_form.html new file mode 100644 index 000000000..6fcbc4c6e --- /dev/null +++ b/sapl/templates/materia/autoria_form.html @@ -0,0 +1,25 @@ +{% extends "crud/form.html" %} +{% load i18n %} +{% load crispy_forms_tags %} +{% load common_tags %} + +{% block extra_js %} + + + +{% endblock %} From 3310df86da170c89e73e79caad8f3e3e008ecc34 Mon Sep 17 00:00:00 2001 From: Eduardo Calil Date: Thu, 13 Jul 2017 12:02:00 -0300 Subject: [PATCH 11/20] Filtra autores por ativos --- sapl/materia/views.py | 145 +++++++++++++++++++++++++++++------------- 1 file changed, 100 insertions(+), 45 deletions(-) diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 4eaef9786..1e60db3f5 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -1069,56 +1069,111 @@ class DocumentoAcessorioCrud(MasterDetailCrud): return context -def filtra_ativos(content_type): - if content_type == Parlamentar: - mandatos_ativos = Mandato.objects.filter() - return - elif content_type == Comissao: - return - elif content_type == Frente: - return - elif content_type == Bancada: - return - elif content_type == Bloco: - return - elif content_type == Orgao: - return - - -def autores_ativos(tipo=None): +def filtra_ativos(content_type, materia): + if content_type.model_class() == Parlamentar: + mandatos_ativos = Mandato.objects.filter(Q( + data_inicio_mandato__lte=materia.data_apresentacao, + data_fim_mandato__isnull=True) | Q( + data_inicio_mandato__lte=materia.data_apresentacao, + data_fim_mandato__gte=materia.data_apresentacao) + ).values_list('parlamentar_id', flat=True).distinct('parlamentar_id') + + return Autor.objects.filter( + content_type=content_type, + object_id__in=mandatos_ativos).order_by( + 'autor_related__nome_completo') + + elif content_type.model_class() == Comissao: + comissoes = Comissao.objects.filter( + data_criacao__lte=materia.data_apresentacao) + comissoes_id = comissoes.filter(Q( + data_extincao__isnull=True, + data_fim_comissao__isnull=True) | Q( + data_extincao__gte=materia.data_apresentacao, + data_fim_comissao__isnull=True) | Q( + data_extincao__gte=materia.data_apresentacao, + data_fim_comissao__isnull=True) | Q( + data_extincao__isnull=True, + data_fim_comissao__gte=materia.data_apresentacao) | Q( + data_extincao__gte=materia.data_apresentacao, + data_fim_comissao__gte=materia.data_apresentacao)).values_list( + 'id', flat=True).distinct() + + return Autor.objects.filter( + content_type=content_type, + object_id__in=comissoes_id).order_by( + 'autor_related__nome') + + elif content_type.model_class() == Frente: + frentes = Frente.objects.filter( + data_criacao__lte=materia.data_apresentacao) + frentes_id = frentes.filter(Q( + data_extincao__isnull=True) | Q( + data_extincao__gte=materia.data_apresentacao)).values_list( + 'id', flat=True).distinct() + + return Autor.objects.filter( + content_type=content_type, + object_id__in=frentes_id).order_by( + 'autor_related__nome') + + elif content_type.model_class() == Bancada: + bancadas = Bancada.objects.filter( + data_criacao__lte=materia.data_apresentacao) + bancadas_id = bancadas.filter(Q( + data_extincao__isnull=True) | Q( + data_extincao__gte=materia.data_apresentacao)).values_list( + 'id', flat=True).distinct() + + return Autor.objects.filter( + content_type=content_type, + object_id__in=bancadas_id).order_by( + 'autor_related__nome') + + elif content_type.model_class() == Bloco: + blocos = Bloco.objects.filter( + data_criacao__lte=materia.data_apresentacao) + blocos_id = blocos.filter(Q( + data_extincao__isnull=True) | Q( + data_extincao__gte=materia.data_apresentacao)).values_list( + 'id', flat=True).distinct() + + return Autor.objects.filter( + content_type=content_type, + object_id__in=blocos_id).order_by( + 'autor_related__nome') + + elif content_type.model_class() == Orgao: + orgaos_id = Orgao.objects.values_list('id', flat=True) + + return Autor.objects.filter( + content_type=content_type, + object_id__in=orgaos_id).order_by( + 'autor_related__nome') + + +def autores_ativos(materia, tipo=None): content_types_list = [] - for ta in TipoAutor: + for ta in TipoAutor.objects.all(): if ta.content_type: content_types_list.append(ta.content_type) autores_by_ct = {} for ct in content_types_list: - autores_by_ct[str(ct.id)] = filtra_ativos(ct) - - # model_parlamentar = ContentType.objects.get_for_model( - # Parlamentar) - # model_comissao = ContentType.objects.get_for_model(Comissao) - # - # lista_parlamentares = Parlamentar.objects.filter( - # ativo=True).values_list( - # 'id', flat=True) - # autor_parlamentar = Autor.objects.filter( - # content_type=model_parlamentar, - # object_id__in=lista_parlamentares) - # - # lista_comissoes = Comissao.objects.filter( - # Q(data_extincao__isnull=True)|Q( - # data_extincao__gt=date.today())).values_list( - # 'id', flat=True) - # - # autor_comissoes = Autor.objects.filter( - # content_type=model_comissao, - # object_id__in=lista_comissoes) - # autores_outros = Autor.objects.exclude( - # content_type__in=[model_parlamentar, - # model_comissao]) - # q = autor_parlamentar | autor_comissoes | autores_outros - return q + autores_by_ct[str(ct.id)] = filtra_ativos(ct, materia) + + if not tipo: + autor_qs = Autor.objects.none() + for key in autores_by_ct: + autor_qs = autor_qs | autores_by_ct[key] + + autores_by_ct['others'] = Autor.objects.exclude( + content_type__in=content_types_list) + + return (autor_qs | autores_by_ct['others']).order_by('content_type') + + else: + return autores_by_ct[tipo] def atualizar_autores(request): @@ -1140,7 +1195,7 @@ class AutoriaCrud(MasterDetailCrud): def get_context_data(self, **kwargs): context = super(CreateView, self).get_context_data(**kwargs) - autores_ativos_list = autores_ativos() + autores_ativos_list = autores_ativos(self.get_object().materia) autores = [] for a in autores_ativos_list: From 619a0153c2a9ff7cc4ce224113656f10123dbf4e Mon Sep 17 00:00:00 2001 From: Eduardo Calil Date: Fri, 14 Jul 2017 09:06:05 -0300 Subject: [PATCH 12/20] Bug fix --- sapl/materia/views.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 1e60db3f5..479abcbbd 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -1080,8 +1080,7 @@ def filtra_ativos(content_type, materia): return Autor.objects.filter( content_type=content_type, - object_id__in=mandatos_ativos).order_by( - 'autor_related__nome_completo') + object_id__in=mandatos_ativos) elif content_type.model_class() == Comissao: comissoes = Comissao.objects.filter( @@ -1101,8 +1100,7 @@ def filtra_ativos(content_type, materia): return Autor.objects.filter( content_type=content_type, - object_id__in=comissoes_id).order_by( - 'autor_related__nome') + object_id__in=comissoes_id) elif content_type.model_class() == Frente: frentes = Frente.objects.filter( @@ -1114,8 +1112,7 @@ def filtra_ativos(content_type, materia): return Autor.objects.filter( content_type=content_type, - object_id__in=frentes_id).order_by( - 'autor_related__nome') + object_id__in=frentes_id) elif content_type.model_class() == Bancada: bancadas = Bancada.objects.filter( @@ -1127,8 +1124,7 @@ def filtra_ativos(content_type, materia): return Autor.objects.filter( content_type=content_type, - object_id__in=bancadas_id).order_by( - 'autor_related__nome') + object_id__in=bancadas_id) elif content_type.model_class() == Bloco: blocos = Bloco.objects.filter( @@ -1140,16 +1136,14 @@ def filtra_ativos(content_type, materia): return Autor.objects.filter( content_type=content_type, - object_id__in=blocos_id).order_by( - 'autor_related__nome') + object_id__in=blocos_id) elif content_type.model_class() == Orgao: orgaos_id = Orgao.objects.values_list('id', flat=True) return Autor.objects.filter( content_type=content_type, - object_id__in=orgaos_id).order_by( - 'autor_related__nome') + object_id__in=orgaos_id) def autores_ativos(materia, tipo=None): @@ -1168,12 +1162,14 @@ def autores_ativos(materia, tipo=None): autor_qs = autor_qs | autores_by_ct[key] autores_by_ct['others'] = Autor.objects.exclude( - content_type__in=content_types_list) + content_type__in=content_types_list).order_by( + 'nome' + ) - return (autor_qs | autores_by_ct['others']).order_by('content_type') + return (autor_qs | autores_by_ct['others']).order_by('nome') else: - return autores_by_ct[tipo] + return autores_by_ct[tipo].order_by('nome') def atualizar_autores(request): @@ -1195,7 +1191,11 @@ class AutoriaCrud(MasterDetailCrud): def get_context_data(self, **kwargs): context = super(CreateView, self).get_context_data(**kwargs) - autores_ativos_list = autores_ativos(self.get_object().materia) + + materia = MateriaLegislativa.objects.get( + id=int(kwargs['root_pk'])) + + autores_ativos_list = autores_ativos(materia) autores = [] for a in autores_ativos_list: From 9d2a42c49c0582bfe433a1747f10643df8c2639b Mon Sep 17 00:00:00 2001 From: Eduardo Calil Date: Fri, 14 Jul 2017 11:05:02 -0300 Subject: [PATCH 13/20] Finaliza a filtragem por tipo de autor --- sapl/materia/urls.py | 5 ++ sapl/materia/views.py | 69 +++++++++++++++++++----- sapl/templates/materia/autoria_form.html | 14 +++++ 3 files changed, 76 insertions(+), 12 deletions(-) diff --git a/sapl/materia/urls.py b/sapl/materia/urls.py index 600fadfc6..3b2cd4211 100644 --- a/sapl/materia/urls.py +++ b/sapl/materia/urls.py @@ -21,6 +21,7 @@ from sapl.materia.views import (AcompanhamentoConfirmarView, TipoFimRelatoriaCrud, TipoMateriaCrud, TipoProposicaoCrud, TramitacaoCrud, TramitacaoEmLoteView, UnidadeTramitacaoCrud, + atualizar_autores, proposicao_texto, recuperar_materia) from .apps import AppConfig @@ -41,6 +42,10 @@ urlpatterns_materia = [ RelatoriaCrud.get_urls() + DocumentoAcessorioCrud.get_urls())), + url(r'^materia/autoria/atualizar-autores$', + atualizar_autores, + name='atualizar_autores'), + url(r'^materia/(?P[0-9]+)/create_simplificado$', CriarProtocoloMateriaView.as_view(), name='materia_create_simplificado'), diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 479abcbbd..21d3d9cd7 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -1147,33 +1147,63 @@ def filtra_ativos(content_type, materia): def autores_ativos(materia, tipo=None): + """ + :param materia: é a matéria para na qual a Autoria será inserida + :param tipo: é o tipo de autor que foi selecionado + :return: Lista dos autores ativos disponíveis para serem inseridos + na matéria em questão. + Primeiramente são inseridos em uma lista uma tupla relacionando + cada TipoAutor ao seu ContentType. Posteriormente, para cada elemento + dessa lista são filtrados os autores ativos para aquele ContentType e, + por convêniencia, inseridos em um dicionário no qual a chave é o id + do TipoAutor. Após isso, com a chave 'others' são inseridos os Autores + os quais seu TipoAutor não possui ContentType. + """ + content_types_list = [] for ta in TipoAutor.objects.all(): if ta.content_type: - content_types_list.append(ta.content_type) + content_types_list.append((ta.content_type, ta)) autores_by_ct = {} - for ct in content_types_list: - autores_by_ct[str(ct.id)] = filtra_ativos(ct, materia) + for ct, ta in content_types_list: + autores_by_ct[str(ta.id)] = filtra_ativos(ct, materia) - if not tipo: - autor_qs = Autor.objects.none() - for key in autores_by_ct: - autor_qs = autor_qs | autores_by_ct[key] + autor_qs = Autor.objects.none() + for key in autores_by_ct: + autor_qs = autor_qs | autores_by_ct[key] - autores_by_ct['others'] = Autor.objects.exclude( - content_type__in=content_types_list).order_by( - 'nome' - ) + ct_list = [c[0] for c in content_types_list] + autores_by_ct['others'] = Autor.objects.exclude( + content_type__in=ct_list).order_by( + 'nome' + ) + if not tipo: return (autor_qs | autores_by_ct['others']).order_by('nome') else: + if not tipo in autores_by_ct: + tipo = 'others' + return autores_by_ct[tipo].order_by('nome') def atualizar_autores(request): - pass + if ('tipo_autor' in request.GET and 'materia_id' in request.GET and + request.GET['tipo_autor'] and request.GET['materia_id']): + tipo_autor = request.GET['tipo_autor'] + materia_id = int(request.GET['materia_id']) + + try: + materia = MateriaLegislativa.objects.get(id=materia_id) + except ObjectDoesNotExist: + autores = [] + else: + autores = autores_ativos(materia, tipo=tipo_autor) + autores_list = [(a.id, a.__str__()) for a in autores] + + return JsonResponse({'lista_autores': autores_list}) class AutoriaCrud(MasterDetailCrud): @@ -1211,6 +1241,21 @@ class AutoriaCrud(MasterDetailCrud): def layout_key(self): return 'AutoriaUpdate' + def get_context_data(self, **kwargs): + context = super(UpdateView, self).get_context_data(**kwargs) + + materia = MateriaLegislativa.objects.get( + id=int(kwargs['root_pk'])) + + autores_ativos_list = autores_ativos(materia) + + autores = [] + for a in autores_ativos_list: + autores.append([a.id, a.__str__()]) + + context['form'].fields['autor'].choices = autores + return context + class DespachoInicialCrud(MasterDetailCrud): model = DespachoInicial diff --git a/sapl/templates/materia/autoria_form.html b/sapl/templates/materia/autoria_form.html index 6fcbc4c6e..b37de27db 100644 --- a/sapl/templates/materia/autoria_form.html +++ b/sapl/templates/materia/autoria_form.html @@ -11,10 +11,24 @@ var tipo_autor = $("#id_tipo_autor").val(); var materia_id = {{ root_pk }} + // Limpa a listagem para atualizar + $("#id_autor").find("option").remove(); + $.get("/materia/autoria/atualizar-autores", {tipo_autor: tipo_autor, materia_id: materia_id}, function(data) { + // Caso não venha nenhum dado da requisição, retorna null + if ($.isEmptyObject(data)){ + return null + } + lista_autores = data['lista_autores']; + // Atualiza a listagem de autores + for (i = 0; i < lista_autores.length; i++) { + $('#id_autor').append( + ''); + } }); } $("#id_tipo_autor").change(function () { From af61c53a30e5ef18137b235555f1450f57fec8ab Mon Sep 17 00:00:00 2001 From: Eduardo Calil Date: Fri, 14 Jul 2017 11:13:30 -0300 Subject: [PATCH 14/20] =?UTF-8?q?Otimiza=C3=A7=C3=A3o=20no=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/materia/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 21d3d9cd7..902b8ec91 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -1169,10 +1169,6 @@ def autores_ativos(materia, tipo=None): for ct, ta in content_types_list: autores_by_ct[str(ta.id)] = filtra_ativos(ct, materia) - autor_qs = Autor.objects.none() - for key in autores_by_ct: - autor_qs = autor_qs | autores_by_ct[key] - ct_list = [c[0] for c in content_types_list] autores_by_ct['others'] = Autor.objects.exclude( content_type__in=ct_list).order_by( @@ -1180,6 +1176,10 @@ def autores_ativos(materia, tipo=None): ) if not tipo: + autor_qs = Autor.objects.none() + for key in autores_by_ct: + autor_qs = autor_qs | autores_by_ct[key] + return (autor_qs | autores_by_ct['others']).order_by('nome') else: From cf6aad83d30d85c515f5e12cd66e322e2951d833 Mon Sep 17 00:00:00 2001 From: Eduardo Calil Date: Tue, 1 Aug 2017 12:43:39 -0300 Subject: [PATCH 15/20] =?UTF-8?q?Ajusta=20mudan=C3=A7as=20para=20a=20tela?= =?UTF-8?q?=20de=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/materia/views.py | 25 +++++++++++++++++++++++- sapl/templates/materia/autoria_form.html | 9 +++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/sapl/materia/views.py b/sapl/materia/views.py index b76bb6784..94eae9a57 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -1215,7 +1215,23 @@ def atualizar_autores(request): autores = autores_ativos(materia, tipo=tipo_autor) autores_list = [(a.id, a.__str__()) for a in autores] - return JsonResponse({'lista_autores': autores_list}) + # Se já houver algum autor selecionado (ex: view de update) + # no campo correspondente e caso o TipoAutor selecionado + # seja o mesmo do autor que está aualmente marcado + # deve ser enviado um sinal (manter autor) para que o javascript + # mantenha este selecionado + manter_autor = False + if 'autor_id' in request.GET and request.GET['autor_id']: + try: + autor = Autor.objects.get(id=request.GET['autor_id']) + except ObjectDoesNotExist: + pass + else: + if autor.tipo.id == int(tipo_autor): + manter_autor = True + + return JsonResponse({'lista_autores': autores_list, + 'manter_autor': manter_autor}) class AutoriaCrud(MasterDetailCrud): @@ -1253,6 +1269,13 @@ class AutoriaCrud(MasterDetailCrud): def layout_key(self): return 'AutoriaUpdate' + def get_initial(self): + return { + 'tipo_autor': self.object.autor.tipo.id, + 'autor': self.object.autor.id, + 'primeiro_autor': self.object.primeiro_autor + } + def get_context_data(self, **kwargs): context = super(UpdateView, self).get_context_data(**kwargs) diff --git a/sapl/templates/materia/autoria_form.html b/sapl/templates/materia/autoria_form.html index b37de27db..7a35884d8 100644 --- a/sapl/templates/materia/autoria_form.html +++ b/sapl/templates/materia/autoria_form.html @@ -9,13 +9,14 @@ function atualizar_autores() { var tipo_autor = $("#id_tipo_autor").val(); - var materia_id = {{ root_pk }} + var materia_id = {{ root_pk }}; + var autor_id = $("#id_autor").val(); // Limpa a listagem para atualizar $("#id_autor").find("option").remove(); $.get("/materia/autoria/atualizar-autores", - {tipo_autor: tipo_autor, materia_id: materia_id}, + {tipo_autor: tipo_autor, materia_id: materia_id, autor_id: autor_id}, function(data) { // Caso não venha nenhum dado da requisição, retorna null if ($.isEmptyObject(data)){ @@ -29,6 +30,10 @@ ''); } + + if (data['manter_autor'] == true){ + $('#id_autor').val(autor_id); + } }); } $("#id_tipo_autor").change(function () { From 116903524f5724813be5e95e5341583f7fe09feb Mon Sep 17 00:00:00 2001 From: Eduardo Calil Date: Wed, 2 Aug 2017 14:24:23 -0300 Subject: [PATCH 16/20] Conserta bug --- sapl/materia/views.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 94eae9a57..93c16dfa5 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -1186,7 +1186,8 @@ def autores_ativos(materia, tipo=None): content_type__in=ct_list).order_by( 'nome' ) - + # import ipdb; + # ipdb.set_trace() if not tipo: autor_qs = Autor.objects.none() for key in autores_by_ct: @@ -1195,10 +1196,11 @@ def autores_ativos(materia, tipo=None): return (autor_qs | autores_by_ct['others']).order_by('nome') else: + tipo_autor = tipo if not tipo in autores_by_ct: - tipo = 'others' + tipo_autor = 'others' - return autores_by_ct[tipo].order_by('nome') + return autores_by_ct[tipo_autor].filter(tipo_id=tipo).order_by('nome') def atualizar_autores(request): From 1fe2cad362393bb2d4cae03d307bb0405a0984fc Mon Sep 17 00:00:00 2001 From: Eduardo Calil Date: Wed, 2 Aug 2017 15:25:46 -0300 Subject: [PATCH 17/20] =?UTF-8?q?Adiciona=20uma=20das=20corre=C3=A7=C3=B5e?= =?UTF-8?q?s=20sugeridas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/materia/forms.py | 4 +++- sapl/materia/views.py | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index df0b72efc..af8a82630 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -1084,7 +1084,9 @@ class ProposicaoForm(forms.ModelForm): texto_original = self.cleaned_data.get('texto_original', False) if texto_original: if texto_original.size > MAX_DOC_UPLOAD_SIZE: - raise ValidationError("Arquivo muito grande. ( > 5mb )") + max_size = str(MAX_DOC_UPLOAD_SIZE / (1024 * 1024)) + raise ValidationError( + "Arquivo muito grande. ( > {0}MB )".format(max_size)) return texto_original def clean(self): diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 93c16dfa5..ab18ff312 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -1186,8 +1186,7 @@ def autores_ativos(materia, tipo=None): content_type__in=ct_list).order_by( 'nome' ) - # import ipdb; - # ipdb.set_trace() + if not tipo: autor_qs = Autor.objects.none() for key in autores_by_ct: From fc4d625cf08dda4b6a15aa4423f3ae43eb044509 Mon Sep 17 00:00:00 2001 From: Eduardo Calil Date: Thu, 3 Aug 2017 10:08:26 -0300 Subject: [PATCH 18/20] =?UTF-8?q?Melhorias=20no=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/materia/views.py | 54 +++++++++++++----------- sapl/templates/materia/autoria_form.html | 11 ++--- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/sapl/materia/views.py b/sapl/materia/views.py index ab18ff312..5c15fae38 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -1195,44 +1195,50 @@ def autores_ativos(materia, tipo=None): return (autor_qs | autores_by_ct['others']).order_by('nome') else: - tipo_autor = tipo if not tipo in autores_by_ct: - tipo_autor = 'others' + return autores_by_ct['others'].filter( + tipo_id=tipo).order_by('nome') - return autores_by_ct[tipo_autor].filter(tipo_id=tipo).order_by('nome') + return autores_by_ct[tipo].order_by('nome') def atualizar_autores(request): - if ('tipo_autor' in request.GET and 'materia_id' in request.GET and - request.GET['tipo_autor'] and request.GET['materia_id']): - tipo_autor = request.GET['tipo_autor'] + if 'materia_id' in request.GET and request.GET['materia_id']: materia_id = int(request.GET['materia_id']) try: materia = MateriaLegislativa.objects.get(id=materia_id) except ObjectDoesNotExist: - autores = [] + pass else: - autores = autores_ativos(materia, tipo=tipo_autor) - autores_list = [(a.id, a.__str__()) for a in autores] + manter_autor = False + if 'tipo_autor' in request.GET and request.GET['tipo_autor']: + tipo_autor = request.GET['tipo_autor'] + autores = autores_ativos(materia, tipo=tipo_autor) + + # Se já houver algum autor selecionado (ex: view de update) + # no campo correspondente e caso o TipoAutor selecionado + # seja o mesmo do autor que está aualmente marcado + # deve ser enviado um sinal (manter autor) para que o + # javascript mantenha este selecionado + if 'autor_id' in request.GET and request.GET['autor_id']: + try: + autor = Autor.objects.get(id=request.GET['autor_id']) + except ObjectDoesNotExist: + pass + else: + if autor.tipo.id == int(tipo_autor): + manter_autor = True - # Se já houver algum autor selecionado (ex: view de update) - # no campo correspondente e caso o TipoAutor selecionado - # seja o mesmo do autor que está aualmente marcado - # deve ser enviado um sinal (manter autor) para que o javascript - # mantenha este selecionado - manter_autor = False - if 'autor_id' in request.GET and request.GET['autor_id']: - try: - autor = Autor.objects.get(id=request.GET['autor_id']) - except ObjectDoesNotExist: - pass else: - if autor.tipo.id == int(tipo_autor): - manter_autor = True + autores = autores_ativos(materia) + + autores_list = [(a.id, a.__str__()) for a in autores] + + return JsonResponse({'lista_autores': autores_list, + 'manter_autor': manter_autor}) - return JsonResponse({'lista_autores': autores_list, - 'manter_autor': manter_autor}) + return JsonResponse({}) class AutoriaCrud(MasterDetailCrud): diff --git a/sapl/templates/materia/autoria_form.html b/sapl/templates/materia/autoria_form.html index 7a35884d8..3c1f3f587 100644 --- a/sapl/templates/materia/autoria_form.html +++ b/sapl/templates/materia/autoria_form.html @@ -25,11 +25,12 @@ lista_autores = data['lista_autores']; // Atualiza a listagem de autores - for (i = 0; i < lista_autores.length; i++) { - $('#id_autor').append( - ''); - } + + $.each(lista_autores, function(index, value) { + $('#id_autor').append( + ''); + }); if (data['manter_autor'] == true){ $('#id_autor').val(autor_id); From b92fc979895f54215d6e0e532fa566951e2cf6a8 Mon Sep 17 00:00:00 2001 From: Eduardo Calil Date: Thu, 3 Aug 2017 11:46:17 -0300 Subject: [PATCH 19/20] =?UTF-8?q?Mudan=C3=A7as=20significativas=20no=20c?= =?UTF-8?q?=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/materia/forms.py | 17 ++++++--- sapl/materia/views.py | 47 +++++++++++++----------- sapl/templates/materia/autoria_form.html | 14 +++---- 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index af8a82630..4a3018735 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -719,13 +719,20 @@ class AutoriaForm(ModelForm): if self.errors: return self.errors - if not self.instance.pk: - if Autoria.objects.filter( - materia=self.instance.materia, - autor=self.cleaned_data['autor'], - ).exists(): + if Autoria.objects.filter( + materia=self.instance.materia, + autor=self.cleaned_data['autor'], + ).exists(): + if not self.instance.pk: msg = _('Esse Autor já foi cadastrado.') raise ValidationError(msg) + else: + autoria = Autoria.objects.get( + materia=self.instance.materia, + autor=self.cleaned_data['autor']) + if autoria != self.instance: + msg = _('Esse Autor já foi cadastrado.') + raise ValidationError(msg) return self.cleaned_data diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 5c15fae38..52d6891b1 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -1211,32 +1211,17 @@ def atualizar_autores(request): except ObjectDoesNotExist: pass else: - manter_autor = False if 'tipo_autor' in request.GET and request.GET['tipo_autor']: tipo_autor = request.GET['tipo_autor'] autores = autores_ativos(materia, tipo=tipo_autor) - # Se já houver algum autor selecionado (ex: view de update) - # no campo correspondente e caso o TipoAutor selecionado - # seja o mesmo do autor que está aualmente marcado - # deve ser enviado um sinal (manter autor) para que o - # javascript mantenha este selecionado - if 'autor_id' in request.GET and request.GET['autor_id']: - try: - autor = Autor.objects.get(id=request.GET['autor_id']) - except ObjectDoesNotExist: - pass - else: - if autor.tipo.id == int(tipo_autor): - manter_autor = True - else: autores = autores_ativos(materia) + empty_option = [('', '---------')] autores_list = [(a.id, a.__str__()) for a in autores] - return JsonResponse({'lista_autores': autores_list, - 'manter_autor': manter_autor}) + return JsonResponse({'lista_autores': empty_option + autores_list}) return JsonResponse({}) @@ -1260,13 +1245,20 @@ class AutoriaCrud(MasterDetailCrud): materia = MateriaLegislativa.objects.get( id=int(kwargs['root_pk'])) - autores_ativos_list = autores_ativos(materia) + if context['form']['tipo_autor'].data: + autores_ativos_list = autores_ativos( + materia, + context['form']['tipo_autor'].data + ) + else: + autores_ativos_list = autores_ativos(materia) autores = [] for a in autores_ativos_list: autores.append([a.id, a.__str__()]) - context['form'].fields['autor'].choices = autores + empty_option = [('', '---------')] + context['form'].fields['autor'].choices = empty_option + autores return context class UpdateView(MasterDetailCrud.UpdateView): @@ -1289,13 +1281,26 @@ class AutoriaCrud(MasterDetailCrud): materia = MateriaLegislativa.objects.get( id=int(kwargs['root_pk'])) - autores_ativos_list = autores_ativos(materia) + if context['form']['tipo_autor'].data is None: + autores_ativos_list = autores_ativos( + materia, + str(context['object'].autor.tipo.id)) + else: + if context['form']['tipo_autor'].data == '': + autores_ativos_list = autores_ativos( + materia) + else: + autores_ativos_list = autores_ativos( + materia, + context['form']['tipo_autor'].data) autores = [] for a in autores_ativos_list: autores.append([a.id, a.__str__()]) - context['form'].fields['autor'].choices = autores + empty_option = [('', '---------')] + context['form'].fields['autor'].choices = empty_option + autores + return context class ListView(MasterDetailCrud.ListView): diff --git a/sapl/templates/materia/autoria_form.html b/sapl/templates/materia/autoria_form.html index 3c1f3f587..1241092db 100644 --- a/sapl/templates/materia/autoria_form.html +++ b/sapl/templates/materia/autoria_form.html @@ -10,13 +10,12 @@ function atualizar_autores() { var tipo_autor = $("#id_tipo_autor").val(); var materia_id = {{ root_pk }}; - var autor_id = $("#id_autor").val(); // Limpa a listagem para atualizar $("#id_autor").find("option").remove(); $.get("/materia/autoria/atualizar-autores", - {tipo_autor: tipo_autor, materia_id: materia_id, autor_id: autor_id}, + {tipo_autor: tipo_autor, materia_id: materia_id}, function(data) { // Caso não venha nenhum dado da requisição, retorna null if ($.isEmptyObject(data)){ @@ -26,15 +25,12 @@ lista_autores = data['lista_autores']; // Atualiza a listagem de autores - $.each(lista_autores, function(index, value) { - $('#id_autor').append( - ''); + $.each(lista_autores, function(index, obj) { + $("#id_autor").append($("") + .attr("value", obj[0]) + .text(obj[1])); }); - if (data['manter_autor'] == true){ - $('#id_autor').val(autor_id); - } }); } $("#id_tipo_autor").change(function () { From 56b6b4f46e08dcb6533059f352d4469ef63e66f1 Mon Sep 17 00:00:00 2001 From: Eduardo Calil Date: Fri, 4 Aug 2017 11:43:29 -0300 Subject: [PATCH 20/20] Fix #1286 --- sapl/crispy_layout_mixin.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sapl/crispy_layout_mixin.py b/sapl/crispy_layout_mixin.py index 29cd7849f..e86badf0d 100644 --- a/sapl/crispy_layout_mixin.py +++ b/sapl/crispy_layout_mixin.py @@ -67,7 +67,11 @@ def get_field_display(obj, fieldname): ou mesmo uma método no model. """ value = getattr(obj, fieldname) - verbose_name = '' + + try: + verbose_name = value.model._meta.verbose_name + except AttributeError: + verbose_name = '' else: verbose_name = str(field.verbose_name)\