Browse Source

Adicona parlamentares do TSE

pull/159/head
Sesostris Vieira 3 years ago
parent
commit
57d5a6cc38
  1. 217
      sigi/apps/casas/admin.py
  2. 16
      sigi/apps/casas/migrations/0024_delete_presidente.py
  3. 43
      sigi/apps/casas/models.py
  4. 10
      sigi/apps/casas/templates/admin/casas/anexo_convenio_snippet.html
  5. 75
      sigi/apps/casas/templates/admin/casas/orgao/change_form.html
  6. 10
      sigi/apps/casas/views.py
  7. 25
      sigi/apps/convenios/migrations/0025_alter_projeto_modelo_minuta_and_more.py
  8. 25
      sigi/apps/convenios/migrations/0026_alter_projeto_modelo_minuta_and_more.py
  9. 25
      sigi/apps/convenios/migrations/0027_alter_projeto_modelo_minuta_and_more.py
  10. 25
      sigi/apps/convenios/migrations/0028_alter_projeto_modelo_minuta_and_more.py
  11. 25
      sigi/apps/convenios/migrations/0029_alter_projeto_modelo_minuta_and_more.py
  12. 16
      sigi/apps/convenios/models.py
  13. 23
      sigi/apps/eventos/forms.py
  14. 23
      sigi/apps/eventos/static/css/convite.css
  15. 4
      sigi/apps/eventos/templates/eventos/alocacao_equipe.html
  16. 2
      sigi/apps/eventos/templates/eventos/calendario.html
  17. 72
      sigi/apps/eventos/templates/eventos/convida_casa.html
  18. 2
      sigi/apps/eventos/templates/eventos/oficio_padrao.html
  19. 11
      sigi/apps/eventos/templates/eventos/snippets/form_presidente_snippet.html
  20. 5
      sigi/apps/eventos/urls.py
  21. 67
      sigi/apps/eventos/views.py
  22. 2
      sigi/apps/ocorrencias/models.py
  23. 386
      sigi/apps/parlamentares/admin.py
  24. 7
      sigi/apps/parlamentares/apps.py
  25. 40
      sigi/apps/parlamentares/forms.py
  26. 4
      sigi/apps/parlamentares/jobs/__init__.py
  27. 0
      sigi/apps/parlamentares/jobs/daily/__init__.py
  28. 0
      sigi/apps/parlamentares/jobs/hourly/__init__.py
  29. 0
      sigi/apps/parlamentares/jobs/minutely/__init__.py
  30. 355
      sigi/apps/parlamentares/jobs/minutely/importa_parlamentar.py
  31. 0
      sigi/apps/parlamentares/jobs/monthly/__init__.py
  32. 9
      sigi/apps/parlamentares/jobs/sample.py
  33. 0
      sigi/apps/parlamentares/jobs/weekly/__init__.py
  34. 0
      sigi/apps/parlamentares/jobs/yearly/__init__.py
  35. 53
      sigi/apps/parlamentares/migrations/0001_initial.py
  36. 6
      sigi/apps/parlamentares/migrations/0002_auto_20210406_1945.py
  37. 98
      sigi/apps/parlamentares/migrations/0004_partido_legenda_alter_cargo_id_alter_coligacao_id_and_more.py
  38. 122
      sigi/apps/parlamentares/migrations/0005_remove_coligacao_legislatura_and_more.py
  39. 18
      sigi/apps/parlamentares/migrations/0006_alter_partido_sigla.py
  40. 17
      sigi/apps/parlamentares/migrations/0007_alter_parlamentar_options.py
  41. 18
      sigi/apps/parlamentares/migrations/0008_alter_parlamentar_foto.py
  42. 38
      sigi/apps/parlamentares/migrations/0009_parlamentar_ano_eleicao_parlamentar_flag_importa_and_more.py
  43. 17
      sigi/apps/parlamentares/migrations/0010_alter_parlamentar_options.py
  44. 243
      sigi/apps/parlamentares/models.py
  45. 11
      sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/cart/change_list_import_cart_export.html
  46. 1
      sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/change_list.html
  47. 94
      sigi/apps/parlamentares/templates/parlamentares/import.html
  48. 36
      sigi/apps/parlamentares/templates/parlamentares/import_email.html
  49. 37
      sigi/apps/parlamentares/templates/parlamentares/import_result.html
  50. 32
      sigi/apps/parlamentares/urls.py
  51. 154
      sigi/apps/parlamentares/views.py
  52. 8
      sigi/menu_conf.yaml
  53. 1
      sigi/settings.py
  54. 1
      sigi/urls.py

