From 57d5a6cc389d862f01fe42f7efd950ae94385ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ses=C3=B3stris=20Vieira?= Date: Wed, 29 Jun 2022 16:14:49 -0300 Subject: [PATCH] Adicona parlamentares do TSE --- sigi/apps/casas/admin.py | 225 ++++------ .../migrations/0024_delete_presidente.py | 16 + sigi/apps/casas/models.py | 43 +- .../admin/casas/anexo_convenio_snippet.html | 10 + .../admin/casas/orgao/change_form.html | 75 ++++ sigi/apps/casas/views.py | 10 +- ...25_alter_projeto_modelo_minuta_and_more.py | 25 ++ ...26_alter_projeto_modelo_minuta_and_more.py | 25 ++ ...27_alter_projeto_modelo_minuta_and_more.py | 25 ++ ...28_alter_projeto_modelo_minuta_and_more.py | 25 ++ ...29_alter_projeto_modelo_minuta_and_more.py | 25 ++ sigi/apps/convenios/models.py | 18 +- sigi/apps/eventos/forms.py | 23 + sigi/apps/eventos/static/css/convite.css | 23 + .../templates/eventos/alocacao_equipe.html | 4 +- .../eventos/templates/eventos/calendario.html | 2 +- .../templates/eventos/convida_casa.html | 196 +++++---- .../templates/eventos/oficio_padrao.html | 4 +- .../snippets/form_presidente_snippet.html | 11 + sigi/apps/eventos/urls.py | 5 + sigi/apps/eventos/views.py | 69 ++- sigi/apps/ocorrencias/models.py | 2 +- sigi/apps/parlamentares/admin.py | 392 ++++++++---------- sigi/apps/parlamentares/apps.py | 7 + sigi/apps/parlamentares/forms.py | 40 ++ sigi/apps/parlamentares/jobs/__init__.py | 4 + .../apps/parlamentares/jobs/daily/__init__.py | 0 .../parlamentares/jobs/hourly/__init__.py | 0 .../parlamentares/jobs/minutely/__init__.py | 0 .../jobs/minutely/importa_parlamentar.py | 355 ++++++++++++++++ .../parlamentares/jobs/monthly/__init__.py | 0 sigi/apps/parlamentares/jobs/sample.py | 9 + .../parlamentares/jobs/weekly/__init__.py | 0 .../parlamentares/jobs/yearly/__init__.py | 0 .../parlamentares/migrations/0001_initial.py | 53 ++- .../migrations/0002_auto_20210406_1945.py | 6 +- ...er_cargo_id_alter_coligacao_id_and_more.py | 98 +++++ ...5_remove_coligacao_legislatura_and_more.py | 122 ++++++ .../migrations/0006_alter_partido_sigla.py | 18 + .../0007_alter_parlamentar_options.py | 17 + .../migrations/0008_alter_parlamentar_foto.py | 18 + ...eicao_parlamentar_flag_importa_and_more.py | 38 ++ .../0010_alter_parlamentar_options.py | 17 + sigi/apps/parlamentares/models.py | 245 +++-------- .../cart/change_list_import_cart_export.html | 11 + .../parlamentar/change_list.html | 1 - .../templates/parlamentares/import.html | 94 +++++ .../templates/parlamentares/import_email.html | 36 ++ .../parlamentares/import_result.html | 37 ++ sigi/apps/parlamentares/urls.py | 32 +- sigi/apps/parlamentares/views.py | 154 +------ sigi/menu_conf.yaml | 8 +- sigi/settings.py | 1 + sigi/urls.py | 1 + 54 files changed, 1779 insertions(+), 896 deletions(-) create mode 100644 sigi/apps/casas/migrations/0024_delete_presidente.py create mode 100644 sigi/apps/casas/templates/admin/casas/anexo_convenio_snippet.html create mode 100644 sigi/apps/casas/templates/admin/casas/orgao/change_form.html create mode 100644 sigi/apps/convenios/migrations/0025_alter_projeto_modelo_minuta_and_more.py create mode 100644 sigi/apps/convenios/migrations/0026_alter_projeto_modelo_minuta_and_more.py create mode 100644 sigi/apps/convenios/migrations/0027_alter_projeto_modelo_minuta_and_more.py create mode 100644 sigi/apps/convenios/migrations/0028_alter_projeto_modelo_minuta_and_more.py create mode 100644 sigi/apps/convenios/migrations/0029_alter_projeto_modelo_minuta_and_more.py create mode 100644 sigi/apps/eventos/static/css/convite.css create mode 100644 sigi/apps/eventos/templates/eventos/snippets/form_presidente_snippet.html create mode 100644 sigi/apps/parlamentares/apps.py create mode 100644 sigi/apps/parlamentares/forms.py create mode 100644 sigi/apps/parlamentares/jobs/__init__.py create mode 100644 sigi/apps/parlamentares/jobs/daily/__init__.py create mode 100644 sigi/apps/parlamentares/jobs/hourly/__init__.py create mode 100644 sigi/apps/parlamentares/jobs/minutely/__init__.py create mode 100644 sigi/apps/parlamentares/jobs/minutely/importa_parlamentar.py create mode 100644 sigi/apps/parlamentares/jobs/monthly/__init__.py create mode 100644 sigi/apps/parlamentares/jobs/sample.py create mode 100644 sigi/apps/parlamentares/jobs/weekly/__init__.py create mode 100644 sigi/apps/parlamentares/jobs/yearly/__init__.py create mode 100644 sigi/apps/parlamentares/migrations/0004_partido_legenda_alter_cargo_id_alter_coligacao_id_and_more.py create mode 100644 sigi/apps/parlamentares/migrations/0005_remove_coligacao_legislatura_and_more.py create mode 100644 sigi/apps/parlamentares/migrations/0006_alter_partido_sigla.py create mode 100644 sigi/apps/parlamentares/migrations/0007_alter_parlamentar_options.py create mode 100644 sigi/apps/parlamentares/migrations/0008_alter_parlamentar_foto.py create mode 100644 sigi/apps/parlamentares/migrations/0009_parlamentar_ano_eleicao_parlamentar_flag_importa_and_more.py create mode 100644 sigi/apps/parlamentares/migrations/0010_alter_parlamentar_options.py create mode 100644 sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/cart/change_list_import_cart_export.html delete mode 100644 sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/change_list.html create mode 100644 sigi/apps/parlamentares/templates/parlamentares/import.html create mode 100644 sigi/apps/parlamentares/templates/parlamentares/import_email.html create mode 100644 sigi/apps/parlamentares/templates/parlamentares/import_result.html diff --git a/sigi/apps/casas/admin.py b/sigi/apps/casas/admin.py index ace3625..f25efa2 100644 --- a/sigi/apps/casas/admin.py +++ b/sigi/apps/casas/admin.py @@ -1,12 +1,13 @@ from django.contrib import admin from django.contrib.contenttypes.admin import GenericTabularInline +from django.template.loader import render_to_string from django.urls import reverse from django.utils.safestring import mark_safe from django.utils.translation import gettext as _ from django_weasyprint.views import WeasyTemplateResponse from import_export.fields import Field from sigi.apps.casas.forms import OrgaoForm -from sigi.apps.casas.models import Orgao, Presidente, Funcionario, TipoOrgao +from sigi.apps.casas.models import Orgao, Funcionario, TipoOrgao from sigi.apps.casas.filters import ( GerentesInterlegisFilter, ConvenioFilter, @@ -16,8 +17,10 @@ from sigi.apps.casas.filters import ( from sigi.apps.contatos.models import Telefone from sigi.apps.convenios.models import Convenio from sigi.apps.ocorrencias.models import Ocorrencia +from sigi.apps.parlamentares.models import Parlamentar from sigi.apps.servicos.models import Servico from sigi.apps.servicos.filters import ServicoAtivoFilter +from sigi.apps.servidores.models import Servidor from sigi.apps.utils import queryset_ascii from sigi.apps.utils.mixins import CartExportReportMixin, LabeledResourse @@ -75,79 +78,30 @@ class TelefonesInline(GenericTabularInline): extra = 1 -class PresidenteInline(admin.StackedInline): - model = Presidente +class ParlamentarInline(admin.StackedInline): + model = Parlamentar fields = ( - "nome", - "sexo", + "foto", + "nome_parlamentar", + "nome_completo", + "partido", + "presidente", "data_nascimento", "cpf", "identidade", - "nota", + "telefones", "email", - "tempo_de_servico", - "ult_alteracao", - "endereco", - "municipio", - "bairro", - "cep", "redes_sociais", - ) - autocomplete_fields = ("municipio",) - readonly_fields = ("ult_alteracao",) - extra = 1 - max_num = 1 - verbose_name_plural = _("Presidente") - - def get_queryset(self, request): - return ( - self.model.objects.exclude(desativado=True) - .extra(select={"ult_null": "ult_alteracao is null"}) - .order_by("ult_null", "-ult_alteracao") - # A função extra foi usada para quando existir um registro com o - # campo igual a null não aparecer na frente dos mais novos - ) - - -class ContatoInterlegisInline(admin.StackedInline): - model = Funcionario - fields = ( - "nome", - "sexo", - "data_nascimento", - "cpf", - "identidade", - "nota", - "email", - "cargo", - "funcao", - "setor", - "tempo_de_servico", "ult_alteracao", - "endereco", - "municipio", - "bairro", - "cep", - "redes_sociais", - "desativado", - "observacoes", ) - autocomplete_fields = ("municipio",) readonly_fields = ("ult_alteracao",) - extra = 1 - inlines = (TelefonesInline,) - verbose_name_plural = _("Contato(s) Interlegis Vigente(s)") + extra = 0 - def get_queryset(self, request): - return ( - self.model.objects.filter(setor="contato_interlegis") - .extra(select={"ult_null": "ult_alteracao is null"}) - .order_by("-ult_alteracao") - ) + def has_add_permission(self, request, *args, **kwargs): + return False - def get_extra(self, request, obj=None, **kwargs): - extra = 0 - return extra + def has_delete_permission(self, request, *args, **kwargs): + return False class FuncionariosInline(admin.StackedInline): @@ -174,15 +128,11 @@ class FuncionariosInline(admin.StackedInline): autocomplete_fields = ("municipio",) readonly_fields = ("ult_alteracao",) extra = 1 - inlines = (TelefonesInline,) - verbose_name_plural = _("Outros Contatos da Casa") + verbose_name_plural = _("Contatos da Casa") def get_queryset(self, request): return ( - self.model.objects.exclude( - cargo="Presidente", - ) - .exclude(desativado=True) + self.model.objects.exclude(desativado=True) .extra(select={"ult_null": "ult_alteracao is null"}) .order_by("ult_null", "-ult_alteracao") # A função extra foi usada para quando existir um registro com @@ -190,71 +140,46 @@ class FuncionariosInline(admin.StackedInline): ) -class ConveniosInline(admin.TabularInline): +class ConveniosInline(admin.StackedInline): model = Convenio - fieldsets = ( - ( - None, - { - "fields": ( - ( - "link_sigad", - "status_convenio", - "num_convenio", - "projeto", - "observacao", - ), - ( - "data_retorno_assinatura", - "data_pub_diario", - ), - ("get_anexos",), - ("link_convenio",), - ) - }, - ), - ) - readonly_fields = [ - "link_convenio", + fields = ( + "num_processo_sf", "link_sigad", "status_convenio", "num_convenio", "projeto", "observacao", - "data_adesao", "data_retorno_assinatura", - "data_termo_aceite", + "data_termino_vigencia", "data_pub_diario", - "data_devolucao_via", - "data_postagem_correio", - "data_devolucao_sem_assinatura", - "data_retorno_sem_assinatura", + "data_sigad", + "data_solicitacao", + "get_anexos", + ) + readonly_fields = [ + "link_sigad", + "status_convenio", "get_anexos", ] + ordering = ("-data_retorno_assinatura",) extra = 0 can_delete = False - ordering = ("-data_retorno_assinatura",) - - def has_add_permission(self, request, obj): - return False + show_change_link = True + @admin.display(description=_("Anexos")) def get_anexos(self, obj): return mark_safe( - "
".join( - [ - f'{a}' - for a in obj.anexo_set.all() - ] + render_to_string( + "admin/casas/anexo_convenio_snippet.html", + context={"anexos": obj.anexo_set.all()}, ) ) - get_anexos.short_description = _("Anexos") - + @admin.display(description=_("Status do convênio")) def status_convenio(self, obj): if obj.pk is None: return "" status = obj.get_status() - if status in ["Vencido", "Desistência", "Cancelado"]: label = r"danger" elif status == "Vigente": @@ -263,33 +188,16 @@ class ConveniosInline(admin.TabularInline): label = r"warning" else: label = r"info" - return mark_safe(f'

{status}

') - status_convenio.short_description = _("Status do convênio") - - def link_convenio(self, obj): - if obj.pk is None: - return "" - opts = self.opts - url = reverse( - f"admin:{opts.app_label}_{opts.model_name}_change", args=[obj.pk] - ) - return mark_safe( - f'edit' - ) - - link_convenio.short_description = _("Editar convenio") - + @admin.display(description=_("Ver no SIGAD")) def link_sigad(self, obj): if obj.pk is None: return "" - return mark_safe(obj.get_sigad_url()) - - link_sigad.short_description = _("Processo no Senado") + return mark_safe(obj.get_sigad_url(display_type="icone")) -class ServicoInline(admin.TabularInline): +class ServicoInline(admin.StackedInline): model = Servico fields = ( "tipo_servico", @@ -301,45 +209,38 @@ class ServicoInline(admin.TabularInline): "motivo_desativacao", ) readonly_fields = ["data_alteracao"] - extra = 1 ordering = ("tipo_servico", "-data_alteracao") + extra = 0 + show_change_link = True -class OcorrenciaInline(admin.TabularInline): +class OcorrenciaInline(admin.StackedInline): model = Ocorrencia fields = ( "data_criacao", + "categoria", + "tipo_contato", "assunto", "prioridade", "status", + "descricao", + "resolucao", + "ticket", "data_modificacao", - "link_editar", ) readonly_fields = ( "data_criacao", - "assunto", - "prioridade", - "status", "data_modificacao", - "link_editar", ) + ordering = ("-data_modificacao",) extra = 0 - max_num = 0 can_delete = False - ordering = ("-data_modificacao",) + show_change_link = True - def link_editar(self, obj): - if obj.pk is None: - return "" - opts = self.opts - url = reverse( - f"admin:{opts.app_label}_{opts.model_name}_change", args=[obj.pk] - ) - return mark_safe( - f'edit' - ) - - link_editar.short_description = _("Editar") + def has_add_permission(self, request, obj): + if Servidor.objects.filter(user=request.user).exists(): + return super().has_add_permission(request, obj) + return False @admin.register(Orgao) @@ -348,8 +249,7 @@ class OrgaoAdmin(CartExportReportMixin, admin.ModelAdmin): resource_class = OrgaoExportResourse inlines = ( TelefonesInline, - PresidenteInline, - ContatoInterlegisInline, + ParlamentarInline, FuncionariosInline, ConveniosInline, ServicoInline, @@ -365,6 +265,7 @@ class OrgaoAdmin(CartExportReportMixin, admin.ModelAdmin): "get_servicos", ) list_display_links = ( + "id", "sigla", "nome", ) @@ -464,6 +365,20 @@ class OrgaoAdmin(CartExportReportMixin, admin.ModelAdmin): queryset = super(OrgaoAdmin, self).get_queryset(request) return queryset.prefetch_related("gerentes_interlegis", "convenio_set") + def save_related(self, request, form, formsets, change): + for formset in formsets: + if formset.model == Ocorrencia: + formset.save(commit=False) + for obj in formset.new_objects: + if ( + not hasattr(obj, "servidor_registro") + or obj.servidor_registro is None + ): + obj.servidor_registro = Servidor.objects.get( + user=request.user + ) + return super().save_related(request, form, formsets, change) + def get_uf(self, obj): return obj.municipio.uf.nome diff --git a/sigi/apps/casas/migrations/0024_delete_presidente.py b/sigi/apps/casas/migrations/0024_delete_presidente.py new file mode 100644 index 0000000..512fbfe --- /dev/null +++ b/sigi/apps/casas/migrations/0024_delete_presidente.py @@ -0,0 +1,16 @@ +# Generated by Django 4.0.4 on 2022-06-18 13:14 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('casas', '0023_funcionario_cpf_funcionario_identidade'), + ] + + operations = [ + migrations.DeleteModel( + name='Presidente', + ), + ] diff --git a/sigi/apps/casas/models.py b/sigi/apps/casas/models.py index e86e196..345ef1d 100644 --- a/sigi/apps/casas/models.py +++ b/sigi/apps/casas/models.py @@ -149,7 +149,7 @@ class Orgao(models.Model): @property def num_parlamentares(self): - return 0 + return self.parlamentar_set.count() @property def telefone(self): @@ -160,28 +160,11 @@ class Orgao(models.Model): @property def presidente(self): - try: - if self.funcionario_set.filter(setor="presidente").count() > 1: - return self.funcionario_set.filter(setor="presidente")[0] - else: - return self.funcionario_set.get(setor="presidente") - except Funcionario.DoesNotExist: - return None + return self.parlamentar_set.filter(presidente=True).first() @property def contato_interlegis(self): - try: - if ( - self.funcionario_set.filter(setor="contato_interlegis").count() - > 1 - ): - return self.funcionario_set.filter(setor="contato_interlegis")[ - 0 - ] - else: - return self.funcionario_set.get(setor="contato_interlegis") - except Funcionario.DoesNotExist: - return None + return self.funcionario_set.filter(setor="contato_interlegis").first() def __str__(self): return self.nome @@ -308,23 +291,3 @@ class Funcionario(models.Model): def __str__(self): return self.nome - - -class PresidenteManager(models.Manager): - def get_queryset(self): - qs = super(PresidenteManager, self).get_queryset() - qs = qs.filter(setor="presidente") - return qs - - -class Presidente(Funcionario): - class Meta: - proxy = True - - objects = PresidenteManager() - - def save(self, *args, **kwargs): - self.setor = "presidente" - self.cargo = "Presidente" - self.funcao = "Presidente" - return super(Presidente, self).save(*args, **kwargs) diff --git a/sigi/apps/casas/templates/admin/casas/anexo_convenio_snippet.html b/sigi/apps/casas/templates/admin/casas/anexo_convenio_snippet.html new file mode 100644 index 0000000..466bfbb --- /dev/null +++ b/sigi/apps/casas/templates/admin/casas/anexo_convenio_snippet.html @@ -0,0 +1,10 @@ +{% load i18n %} +{% if not anexos %} +

{% trans "Nenhum anexo no convênio" %}

+{% else %} +
+ {% for anexo in anexos %} + {{ anexo }} + {% endfor %} +
+{% endif %} \ No newline at end of file diff --git a/sigi/apps/casas/templates/admin/casas/orgao/change_form.html b/sigi/apps/casas/templates/admin/casas/orgao/change_form.html new file mode 100644 index 0000000..37ed774 --- /dev/null +++ b/sigi/apps/casas/templates/admin/casas/orgao/change_form.html @@ -0,0 +1,75 @@ +{% extends "admin/change_form.html" %} +{% load i18n %} + +{% block extrastyle %} + {{ block.super }} +{% endblock %} + +{% block form_top %} +
+
+ +
+
+{% endblock %} + +{% block field_sets %} +{% for fieldset in adminform %} +
+ {% include "admin/includes/fieldset.html" %} +
+{% endfor %} +{% endblock %} + +{% block inline_field_sets %} +{% for inline_admin_formset in inline_admin_formsets %} +
+ {% include inline_admin_formset.opts.template %} +
+{% endfor %} +{% endblock %} + + +{% block footer %} + {{ block.super }} + + +{% endblock %} diff --git a/sigi/apps/casas/views.py b/sigi/apps/casas/views.py index f00bde6..c2ab642 100644 --- a/sigi/apps/casas/views.py +++ b/sigi/apps/casas/views.py @@ -1,23 +1,15 @@ import csv from functools import reduce - -from django.contrib import messages -from sigi.apps.utils import to_ascii - -from django.conf import settings +from django.db.models import Count, Q from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator, InvalidPage, EmptyPage -from django.db.models import Count, Q from django.http import ( HttpResponse, HttpResponseRedirect, - HttpResponseForbidden, ) from django.shortcuts import render, get_object_or_404 from django.utils.translation import gettext as _, ngettext -from django.views.generic import View from django_weasyprint.views import WeasyTemplateView - from sigi.apps.casas.forms import PortfolioForm, AtualizaCasaForm from sigi.apps.casas.models import Orgao, TipoOrgao, Funcionario from sigi.apps.servidores.models import Servidor diff --git a/sigi/apps/convenios/migrations/0025_alter_projeto_modelo_minuta_and_more.py b/sigi/apps/convenios/migrations/0025_alter_projeto_modelo_minuta_and_more.py new file mode 100644 index 0000000..81fe8c1 --- /dev/null +++ b/sigi/apps/convenios/migrations/0025_alter_projeto_modelo_minuta_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0.4 on 2022-06-20 23:56 + +import django.core.validators +from django.db import migrations, models +import tinymce.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('convenios', '0024_alter_anexo_data_pub'), + ] + + operations = [ + migrations.AlterField( + model_name='projeto', + name='modelo_minuta', + field=models.FileField(blank=True, help_text='\nUtilize os seguintes placeholders\n\n', upload_to='convenios/minutas/', validators=[django.core.validators.FileExtensionValidator(['docx'])], verbose_name='Modelo de minuta'), + ), + migrations.AlterField( + model_name='projeto', + name='texto_oficio', + field=tinymce.models.HTMLField(blank=True, help_text='\nUtilize os seguintes placeholders\n\n', verbose_name='texto do ofício'), + ), + ] diff --git a/sigi/apps/convenios/migrations/0026_alter_projeto_modelo_minuta_and_more.py b/sigi/apps/convenios/migrations/0026_alter_projeto_modelo_minuta_and_more.py new file mode 100644 index 0000000..ce7dc7d --- /dev/null +++ b/sigi/apps/convenios/migrations/0026_alter_projeto_modelo_minuta_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0.4 on 2022-06-21 12:05 + +import django.core.validators +from django.db import migrations, models +import tinymce.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('convenios', '0025_alter_projeto_modelo_minuta_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='projeto', + name='modelo_minuta', + field=models.FileField(blank=True, help_text='\nUtilize os seguintes placeholders\n\n', upload_to='convenios/minutas/', validators=[django.core.validators.FileExtensionValidator(['docx'])], verbose_name='Modelo de minuta'), + ), + migrations.AlterField( + model_name='projeto', + name='texto_oficio', + field=tinymce.models.HTMLField(blank=True, help_text='\nUtilize os seguintes placeholders\n\n', verbose_name='texto do ofício'), + ), + ] diff --git a/sigi/apps/convenios/migrations/0027_alter_projeto_modelo_minuta_and_more.py b/sigi/apps/convenios/migrations/0027_alter_projeto_modelo_minuta_and_more.py new file mode 100644 index 0000000..aaa374a --- /dev/null +++ b/sigi/apps/convenios/migrations/0027_alter_projeto_modelo_minuta_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0.4 on 2022-06-21 13:23 + +import django.core.validators +from django.db import migrations, models +import tinymce.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('convenios', '0026_alter_projeto_modelo_minuta_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='projeto', + name='modelo_minuta', + field=models.FileField(blank=True, help_text='\nUtilize os seguintes placeholders\n\n', upload_to='convenios/minutas/', validators=[django.core.validators.FileExtensionValidator(['docx'])], verbose_name='Modelo de minuta'), + ), + migrations.AlterField( + model_name='projeto', + name='texto_oficio', + field=tinymce.models.HTMLField(blank=True, help_text='\nUtilize os seguintes placeholders\n\n', verbose_name='texto do ofício'), + ), + ] diff --git a/sigi/apps/convenios/migrations/0028_alter_projeto_modelo_minuta_and_more.py b/sigi/apps/convenios/migrations/0028_alter_projeto_modelo_minuta_and_more.py new file mode 100644 index 0000000..8bffff2 --- /dev/null +++ b/sigi/apps/convenios/migrations/0028_alter_projeto_modelo_minuta_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0.4 on 2022-06-21 19:28 + +import django.core.validators +from django.db import migrations, models +import tinymce.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('convenios', '0027_alter_projeto_modelo_minuta_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='projeto', + name='modelo_minuta', + field=models.FileField(blank=True, help_text='\nUtilize os seguintes placeholders\n\n', upload_to='convenios/minutas/', validators=[django.core.validators.FileExtensionValidator(['docx'])], verbose_name='Modelo de minuta'), + ), + migrations.AlterField( + model_name='projeto', + name='texto_oficio', + field=tinymce.models.HTMLField(blank=True, help_text='\nUtilize os seguintes placeholders\n\n', verbose_name='texto do ofício'), + ), + ] diff --git a/sigi/apps/convenios/migrations/0029_alter_projeto_modelo_minuta_and_more.py b/sigi/apps/convenios/migrations/0029_alter_projeto_modelo_minuta_and_more.py new file mode 100644 index 0000000..66f1196 --- /dev/null +++ b/sigi/apps/convenios/migrations/0029_alter_projeto_modelo_minuta_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0.4 on 2022-06-21 22:24 + +import django.core.validators +from django.db import migrations, models +import tinymce.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('convenios', '0028_alter_projeto_modelo_minuta_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='projeto', + name='modelo_minuta', + field=models.FileField(blank=True, help_text='\nUtilize os seguintes placeholders\n\n', upload_to='convenios/minutas/', validators=[django.core.validators.FileExtensionValidator(['docx'])], verbose_name='Modelo de minuta'), + ), + migrations.AlterField( + model_name='projeto', + name='texto_oficio', + field=tinymce.models.HTMLField(blank=True, help_text='\nUtilize os seguintes placeholders\n\n', verbose_name='texto do ofício'), + ), + ] diff --git a/sigi/apps/convenios/models.py b/sigi/apps/convenios/models.py index 379757c..abd8143 100644 --- a/sigi/apps/convenios/models.py +++ b/sigi/apps/convenios/models.py @@ -11,6 +11,7 @@ from django.utils.translation import gettext as _ from tinymce.models import HTMLField from sigi.apps.contatos.models import Municipio, UnidadeFederativa from sigi.apps.eventos.models import Evento +from sigi.apps.parlamentares.models import Parlamentar from sigi.apps.utils import to_ascii from sigi.apps.casas.models import Funcionario, Orgao from sigi.apps.servidores.models import Servidor, Servico @@ -23,7 +24,7 @@ class Projeto(models.Model): [ ("evento", Evento), ("casa", Orgao), - ("presidente", Funcionario), + ("presidente", Parlamentar), ("contato", Funcionario), ("casa.municipio", Municipio), ("casa.municipio.uf", UnidadeFederativa), @@ -36,7 +37,7 @@ class Projeto(models.Model): [ ("evento", Evento), ("casa", Orgao), - ("presidente", Funcionario), + ("presidente", Parlamentar), ("contato", Funcionario), ("casa.municipio", Municipio), ("casa.municipio.uf", UnidadeFederativa), @@ -263,20 +264,27 @@ class Convenio(models.Model): return "" return obj.get_sigad_url() - def get_sigad_url(self): + def get_sigad_url(self, display_type="numero"): m = re.match( r"(?P00100|00200)\.(?P\d{6})/(?P\d{4})-\d{2}", self.num_processo_sf, ) if m: orgao, sequencial, ano = m.groups() + if display_type == "numero": + display = self.num_processo_sf + else: + display = "visibility" return ( f'{self.num_processo_sf}' + f'target="_blank">{display}' ) - return self.num_processo_sf + if display_type == "numero": + return self.num_processo_sf + else: + return "visibility_off" def save(self, *args, **kwargs): self.conveniada = self.data_retorno_assinatura is not None diff --git a/sigi/apps/eventos/forms.py b/sigi/apps/eventos/forms.py index b14da76..e56b4b9 100644 --- a/sigi/apps/eventos/forms.py +++ b/sigi/apps/eventos/forms.py @@ -3,6 +3,7 @@ from django.utils.translation import gettext as _ from material.admin.widgets import MaterialAdminTextareaWidget from sigi.apps.casas.models import Funcionario, Orgao from sigi.apps.eventos.models import Convite, ModeloDeclaracao, Evento +from sigi.apps.parlamentares.models import Parlamentar class EventoAdminForm(forms.ModelForm): @@ -79,3 +80,25 @@ class FuncionarioForm(forms.ModelForm): "nota": MaterialAdminTextareaWidget, "redes_sociais": MaterialAdminTextareaWidget, } + + +class ParlamentarForm(forms.ModelForm): + class Meta: + model = Parlamentar + fields = [ + "nome_completo", + "nome_parlamentar", + "data_nascimento", + "cpf", + "identidade", + "telefones", + "email", + "redes_sociais", + "observacoes", + ] + widgets = { + "nome_completo": forms.HiddenInput, + "redes_sociais": MaterialAdminTextareaWidget, + "observacoes": MaterialAdminTextareaWidget, + "status_mandato": forms.RadioSelect, + } diff --git a/sigi/apps/eventos/static/css/convite.css b/sigi/apps/eventos/static/css/convite.css new file mode 100644 index 0000000..1d9e791 --- /dev/null +++ b/sigi/apps/eventos/static/css/convite.css @@ -0,0 +1,23 @@ +.lista_parlamentares { + width: 100%; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .14), 0 3px 1px -2px rgba(0, 0, 0, .12), 0 1px 5px 0 rgba(0, 0, 0, .2); + margin: .5rem 0 1rem 0 !important; +} + +.input-field>label { + position: relative; + font-size: 0.8rem; +} + +.suplente { + color: #a5d6a7; +} + +.inativo { + color: #9e9e9e; +} + +.presidente-foto .card-title { + position: relative; + bottom: 2em; +} \ No newline at end of file diff --git a/sigi/apps/eventos/templates/eventos/alocacao_equipe.html b/sigi/apps/eventos/templates/eventos/alocacao_equipe.html index 43a1131..d8b1fa3 100644 --- a/sigi/apps/eventos/templates/eventos/alocacao_equipe.html +++ b/sigi/apps/eventos/templates/eventos/alocacao_equipe.html @@ -57,8 +57,8 @@ print {% include "eventos/snippets/alocacao_equipe_snippet.html" with mode="html" %} diff --git a/sigi/apps/eventos/templates/eventos/calendario.html b/sigi/apps/eventos/templates/eventos/calendario.html index 2e9f3b1..229c80e 100644 --- a/sigi/apps/eventos/templates/eventos/calendario.html +++ b/sigi/apps/eventos/templates/eventos/calendario.html @@ -23,7 +23,7 @@ date_range {% endif %}
  • - picture_as_pdf + picture_as_pdf
  • diff --git a/sigi/apps/eventos/templates/eventos/convida_casa.html b/sigi/apps/eventos/templates/eventos/convida_casa.html index 077c737..08780f9 100644 --- a/sigi/apps/eventos/templates/eventos/convida_casa.html +++ b/sigi/apps/eventos/templates/eventos/convida_casa.html @@ -1,98 +1,152 @@ {% extends "admin/base_site.html" %} {% load i18n static %} +{% block extrastyle %} + {{ block.super }} + +{% endblock %} + {% block content %} -{{ block.super }} -
    {% csrf_token %} -
    -
    -
    - {% blocktranslate with casa_nome=casa.nome evento_nome=evento.nome %} - Convidar {{ casa_nome }} para {{ evento_nome }} - {% endblocktranslate %} -
    + {{ block.super }} + + {% csrf_token %} +
    +
    +
    + {% blocktranslate with casa_nome=casa.nome evento_nome=evento.nome %} + Convidar {{ casa_nome }} para {{ evento_nome }} + {% endblocktranslate %} +
    +
    -
    -
    -
    -
    -
    - {% trans "Convite" %} - {{ form_convite }} +
    +
    +
    +
    + {% trans "Convite" %} + {{ form_convite }} +
    -
    -
    -
    -
    -
    - {% trans "Casa" %} - {{ form_casa }} +
    +
    +
    +
    + {% trans "Casa" %} + {{ form_casa }} +
    -
    -
    -
    -
    -
    - {% trans "Dados do presidente" %} - {{ form_presidente }} +
    +
    +
    +
    + {% trans "Identifique o presidente" %} +
    +
    +
    +
    + search + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    {% trans "Carregando dados do parlamentar..." %}

    +
    +
    + {% if form_presidente %} + {% include "eventos/snippets/form_presidente_snippet.html" %} + {% endif %} +
    +
    -
    -
    -
    -
    -
    - - {% trans "Dados do contato Interlegis" %} - - content_copy - {% trans "Copiar dados do presidente" %} - - - {{ form_contato }} +
    +
    +
    +
    + + {% trans "Dados do contato Interlegis" %} + + content_copy + {% trans "Copiar dados do presidente" %} + + + {{ form_contato }} +
    -
    -
    -
    -
    -
    - - {% for proj in projetos %} - - {% endfor %} +
    +
    +
    +
    + + {% for proj in projetos %} + + {% endfor %} +
    -
    - + {% endblock %} {% block footer %} {{ block.super }} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/sigi/apps/eventos/templates/eventos/oficio_padrao.html b/sigi/apps/eventos/templates/eventos/oficio_padrao.html index 66e5432..19329b1 100644 --- a/sigi/apps/eventos/templates/eventos/oficio_padrao.html +++ b/sigi/apps/eventos/templates/eventos/oficio_padrao.html @@ -55,7 +55,9 @@ {% block body_content %} {{ block.super }} {% endblock %} diff --git a/sigi/apps/eventos/templates/eventos/snippets/form_presidente_snippet.html b/sigi/apps/eventos/templates/eventos/snippets/form_presidente_snippet.html new file mode 100644 index 0000000..6c33662 --- /dev/null +++ b/sigi/apps/eventos/templates/eventos/snippets/form_presidente_snippet.html @@ -0,0 +1,11 @@ +{% load i18n %} + +{% if presidente.foto %} +
    + {{ presidente.nome_completo }} + +{% endif %} +{% trans "Complemente os dados do presidente" %} +{% if presidente.foto %}
    {% endif %} +{{ form_presidente }} + diff --git a/sigi/apps/eventos/urls.py b/sigi/apps/eventos/urls.py index d507e63..debf6a4 100644 --- a/sigi/apps/eventos/urls.py +++ b/sigi/apps/eventos/urls.py @@ -17,6 +17,11 @@ urlpatterns = [ views.declaracao, name="evento-declaracao", ), + path( + "evento/presidente//", + views.presidente_form, + name="presidente-form-snippet", + ), ] # from django.conf.urls import patterns, url diff --git a/sigi/apps/eventos/views.py b/sigi/apps/eventos/views.py index 1dd2de0..620f310 100644 --- a/sigi/apps/eventos/views.py +++ b/sigi/apps/eventos/views.py @@ -1,13 +1,13 @@ import calendar import csv import locale -from datetime import datetime from functools import reduce from typing import OrderedDict +from django import forms from django.contrib import messages from django.contrib.admin.sites import site from django.contrib.auth.decorators import login_required -from django.http import HttpResponse, JsonResponse +from django.http import HttpResponse from django.shortcuts import redirect, render, get_object_or_404 from django.template import Template, Context from django.template.exceptions import TemplateSyntaxError @@ -24,15 +24,17 @@ from django_weasyprint.utils import django_url_fetcher from django_weasyprint.views import WeasyTemplateResponse from docx import Document from weasyprint import HTML -from sigi.apps.casas.models import Funcionario, Orgao, Presidente +from sigi.apps.casas.models import Funcionario, Orgao from sigi.apps.convenios.models import Projeto -from sigi.apps.eventos.models import Evento, Equipe, Convite, Modulo, Anexo +from sigi.apps.eventos.models import Evento, Convite, Anexo from sigi.apps.eventos.forms import ( SelecionaModeloForm, ConviteForm, CasaForm, FuncionarioForm, + ParlamentarForm, ) +from sigi.apps.parlamentares.models import Parlamentar from sigi.apps.servidores.models import Servidor @@ -215,6 +217,8 @@ def convida_casa(request, evento_id, casa_id): else: run_final.text += run.text run.text = "" + if run_final.text.count("{{") != run_final.text.count("}}"): + continue try: run_final.text = Template(run_final.text).render(context) run_final = None @@ -242,17 +246,23 @@ def convida_casa(request, evento_id, casa_id): data_convite=timezone.localdate(), ) - presidente = casa.presidente or Funcionario( - casa_legislativa=casa, setor="presidente" - ) contato = casa.contato_interlegis or Funcionario( casa_legislativa=casa, setor="contato_interlegis" ) + presidente = casa.presidente + parlamentares = casa.parlamentar_set.all() if request.method == "POST": + id_presidente = request.POST.get("id_presidente", None) + + if id_presidente is None: + messages.error(request, _("Presidente não foi identificado")) + return redirect(".") + presidente = get_object_or_404(Parlamentar, id=id_presidente) + form_convite = ConviteForm(request.POST, instance=convite) form_casa = CasaForm(request.POST, request.FILES, instance=casa) - form_presidente = FuncionarioForm( + form_presidente = ParlamentarForm( request.POST, instance=presidente, prefix="presidente" ) form_contato = FuncionarioForm( @@ -267,8 +277,13 @@ def convida_casa(request, evento_id, casa_id): form_contato.is_valid(), ] ): - contato = form_contato.save() - presidente = form_presidente.save() + contato = form_contato.save(commit=False) + contato.setor = "contato_interlegis" + contato.save() + presidente = form_presidente.save(commit=False) + presidente.status_mandato = "E" + presidente.presidente = True + presidente.save() casa = form_casa.save() convite = form_convite.save() @@ -321,7 +336,9 @@ def convida_casa(request, evento_id, casa_id): ) nome = f"Minuta de {projeto.sigla} da {casa.nome}"[:70] minuta = Anexo(descricao=nome, evento=evento) - minuta.arquivo.name = slugify(nome) + ".docx" + minuta.arquivo.name = ( + f"{Anexo.arquivo.field.upload_to}/{slugify(nome)}.docx" + ) doc.save(minuta.arquivo.path) minuta.save() query_str += f"anexo_id={minuta.id}" @@ -330,34 +347,52 @@ def convida_casa(request, evento_id, casa_id): else: return redirect(evento.get_absolute_url()) else: - messages.error(_("Preencha corretamente o convite")) + messages.error(request, _("Preencha corretamente o convite")) else: form_convite = ConviteForm(instance=convite) form_casa = CasaForm(instance=casa) - form_presidente = FuncionarioForm( - instance=presidente, prefix="presidente" - ) form_contato = FuncionarioForm(instance=contato, prefix="contato") + if presidente: + form_presidente = ParlamentarForm( + instance=presidente, prefix="presidente" + ) + else: + form_presidente = "" - context = site.each_context(request) + context = site.each_context(request) or {} context.update( { "form_convite": form_convite, "form_casa": form_casa, - "form_presidente": form_presidente, "form_contato": form_contato, + "form_presidente": form_presidente, "evento": evento, "convite": convite, "casa": casa, "presidente": presidente, "contato": contato, "projetos": projetos, + "parlamentares": parlamentares, } ) return render(request, "eventos/convida_casa.html", context) +@login_required +def presidente_form(request, presidente_id): + presidente = get_object_or_404(Parlamentar, pk=presidente_id) + form = ParlamentarForm(instance=presidente, prefix="presidente") + return render( + request, + "eventos/snippets/form_presidente_snippet.html", + { + "form_presidente": form, + "presidente": presidente, + }, + ) + + def gerar_anexo(casa, presidente, contato, path, modelo, nome, texto): template_string = ( f'{{% extends "eventos/{modelo}" %}}' diff --git a/sigi/apps/ocorrencias/models.py b/sigi/apps/ocorrencias/models.py index b6e369a..3ea0928 100644 --- a/sigi/apps/ocorrencias/models.py +++ b/sigi/apps/ocorrencias/models.py @@ -116,7 +116,7 @@ class Ocorrencia(models.Model): raise ValidationError( { "ticket": _( - "Já existe ocorrência " "registrada para este ticket" + "Já existe ocorrência registrada para este ticket" ) } ) diff --git a/sigi/apps/parlamentares/admin.py b/sigi/apps/parlamentares/admin.py index 5381490..312141e 100644 --- a/sigi/apps/parlamentares/admin.py +++ b/sigi/apps/parlamentares/admin.py @@ -1,43 +1,51 @@ -# -*- coding: utf-8 -*- +import csv +import json +from django.db import transaction from django.contrib import admin -from django.contrib.contenttypes import generic +from django.contrib import messages +from django.contrib.contenttypes.admin import GenericTabularInline +from django.core.files.temp import NamedTemporaryFile from django.http import HttpResponseRedirect, HttpResponse +from django.shortcuts import redirect, render +from django.urls import path, reverse +from django.utils import timezone from django.utils.html import escape, escapejs +from django.utils.safestring import mark_safe from django.utils.translation import gettext as _ - -from sigi.apps.contatos.models import Telefone -from sigi.apps.parlamentares.models import ( - Partido, - Parlamentar, - Mandato, - Legislatura, - Coligacao, - ComposicaoColigacao, - SessaoLegislativa, - MesaDiretora, - Cargo, - MembroMesaDiretora, -) -from sigi.apps.parlamentares.views import adicionar_parlamentar_carrinho -from sigi.apps.utils.base_admin import BaseModelAdmin +from sigi.apps.casas.models import Orgao +from sigi.apps.parlamentares.jobs import import_path, json_path +from sigi.apps.parlamentares.models import Partido, Parlamentar +from sigi.apps.parlamentares.forms import ImportForm from sigi.apps.utils.filters import AlphabeticFilter +from sigi.apps.utils.mixins import ( + ImportCartExportMixin, + CartExportMixin, + LabeledResourse, +) -class MandatosInline(admin.TabularInline): - model = Mandato - extra = 1 - raw_id_fields = ("legislatura", "partido") - - -class TelefonesInline(generic.GenericTabularInline): - model = Telefone - extra = 2 - - -class PartidoAdmin(BaseModelAdmin): - list_display = ("nome", "sigla") - list_display_links = ("nome", "sigla") - search_fields = ("nome", "sigla") +class ParlamentarResource(LabeledResourse): + class Meta: + model = Parlamentar + fields = ( + "casa_legislativa__nome", + "casa_legislativa__municipio__uf__sigla", + "partido__legenda", + "partido__sigla", + "partido__nome", + "presidente", + "nome_completo", + "nome_parlamentar", + "data_nascimento", + "cpf", + "identidade", + "telefones", + "email", + "redes_sociais", + "ult_alteracao", + "observacoes", + ) + export_order = fields class ParlamentarNomeCompletoFilter(AlphabeticFilter): @@ -45,221 +53,145 @@ class ParlamentarNomeCompletoFilter(AlphabeticFilter): parameter_name = "nome_completo" -class ParlamentarAdmin(BaseModelAdmin): - inlines = (TelefonesInline, MandatosInline) - list_display = ("nome_completo", "nome_parlamentar", "sexo") - list_display_links = ("nome_completo", "nome_parlamentar") - list_filter = (ParlamentarNomeCompletoFilter,) - actions = [ - "adiciona_parlamentar", - ] +@admin.register(Partido) +class PartidoAdmin(ImportCartExportMixin, admin.ModelAdmin): + list_display = ("legenda", "nome", "sigla") + search_fields = ("legenda", "nome", "sigla") + + +@admin.register(Parlamentar) +class ParlamentarAdmin(CartExportMixin, admin.ModelAdmin): + resource_class = ParlamentarResource + change_list_template = ( + "admin/parlamentares/parlamentar/cart/" + "change_list_import_cart_export.html" + ) + list_display = ( + "get_foto", + "nome_completo", + "casa_legislativa", + "status_mandato", + "get_uf", + "partido", + ) + list_filter = ( + "casa_legislativa__municipio__uf", + ("casa_legislativa__tipo", admin.RelatedOnlyFieldListFilter), + "partido", + "status_mandato", + "presidente", + ParlamentarNomeCompletoFilter, + ) fieldsets = ( ( - None, + _("mandato"), { - "fields": ("nome_completo", "nome_parlamentar", "sexo"), + "fields": ( + "casa_legislativa", + "ano_eleicao", + "partido", + "presidente", + ) }, ), - # (_('Endereço'), { - # 'fields': ('logradouro', 'bairro', 'municipio', 'cep'), - # }), ( - _("Outras informações"), + _("dados pessoais"), { - "fields": ("data_nascimento", "email", "pagina_web", "foto"), + "fields": ( + "nome_completo", + "nome_parlamentar", + "foto", + "data_nascimento", + "cpf", + "identidade", + ), }, ), + ( + _("contatos"), + {"fields": ("telefones", "email", "redes_sociais")}, + ), ) - radio_fields = {"sexo": admin.VERTICAL} - # raw_id_fields = ('municipio',) + autocomplete_fields = ("casa_legislativa",) search_fields = ( "nome_completo", "nome_parlamentar", "email", - "pagina_web", + "casa_legislativa__search_text", ) - def adiciona_parlamentar(self, request, queryset): - if "carrinho_parlametar" in request.session: - q1 = len(request.session["carrinho_parlamentar"]) - else: - q1 = 0 - adicionar_parlamentar_carrinho(request, queryset=queryset) - q2 = len(request.session["carrinho_parlamentar"]) - quant = q2 - q1 - if quant: - self.message_user( - request, _("%s Parlamentares adicionados no carrinho") % (quant) - ) + def get_urls(self): + urls = super().get_urls() + info = self.get_model_info() + my_urls = [ + path( + "import/", + self.admin_site.admin_view(self.import_action), + name="%s_%s_import" % info, + ), + path( + "import_result/", + self.admin_site.admin_view(self.result_import_action), + name="%s_%s_import_result" % info, + ), + ] + return my_urls + urls + + @admin.display( + description=_("UF"), ordering="casa_legislativa__municipio__uf__nome" + ) + def get_uf(self, obj): + return obj.casa_legislativa.municipio.uf.nome + + @mark_safe + @admin.display(description=_("Foto")) + def get_foto(self, obj): + if obj.foto: + return f'' else: - self.message_user( - request, - _( - "Os parlamentares selecionadas já foram adicionadas anteriormente" - ), + return ( + 'account_circle' ) - return HttpResponseRedirect(".") - - adiciona_parlamentar.short_description = _( - "Armazenar parlamentar no carrinho para exportar" - ) - - -class MandatoAdmin(BaseModelAdmin): - list_display = ( - "parlamentar", - "legislatura", - "partido", - "inicio_mandato", - "fim_mandato", - "is_afastado", - ) - list_filter = ("is_afastado", "partido") - search_fields = ( - "legislatura__numero", - "parlamentar__nome_completo", - "parlamentar__nome_parlamentar", - "partido__nome", - "partido__sigla", - ) - raw_id_fields = ("parlamentar", "legislatura", "partido") - - -# radio_fields = {'suplencia': admin.VERTICAL} - -class MandatoInline(admin.TabularInline): - model = Mandato - raw_id_fields = [ - "parlamentar", - ] - - -class LegislaturaAdmin(BaseModelAdmin): - date_hierarchy = "data_inicio" - list_display = ( - "numero", - "casa_legislativa", - "uf", - "data_inicio", - "data_fim", - "data_eleicao", - "total_parlamentares", - ) - raw_id_fields = ("casa_legislativa",) - list_display_links = ("numero",) - list_filter = ("casa_legislativa__municipio__uf",) - search_fields = ( - "casa_legislativa__nome", - "casa_legislativa__municipio__nome", - ) - inlines = (MandatoInline,) - - def uf(self, obj): - return obj.casa_legislativa.municipio.uf.sigla - - uf.short_description = _("UF") - uf.admin_order_field = "casa_legislativa__municipio__uf" - - def lookup_allowed(self, lookup, value): - return super(LegislaturaAdmin, self).lookup_allowed( - lookup, value - ) or lookup in ["casa_legislativa__municipio__uf__codigo_ibge__exact"] - - def response_change(self, request, obj): - response = super(LegislaturaAdmin, self).response_change(request, obj) - if "_popup" in request.POST: - response = HttpResponse( - '' - % - # escape() calls force_unicode. - (escape(obj.pk), escapejs(obj)) + def import_action(self, request, *args, **kwargs): + def save_file(uploaded, destination_path): + with open(destination_path / uploaded.name, "wb") as dst_file: + for chunck in uploaded: + dst_file.write(chunck) + dst_file.flush() + + form = ImportForm(request.POST, request.FILES) + context = admin.site.each_context(request) or {} + context["opts"] = self.model._meta + context["form"] = form + context["last_result"] = import_path / "result.html" + if request.method == "POST" and form.is_valid(): + json_data = { + "upload_time": timezone.localtime(), + "user_id": request.user.id, + "tipo_candidatos": form.cleaned_data["tipo_candidatos"], + "suplentes": form.cleaned_data["suplentes"], + "codificacao": form.cleaned_data["codificacao"], + "sigla_uf": form.cleaned_data["uf_importar"], + } + if form.cleaned_data["arquivo_tse"]: + save_file(form.cleaned_data["arquivo_tse"], import_path) + json_data["resultados"] = form.cleaned_data["arquivo_tse"].name + if form.cleaned_data["arquivo_redes"]: + save_file(form.cleaned_data["arquivo_redes"], import_path) + json_data["redes_sociais"] = form.cleaned_data[ + "arquivo_redes" + ].name + if form.cleaned_data["arquivo_fotos"]: + save_file(form.cleaned_data["arquivo_fotos"], import_path) + json_data["fotos"] = form.cleaned_data["arquivo_fotos"].name + json_path.write_text(json.dumps(json_data, default=str)) + return redirect( + reverse("admin:%s_%s_import_result" % self.get_model_info()) ) - return response - - -class ColigacaoAdmin(BaseModelAdmin): - list_display = ("nome", "legislatura", "numero_votos") - list_display_links = ("nome",) - raw_id_fields = ("legislatura",) - search_fields = ("nome", "legislatura__numero") - - -class ComposicaoColigacaoAdmin(BaseModelAdmin): - list_display = ("coligacao", "partido") - list_display_links = ("coligacao", "partido") - list_filter = ("partido",) - raw_id_fields = ("coligacao", "partido") - search_fields = ("coligacao__nome", "partido__nome", "partido__sigla") - - -class SessaoLegislativaAdmin(BaseModelAdmin): - list_display = ( - "numero", - "mesa_diretora", - "legislatura", - "tipo", - "data_inicio", - "data_fim", - ) - list_display_links = ("numero",) - list_filter = ("tipo",) - fieldsets = ( - (None, {"fields": ("numero", "mesa_diretora", "legislatura", "tipo")}), - ( - None, - { - "fields": ( - ("data_inicio", "data_fim"), - ("data_inicio_intervalo", "data_fim_intervalo"), - ) - }, - ), - ) - radio_fields = {"tipo": admin.VERTICAL} - raw_id_fields = ("mesa_diretora", "legislatura") - search_fields = ("numero", "mesa_diretora__casa_legislativa__nome") - - -class CargoAdmin(BaseModelAdmin): - list_display = ("descricao",) - search_fields = ("descricao",) - - -class MembroMesaDiretoraInline(admin.TabularInline): - model = MembroMesaDiretora - max_num = 11 - extra = 4 - raw_id_fields = ("parlamentar", "cargo") - - -class MembroMesaDiretoraAdmin(BaseModelAdmin): - list_display = ("parlamentar", "cargo", "mesa_diretora") - list_display_links = ("parlamentar",) - list_filter = ("cargo",) - raw_id_fields = ("parlamentar", "cargo", "mesa_diretora") - search_fields = ( - "cargo__descricao", - "parlamentar__nome_completo", - "parlamentar__nome_parlamentar", - "mesa_diretora__casa_legislativa__nome", - ) - - -class MesaDiretoraAdmin(BaseModelAdmin): - inlines = (MembroMesaDiretoraInline,) - raw_id_fields = ("casa_legislativa",) - list_display = ("id", "casa_legislativa") - search_fields = ("casa_legislativa__nome",) - + return render(request, "parlamentares/import.html", context) -admin.site.register(Partido, PartidoAdmin) -admin.site.register(Parlamentar, ParlamentarAdmin) -admin.site.register(Mandato, MandatoAdmin) -admin.site.register(Legislatura, LegislaturaAdmin) -admin.site.register(Coligacao, ColigacaoAdmin) -admin.site.register(ComposicaoColigacao, ComposicaoColigacaoAdmin) -admin.site.register(SessaoLegislativa, SessaoLegislativaAdmin) -admin.site.register(MesaDiretora, MesaDiretoraAdmin) -admin.site.register(Cargo, CargoAdmin) -admin.site.register(MembroMesaDiretora, MembroMesaDiretoraAdmin) + def result_import_action(self, request, *args, **kwargs): + context = admin.site.each_context(request) or {} + context["opts"] = self.model._meta + return render(request, "parlamentares/import_result.html", context) diff --git a/sigi/apps/parlamentares/apps.py b/sigi/apps/parlamentares/apps.py new file mode 100644 index 0000000..ea211ec --- /dev/null +++ b/sigi/apps/parlamentares/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class CasasConfig(AppConfig): + name = "sigi.apps.parlamentares" + verbose_name = _("parlamentares") diff --git a/sigi/apps/parlamentares/forms.py b/sigi/apps/parlamentares/forms.py new file mode 100644 index 0000000..907b9ab --- /dev/null +++ b/sigi/apps/parlamentares/forms.py @@ -0,0 +1,40 @@ +from requests import options +from django import forms +from django.utils.translation import gettext as _ +from sigi.apps.contatos.models import UnidadeFederativa + + +class ImportForm(forms.Form): + CODIFICACAO_CHOICES = ( + ("iso8859-1", _("LATIN 1 (iso-8859-1)")), + ("utf-8", _("UTF-8")), + ) + UF_CHOICES = [ + ("BR", _("Todo o Brasil")), + ] + [(uf.sigla, uf.nome) for uf in UnidadeFederativa.objects.all()] + TIPO_CHOICES = [ + ("V", _("Vereador")), + ("D", _("Deputado Estadual")), + ] + codificacao = forms.ChoiceField( + label=_("Codificação de caracteres"), + choices=CODIFICACAO_CHOICES, + help_text=_( + "Verifique no PDF do TSE a codificação de caracteres dos " + "arquivos" + ), + ) + arquivo_tse = forms.FileField(label=_("Arquivo do TSE"), required=False) + arquivo_redes = forms.FileField( + label=_("Arquivo de redes sociais"), required=False + ) + arquivo_fotos = forms.FileField(label=_("Zip das fotos"), required=False) + tipo_candidatos = forms.ChoiceField( + label=_("Tipo de candidatos"), choices=TIPO_CHOICES + ) + suplentes = forms.BooleanField( + label=_("Importar suplentes"), required=False + ) + uf_importar = forms.ChoiceField( + label=_("Unidade Federativa"), choices=UF_CHOICES + ) diff --git a/sigi/apps/parlamentares/jobs/__init__.py b/sigi/apps/parlamentares/jobs/__init__.py new file mode 100644 index 0000000..9dc33a6 --- /dev/null +++ b/sigi/apps/parlamentares/jobs/__init__.py @@ -0,0 +1,4 @@ +from django.conf import settings + +import_path = settings.MEDIA_ROOT / "parlamentares/parlamentar/import" +json_path = import_path / "config.json" diff --git a/sigi/apps/parlamentares/jobs/daily/__init__.py b/sigi/apps/parlamentares/jobs/daily/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sigi/apps/parlamentares/jobs/hourly/__init__.py b/sigi/apps/parlamentares/jobs/hourly/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sigi/apps/parlamentares/jobs/minutely/__init__.py b/sigi/apps/parlamentares/jobs/minutely/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sigi/apps/parlamentares/jobs/minutely/importa_parlamentar.py b/sigi/apps/parlamentares/jobs/minutely/importa_parlamentar.py new file mode 100644 index 0000000..b8c13f8 --- /dev/null +++ b/sigi/apps/parlamentares/jobs/minutely/importa_parlamentar.py @@ -0,0 +1,355 @@ +import csv +import zipfile +from datetime import datetime +import json +import logging +from django.contrib.auth import get_user_model +from django.conf import settings +from django.db import transaction +from django.core.mail import send_mail +from django.template.loader import render_to_string +from django.utils.translation import gettext as _ +from django_extensions.management.jobs import MinutelyJob +from sigi.apps.casas.models import Orgao +from sigi.apps.parlamentares.jobs import import_path, json_path +from sigi.apps.parlamentares.models import Parlamentar, Partido + + +class Job(MinutelyJob): + help = "Importa parlamentares de arquivo do TSE" + + def execute(self): + json_data = self.get_json_data() + if json_data is None: + return + json_data["inicio_processamento"] = str(datetime.now()) + result_final = [] + # Importa parlamentares # + if "resultados" in json_data: + result = self.importa_parlamentares( + import_path / json_data["resultados"], json_data + ) + if result["erros"]: + self.remove_files(json_data) + self.send_mail(result["erros"], json_data) + return + result_final.append(_("* IMPORTAÇÃO DOS PARLAMENTARES *")) + result_final.extend(result["infos"]) + if "redes_sociais" in json_data: + result = self.importa_redes( + import_path / json_data["redes_sociais"], + json_data["codificacao"], + ) + result_final.append(_("* IMPORTAÇÃO DAS REDES SOCIAIS *")) + result_final.extend(result["infos"]) + result_final.append(_("* IMPORTAÇÃO DAS REDES SOCIAIS - ERROS *")) + result_final.extend(result["erros"]) + + if "fotos" in json_data: + result = self.importa_fotos(import_path / json_data["fotos"]) + result_final.append(_("* IMPORTAÇÃO DAS FOTOS *")) + result_final.extend(result["infos"]) + result_final.append(_("* IMPORTAÇÃO DAS FOTOS - ERROS *")) + result_final.extend(result["erros"]) + self.remove_files(json_data) + self.send_mail(result_final, json_data) + return + + def get_json_data(self): + if json_path.is_file(): + data = json.loads(json_path.read_text()) + json_path.unlink(missing_ok=True) + return data + return None + + def remove_files(self, json_data): + if "resultados" in json_data: + (import_path / json_data["resultados"]).unlink(missing_ok=True) + if "redes_sociais" in json_data: + (import_path / json_data["redes_sociais"]).unlink(missing_ok=True) + if "fotos" in json_data: + (import_path / json_data["fotos"]).unlink(missing_ok=True) + + def send_mail(self, result, json_data): + user = get_user_model().objects.get(id=int(json_data["user_id"])) + json_data["fim_processamento"] = str(datetime.now()) + json_data["user"] = user.get_full_name() + del json_data["user_id"] + result = list(dict.fromkeys(result)) + txt_message = "\n".join(result) + html_message = render_to_string( + "parlamentares/import_email.html", + {"result": result, "json_data": json_data}, + ) + recipient_list = [a[1] for a in settings.ADMINS].append(user.email) + result_file = import_path / "result.html" + result_file.write_text(html_message, encoding="utf-8") + send_mail( + subject="Resultados da importação de dados de parlamentares", + message=txt_message, + recipient_list=recipient_list, + html_message=html_message, + ) + + def importa_parlamentares(self, file_name, json_data): + def limpa_flag(): + # Limpa o flag de importação para garantir que nada seja apagado # + # indevidamente # + Parlamentar.objects.all().update(flag_importa="") + + def marcar_antigos(): + if json_data["sigla_uf"] == "BR": + Parlamentar.objects.filter( + casa_legislativa__tipo__sigla__in=tipo_casa + ).update(flag_importa="E") + else: + Parlamentar.objects.filter( + casa_legislativa__municipio__uf__sigla=json_data[ + "sigla_uf" + ], + casa_legislativa__tipo__sigla__in=tipo_casa, + ).update(flag_importa="E") + + def apagar_antigos(): + Parlamentar.objects.filter(flag_importa="E").delete() + + def apagar_novos(): + Parlamentar.objects.filter(flag_importa="N").delete() + + if json_data["tipo_candidatos"] == "D": + tipo_casa = ["AL", "CT"] + cargos = ["7", "8"] # Deputado Estadual e Distrital + else: + tipo_casa = ["CM"] + cargos = ["13"] + + cod_situacao = ["1", "2", "3"] # Eleito, por qp, por média + if json_data["suplentes"]: + cod_situacao.append("5") # suplente + + result = {"infos": [], "erros": []} + + with open(file_name, "r", encoding=json_data["codificacao"]) as f: + if f.encoding != json_data["codificacao"]: + result["erros"].append( + f"Codificação de caracteres do arquivo {file_name} é " + f"{f.encoding}. Precisa converter para " + f"{json_data['codificacao']}." + ) + reader = csv.DictReader(f, delimiter=";") + fields = { + "ANO_ELEICAO", + "SG_UE", + "NM_UE", + "CD_CARGO", + "SQ_CANDIDATO", + "NM_CANDIDATO", + "NM_URNA_CANDIDATO", + "NR_PARTIDO", + "NM_PARTIDO", + "CD_SIT_TOT_TURNO", + } + try: + fieldnames = reader.fieldnames + except Exception as e: + result["erros"].append(str(e)) + fieldnames = [] + + if not fields.issubset(set(fieldnames)): + result["erros"].append( + "Nao foram encontrados todos os campos necessários no " + "arquivo. São esperados os seguintes campos: " + + ", ".join(fields) + ) + + if result["erros"]: + return result + + limpa_flag() + marcar_antigos() + + skiped = 0 + imported = 0 + total = 0 + + apenas_verificar = False + + for row in reader: + total += 1 + if (total % 1000) == 0: + print(total) + if not ( + row["CD_CARGO"] in cargos + and row["CD_SIT_TOT_TURNO"] in cod_situacao + ): + skiped += 1 + continue + cod_tse = row["SG_UE"] + legenda = int(row["NR_PARTIDO"]) + # Hack para 2022 - fusão de partidos # + if legenda in [17, 25]: + legenda = 44 + try: + if json_data["tipo_candidatos"] == "V": + casa = Orgao.objects.get( + municipio__codigo_tse=int(cod_tse), + tipo__sigla__in=tipo_casa, + ) + else: + casa = Orgao.objects.get( + municipio__uf__sigla=cod_tse, + tipo__sigla__in=tipo_casa, + ) + except: + # De agora em diante apenas procura erros, sem criar + # novos parlamentares, para agilizar o processo + apenas_verificar = True + result["erros"].append( + "Não foi encontrada a Casa Legislativa com " + f"o código TSE {cod_tse}. O nome do " + f"ente da federação é {row['NM_UE']}. " + "Corrija o cadastro do SIGI e tente novamente." + ) + try: + partido = Partido.objects.get(legenda=legenda) + except: + # De agora em diante apenas procura erros, sem criar + # novos parlamentares, para agilizar o processo + apenas_verificar = True + result["erros"].append( + f"O partido {row['NM_PARTIDO']} de legenda " + f"{legenda} não foi encontrado no SIGI." + ) + + if not apenas_verificar: + Parlamentar.objects.update_or_create( + flag_importa="N", + sequencial_tse=row["SQ_CANDIDATO"], + ano_eleicao=row["ANO_ELEICAO"], + nome_completo=row["NM_CANDIDATO"], + nome_parlamentar=row["NM_URNA_CANDIDATO"], + partido=partido, + casa_legislativa=casa, + status_mandato="S" + if row["CD_SIT_TOT_TURNO"] == "5" + else "E", + ) + imported += 1 + if result["erros"]: + apagar_novos() + result["infos"] = [] + else: + apagar_antigos() + + limpa_flag() + + result["infos"].append(f"Total de registros lidos: {total}") + result["infos"].append(f"Total de registros ignorados: {skiped}") + result["infos"].append(f"Total de registros importados: {imported}") + + return result + + def importa_redes(self, file_name, codificacao): + result = {"infos": [], "erros": []} + with open(file_name, "r", encoding=codificacao) as f: + if f.encoding != codificacao: + result["erros"].append( + f"Codificação de caracteres do arquivo {file_name} é " + f"{f.encoding}. Precisa converter para {codificacao}." + ) + reader = csv.DictReader(f, delimiter=";") + fields = { + "SQ_CANDIDATO", + "DS_URL", + } + try: + fieldnames = reader.fieldnames + except Exception as e: + result["erros"].append(str(e)) + fieldnames = [] + + if not fields.issubset(set(fieldnames)): + result["erros"].append( + "Nao foram encontrados todos os campos necessários no " + "arquivo. São esperados os seguintes campos: " + + ", ".join(fields) + ) + + if result["erros"]: + return result + + skiped = 0 + imported = 0 + total = 0 + + for row in reader: + total += 1 + try: + parlamentar = Parlamentar.objects.get( + sequencial_tse=row["SQ_CANDIDATO"] + ) + if ( + row["DS_URL"] + not in parlamentar.redes_sociais.splitlines() + ): + parlamentar.redes_sociais += "\n" + row["DS_URL"] + parlamentar.save() + imported += 1 + else: + skiped += 1 + except Parlamentar.DoesNotExist: + skiped += 1 + result["infos"].append(f"Total de registros lidos: {total}") + result["infos"].append(f"Total de registros ignorados: {skiped}") + result["infos"].append(f"Total de registros importados: {imported}") + return result + + def importa_fotos(self, file_name): + result = {"erros": [], "infos": []} + if not zipfile.is_zipfile(file_name): + result["erros"].append("Arquivo de fotos deve ser um ZIP") + return result + + with zipfile.ZipFile(file_name, mode="r") as zip_file: + if zip_file.testzip() is not None: + result["erros"].append("Arquivo de fotos está corrompido") + return result + + sequenciais = {n[3:14]: n for n in zip_file.namelist()} + print(f"Importar {len(sequenciais)} fotos") + parlamentares = Parlamentar.objects.filter( + sequencial_tse__in=sequenciais.keys() + ) + + total = len(zip_file.namelist()) + imported = parlamentares.count() + skiped = total - imported + print(f"{imported} parlamentares encontrados") + + if imported <= 0: + result["erros"].append( + "Nenhuma das fotos corresponde a algum parlamentar" + ) + return result + + relative_path = Parlamentar.foto.field.upload_to + foto_folder = settings.MEDIA_ROOT / relative_path + + for parlamentar in parlamentares: + foto_nome = sequenciais[parlamentar.sequencial_tse] + try: + zip_file.extract(foto_nome, foto_folder) + parlamentar.foto.name = str(f"{relative_path}/{foto_nome}") + parlamentar.save() + except Exception as e: + result["erros"].append(str(e)) + + result["infos"].extend( + [ + f"Total de fotos no arquivo: {total}", + f"Número de fotos importadas: {imported}", + f"Número de fotos ignoradas: {skiped}", + ] + ) + + return result diff --git a/sigi/apps/parlamentares/jobs/monthly/__init__.py b/sigi/apps/parlamentares/jobs/monthly/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sigi/apps/parlamentares/jobs/sample.py b/sigi/apps/parlamentares/jobs/sample.py new file mode 100644 index 0000000..b1ae62c --- /dev/null +++ b/sigi/apps/parlamentares/jobs/sample.py @@ -0,0 +1,9 @@ +from django_extensions.management.jobs import BaseJob + + +class Job(BaseJob): + help = "My sample job." + + def execute(self): + # executing empty sample job + pass diff --git a/sigi/apps/parlamentares/jobs/weekly/__init__.py b/sigi/apps/parlamentares/jobs/weekly/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sigi/apps/parlamentares/jobs/yearly/__init__.py b/sigi/apps/parlamentares/jobs/yearly/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sigi/apps/parlamentares/migrations/0001_initial.py b/sigi/apps/parlamentares/migrations/0001_initial.py index af6b63a..9664425 100644 --- a/sigi/apps/parlamentares/migrations/0001_initial.py +++ b/sigi/apps/parlamentares/migrations/0001_initial.py @@ -79,6 +79,7 @@ class Migration(migrations.Migration): models.ForeignKey( verbose_name="coliga\xe7\xe3o", to="parlamentares.Coligacao", + on_delete=models.CASCADE, ), ), ], @@ -120,7 +121,9 @@ class Migration(migrations.Migration): ), ( "casa_legislativa", - models.ForeignKey(to="casas.CasaLegislativa"), + models.ForeignKey( + to="casas.CasaLegislativa", on_delete=models.CASCADE + ), ), ], options={ @@ -159,10 +162,17 @@ class Migration(migrations.Migration): verbose_name="afastado", ), ), - ("cargo", models.ForeignKey(to="parlamentares.Cargo")), + ( + "cargo", + models.ForeignKey( + to="parlamentares.Cargo", on_delete=models.CASCADE + ), + ), ( "legislatura", - models.ForeignKey(to="parlamentares.Legislatura"), + models.ForeignKey( + to="parlamentares.Legislatura", on_delete=models.CASCADE + ), ), ], options={}, @@ -180,7 +190,12 @@ class Migration(migrations.Migration): primary_key=True, ), ), - ("cargo", models.ForeignKey(to="parlamentares.Cargo")), + ( + "cargo", + models.ForeignKey( + to="parlamentares.Cargo", on_delete=models.CASCADE + ), + ), ], options={ "ordering": ("parlamentar",), @@ -206,6 +221,7 @@ class Migration(migrations.Migration): models.ForeignKey( verbose_name="Casa Legislativa", to="casas.CasaLegislativa", + on_delete=models.CASCADE, ), ), ], @@ -346,13 +362,16 @@ class Migration(migrations.Migration): ), ( "legislatura", - models.ForeignKey(to="parlamentares.Legislatura"), + models.ForeignKey( + to="parlamentares.Legislatura", on_delete=models.CASCADE + ), ), ( "mesa_diretora", models.ForeignKey( verbose_name="Mesa Diretora", to="parlamentares.MesaDiretora", + on_delete=models.CASCADE, ), ), ], @@ -366,13 +385,17 @@ class Migration(migrations.Migration): migrations.AddField( model_name="membromesadiretora", name="mesa_diretora", - field=models.ForeignKey(to="parlamentares.MesaDiretora"), + field=models.ForeignKey( + to="parlamentares.MesaDiretora", on_delete=models.CASCADE + ), preserve_default=True, ), migrations.AddField( model_name="membromesadiretora", name="parlamentar", - field=models.ForeignKey(to="parlamentares.Parlamentar"), + field=models.ForeignKey( + to="parlamentares.Parlamentar", on_delete=models.CASCADE + ), preserve_default=True, ), migrations.AlterUniqueTogether( @@ -382,13 +405,17 @@ class Migration(migrations.Migration): migrations.AddField( model_name="mandato", name="parlamentar", - field=models.ForeignKey(to="parlamentares.Parlamentar"), + field=models.ForeignKey( + to="parlamentares.Parlamentar", on_delete=models.CASCADE + ), preserve_default=True, ), migrations.AddField( model_name="mandato", name="partido", - field=models.ForeignKey(to="parlamentares.Partido"), + field=models.ForeignKey( + to="parlamentares.Partido", on_delete=models.CASCADE + ), preserve_default=True, ), migrations.AlterUniqueTogether( @@ -398,13 +425,17 @@ class Migration(migrations.Migration): migrations.AddField( model_name="composicaocoligacao", name="partido", - field=models.ForeignKey(to="parlamentares.Partido"), + field=models.ForeignKey( + to="parlamentares.Partido", on_delete=models.CASCADE + ), preserve_default=True, ), migrations.AddField( model_name="coligacao", name="legislatura", - field=models.ForeignKey(to="parlamentares.Legislatura"), + field=models.ForeignKey( + to="parlamentares.Legislatura", on_delete=models.CASCADE + ), preserve_default=True, ), ] diff --git a/sigi/apps/parlamentares/migrations/0002_auto_20210406_1945.py b/sigi/apps/parlamentares/migrations/0002_auto_20210406_1945.py index 3b5a6b1..e9e5f95 100644 --- a/sigi/apps/parlamentares/migrations/0002_auto_20210406_1945.py +++ b/sigi/apps/parlamentares/migrations/0002_auto_20210406_1945.py @@ -15,14 +15,16 @@ class Migration(migrations.Migration): migrations.AlterField( model_name="legislatura", name="casa_legislativa", - field=models.ForeignKey(to="casas.Orgao"), + field=models.ForeignKey(to="casas.Orgao", on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterField( model_name="mesadiretora", name="casa_legislativa", field=models.ForeignKey( - verbose_name="Casa Legislativa", to="casas.Orgao" + verbose_name="Casa Legislativa", + to="casas.Orgao", + on_delete=models.CASCADE, ), preserve_default=True, ), diff --git a/sigi/apps/parlamentares/migrations/0004_partido_legenda_alter_cargo_id_alter_coligacao_id_and_more.py b/sigi/apps/parlamentares/migrations/0004_partido_legenda_alter_cargo_id_alter_coligacao_id_and_more.py new file mode 100644 index 0000000..ebab244 --- /dev/null +++ b/sigi/apps/parlamentares/migrations/0004_partido_legenda_alter_cargo_id_alter_coligacao_id_and_more.py @@ -0,0 +1,98 @@ +# Generated by Django 4.0.4 on 2022-05-28 13:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0003_auto_20210416_0841'), + ] + + operations = [ + migrations.AddField( + model_name='partido', + name='legenda', + field=models.PositiveIntegerField(default=0, verbose_name='nº da legenda'), + ), + migrations.AlterField( + model_name='cargo', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='coligacao', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='composicaocoligacao', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='legislatura', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='mandato', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='membromesadiretora', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='mesadiretora', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='parlamentar', + name='email', + field=models.EmailField(blank=True, max_length=254, verbose_name='e-mail'), + ), + migrations.AlterField( + model_name='parlamentar', + name='foto', + field=models.ImageField(blank=True, height_field='foto_altura', upload_to='fotos/parlamentares', width_field='foto_largura'), + ), + migrations.AlterField( + model_name='parlamentar', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='parlamentar', + name='sexo', + field=models.CharField(choices=[('M', 'Masculino'), ('F', 'Feminino')], max_length=1), + ), + migrations.AlterField( + model_name='partido', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='partido', + name='nome', + field=models.CharField(max_length=50, verbose_name='nome'), + ), + migrations.AlterField( + model_name='partido', + name='sigla', + field=models.CharField(max_length=20, verbose_name='silga'), + ), + migrations.AlterField( + model_name='sessaolegislativa', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='sessaolegislativa', + name='tipo', + field=models.CharField(choices=[('O', 'Ordinária'), ('E', 'Extraordinária')], default='O', max_length=1), + ), + ] diff --git a/sigi/apps/parlamentares/migrations/0005_remove_coligacao_legislatura_and_more.py b/sigi/apps/parlamentares/migrations/0005_remove_coligacao_legislatura_and_more.py new file mode 100644 index 0000000..a110b6d --- /dev/null +++ b/sigi/apps/parlamentares/migrations/0005_remove_coligacao_legislatura_and_more.py @@ -0,0 +1,122 @@ +# Generated by Django 4.0.4 on 2022-05-28 13:44 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("casas", "0023_funcionario_cpf_funcionario_identidade"), + ( + "parlamentares", + "0004_partido_legenda_alter_cargo_id_alter_coligacao_id_and_more", + ), + ] + + operations = [ + migrations.RemoveField( + model_name="parlamentar", + name="pagina_web", + ), + migrations.RemoveField( + model_name="parlamentar", + name="sexo", + ), + migrations.AddField( + model_name="parlamentar", + name="casa_legislativa", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to="casas.orgao", + verbose_name="casa legislativa", + ), + preserve_default=False, + ), + migrations.AddField( + model_name="parlamentar", + name="cpf", + field=models.CharField( + blank=True, max_length=20, verbose_name="CPF" + ), + ), + migrations.AddField( + model_name="parlamentar", + name="identidade", + field=models.CharField( + blank=True, + help_text="Informe o RG e o órgão emissor.", + max_length=30, + verbose_name="Identidade (RG)", + ), + ), + migrations.AddField( + model_name="parlamentar", + name="observacoes", + field=models.TextField(blank=True, verbose_name="observações"), + ), + migrations.AddField( + model_name="parlamentar", + name="partido", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to="parlamentares.partido", + verbose_name="partido", + ), + preserve_default=False, + ), + migrations.AddField( + model_name="parlamentar", + name="presidente", + field=models.BooleanField(default=False, verbose_name="presidente"), + ), + migrations.AddField( + model_name="parlamentar", + name="redes_sociais", + field=models.TextField( + blank=True, + help_text="Colocar um por linha", + verbose_name="redes sociais", + ), + ), + migrations.AddField( + model_name="parlamentar", + name="telefones", + field=models.CharField( + blank=True, max_length=250, null=True, verbose_name="telefones" + ), + ), + migrations.AddField( + model_name="parlamentar", + name="ult_alteracao", + field=models.DateTimeField( + auto_now=True, null=True, verbose_name="última alteração" + ), + ), + migrations.DeleteModel( + name="Mandato", + ), + migrations.DeleteModel( + name="MembroMesaDiretora", + ), + migrations.DeleteModel( + name="Cargo", + ), + migrations.DeleteModel( + name="SessaoLegislativa", + ), + migrations.DeleteModel( + name="MesaDiretora", + ), + migrations.DeleteModel( + name="ComposicaoColigacao", + ), + migrations.DeleteModel( + name="Coligacao", + ), + migrations.DeleteModel( + name="Legislatura", + ), + ] diff --git a/sigi/apps/parlamentares/migrations/0006_alter_partido_sigla.py b/sigi/apps/parlamentares/migrations/0006_alter_partido_sigla.py new file mode 100644 index 0000000..6ac1652 --- /dev/null +++ b/sigi/apps/parlamentares/migrations/0006_alter_partido_sigla.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.4 on 2022-05-29 13:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0005_remove_coligacao_legislatura_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='partido', + name='sigla', + field=models.CharField(max_length=20, verbose_name='sigla'), + ), + ] diff --git a/sigi/apps/parlamentares/migrations/0007_alter_parlamentar_options.py b/sigi/apps/parlamentares/migrations/0007_alter_parlamentar_options.py new file mode 100644 index 0000000..f48a030 --- /dev/null +++ b/sigi/apps/parlamentares/migrations/0007_alter_parlamentar_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0.4 on 2022-06-18 13:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0006_alter_partido_sigla'), + ] + + operations = [ + migrations.AlterModelOptions( + name='parlamentar', + options={'ordering': ('presidente', 'nome_completo'), 'verbose_name_plural': 'parlamentares'}, + ), + ] diff --git a/sigi/apps/parlamentares/migrations/0008_alter_parlamentar_foto.py b/sigi/apps/parlamentares/migrations/0008_alter_parlamentar_foto.py new file mode 100644 index 0000000..bc47080 --- /dev/null +++ b/sigi/apps/parlamentares/migrations/0008_alter_parlamentar_foto.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.4 on 2022-06-20 23:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0007_alter_parlamentar_options'), + ] + + operations = [ + migrations.AlterField( + model_name='parlamentar', + name='foto', + field=models.ImageField(blank=True, height_field='foto_altura', upload_to='parlamentares/parlamentar/fotos', width_field='foto_largura'), + ), + ] diff --git a/sigi/apps/parlamentares/migrations/0009_parlamentar_ano_eleicao_parlamentar_flag_importa_and_more.py b/sigi/apps/parlamentares/migrations/0009_parlamentar_ano_eleicao_parlamentar_flag_importa_and_more.py new file mode 100644 index 0000000..3bb53fe --- /dev/null +++ b/sigi/apps/parlamentares/migrations/0009_parlamentar_ano_eleicao_parlamentar_flag_importa_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.0.4 on 2022-06-24 01:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0008_alter_parlamentar_foto'), + ] + + operations = [ + migrations.AddField( + model_name='parlamentar', + name='ano_eleicao', + field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Ano de eleição'), + ), + migrations.AddField( + model_name='parlamentar', + name='flag_importa', + field=models.CharField(blank=True, default='', editable=False, max_length=1), + ), + migrations.AddField( + model_name='parlamentar', + name='sequencial_tse', + field=models.CharField(blank=True, default='', editable=False, max_length=20, verbose_name='Sequencial TSE'), + ), + migrations.AddField( + model_name='parlamentar', + name='status_mandato', + field=models.CharField(choices=[('E', 'Em exercício'), ('S', 'Suplente'), ('I', 'Inativo')], default='E', max_length=1, verbose_name='status do mandato'), + ), + migrations.AlterField( + model_name='parlamentar', + name='foto', + field=models.ImageField(blank=True, height_field='foto_altura', max_length=200, upload_to='parlamentares/parlamentar/fotos', width_field='foto_largura'), + ), + ] diff --git a/sigi/apps/parlamentares/migrations/0010_alter_parlamentar_options.py b/sigi/apps/parlamentares/migrations/0010_alter_parlamentar_options.py new file mode 100644 index 0000000..5c20750 --- /dev/null +++ b/sigi/apps/parlamentares/migrations/0010_alter_parlamentar_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0.4 on 2022-06-29 02:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('parlamentares', '0009_parlamentar_ano_eleicao_parlamentar_flag_importa_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='parlamentar', + options={'ordering': ('status_mandato', 'presidente', 'nome_completo'), 'verbose_name_plural': 'parlamentares'}, + ), + ] diff --git a/sigi/apps/parlamentares/models.py b/sigi/apps/parlamentares/models.py index 4dadc9f..dc39671 100644 --- a/sigi/apps/parlamentares/models.py +++ b/sigi/apps/parlamentares/models.py @@ -1,219 +1,104 @@ -# -*- coding: utf-8 -*- from django.db import models from django.utils.translation import gettext as _ - from sigi.apps.casas.models import Orgao class Partido(models.Model): - nome = models.CharField(max_length=50) - sigla = models.CharField(max_length=10) + nome = models.CharField(_("nome"), max_length=50) + sigla = models.CharField(_("sigla"), max_length=20) + legenda = models.PositiveIntegerField(_("nº da legenda"), default=0) class Meta: ordering = ("nome",) - def __unicode__(self): - return "%s (%s)" % (unicode(self.nome), unicode(self.sigla)) + def __str__(self): + return _(f"{self.sigla} - {self.nome}") class Parlamentar(models.Model): - SEXO_CHOICES = ( - ("M", _("Masculino")), - ("F", _("Feminino")), + STATUS_CHOICE = ( + ("E", _("Em exercício")), + ("S", _("Suplente")), + ("I", _("Inativo")), + ) + casa_legislativa = models.ForeignKey( + Orgao, verbose_name=_("casa legislativa"), on_delete=models.CASCADE + ) + partido = models.ForeignKey( + Partido, verbose_name=_("partido"), on_delete=models.CASCADE + ) + ano_eleicao = models.PositiveIntegerField( + _("Ano de eleição"), blank=True, null=True + ) + status_mandato = models.CharField( + _("status do mandato"), max_length=1, choices=STATUS_CHOICE, default="E" ) + presidente = models.BooleanField(_("presidente"), default=False) nome_completo = models.CharField(max_length=128) nome_parlamentar = models.CharField(max_length=35, blank=True) foto = models.ImageField( - upload_to="fotos/parlamentares", + max_length=200, + upload_to="parlamentares/parlamentar/fotos", width_field="foto_largura", height_field="foto_altura", blank=True, ) foto_largura = models.SmallIntegerField(editable=False, null=True) foto_altura = models.SmallIntegerField(editable=False, null=True) - sexo = models.CharField( - max_length=1, - choices=SEXO_CHOICES, - ) data_nascimento = models.DateField( _("data de nascimento"), blank=True, null=True, ) - email = models.EmailField(_("e-mail"), blank=True) - pagina_web = models.URLField(_("página web"), blank=True) - - class Meta: - ordering = ("nome_completo",) - verbose_name_plural = _("parlamentares") - - def __unicode__(self): - if self.nome_parlamentar: - return self.nome_parlamentar - return self.nome_completo - - -class Mandato(models.Model): - SUPLENCIA_CHOICES = ( - ("T", _("Titular")), - ("S", _("Suplente")), - ) - parlamentar = models.ForeignKey(Parlamentar, on_delete=models.CASCADE) - legislatura = models.ForeignKey( - "parlamentares.Legislatura", on_delete=models.CASCADE - ) - partido = models.ForeignKey(Partido, on_delete=models.CASCADE) - cargo = models.ForeignKey("parlamentares.Cargo", on_delete=models.PROTECT) - inicio_mandato = models.DateField(_("início de mandato")) - fim_mandato = models.DateField(_("fim de mandato")) - is_afastado = models.BooleanField( - _("afastado"), - default=False, - help_text=_("Marque caso parlamentar não esteja ativo."), - ) - - # suplencia = models.CharField( - # _('suplência'), - # max_length=1, - # choices=SUPLENCIA_CHOICES, - # ) - - def __unicode__(self): - return str(self.id) - - -class Legislatura(models.Model): - casa_legislativa = models.ForeignKey(Orgao, on_delete=models.CASCADE) - numero = models.PositiveSmallIntegerField(_("número legislatura")) - data_inicio = models.DateField(_("início")) - data_fim = models.DateField(_("fim")) - data_eleicao = models.DateField(_("data da eleição")) - total_parlamentares = models.PositiveIntegerField( - _("Total de parlamentares") - ) - - casa_legislativa.convenio_uf_filter = True - casa_legislativa.convenio_cl_tipo_filter = True - - class Meta: - unique_together = ("casa_legislativa", "numero") - ordering = ["casa_legislativa__municipio__uf__sigla", "-data_inicio"] - - def __unicode__(self): - return _( - "%(number)sª legislatura da %(parliament)s (%(initial_year)s-%(final_year)s)" - ) % dict( - number=self.numero, - parliament=self.casa_legislativa.__unicode__(), - initial_year=self.data_inicio.year, - final_year=self.data_fim.year, - ) - - -class Coligacao(models.Model): - nome = models.CharField(max_length=50) - legislatura = models.ForeignKey(Legislatura, on_delete=models.CASCADE) - numero_votos = models.PositiveIntegerField( - _("número de votos"), + cpf = models.CharField(_("CPF"), max_length=20, blank=True) + identidade = models.CharField( + _("Identidade (RG)"), + max_length=30, blank=True, - null=True, + help_text=_("Informe o RG e o órgão emissor."), ) - - class Meta: - ordering = ("legislatura", "nome") - verbose_name = _("coligação") - verbose_name_plural = _("coligações") - - def __unicode__(self): - return self.nome - - -class ComposicaoColigacao(models.Model): - coligacao = models.ForeignKey( - Coligacao, on_delete=models.CASCADE, verbose_name=_("coligação") - ) - partido = models.ForeignKey( - "parlamentares.Partido", on_delete=models.CASCADE + telefones = models.CharField( + _("telefones"), max_length=250, null=True, blank=True ) - - class Meta: - verbose_name = _("composição da coligação") - verbose_name_plural = _("composições das coligações") - - def __unicode__(self): - return str(self.id) - - -class SessaoLegislativa(models.Model): - SESSAO_CHOICES = ( - ("O", _("Ordinária")), - ("E", _("Extraordinária")), - ) - numero = models.PositiveSmallIntegerField( - _("número da sessão"), unique=True + email = models.EmailField(_("e-mail"), blank=True) + redes_sociais = models.TextField( + _("redes sociais"), help_text=_("Colocar um por linha"), blank=True ) - mesa_diretora = models.ForeignKey( - "MesaDiretora", - on_delete=models.PROTECT, - verbose_name=_("Mesa Diretora"), + ult_alteracao = models.DateTimeField( + _("última alteração"), + null=True, + blank=True, + editable=True, + auto_now=True, ) - legislatura = models.ForeignKey(Legislatura, on_delete=models.CASCADE) - tipo = models.CharField(max_length=1, choices=SESSAO_CHOICES, default="O") - data_inicio = models.DateField(_("início")) - data_fim = models.DateField(_("fim")) - data_inicio_intervalo = models.DateField( - _("início de intervalo"), blank=True, null=True + observacoes = models.TextField(_("observações"), blank=True) + sequencial_tse = models.CharField( + _("Sequencial TSE"), + max_length=20, + blank=True, + default="", + editable=False, ) - data_fim_intervalo = models.DateField( - _("fim de intervalo"), blank=True, null=True + flag_importa = models.CharField( + max_length=1, blank=True, default="", editable=False ) class Meta: - ordering = ("legislatura", "numero") - verbose_name = _("Sessão Legislativa") - verbose_name_plural = _("Sessões Legislativas") - - def __unicode__(self): - return str(self.numero) - - -class MesaDiretora(models.Model): - casa_legislativa = models.ForeignKey( - "casas.Orgao", - on_delete=models.CASCADE, - verbose_name=_("Casa Legislativa"), - ) - - class Meta: - verbose_name = _("Mesa Diretora") - verbose_name_plural = _("Mesas Diretoras") - - def __unicode__(self): - return _("Mesa Diretora da %s") % unicode(self.casa_legislativa) - - -class Cargo(models.Model): - descricao = models.CharField(_("descrição"), max_length=30) - - class Meta: - ordering = ("descricao",) - - def __unicode__(self): - return self.descricao - - -class MembroMesaDiretora(models.Model): - parlamentar = models.ForeignKey( - "parlamentares.Parlamentar", on_delete=models.CASCADE - ) - cargo = models.ForeignKey(Cargo, on_delete=models.PROTECT) - mesa_diretora = models.ForeignKey(MesaDiretora, on_delete=models.CASCADE) + ordering = ( + "status_mandato", + "presidente", + "nome_completo", + ) + verbose_name_plural = _("parlamentares") - class Meta: - ordering = ("parlamentar",) - unique_together = ("cargo", "mesa_diretora") - verbose_name = _("membro de Mesa Diretora") - verbose_name_plural = _("membros de Mesa Diretora") + def __str__(self): + if self.nome_parlamentar: + return self.nome_parlamentar + return self.nome_completo - def __unicode__(self): - return "%s (%s)" % (unicode(self.parlamentar), unicode(self.cargo)) + def save(self, *args, **kwargs): + if self.presidente: + self.casa_legislativa.parlamentar_set.filter( + presidente=True + ).update(presidente=False) + return super().save(*args, **kwargs) diff --git a/sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/cart/change_list_import_cart_export.html b/sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/cart/change_list_import_cart_export.html new file mode 100644 index 0000000..33ee26c --- /dev/null +++ b/sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/cart/change_list_import_cart_export.html @@ -0,0 +1,11 @@ +{% extends "admin/cart/change_list_cart_export.html" %} +{% load admin_urls i18n %} + +{% block object-tools-items %} +
  • + + file_upload + +
  • +{{ block.super }} +{% endblock %} diff --git a/sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/change_list.html b/sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/change_list.html deleted file mode 100644 index 58fe3b7..0000000 --- a/sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/change_list.html +++ /dev/null @@ -1 +0,0 @@ -{% extends "change_list_with_cart.html" %} diff --git a/sigi/apps/parlamentares/templates/parlamentares/import.html b/sigi/apps/parlamentares/templates/parlamentares/import.html new file mode 100644 index 0000000..a7a138b --- /dev/null +++ b/sigi/apps/parlamentares/templates/parlamentares/import.html @@ -0,0 +1,94 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_urls %} + +{% block extrastyle %} + {{ block.super }} + +{% endblock %} + +{% block breadcrumbs %}{% endblock %} +{% block content_title %} +

    {% trans "Importar dados do TSE" %}

    +{% endblock %} +{% block content %} +
    + {% csrf_token %} +
    + {% if last_result.exists %} + + {% endif %} + {{ form }} +
    + + + navigate_before + {% trans "Voltar" %} + +
    + +
    +
    {% trans "ATENÇÃO" %}
    +
      +
    • + {% blocktrans %} + A importação é um processo demorado, principalmente se o arquivo for + muito grande. Por isso a importação não será realizada imediatamente + agora, mas de forma assíncrona. Quando o sistema concluir o processo de + importação, um e-mail com um resumo dos resultados será enviado para + você e para os administradores do SIGI.{% endblocktrans %} +
    • + {% if last_result.exists %} +
    • + {% blocktrans %} + O resumo dos resultados da última importação realizada pelo sistema + pode ser{% endblocktrans %} + + {% trans "visualizado aqui" %} + +
    • + {% endif %} +
    • + {% trans "Os arquivos de resultados das eleiçoes podem ser encontrados no" %} + {% trans "Portal de dados abertos do TSE" %} +
    • +
    • + {% blocktrans %} + Os arquivos no formato CSV devem ser + EXTRAÍDOS + do ZIP baixado do TSE. + Não envie o ZIP inteiro, pois o SIGI não consegue descompactar. + Já os arquivos que contêm as fotos + DEVEM + ser enviados em formato ZIP.{% endblocktrans %} +
    • +
    • + {% blocktrans %} + O CSV do TSE vem com os caracteres codificados em 'LATIN 1' (iso-8859-1). + O SIGI trabalha com codificação UTF-8. O SIGI tenta fazer a conversão + se você selecionar LATIN 1, mas o ideal é que você faça a conversão + manualmente antes de importar. Para isso, pode usar o Excel ou + Libreoffice-calc.{% endblocktrans %} +
      + {% trans "Se estiver usando Linux, comande:" %} +
      + iconv -f iso8859-1 -t utf-8 arquivo_tse.csv -o arquivo_convertido.csv +
      +
    • +
    +
    + {% endblock %} diff --git a/sigi/apps/parlamentares/templates/parlamentares/import_email.html b/sigi/apps/parlamentares/templates/parlamentares/import_email.html new file mode 100644 index 0000000..4176f7a --- /dev/null +++ b/sigi/apps/parlamentares/templates/parlamentares/import_email.html @@ -0,0 +1,36 @@ +{% load i18n %} + +{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %} + + + + + +

    Resultado da importação de dados de parlamentares

    + + {% for key, value in json_data.items %} + + + + + {% endfor %} +
    {{ key }}{{ value }}
    +
    +
      + {% for row in result %} + {% if row|first == '*' %} +
    • +
      {{ row }}
      +
    • + {% else %} +
    • {{ row }}
    • + {% endif %} + {% endfor %} +
    + + diff --git a/sigi/apps/parlamentares/templates/parlamentares/import_result.html b/sigi/apps/parlamentares/templates/parlamentares/import_result.html new file mode 100644 index 0000000..aa43ced --- /dev/null +++ b/sigi/apps/parlamentares/templates/parlamentares/import_result.html @@ -0,0 +1,37 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_urls %} + +{% block extrastyle %} + {{ block.super }} + +{% endblock %} + +{% block breadcrumbs %}{% endblock %} +{% block content_title %} +

    {% trans "Importação de dados do TSE agendada" %}

    +{% endblock %} +{% block content %} +
    +

    + {% blocktrans %} + A importação foi agendada com sucesso e o arquivo do TSE + será processado em breve. Ao concluir a importação, o SIGI + enviará um e-mail para você e para os administradores do + sistema, avisando dos resultados do processo. + {% endblocktrans %} +

    +
    + +{% endblock %} diff --git a/sigi/apps/parlamentares/urls.py b/sigi/apps/parlamentares/urls.py index 9b1b8b1..a940169 100644 --- a/sigi/apps/parlamentares/urls.py +++ b/sigi/apps/parlamentares/urls.py @@ -1,26 +1,10 @@ -# coding: utf-8 -from django.conf.urls import patterns, url +from django.urls import path, include +from sigi.apps.parlamentares import views -urlpatterns = patterns( - "sigi.apps.parlamentares.views", - # Reports labels parlamentares - url(r"^parlamentar/labels/$", "labels_report", name="labels-report-all"), - url( - r"^parlamentar/(?P\w+)/labels/$", - "labels_report", - name="labels-report-id", +urlpatterns = [ + path( + "parlamentar_json//", + views.parlamentar_json, + name="parlamentar-json", ), - # Carrinho - url( - r"^parlamentar/carrinho/$", - "visualizar_carrinho", - name="visualizar-carrinho", - ), - url( - r"^parlamentar/carrinho/deleta_itens_carrinho$", - "deleta_itens_carrinho", - name="deleta-itens-carrinho", - ), - # A view excluir_carrinho n existe ainda. - # url(r'^parlamentar/carrinho/exluir_carrinho$', 'excluir_carrinho', name='excluir-carrinho'), -) +] diff --git a/sigi/apps/parlamentares/views.py b/sigi/apps/parlamentares/views.py index 74bd63b..0fabb0a 100644 --- a/sigi/apps/parlamentares/views.py +++ b/sigi/apps/parlamentares/views.py @@ -1,152 +1,22 @@ -# coding: utf-8 -import datetime -import csv - -from django.template import Context, loader -from django.core.paginator import Paginator, InvalidPage, EmptyPage from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.core.paginator import Paginator, InvalidPage, EmptyPage +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.shortcuts import render, get_list_or_404 -from django.http import HttpResponse, HttpResponseRedirect +from django.template import Context, loader, RequestContext from django.views.decorators.csrf import csrf_protect -from django.template import RequestContext - from sigi.apps.casas.models import Orgao from sigi.apps.parlamentares.models import Parlamentar -from sigi.apps.parlamentares.reports import ParlamentaresLabels - -from geraldo.generators import PDFGenerator -from django.contrib.auth.decorators import login_required - - -def adicionar_parlamentar_carrinho(request, queryset=None, id=None): - if request.method == "POST": - ids_selecionados = request.POST.getlist("_selected_action") - if "carrinho_parlametar" not in request.session: - request.session["carrinho_parlamentar"] = ids_selecionados - else: - lista = request.session["carrinho_parlamentar"] - # Verifica se id já não está adicionado - for id in ids_selecionados: - if id not in lista: - lista.append(id) - request.session["carrinho_parlamentar"] = lista @login_required -@csrf_protect -def visualizar_carrinho(request): - - qs = carrinhoOrGet_for_qs(request) - - paginator = Paginator(qs, 100) - - # Make sure page request is an int. If not, deliver first page. - # Esteja certo de que o `page request` é um inteiro. Se não, mostre a primeira página. - try: - page = int(request.GET.get("page", "1")) - except ValueError: - page = 1 - - # Se o page request (9999) está fora da lista, mostre a última página. - try: - paginas = paginator.page(page) - except (EmptyPage, InvalidPage): - paginas = paginator.page(paginator.num_pages) - - carrinhoIsEmpty = not ("carrinho_parlamentares" in request.session) - - return render( - request, - "parlamentares/carrinho.html", +def parlamentar_json(request, casa_id): + return JsonResponse( { - "carIsEmpty": carrinhoIsEmpty, - "paginas": paginas, - "query_str": "?" + request.META["QUERY_STRING"], - }, + p.nome_completo: { + "foto": p.foto.url if p.foto else None, + "id": p.id, + } + for p in Parlamentar.objects.filter(casa_legislativa_id=casa_id) + } ) - - -def carrinhoOrGet_for_qs(request): - """ - Verifica se existe parlamentares na sessão se não verifica get e retorna qs correspondente. - """ - if "carrinho_parlamentar" in request.session: - ids = request.session["carrinho_parlamentar"] - qs = Parlamentar.objects.filter(pk__in=ids) - else: - qs = Parlamentar.objects.all() - if request.GET: - qs = get_for_qs(request.GET, qs) - return qs - - -def query_ordena(qs, o, ot): - list_display = ("nome_completo",) - - aux = list_display[(int(o) - 1)] - if ot == "asc": - qs = qs.order_by(aux) - else: - qs = qs.order_by("-" + aux) - return qs - - -def get_for_qs(get, qs): - """ - Verifica atributos do GET e retorna queryset correspondente - """ - kwargs = {} - for k, v in get.iteritems(): - if not (k == "page" or k == "pop" or k == "q"): - if not k == "o": - if k == "ot": - qs = query_ordena(qs, get["o"], get["ot"]) - else: - kwargs[str(k)] = v - qs = qs.filter(**kwargs) - return qs - - -@login_required -def deleta_itens_carrinho(request): - """ - Deleta itens selecionados do carrinho - """ - if request.method == "POST": - ids_selecionados = request.POST.getlist("_selected_action") - if "carrinho_parlamentar" in request.session: - lista = request.session["carrinho_parlamentar"] - for item in ids_selecionados: - lista.remove(item) - if lista: - request.session["carrinho_parlamentar"] = lista - else: - del lista - del request.session["carrinho_parlamentar"] - - return HttpResponseRedirect(".") - - -@login_required -def labels_report(request, id=None, formato="3x9_etiqueta"): - """TODO: adicionar suporte para resultado de pesquisa do admin.""" - - if request.POST: - if "tipo_etiqueta" in request.POST: - tipo = request.POST["tipo_etiqueta"] - - if id: - qs = Parlamentar.objects.filter(pk=id) - - else: - qs = carrinhoOrGet_for_qs(request) - - if not qs: - return HttpResponseRedirect("../") - - response = HttpResponse(content_type="application/pdf") - response["Content-Disposition"] = "attachment; filename=casas.pdf" - report = ParlamentaresLabels(queryset=qs, formato=formato) - report.generate_by(PDFGenerator, filename=response) - - return response diff --git a/sigi/menu_conf.yaml b/sigi/menu_conf.yaml index 81fa240..e6d44d7 100644 --- a/sigi/menu_conf.yaml +++ b/sigi/menu_conf.yaml @@ -31,8 +31,8 @@ main_menu: - title: Assembléias Legislativas view_name: admin:casas_orgao_changelist querystr: tipo__sigla__exact=AL - # - title: Parlamentares - # view_name: admin:parlamentares_parlamentar_changelist + - title: Parlamentares + view_name: admin:parlamentares_parlamentar_changelist - title: Demais órgãos view_name: admin:casas_orgao_changelist querystr: tipo__legislativo__exact=0 @@ -115,5 +115,5 @@ main_menu: view_name: admin:eventos_funcao_changelist - title: Modelos de declaração view_name: admin:eventos_modelodeclaracao_changelist - # - title: Partidos políticos - # view_name: admin:parlamentares_partido_changelist + - title: Partidos políticos + view_name: admin:parlamentares_partido_changelist diff --git a/sigi/settings.py b/sigi/settings.py index 8277aae..6830066 100644 --- a/sigi/settings.py +++ b/sigi/settings.py @@ -44,6 +44,7 @@ INSTALLED_APPS = [ "sigi.apps.home", "sigi.apps.inventario", "sigi.apps.ocorrencias", + "sigi.apps.parlamentares", "sigi.apps.servicos", "sigi.apps.servidores", "sigi.apps.utils", diff --git a/sigi/urls.py b/sigi/urls.py index ff3394c..4f84def 100644 --- a/sigi/urls.py +++ b/sigi/urls.py @@ -23,6 +23,7 @@ urlpatterns = [ path("admin/convenios/", include("sigi.apps.convenios.urls")), path("admin/ocorrencias/", include("sigi.apps.ocorrencias.urls")), path("eventos/", include("sigi.apps.eventos.urls")), + path("parlamentares/", include("sigi.apps.parlamentares.urls")), path("admin/", admin.site.urls), path("tinymce/", include("tinymce.urls")), path("", include("sigi.apps.home.urls")),