Browse Source

Adicionar Pauta Reunião (#2778)

* Refatorar reuniao_detail p/ add seleção de pauta

* Refatorar reuniao_detail p/ add CRUD Pauta Reunião

* Adicionar model PautaReuniao e migração

* Adicionar config. iniciais p/ CRUD Pauta

* Refatorar código

* Adicionar CRUD Pauta Reunião

* Adicionar permissões necessárias

* Update sapl/comissoes/views.py

Co-Authored-By: Edward <edwardoliveira@users.noreply.github.com>

* Update sapl/comissoes/views.py

Co-Authored-By: Edward <edwardoliveira@users.noreply.github.com>

* Update sapl/comissoes/views.py

Co-Authored-By: Edward <edwardoliveira@users.noreply.github.com>

* Atualizar sapl/comissoes/views.py

* Update views.py
pull/2793/head
João Rodrigues 6 years ago
committed by Edward
parent
commit
343fc26ea1
  1. 8
      sapl/comissoes/forms.py
  2. 6
      sapl/comissoes/urls.py
  3. 151
      sapl/comissoes/views.py
  4. 29
      sapl/materia/migrations/0049_pautareuniao.py
  5. 26
      sapl/materia/models.py
  6. 4
      sapl/materia/views.py
  7. 2
      sapl/protocoloadm/views.py
  8. 1
      sapl/rules/map_rules.py
  9. 68
      sapl/templates/comissoes/pauta.html
  10. 65
      sapl/templates/comissoes/reuniao_detail.html

8
sapl/comissoes/forms.py

@ -11,6 +11,7 @@ from django.utils.translation import ugettext_lazy as _
from sapl.base.models import Autor, TipoAutor from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import (Comissao, Composicao, DocumentoAcessorio, from sapl.comissoes.models import (Comissao, Composicao, DocumentoAcessorio,
Participacao, Reuniao, Periodo) Participacao, Reuniao, Periodo)
from sapl.materia.models import PautaReuniao
from sapl.parlamentares.models import Legislatura, Mandato, Parlamentar from sapl.parlamentares.models import Legislatura, Mandato, Parlamentar
from sapl.utils import FileFieldCheckMixin from sapl.utils import FileFieldCheckMixin
@ -383,6 +384,13 @@ class ReuniaoForm(ModelForm):
return self.cleaned_data return self.cleaned_data
class PautaReuniaoForm(forms.ModelForm):
class Meta:
model = PautaReuniao
exclude = ['reuniao']
class DocumentoAcessorioCreateForm(FileFieldCheckMixin, forms.ModelForm): class DocumentoAcessorioCreateForm(FileFieldCheckMixin, forms.ModelForm):
parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput()) parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput())

6
sapl/comissoes/urls.py