217
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": (
(
fields = (
"num_processo_sf",
"link_sigad",
"status_convenio",
"num_convenio",
"projeto",
"observacao",
),
(
"data_retorno_assinatura",
"data_termino_vigencia",
"data_pub_diario",
),
("get_anexos",),
("link_convenio",),
)
},
),
"data_sigad",
"data_solicitacao",
"get_anexos",
)
readonly_fields = [
"link_convenio",
"link_sigad",
"status_convenio",
"num_convenio",
"projeto",
"observacao",
"data_adesao",
"data_retorno_assinatura",
"data_termo_aceite",
"data_pub_diario",
"data_devolucao_via",
"data_postagem_correio",
"data_devolucao_sem_assinatura",
"data_retorno_sem_assinatura",
"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(
"<br/>".join(
[
f'<a href="{a.arquivo.url}" target="_blank">{a}</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'<p class="label label-{label}">{status}</p>')
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'<a href="{url}"><i class="material-icons Tiny">edit</i></a>'
)
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())
return mark_safe(obj.get_sigad_url(display_type="icone"))
link_sigad.short_description = _("Processo no Senado")
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'<a href="{url}"><i class="material-icons Tiny">edit</i></a>'
)
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

16
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',
),
]

43
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)

10
sigi/apps/casas/templates/admin/casas/anexo_convenio_snippet.html

@ -0,0 +1,10 @@
{% load i18n %}
{% if not anexos %}
<p>{% trans "Nenhum anexo no convênio" %}</p>
{% else %}
<div class="collection">
{% for anexo in anexos %}
<a href="{{ anexo.arquivo.url }}" target="_blank" class="collection-item">{{ anexo }}</a>
{% endfor %}
</div>
{% endif %}

75
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 %}
<div class="row">
<div class="col s12">
<ul class="tabs">
{% for fieldset in adminform %}
<li class="tab">
<a href="#{{ fieldset.name|default:'geral'|slugify }}">
{{ fieldset.name|default:_("Geral") }}
</a>
</li>
{% endfor %}
{% for inline_admin_formset in inline_admin_formsets %}
<li class="tab">
<a href="#{{ inline_admin_formset.opts.verbose_name_plural|slugify }}">
{{ inline_admin_formset.opts.verbose_name_plural|default:_("inline") }}
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}
{% block field_sets %}
{% for fieldset in adminform %}
<div id="{{ fieldset.name|default:'geral'|slugify }}" class="col s12" style="height: auto;">
{% include "admin/includes/fieldset.html" %}
</div>
{% endfor %}
{% endblock %}
{% block inline_field_sets %}
{% for inline_admin_formset in inline_admin_formsets %}
<div id="{{ inline_admin_formset.opts.verbose_name_plural|default:'inline'|slugify }}" class="col s12">
{% include inline_admin_formset.opts.template %}
</div>
{% endfor %}
{% endblock %}
{% block footer %}
{{ block.super }}
<script type="text/javascript">
const resize_tab_content = function($tab_parent, $tab) {
if ($tab_parent.height() != $tab.height()+24) {
$tab_parent.height($tab.height()+24);
}
}
$(document).ready(function(){
M.Tabs.init($('.tabs'),{swipeable: true, onShow: function(tab) {
var $tab = $(tab);
resize_tab_content($tab.parent(), $tab);
}});
$(".tabs-content").on("DOMSubtreeModified", function() {
var $tab_parent = $(this);
resize_tab_content($tab_parent, $tab_parent.children(".active"));
})
})
</script>
<style>
/* .tabs-content {
overflow-y: auto !important;
} */
.carousel-item {
height: auto !important;
}
</style>
{% endblock %}

10
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

25
sigi/apps/convenios/migrations/0025_alter_projeto_modelo_minuta_and_more.py

