diff --git a/sapl/comissoes/forms.py b/sapl/comissoes/forms.py index 9b0e69d72..e999f0b8d 100644 --- a/sapl/comissoes/forms.py +++ b/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.comissoes.models import (Comissao, Composicao, DocumentoAcessorio, Participacao, Reuniao, Periodo) +from sapl.materia.models import PautaReuniao from sapl.parlamentares.models import Legislatura, Mandato, Parlamentar from sapl.utils import FileFieldCheckMixin @@ -383,6 +384,13 @@ class ReuniaoForm(ModelForm): return self.cleaned_data +class PautaReuniaoForm(forms.ModelForm): + + class Meta: + model = PautaReuniao + exclude = ['reuniao'] + + class DocumentoAcessorioCreateForm(FileFieldCheckMixin, forms.ModelForm): parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput()) diff --git a/sapl/comissoes/urls.py b/sapl/comissoes/urls.py index f22f32e1d..5e48ee891 100644 --- a/sapl/comissoes/urls.py +++ b/sapl/comissoes/urls.py @@ -1,7 +1,8 @@ from django.conf.urls import include, url from sapl.comissoes.views import (CargoCrud, ComissaoCrud, ComposicaoCrud, DocumentoAcessorioCrud, MateriasTramitacaoListView, ParticipacaoCrud, - PeriodoComposicaoCrud, ReuniaoCrud, TipoComissaoCrud, get_participacoes_comissao) + PeriodoComposicaoCrud, ReuniaoCrud, TipoComissaoCrud, get_participacoes_comissao, + AdicionaPautaView, RemovePautaView) from .apps import AppConfig @@ -17,6 +18,9 @@ urlpatterns = [ url(r'^comissao/(?P\d+)/materias-em-tramitacao$', MateriasTramitacaoListView.as_view(), name='materias_em_tramitacao'), + url(r'^comissao/(?P\d+)/pauta/add', AdicionaPautaView.as_view(), name='pauta_add'), + url(r'^comissao/(?P\d+)/pauta/remove', RemovePautaView.as_view(), name='pauta_remove'), + url(r'^sistema/comissao/cargo/', include(CargoCrud.get_urls())), url(r'^sistema/comissao/periodo-composicao/', include(PeriodoComposicaoCrud.get_urls())), diff --git a/sapl/comissoes/views.py b/sapl/comissoes/views.py index 1db8d23df..5fa132eab 100644 --- a/sapl/comissoes/views.py +++ b/sapl/comissoes/views.py @@ -1,13 +1,16 @@ import logging 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.http.response import HttpResponseRedirect, JsonResponse 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.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.comissoes.apps import AppConfig @@ -15,11 +18,11 @@ from sapl.comissoes.forms import (ComissaoForm, ComposicaoForm, DocumentoAcessorioCreateForm, DocumentoAcessorioEditForm, ParticipacaoCreateForm, ParticipacaoEditForm, - PeriodoForm, ReuniaoForm) + PeriodoForm, ReuniaoForm, PautaReuniaoForm) from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, MasterDetailCrud, PermissionRequiredForAppCrudMixin) -from sapl.materia.models import MateriaLegislativa, Tramitacao +from sapl.materia.models import MateriaLegislativa, Tramitacao, PautaReuniao from .models import (CargoComissao, Comissao, Composicao, DocumentoAcessorio, Participacao, Periodo, Reuniao, TipoComissao) @@ -162,25 +165,26 @@ class ComissaoCrud(Crud): return super(Crud.UpdateView, self).form_valid(form) +def lista_materias_comissao(comissao_pk): + ts = Tramitacao.objects.order_by( + 'materia', '-data_tramitacao', '-id').annotate( + comissao=F('unidade_tramitacao_destino__comissao')).distinct( + 'materia').values_list('materia', 'comissao') + + ts = [m for (m,c) in ts if c == int(comissao_pk)] + + materias = MateriaLegislativa.objects.filter( + pk__in=ts).order_by('tipo', '-ano', '-numero') + + return materias + + class MateriasTramitacaoListView(ListView): template_name = "comissoes/materias_em_tramitacao.html" paginate_by = 10 def get_queryset(self): - # FIXME: Otimizar consulta - ts = Tramitacao.objects.order_by( - 'materia', '-data_tramitacao', '-id').annotate( - comissao=F('unidade_tramitacao_destino__comissao')).distinct( - 'materia').values_list('materia', 'comissao') - - ts = list(filter(lambda x: x[1] == int(self.kwargs['pk']), ts)) - ts = list(zip(*ts)) - ts = ts[0] if ts else [] - - materias = MateriaLegislativa.objects.filter( - pk__in=ts).order_by('tipo', '-ano', '-numero') - - return materias + return lista_materias_comissao(self.kwargs['pk']) def get_context_data(self, **kwargs): context = super( @@ -193,13 +197,38 @@ class MateriasTramitacaoListView(ListView): class ReuniaoCrud(MasterDetailCrud): model = Reuniao parent_field = 'comissao' - model_set = 'documentoacessorio_set' public = [RP_LIST, RP_DETAIL, ] class BaseMixin(MasterDetailCrud.BaseMixin): list_field_names = ['data', 'nome', 'tema', 'upload_ata'] 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): logger = logging.getLogger(__name__) paginate_by = 10 @@ -249,6 +278,100 @@ class ReuniaoCrud(MasterDetailCrud): 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): model = DocumentoAcessorio parent_field = 'reuniao__comissao' diff --git a/sapl/materia/migrations/0049_pautareuniao.py b/sapl/materia/migrations/0049_pautareuniao.py new file mode 100644 index 000000000..d502c8bc2 --- /dev/null +++ b/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', + }, + ), + ] diff --git a/sapl/materia/models.py b/sapl/materia/models.py index aad491b01..182c705b9 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -11,7 +11,7 @@ from model_utils import Choices import reversion 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, TextoArticulado) 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() class Anexada(models.Model): materia_principal = models.ForeignKey( diff --git a/sapl/materia/views.py b/sapl/materia/views.py index e2e117caa..d1d933e47 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -2190,8 +2190,8 @@ class MateriaAnexadaEmLoteView(PermissionRequiredMixin, FilterView): msg = _('Matéria(s) anexada(s).') messages.add_message(request, messages.SUCCESS, msg) - sucess_url = reverse('sapl_index') + 'materia/' + kwargs['pk'] + '/anexada' - return HttpResponseRedirect(sucess_url) + success_url = reverse('sapl.materia:anexada_list', kwargs={'pk': kwargs['pk']}) + return HttpResponseRedirect(success_url) class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index 6081be6ef..050696504 100755 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -1090,7 +1090,7 @@ class DocumentoAnexadoEmLoteView(PermissionRequiredMixin, FilterView): msg = _('Documento(s) anexado(s).') 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) diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index 75fc76f33..4f1b4b515 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -103,6 +103,7 @@ rules_group_protocolo = { rules_group_comissoes = { 'group': SAPL_GROUP_COMISSOES, 'rules': [ + (materia.PautaReuniao, __base__, __perms_publicas__), (comissoes.Comissao, __base__, __perms_publicas__), (comissoes.Composicao, __base__, __perms_publicas__), (comissoes.Participacao, __base__, __perms_publicas__), diff --git a/sapl/templates/comissoes/pauta.html b/sapl/templates/comissoes/pauta.html new file mode 100644 index 000000000..757c4cf86 --- /dev/null +++ b/sapl/templates/comissoes/pauta.html @@ -0,0 +1,68 @@ +{% extends "crud/detail.html" %} +{% load i18n crispy_forms_tags %} +{% block actions %}{% endblock %} + +{% block title %} +