@ -1,7 +1,8 @@
from django.conf.urls import include, url from django.conf.urls import include, url
from sapl.comissoes.views import (CargoCrud, ComissaoCrud, ComposicaoCrud, from sapl.comissoes.views import (CargoCrud, ComissaoCrud, ComposicaoCrud,
DocumentoAcessorioCrud, MateriasTramitacaoListView, ParticipacaoCrud, DocumentoAcessorioCrud, MateriasTramitacaoListView, ParticipacaoCrud,
PeriodoComposicaoCrud, ReuniaoCrud, TipoComissaoCrud, get_participacoes_comissao) PeriodoComposicaoCrud, ReuniaoCrud, TipoComissaoCrud, get_participacoes_comissao,
AdicionaPautaView, RemovePautaView)
from .apps import AppConfig from .apps import AppConfig
@ -17,6 +18,9 @@ urlpatterns = [
url(r'^comissao/(?P<pk>\d+)/materias-em-tramitacao$', url(r'^comissao/(?P<pk>\d+)/materias-em-tramitacao$',
MateriasTramitacaoListView.as_view(), name='materias_em_tramitacao'), MateriasTramitacaoListView.as_view(), name='materias_em_tramitacao'),
url(r'^comissao/(?P<pk>\d+)/pauta/add', AdicionaPautaView.as_view(), name='pauta_add'),
url(r'^comissao/(?P<pk>\d+)/pauta/remove', RemovePautaView.as_view(), name='pauta_remove'),
url(r'^sistema/comissao/cargo/', include(CargoCrud.get_urls())), url(r'^sistema/comissao/cargo/', include(CargoCrud.get_urls())),
url(r'^sistema/comissao/periodo-composicao/', url(r'^sistema/comissao/periodo-composicao/',
include(PeriodoComposicaoCrud.get_urls())), include(PeriodoComposicaoCrud.get_urls())),

151
sapl/comissoes/views.py

@ -1,13 +1,16 @@
import logging import logging
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import F from django.db.models import F
from django.http.response import HttpResponseRedirect, JsonResponse from django.http.response import HttpResponseRedirect, JsonResponse
from django.views.decorators.clickjacking import xframe_options_exempt from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import ListView from django.views.generic import ListView, CreateView, DeleteView
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin, UpdateView
from django.utils.translation import ugettext_lazy as _
from sapl.base.models import AppConfig as AppsAppConfig from sapl.base.models import AppConfig as AppsAppConfig
from sapl.comissoes.apps import AppConfig from sapl.comissoes.apps import AppConfig
@ -15,11 +18,11 @@ from sapl.comissoes.forms import (ComissaoForm, ComposicaoForm,
DocumentoAcessorioCreateForm, DocumentoAcessorioCreateForm,
DocumentoAcessorioEditForm, DocumentoAcessorioEditForm,
ParticipacaoCreateForm, ParticipacaoEditForm, ParticipacaoCreateForm, ParticipacaoEditForm,
PeriodoForm, ReuniaoForm) PeriodoForm, ReuniaoForm, PautaReuniaoForm)
from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
MasterDetailCrud, MasterDetailCrud,
PermissionRequiredForAppCrudMixin) PermissionRequiredForAppCrudMixin)
from sapl.materia.models import MateriaLegislativa, Tramitacao from sapl.materia.models import MateriaLegislativa, Tramitacao, PautaReuniao
from .models import (CargoComissao, Comissao, Composicao, DocumentoAcessorio, from .models import (CargoComissao, Comissao, Composicao, DocumentoAcessorio,
Participacao, Periodo, Reuniao, TipoComissao) Participacao, Periodo, Reuniao, TipoComissao)
@ -162,26 +165,27 @@ class ComissaoCrud(Crud):
return super(Crud.UpdateView, self).form_valid(form) return super(Crud.UpdateView, self).form_valid(form)
class MateriasTramitacaoListView(ListView): def lista_materias_comissao(comissao_pk):
template_name = "comissoes/materias_em_tramitacao.html"
paginate_by = 10
def get_queryset(self):
# FIXME: Otimizar consulta
ts = Tramitacao.objects.order_by( ts = Tramitacao.objects.order_by(
'materia', '-data_tramitacao', '-id').annotate( 'materia', '-data_tramitacao', '-id').annotate(
comissao=F('unidade_tramitacao_destino__comissao')).distinct( comissao=F('unidade_tramitacao_destino__comissao')).distinct(
'materia').values_list('materia', 'comissao') 'materia').values_list('materia', 'comissao')
ts = list(filter(lambda x: x[1] == int(self.kwargs['pk']), ts)) ts = [m for (m,c) in ts if c == int(comissao_pk)]
ts = list(zip(*ts))
ts = ts[0] if ts else []
materias = MateriaLegislativa.objects.filter( materias = MateriaLegislativa.objects.filter(
pk__in=ts).order_by('tipo', '-ano', '-numero') pk__in=ts).order_by('tipo', '-ano', '-numero')
return materias return materias
class MateriasTramitacaoListView(ListView):
template_name = "comissoes/materias_em_tramitacao.html"
paginate_by = 10
def get_queryset(self):
return lista_materias_comissao(self.kwargs['pk'])
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super( context = super(
MateriasTramitacaoListView, self).get_context_data(**kwargs) MateriasTramitacaoListView, self).get_context_data(**kwargs)
@ -193,13 +197,38 @@ class MateriasTramitacaoListView(ListView):
class ReuniaoCrud(MasterDetailCrud): class ReuniaoCrud(MasterDetailCrud):
model = Reuniao model = Reuniao
parent_field = 'comissao' parent_field = 'comissao'
model_set = 'documentoacessorio_set'
public = [RP_LIST, RP_DETAIL, ] public = [RP_LIST, RP_DETAIL, ]
class BaseMixin(MasterDetailCrud.BaseMixin): class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['data', 'nome', 'tema', 'upload_ata'] list_field_names = ['data', 'nome', 'tema', 'upload_ata']
ordering = '-data' ordering = '-data'
class DetailView(MasterDetailCrud.DetailView):
template_name = "comissoes/reuniao_detail.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
docs = []
documentos = DocumentoAcessorio.objects.filter(reuniao=self.kwargs['pk']).order_by('nome')
docs.extend(documentos)
context['docs'] = docs
context['num_docs'] = len(docs)
mats = []
materias_pauta = PautaReuniao.objects.filter(reuniao=self.kwargs['pk'])
materias_pk = [materia_pauta.materia.pk for materia_pauta in materias_pauta]
context['mats'] = MateriaLegislativa.objects.filter(
pk__in=materias_pk
).order_by('tipo', '-ano', '-numero')
context['num_mats'] = len(context['mats'])
context['reuniao_pk'] = self.kwargs['pk']
return context
class ListView(MasterDetailCrud.ListView): class ListView(MasterDetailCrud.ListView):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
paginate_by = 10 paginate_by = 10
@ -249,6 +278,100 @@ class ReuniaoCrud(MasterDetailCrud):
return {'comissao': comissao} return {'comissao': comissao}
class RemovePautaView(PermissionRequiredMixin, CreateView):
model = PautaReuniao
form_class = PautaReuniaoForm
template_name = 'comissoes/pauta.html'
permission_required = ('comissoes.add_reuniao', )
def get_context_data(self, **kwargs):
context = super(
RemovePautaView, self
).get_context_data(**kwargs)
# Remove = 0; Adiciona = 1
context['opcao'] = 0
context['object'] = Reuniao.objects.get(pk=self.kwargs['pk'])
context['root_pk'] = context['object'].comissao.pk
materias_pauta = PautaReuniao.objects.filter(reuniao=context['object'])
materias_pk = [materia_pauta.materia.pk for materia_pauta in materias_pauta]
context['materias'] = MateriaLegislativa.objects.filter(
pk__in=materias_pk
).order_by('tipo', '-ano', '-numero')
context['num_materias'] = len(context['materias'])
return context
def post(self, request, *args, **kwargs):
success_url = reverse('sapl.comissoes:reuniao_detail', kwargs={'pk':kwargs['pk']})
marcadas = request.POST.getlist('materia_id')
if not marcadas:
msg=_('Nenhuma matéria foi selecionada.')
messages.add_message(request, messages.WARNING, msg)
return HttpResponseRedirect(success_url)
reuniao = Reuniao.objects.get(pk=kwargs['pk'])
for materia in MateriaLegislativa.objects.filter(id__in=marcadas):
PautaReuniao.objects.filter(reuniao=reuniao,materia=materia).delete()
msg=_('Matéria(s) removida(s) com sucesso!')
messages.add_message(request, messages.SUCCESS, msg)
return HttpResponseRedirect(success_url)
class AdicionaPautaView(PermissionRequiredMixin, CreateView):
model = PautaReuniao
form_class = PautaReuniaoForm
template_name = 'comissoes/pauta.html'
permission_required = ('comissoes.add_reuniao', )
def get_context_data(self, **kwargs):
context = super(
AdicionaPautaView, self
).get_context_data(**kwargs)
# Adiciona = 1; Remove = 0
context['opcao'] = 1
context['object'] = Reuniao.objects.get(pk=self.kwargs['pk'])
context['root_pk'] = context['object'].comissao.pk
materias_comissao = lista_materias_comissao(context['object'].comissao.pk)
materias_pauta = PautaReuniao.objects.filter(reuniao=context['object'])
nao_listar = [mp.materia.pk for mp in materias_pauta]
context['materias'] = materias_comissao.exclude(pk__in=nao_listar)
context['num_materias'] = len(context['materias'])
return context
def post(self, request, *args, **kwargs):
success_url = reverse('sapl.comissoes:reuniao_detail', kwargs={'pk':kwargs['pk']})
marcadas = request.POST.getlist('materia_id')
if not marcadas:
msg = _('Nenhuma máteria foi selecionada.')
messages.add_message(request, messages.WARNING, msg)
return HttpResponseRedirect(success_url)
reuniao = Reuniao.objects.get(pk=kwargs['pk'])
pautas = []
for materia in MateriaLegislativa.objects.filter(id__in=marcadas):
pauta = PautaReuniao()
pauta.reuniao = reuniao
pauta.materia = materia
pautas.append(pauta)
PautaReuniao.objects.bulk_create(pautas)
msg = _('Matéria(s) adicionada(s) com sucesso!')
messages.add_message(request, messages.SUCCESS, msg)
return HttpResponseRedirect(success_url)
class DocumentoAcessorioCrud(MasterDetailCrud): class DocumentoAcessorioCrud(MasterDetailCrud):
model = DocumentoAcessorio model = DocumentoAcessorio
parent_field = 'reuniao__comissao' parent_field = 'reuniao__comissao'