File diff suppressed because one or more lines are too long

25
sigi/apps/convenios/migrations/0026_alter_projeto_modelo_minuta_and_more.py

File diff suppressed because one or more lines are too long

25
sigi/apps/convenios/migrations/0027_alter_projeto_modelo_minuta_and_more.py

File diff suppressed because one or more lines are too long

25
sigi/apps/convenios/migrations/0028_alter_projeto_modelo_minuta_and_more.py

File diff suppressed because one or more lines are too long

25
sigi/apps/convenios/migrations/0029_alter_projeto_modelo_minuta_and_more.py

File diff suppressed because one or more lines are too long

16
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"(?P<orgao>00100|00200)\.(?P<sequencial>\d{6})/(?P<ano>\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 = "<i class='material-icons'>visibility</i>"
return (
f'<a href="https://intra.senado.leg.br/sigad/novo/protocolo/'
f"impressao.asp?area=processo&txt_numero_orgao={orgao}"
f'&txt_numero_sequencial={sequencial}&txt_numero_ano={ano}" '
f'target="_blank">{self.num_processo_sf}</a>'
f'target="_blank">{display}</a>'
)
if display_type == "numero":
return self.num_processo_sf
else:
return "<i class='material-icons'>visibility_off</i>"
def save(self, *args, **kwargs):
self.conveniada = self.data_retorno_assinatura is not None

23
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,
}

23
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;
}

4
sigi/apps/eventos/templates/eventos/alocacao_equipe.html

@ -57,8 +57,8 @@
<i class="large material-icons">print</i>
</a>
<ul>
<li><a class="btn-floating" href="?ano={{ ano_pesquisa|safe }}{% if mes_pesquisa %}&mes={{ mes_pesquisa|safe }}{% endif %}{% if semana_pesquisa %}&semana={{ semana_pesquisa|safe }}{% endif %}&fmt=pdf" title="{% trans "Exportar para PDF" %}"><i class="material-icons">picture_as_pdf</i></a></li>
<li><a class="btn-floating" href="?ano={{ ano_pesquisa|safe }}{% if mes_pesquisa %}&mes={{ mes_pesquisa|safe }}{% endif %}{% if semana_pesquisa %}&semana={{ semana_pesquisa|safe }}{% endif %}&fmt=csv" title="{% trans "Exportar para CSV" %}"><i class="material-icons">file_download</i></a></li>
<li><a class="btn-floating" href="?ano={{ ano_pesquisa|safe }}{% if mes_pesquisa %}&mes={{ mes_pesquisa|safe }}{% endif %}{% if semana_pesquisa %}&semana={{ semana_pesquisa|safe }}{% endif %}&fmt=pdf" title="{% trans 'Exportar para PDF' %}" target="_blank"><i class="material-icons">picture_as_pdf</i></a></li>
<li><a class="btn-floating" href="?ano={{ ano_pesquisa|safe }}{% if mes_pesquisa %}&mes={{ mes_pesquisa|safe }}{% endif %}{% if semana_pesquisa %}&semana={{ semana_pesquisa|safe }}{% endif %}&fmt=csv" title="{% trans 'Exportar para CSV' %}" target="_blank"><i class="material-icons">file_download</i></a></li>
</ul>
</div>
{% include "eventos/snippets/alocacao_equipe_snippet.html" with mode="html" %}

2
sigi/apps/eventos/templates/eventos/calendario.html

@ -23,7 +23,7 @@
<a class="btn-floating btn-small" href="?ano={{ ano_pesquisa|safe }}&mes={{ mes_pesquisa|safe }}&fmt=cal" title="{% trans 'Formato de calendário' %}"><i class="material-icons">date_range</i></a>
{% endif %}
<li>
<a class="btn-floating btn-small" href="?ano={{ ano_pesquisa|safe }}&mes={{ mes_pesquisa|safe }}&pdf=1"><i class="material-icons">picture_as_pdf</i></a>
<a class="btn-floating btn-small" href="?ano={{ ano_pesquisa|safe }}&mes={{ mes_pesquisa|safe }}&pdf=1" target="_blank"><i class="material-icons">picture_as_pdf</i></a>
</li>
</ul>
</div>