+ {% if opcao %} + Adicionar Matérias à Pauta (Reunião: {{object}}) + {% else %} + Remover Matérias da Pauta (Reunião: {{object}}) + {% endif %} +

+{% endblock %} + +{% block detail_content %} + {% if materias %} + {% if num_materias == 1 %} + Há {{num_materias}} matéria disponível.

+ {% else %} + Há {{num_materias}} matérias disponíveis.

+ {% endif %} +
+ {% csrf_token %} +
+ +
+
+ +
+
+ + + + + {% for materia in materias %} + + + + {% endfor %} + +
Matéria
+ + {{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}} - {{materia.tipo.descricao}} +
+
+ {% if opcao %} + + {% else %} + + {% endif %} +
+ {% else %} + Não há matéria disponível.

+ {% endif %} +{% endblock %} +{% block extra_js %} + +{% endblock %} diff --git a/sapl/templates/comissoes/reuniao_detail.html b/sapl/templates/comissoes/reuniao_detail.html new file mode 100644 index 000000000..c8c32cd98 --- /dev/null +++ b/sapl/templates/comissoes/reuniao_detail.html @@ -0,0 +1,65 @@ +{% extends "crud/detail.html" %} +{% load i18n %} + +{% block detail_content %} + {{ block.super }} +

Pauta

+ {% if mats %} +

Total de Registros: {{num_mats}}

+ + + + + + + + {% for mat in mats %} + + + + {% endfor %} + +
Matéria
+ {{mat}} +
+ {% if perms.comissoes.add_reuniao %} + + {% endif %} + {% else %} + {% if perms.comissoes.add_reuniao %} + {% trans 'Adicionar Matéria' %} + {% endif %} + {% endif %} +

+

Documentos Acessórios

+ {% if docs %} +

Total de registros: {{num_docs}}

+ + + + + + + + {% for doc in docs %} + + + + {% endfor %} + +
Documento Acessório
+ {{ doc.nome }} +
+ {% if perms.comissoes.add_reuniao %} + {% trans 'Adicionar Documento' %} + {% endif %} + {% else %} + {% if perms.comissoes.add_reuniao %} + {% trans 'Adicionar Documento' %} + {% endif %} + {% endif %} +

+{% endblock detail_content %}