29
sapl/materia/migrations/0049_pautareuniao.py

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-05-14 20:11
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('comissoes', '0019_auto_20181214_1023'),
('materia', '0048_merge_20190426_0828'),
]
operations = [
migrations.CreateModel(
name='PautaReuniao',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('materia', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='materia_set', to='materia.MateriaLegislativa', verbose_name='Matéria')),
('reuniao', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reuniao_set', to='comissoes.Reuniao', verbose_name='Reunião')),
],
options={
'verbose_name': 'Matéria da Pauta',
'verbose_name_plural': 'Matérias da Pauta',
},
),
]

26
sapl/materia/models.py

@ -11,7 +11,7 @@ from model_utils import Choices
import reversion import reversion
from sapl.base.models import SEQUENCIA_NUMERACAO_PROTOCOLO, Autor from sapl.base.models import SEQUENCIA_NUMERACAO_PROTOCOLO, Autor
from sapl.comissoes.models import Comissao from sapl.comissoes.models import Comissao, Reuniao
from sapl.compilacao.models import (PerfilEstruturalTextoArticulado, from sapl.compilacao.models import (PerfilEstruturalTextoArticulado,
TextoArticulado) TextoArticulado)
from sapl.parlamentares.models import Parlamentar from sapl.parlamentares.models import Parlamentar
@ -401,6 +401,30 @@ class AcompanhamentoMateria(models.Model):
} }
@reversion.register()
class PautaReuniao(models.Model):
reuniao = models.ForeignKey(
Reuniao, related_name='reuniao_set',
on_delete=models.CASCADE,
verbose_name=_('Reunião')
)
materia = models.ForeignKey(
MateriaLegislativa, related_name='materia_set',
verbose_name=_('Matéria')
)
class Meta:
verbose_name = _('Matéria da Pauta')
verbose_name_plural = ('Matérias da Pauta')
def __str__(self):
return _('Reunião: %(reuniao)s'
' - Matéria: %(materia)s') % {
'reuniao': self.reuniao,
'materia': self.materia
}
@reversion.register() @reversion.register()
class Anexada(models.Model): class Anexada(models.Model):
materia_principal = models.ForeignKey( materia_principal = models.ForeignKey(

4
sapl/materia/views.py

@ -2190,8 +2190,8 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView):
msg = _('Matéria(s) anexada(s).') msg = _('Matéria(s) anexada(s).')
messages.add_message(request, messages.SUCCESS, msg) messages.add_message(request, messages.SUCCESS, msg)
sucess_url = reverse('sapl_index') + 'materia/' + kwargs['pk'] + '/anexada' success_url = reverse('sapl.materia:anexada_list', kwargs={'pk': kwargs['pk']})
return HttpResponseRedirect(sucess_url) return HttpResponseRedirect(success_url)
class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):