72
sigi/apps/eventos/templates/eventos/convida_casa.html

@ -1,9 +1,15 @@
{% extends "admin/base_site.html" %}
{% load i18n static %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'css/convite.css' %}">
{% endblock %}
{% block content %}
{{ block.super }}
<form action="" method="post" name="convite" enctype="multipart/form-data">{% csrf_token %}
<form action="" method="post" name="convite" enctype="multipart/form-data">
{% csrf_token %}
<div class="row">
<div class="col s12">
<h5>
@ -37,8 +43,36 @@
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">{% trans "Dados do presidente" %}</span>
{{ form_presidente }}
<span class="card-title">{% trans "Identifique o presidente" %}</span>
<div class="row">
<div class="col s12">
<div class="row">
<div class="input-field col s12">
<i class="material-icons prefix">search</i>
<input type="text" id="busca_parlamentar" class="autocomplete"{% if presidente %} value="{{ presidente.nome_completo }}"{% endif %}>
</div>
</div>
</div>
</div>
<div class="center-align hide" id="load_presidente_form">
<div class="preloader-wrapper small active">
<div class="spinner-layer spinner-green-only">
<div class="circle-clipper left">
<div class="circle"></div>
</div><div class="gap-patch">
<div class="circle"></div>
</div><div class="circle-clipper right">
<div class="circle"></div>
</div>
</div>
</div>
<p>{% trans "Carregando dados do parlamentar..." %}</p>
</div>
<div id="presidente-form">
{% if form_presidente %}
{% include "eventos/snippets/form_presidente_snippet.html" %}
{% endif %}
</div>
</div>
</div>
</div>
@ -86,12 +120,32 @@
$(document).ready(function() {
$("#copiar-presidente").click(function(event) {
event.preventDefault();
var dados = {{% for field in form_presidente %}
{{ field.name }}: $("#{{ field.id_for_label }}").val(),{% endfor %}
};
{% for field in form_contato %}
$("#{{ field.id_for_label }}").val(dados.{{ field.name }});
{% endfor %}
var campos = [['nome_completo', 'nome'], ['cpf', 'cpf'], ['identidade', 'identidade'], ['telefones', 'nota'], ['email', 'email']];
for (var x=0; x<campos.length; x++) {
$(`#id_contato-${campos[x][1]}`).val($(`#id_presidente-${campos[x][0]}`).val());
}
});
$.ajax("{% url 'parlamentar-json' casa.id %}", {
dataType: 'json',
success: function(data) {
function complete(nome) {
$("#load_presidente_form").removeClass("hide")
$("div#presidente-form").html("");
$.get(`/eventos/evento/presidente/${data[nome]["id"]}/`, function(html) {
$("div#presidente-form").html(html);
$("#load_presidente_form").addClass("hide")
})
}
var options = {};
for (nome in data) {options[nome] = data[nome]["foto"]};
M.Autocomplete.init(
$('input#busca_parlamentar'),
{data: options,
onAutocomplete: complete });
},
error: function(jqXHR, textStatus) {
alert(`Erro ao carregar parlamentares, com status ${textStatus}`);
}
})
})
</script>

2
sigi/apps/eventos/templates/eventos/oficio_padrao.html

@ -55,7 +55,9 @@
{% block body_content %}
<div id="logo" class="logo">
{% if casa.brasao %}
<img src="{{ casa.brasao.url }}" class="logo-image">
{% endif %}
</div>
{{ block.super }}
{% endblock %}

11
sigi/apps/eventos/templates/eventos/snippets/form_presidente_snippet.html

@ -0,0 +1,11 @@
{% load i18n %}
{% if presidente.foto %}
<div class="presidente-foto">
<img class="materialboxed" width="120" src="{{ presidente.foto.url }}" alt="{{ presidente.nome_completo }}">
<script>$(document).ready(function(){M.Materialbox.init($('.materialboxed'));})</script>
{% endif %}
<span class="card-title">{% trans "Complemente os dados do presidente" %}</span>
{% if presidente.foto %}</div>{% endif %}
{{ form_presidente }}
<input type="hidden" name="id_presidente" value="{{ presidente.id|safe }}">

5
sigi/apps/eventos/urls.py

@ -17,6 +17,11 @@ urlpatterns = [
views.declaracao,
name="evento-declaracao",
),
path(
"evento/presidente/<int:presidente_id>/",
views.presidente_form,
name="presidente-form-snippet",
),
]
# from django.conf.urls import patterns, url

67
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(
form_contato = FuncionarioForm(instance=contato, prefix="contato")
if presidente:
form_presidente = ParlamentarForm(
instance=presidente, prefix="presidente"
)
form_contato = FuncionarioForm(instance=contato, prefix="contato")
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}" %}}'

2
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"
)
}
)

386
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)
)
else:
self.message_user(
request,
_(
"Os parlamentares selecionadas já foram adicionadas anteriormente"
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 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",
]
return my_urls + urls
class LegislaturaAdmin(BaseModelAdmin):
date_hierarchy = "data_inicio"
list_display = (
"numero",
"casa_legislativa",
"uf",
"data_inicio",
"data_fim",
"data_eleicao",
"total_parlamentares",
@admin.display(
description=_("UF"), ordering="casa_legislativa__municipio__uf__nome"
)
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(
'<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>'
%
# escape() calls force_unicode.
(escape(obj.pk), escapejs(obj))
)
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"),
)
},
),
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'<img class="circle" src="{obj.foto.url}" style="width: 58px; height: 58px;"/>'
else:
return (
'<i class="material-icons medium grey-text">account_circle</i>'
)
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",
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 render(request, "parlamentares/import.html", context)
class MesaDiretoraAdmin(BaseModelAdmin):
inlines = (MembroMesaDiretoraInline,)
raw_id_fields = ("casa_legislativa",)
list_display = ("id", "casa_legislativa")
search_fields = ("casa_legislativa__nome",)
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)

7
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")

40
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
)

4
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"

0
sigi/apps/parlamentares/jobs/daily/__init__.py

0
sigi/apps/parlamentares/jobs/hourly/__init__.py

0
sigi/apps/parlamentares/jobs/minutely/__init__.py

355
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

0
sigi/apps/parlamentares/jobs/monthly/__init__.py

9
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

0
sigi/apps/parlamentares/jobs/weekly/__init__.py

0
sigi/apps/parlamentares/jobs/yearly/__init__.py

53
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,
),
]

6
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,
),

98
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),
),
]

122
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",
),
]

18
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'),
),
]

17
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'},
),
]

18
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'),
),
]

38
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'),
),
]

17
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'},
),
]