2
sapl/protocoloadm/views.py

@ -1090,7 +1090,7 @@ class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView):
msg = _('Documento(s) anexado(s).') msg = _('Documento(s) anexado(s).')
messages.add_message(request, messages.SUCCESS, msg) messages.add_message(request, messages.SUCCESS, msg)
success_url = reverse('sapl_index') + 'docadm/' + kwargs['pk'] + '/anexado' success_url = reverse('sapl.protocoloadm:anexado_list', kwargs={'pk': kwargs['pk']})
return HttpResponseRedirect(success_url) return HttpResponseRedirect(success_url)

1
sapl/rules/map_rules.py

@ -103,6 +103,7 @@ rules_group_protocolo = {
rules_group_comissoes = { rules_group_comissoes = {
'group': SAPL_GROUP_COMISSOES, 'group': SAPL_GROUP_COMISSOES,
'rules': [ 'rules': [
(materia.PautaReuniao, __base__, __perms_publicas__),
(comissoes.Comissao, __base__, __perms_publicas__), (comissoes.Comissao, __base__, __perms_publicas__),
(comissoes.Composicao, __base__, __perms_publicas__), (comissoes.Composicao, __base__, __perms_publicas__),
(comissoes.Participacao, __base__, __perms_publicas__), (comissoes.Participacao, __base__, __perms_publicas__),

68
sapl/templates/comissoes/pauta.html

@ -0,0 +1,68 @@
{% extends "crud/detail.html" %}
{% load i18n crispy_forms_tags %}
{% block actions %}{% endblock %}
{% block title %}
<h1 class="page-header">
{% if opcao %}
Adicionar Matérias à Pauta <small>(Reunião: {{object}})</small>
{% else %}
Remover Matérias da Pauta <small>(Reunião: {{object}})</small>
{% endif %}
</h1>
{% endblock %}
{% block detail_content %}
{% if materias %}
{% if num_materias == 1 %}
<b>Há {{num_materias}} matéria disponível.</b> <br><br>
{% else %}
<b>Há {{num_materias}} matérias disponíveis.</b> <br><br>
{% endif %}
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset>
<table class="table table-striped table-hover">
<div class="controls">
<div class="checkbox">
<label for="id_check_all">
<input type="checkbox" id="id_check_all" onchange="checkAll(this)" /> Marcar/Desmarcar Todos
</label>
</div>
</div>
<thead>
<tr><th>Matéria</th></tr>
</thead>
<tbody onload="checks(materias_checked)">
{% for materia in materias %}
<tr>
<td>
<input type="checkbox" name="materia_id" value="{{materia.id}}" {% if check %} checked {% endif %}/>
{{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}} - {{materia.tipo.descricao}}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</fieldset>
{% if opcao %}
<input type="submit" value="Salvar" class="btn btn-primary"S>
{% else %}
<input type="submit" value="Remover" class="btn btn-danger"S>
{% endif %}
</form>
{% else %}
<b>Não há matéria disponível.</b> <br><br>
{% endif %}
{% endblock %}
{% block extra_js %}
<script language="JavaScript">
function checkAll(elem){
let checkboxes = document.getElementsByName('materia_id');
for(let i=0; i<checkboxes.length; i++){
if(checkboxes[i].type == 'checkbox')
checkboxes[i].checked = elem.checked
}
}
</script>
{% endblock %}

65
sapl/templates/comissoes/reuniao_detail.html

@ -0,0 +1,65 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% block detail_content %}
{{ block.super }}
<h2 class="legend">Pauta</h2>
{% if mats %}
<p>Total de Registros: <b>{{num_mats}}</b></p>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Matéria</th>
</tr>
</thead>
<tbody>
{% for mat in mats %}
<tr>
<td>
<a href="{% url 'sapl.materia:materialegislativa_detail' mat.pk %}">{{mat}}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if perms.comissoes.add_reuniao %}
<div class="actions btn-group float-right" role="group">
<a href="{% url 'sapl.comissoes:pauta_add' reuniao_pk %}" class="btn btn-outline-primary">{% trans 'Adicionar Matéria' %}</a>
<a href="{% url 'sapl.comissoes:pauta_remove' reuniao_pk %}" class="btn btn-outline-primary btn-outline-danger">{% trans 'Remover Matéria' %}</a>
</div>
{% endif %}
{% else %}
{% if perms.comissoes.add_reuniao %}
<a href="{% url 'sapl.comissoes:pauta_add' reuniao_pk %}" class="btn btn-outline-primary">{% trans 'Adicionar Matéria' %}</a>
{% endif %}
{% endif %}
<br /><br />
<h2 class="legend">Documentos Acessórios</h2>
{% if docs %}
<p>Total de registros: <b>{{num_docs}}</b></p>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Documento Acessório</th>
</tr>
</thead>
<tbody>
{% for doc in docs %}
<tr>
<td>
<a href="{% url 'sapl.comissoes:documentoacessorio_detail' doc.pk %}">{{ doc.nome }}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if perms.comissoes.add_reuniao %}
<a href="{% url 'sapl.comissoes:documentoacessorio_create' reuniao_pk %}" class="btn btn-outline-primary float-right">{% trans 'Adicionar Documento' %}</a>
{% endif %}
{% else %}
{% if perms.comissoes.add_reuniao %}
<a href="{% url 'sapl.comissoes:documentoacessorio_create' reuniao_pk %}" class="btn btn-outline-primary">{% trans 'Adicionar Documento' %}</a>
{% endif %}
{% endif %}
<br /><br />
{% endblock detail_content %}
Loading…
Cancel
Save