243
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
)
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")),
telefones = models.CharField(
_("telefones"), max_length=250, null=True, blank=True
)
numero = models.PositiveSmallIntegerField(
_("número da sessão"), unique=True
)
mesa_diretora = models.ForeignKey(
"MesaDiretora",
on_delete=models.PROTECT,
verbose_name=_("Mesa Diretora"),
email = models.EmailField(_("e-mail"), blank=True)
redes_sociais = models.TextField(
_("redes sociais"), help_text=_("Colocar um por linha"), blank=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
ult_alteracao = models.DateTimeField(
_("última alteração"),
null=True,
blank=True,
editable=True,
auto_now=True,
)
data_fim_intervalo = models.DateField(
_("fim 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,
)
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"),
flag_importa = models.CharField(
max_length=1, blank=True, default="", editable=False
)
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
ordering = (
"status_mandato",
"presidente",
"nome_completo",
)
cargo = models.ForeignKey(Cargo, on_delete=models.PROTECT)
mesa_diretora = models.ForeignKey(MesaDiretora, on_delete=models.CASCADE)
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)

11
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 %}
<li>
<a class="btn-floating tooltipped waves-effect waves-light" href="{% url opts|admin_urlname:'import' %}{{cl.get_query_string}}" data-position="left" data-tooltip="{% trans "Importar dados do TSE" %}">
<i class="material-icons">file_upload</i>
</a>
</li>
{{ block.super }}
{% endblock %}

1
sigi/apps/parlamentares/templates/admin/parlamentares/parlamentar/change_list.html

@ -1 +0,0 @@
{% extends "change_list_with_cart.html" %}

94
sigi/apps/parlamentares/templates/parlamentares/import.html

@ -0,0 +1,94 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_urls %}
{% block extrastyle %}
{{ block.super }}
<style>
#content {
display: block;
}
.submit-row>a {
color: #fff;
}
</style>
{% endblock %}
{% block breadcrumbs %}{% endblock %}
{% block content_title %}
<h4>{% trans "Importar dados do TSE" %}</h4>
{% endblock %}
{% block content %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="container">
{% if last_result.exists %}
<div class="breadcrumbs">
<a href="{{ MEDIA_URL }}parlamentares/parlamentar/import/result.html" target="_blank">
<i class="material-icons left">visibility</i>
{% trans "Visualizar o resultado da última importação" %}
</a>
</div>
{% endif %}
{{ form }}
<div class="submit-row">
<button class="btn waves-effect waves-light" type="submit" name="import">
<i class="material-icons left">done</i>
{% trans "Agendar importação" %}
</button>
<a class="btn waves-effect waves-light" href="{% url opts|admin_urlname:'changelist' %}">
<i class="material-icons left">navigate_before</i>
{% trans "Voltar" %}
</a>
</div>
</form>
<div class="container">
<h6>{% trans "ATENÇÃO" %}</h6>
<ul class="collection">
<li class="collection-item">
{% 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 %}
</li>
{% if last_result.exists %}
<li class="collection-item">
{% blocktrans %}
O resumo dos resultados da última importação realizada pelo sistema
pode ser{% endblocktrans %}
<a href="{{ MEDIA_URL }}parlamentares/parlamentar/import/result.html" target="_blank">
{% trans "visualizado aqui" %}
</a>
</li>
{% endif %}
<li class="collection-item">
{% trans "Os arquivos de resultados das eleiçoes podem ser encontrados no" %}
<a href="https://dadosabertos.tse.jus.br/" target="_blank">{% trans "Portal de dados abertos do TSE" %}</a>
</li>
<li class="collection-item">
{% blocktrans %}
Os arquivos no formato CSV devem ser
<em>EXTRAÍDOS</em>
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
<em>DEVEM</em>
ser enviados em formato ZIP.{% endblocktrans %}
</li>
<li class="collection-item">
{% 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 %}
<blockquote>
{% trans "Se estiver usando Linux, comande:" %}
<br>
<code>iconv -f iso8859-1 -t utf-8 arquivo_tse.csv -o arquivo_convertido.csv</code>
</blockquote>
</li>
</ul>
</div>
{% endblock %}

36
sigi/apps/parlamentares/templates/parlamentares/import_email.html

@ -0,0 +1,36 @@
{% load i18n %}
<!DOCTYPE html>
{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
<html
lang="{{ LANGUAGE_CODE|default:"
en-us
" }}"
dir="{{ LANGUAGE_BIDI|yesno:'rtl,ltr,auto' }}"
>
<head>
<meta charset="utf-8">
</head>
<body>
<h4>Resultado da importação de dados de parlamentares</h4>
<table>
{% for key, value in json_data.items %}
<tr>
<th>{{ key }}</th>
<td>{{ value }}</td>
</tr>
{% endfor %}
</table>
<br>
<ul>
{% for row in result %}
{% if row|first == '*' %}
<li>
<h5>{{ row }}</h5>
</li>
{% else %}
<li>{{ row }}</li>
{% endif %}
{% endfor %}
</ul>
</body>
</html>

37
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 }}
<style>
#content {
display: block;
}
.submit-row>a {
color: #fff;
}
</style>
{% endblock %}
{% block breadcrumbs %}{% endblock %}
{% block content_title %}
<h4>{% trans "Importação de dados do TSE agendada" %}</h4>
{% endblock %}
{% block content %}
<div class="container">
<p>
{% 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 %}
</p>
</div>
<div class="submit-row">
<a class="btn waves-effect waves-light" href="{% url opts|admin_urlname:'changelist' %}">
<i class="material-icons left">navigate_before</i>
{% trans "Voltar" %}
</a>
</div>
{% endblock %}

32
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<id>\w+)/labels/$",
"labels_report",
name="labels-report-id",
urlpatterns = [
path(
"parlamentar_json/<int:casa_id>/",
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'),
)
]

154
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

8
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

1
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",

1
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")),

Loading…
Cancel
Save