From c6e61e7029ccb91d51e2efb2786f028eeaca0536 Mon Sep 17 00:00:00 2001 From: VictorFabreF Date: Tue, 6 Mar 2018 11:09:23 -0300 Subject: [PATCH 1/4] Fix #1669 (#1734) --- sapl/base/forms.py | 41 ++++++++++++++++++- sapl/base/urls.py | 6 ++- sapl/base/views.py | 19 ++++++++- ...elatorioDataFimPrazoTramitacao_filter.html | 34 +++++++++++++++ sapl/templates/base/relatorios_list.html | 4 ++ 5 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 sapl/templates/base/RelatorioDataFimPrazoTramitacao_filter.html diff --git a/sapl/base/forms.py b/sapl/base/forms.py index 1181650ee..cdc6d25c8 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -466,7 +466,46 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet): self.form.helper = FormHelper() self.form.helper.form_method = 'GET' self.form.helper.layout = Layout( - Fieldset(_('Histórico de Tramita'), + Fieldset(_('Histórico de Tramitação'), + row1, row2, + form_actions(label='Pesquisar')) + ) + +class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet): + + filter_overrides = {models.DateField: { + 'filter_class': django_filters.DateFromToRangeFilter, + 'extra': lambda f: { + 'label': '%s (%s)' % (f.verbose_name, _('Inicial - Final')), + 'widget': RangeWidgetOverride} + }} + + @property + def qs(self): + parent = super(RelatorioDataFimPrazoTramitacaoFilterSet, self).qs + return parent.distinct().order_by('-ano', 'tipo', 'numero') + + class Meta: + model = MateriaLegislativa + fields = ['tipo', 'tramitacao__unidade_tramitacao_local', + 'tramitacao__status', 'tramitacao__data_fim_prazo'] + + def __init__(self, *args, **kwargs): + super(RelatorioDataFimPrazoTramitacaoFilterSet, self).__init__( + *args, **kwargs) + + self.filters['tipo'].label = 'Tipo de Matéria' + + row1 = to_row([('tramitacao__data_fim_prazo', 12)]) + row2 = to_row( + [('tipo', 4), + ('tramitacao__unidade_tramitacao_local', 4), + ('tramitacao__status', 4)]) + + self.form.helper = FormHelper() + self.form.helper.form_method = 'GET' + self.form.helper.layout = Layout( + Fieldset(_('Tramitações por fim de prazo'), row1, row2, form_actions(label='Pesquisar')) ) diff --git a/sapl/base/urls.py b/sapl/base/urls.py index 143a89ba8..3b7d76e40 100644 --- a/sapl/base/urls.py +++ b/sapl/base/urls.py @@ -16,7 +16,8 @@ from .views import (AlterarSenha, AppConfigCrud, CasaLegislativaCrud, RelatorioMateriasPorAnoAutorTipoView, RelatorioMateriasPorAutorView, RelatorioMateriasTramitacaoView, - RelatorioPresencaSessaoView, SaplSearchView) + RelatorioPresencaSessaoView, SaplSearchView, + RelatorioDataFimPrazoTramitacaoView) app_name = AppConfig.name @@ -83,6 +84,9 @@ urlpatterns = [ url(r'^sistema/relatorios/historico-tramitacoes$', RelatorioHistoricoTramitacaoView.as_view(), name='historico_tramitacoes'), + url(r'^sistema/relatorios/data-fim-prazo-tramitacoes$', + RelatorioDataFimPrazoTramitacaoView.as_view(), + name='data_fim_prazo_tramitacoes'), url(r'^sistema/relatorios/presenca$', RelatorioPresencaSessaoView.as_view(), name='presenca_sessao'), diff --git a/sapl/base/views.py b/sapl/base/views.py index f31ed1c58..386364420 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -33,7 +33,8 @@ from .forms import (AlterarSenhaForm, CasaLegislativaForm, RelatorioMateriasPorAnoAutorTipoFilterSet, RelatorioMateriasPorAutorFilterSet, RelatorioMateriasTramitacaoilterSet, - RelatorioPresencaSessaoFilterSet) + RelatorioPresencaSessaoFilterSet, + RelatorioDataFimPrazoTramitacaoFilterSet) from .models import AppConfig, CasaLegislativa @@ -357,6 +358,22 @@ class RelatorioHistoricoTramitacaoView(FilterView): return context +class RelatorioDataFimPrazoTramitacaoView(FilterView): + model = MateriaLegislativa + filterset_class = RelatorioDataFimPrazoTramitacaoFilterSet + template_name = 'base/RelatorioDataFimPrazoTramitacao_filter.html' + + def get_context_data(self, **kwargs): + context = super(RelatorioDataFimPrazoTramitacaoView, + self).get_context_data(**kwargs) + context['title'] = _('Fim de Prazo de Tramitações') + qr = self.request.GET.copy() + context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' + + context['show_results'] = show_results_filter_set(qr) + + return context + class RelatorioMateriasTramitacaoView(FilterView): model = MateriaLegislativa diff --git a/sapl/templates/base/RelatorioDataFimPrazoTramitacao_filter.html b/sapl/templates/base/RelatorioDataFimPrazoTramitacao_filter.html new file mode 100644 index 000000000..1fccf1268 --- /dev/null +++ b/sapl/templates/base/RelatorioDataFimPrazoTramitacao_filter.html @@ -0,0 +1,34 @@ +{% extends "crud/list.html" %} +{% load i18n %} +{% load crispy_forms_tags %} + +{% block base_content %} + {% if not show_results %} + {% crispy filter.form %} + {% endif %} + + {% if show_results %} +
+ {% trans 'Fazer nova pesquisa' %} +
+



+ + + + + + + + + {% for materia in object_list %} + + + + + {% endfor %} + +
MatériaEmenta
+ {{materia.tipo.descricao}} - {{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}} + {{materia.ementa}}
+ {% endif %} +{% endblock base_content %} \ No newline at end of file diff --git a/sapl/templates/base/relatorios_list.html b/sapl/templates/base/relatorios_list.html index c7e47bd69..1bd0f5eda 100644 --- a/sapl/templates/base/relatorios_list.html +++ b/sapl/templates/base/relatorios_list.html @@ -36,6 +36,10 @@ Histórico de tramitações Histórico de tramitações por período e local informados. + + Tramitações por fim de prazo + Tramitações com fim de prazo no intervalo informado. + Date: Tue, 6 Mar 2018 15:46:42 -0300 Subject: [PATCH 2/4] 1566 cadastro de reunioes (#1614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adiciona a model, forms e views da reunião de comissão * Adiciona a model reunião no map_rules * Adiciona telas de cadastro de reunião * Adiciona layouts e legacy * Adiciona template, modifica o subnav e o ListView * Resolve conflitos * corrige form de reunião * Troca Crud por MasterCrudDetail * Adiciona template de cadastro de reunião * Corrige Createview e Listview * Ajusta comissoes * Corrige a model e o layout da tela de criação * Corrige o deleteview e o detailview * FIX #1566 * Muda o layout do create de reunião * Corrige layout e model de reunião * HOT-FIX: tirar ipdb do materia/form * Muda o layout do create de reunião * ajusta campos timefield em reunião de comissões --- sapl/comissoes/forms.py | 33 ++++- sapl/comissoes/legacy.yaml | 18 +++ sapl/comissoes/migrations/0003_reuniao.py | 44 ++++++ sapl/comissoes/migrations/0005_merge.py | 16 +++ .../migrations/0006_auto_20180227_0842.py | 25 ++++ .../migrations/0007_auto_20180227_1025.py | 25 ++++ .../migrations/0008_auto_20180227_1111.py | 25 ++++ .../migrations/0009_auto_20180301_1011.py | 41 ++++++ .../migrations/0010_auto_20180306_0918.py | 25 ++++ sapl/comissoes/models.py | 133 ++++++++++++++++-- sapl/comissoes/urls.py | 3 +- sapl/comissoes/views.py | 57 +++++++- sapl/crispy_layout_mixin.py | 2 +- sapl/materia/forms.py | 1 - sapl/redireciona_urls/views.py | 49 +++++++ sapl/rules/map_rules.py | 1 + sapl/templates/comissoes/layouts.yaml | 15 +- sapl/templates/comissoes/reunioes.html | 8 -- sapl/templates/comissoes/subnav.yaml | 2 + 19 files changed, 491 insertions(+), 32 deletions(-) create mode 100644 sapl/comissoes/migrations/0003_reuniao.py create mode 100644 sapl/comissoes/migrations/0005_merge.py create mode 100644 sapl/comissoes/migrations/0006_auto_20180227_0842.py create mode 100644 sapl/comissoes/migrations/0007_auto_20180227_1025.py create mode 100644 sapl/comissoes/migrations/0008_auto_20180227_1111.py create mode 100644 sapl/comissoes/migrations/0009_auto_20180301_1011.py create mode 100644 sapl/comissoes/migrations/0010_auto_20180306_0918.py delete mode 100644 sapl/templates/comissoes/reunioes.html diff --git a/sapl/comissoes/forms.py b/sapl/comissoes/forms.py index 2ce7dcfaa..33be2003d 100644 --- a/sapl/comissoes/forms.py +++ b/sapl/comissoes/forms.py @@ -3,9 +3,10 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import transaction from django.db.models import Q +from django.forms import ModelForm from django.utils.translation import ugettext_lazy as _ from sapl.base.models import Autor, TipoAutor -from sapl.comissoes.models import Comissao, Composicao, Participacao +from sapl.comissoes.models import Comissao, Composicao, Participacao, Reuniao from sapl.parlamentares.models import Legislatura, Mandato, Parlamentar @@ -69,7 +70,8 @@ class ParticipacaoCreateForm(forms.ModelForm): composicao = Composicao.objects.get(id=self.initial['parent_pk']) participantes = composicao.participacao_set.all() participantes_id = [p.parlamentar.id for p in participantes] - parlamentares = Parlamentar.objects.all().exclude(id__in=participantes_id).order_by('nome_completo') + parlamentares = Parlamentar.objects.all().exclude( + id__in=participantes_id).order_by('nome_completo') parlamentares = [p for p in parlamentares if p.ativo] lista = [] @@ -142,3 +144,30 @@ class ComissaoForm(forms.ModelForm): nome=nome ) return comissao + + +class ReuniaoForm(ModelForm): + + comissao = forms.ModelChoiceField(queryset=Comissao.objects.all(), + widget=forms.HiddenInput()) + + class Meta: + model = Reuniao + exclude = ['cod_andamento_reuniao'] + widgets = { + 'hora_fim': forms.TimeInput(format='%H:%M'), + 'hora_inicio': forms.TimeInput(format='%H:%M'), + } + + def clean(self): + super(ReuniaoForm, self).clean() + + if self.errors: + return + + if self.cleaned_data['hora_fim'] < self.cleaned_data['hora_inicio']: + msg = _('A hora de término da reunião não pode ' + 'ser menor que a de início') + raise ValidationError(msg) + + return self.cleaned_data diff --git a/sapl/comissoes/legacy.yaml b/sapl/comissoes/legacy.yaml index a1093c4d2..2414df885 100644 --- a/sapl/comissoes/legacy.yaml +++ b/sapl/comissoes/legacy.yaml @@ -43,3 +43,21 @@ Participacao (ComposicaoComissao): observacao: obs_composicao parlamentar: cod_parlamentar titular: ind_titular + +Reuniao: + periodo: periodo_reuniao + comissao: cod_comissao + tipo: tipo_comissao + numero: num_comissao + nome: nom_reuniao + tema: tem_reuniao + data: dat_reuniao + hora_inicio: hora_inicio_reuniao + hora_fim: hora_fim_reuniao + local_reuniao: local + observacao: obs_reuniao + ulr_audio: audio_reuniao + url_video: video_reuniao + upload_pauta: pauta_reuniao + upload_ata: ata_reuniao + upload_anexo: anexo_reuniao diff --git a/sapl/comissoes/migrations/0003_reuniao.py b/sapl/comissoes/migrations/0003_reuniao.py new file mode 100644 index 000000000..acdf8bf76 --- /dev/null +++ b/sapl/comissoes/migrations/0003_reuniao.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2017-11-23 13:07 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import sapl.comissoes.models +import sapl.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0002_auto_20170809_1236'), + ] + + operations = [ + migrations.CreateModel( + name='Reuniao', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('numero', models.PositiveIntegerField(verbose_name='Número')), + ('nome', models.CharField(max_length=100, verbose_name='Nome da Reunião')), + ('tema', models.CharField(max_length=100, verbose_name='Tema da Reunião')), + ('data', models.DateField(verbose_name='Data')), + ('hora_inicio', models.CharField(max_length=5, verbose_name='Horário (hh:mm)')), + ('hora_fim', models.CharField(max_length=5, verbose_name='Horário (hh:mm)')), + ('local_reuniao', models.CharField(blank=True, max_length=100, verbose_name='Local Reunião')), + ('observacao', models.CharField(blank=True, max_length=150, verbose_name='Observação')), + ('url_audio', models.URLField(blank=True, max_length=150, verbose_name='URL Arquivo Áudio (Formatos MP3 / AAC)')), + ('url_video', models.URLField(blank=True, max_length=150, verbose_name='URL Arquivo Vídeo (Formatos MP4 / FLV / WebM)')), + ('upload_pauta', models.FileField(blank=True, null=True, upload_to=sapl.comissoes.models.pauta_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Pauta da Reunião')), + ('upload_ata', models.FileField(blank=True, null=True, upload_to=sapl.comissoes.models.ata_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Ata da Reunião')), + ('upload_anexo', models.FileField(blank=True, null=True, upload_to=sapl.comissoes.models.anexo_upload_path, verbose_name='Anexo da Reunião')), + ('comissao', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='comissoes.Comissao', verbose_name='Comissão')), + ('periodo', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='comissoes.Periodo', verbose_name='Periodo da Composicão da Comissão')), + ('tipo', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='comissoes.TipoComissao', verbose_name='Tipo')), + ], + options={ + 'verbose_name': 'Reunião de Comissão', + 'verbose_name_plural': 'Reuniões de Comissão', + }, + ), + ] diff --git a/sapl/comissoes/migrations/0005_merge.py b/sapl/comissoes/migrations/0005_merge.py new file mode 100644 index 000000000..f171eb151 --- /dev/null +++ b/sapl/comissoes/migrations/0005_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-02-26 10:41 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0004_auto_20180102_1652'), + ('comissoes', '0003_reuniao'), + ] + + operations = [ + ] diff --git a/sapl/comissoes/migrations/0006_auto_20180227_0842.py b/sapl/comissoes/migrations/0006_auto_20180227_0842.py new file mode 100644 index 000000000..ebe3e0029 --- /dev/null +++ b/sapl/comissoes/migrations/0006_auto_20180227_0842.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-02-27 11:42 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0005_merge'), + ] + + operations = [ + migrations.AlterField( + model_name='reuniao', + name='url_audio', + field=models.URLField(blank=True, max_length=150, null=True, verbose_name='URL Arquivo Áudio (Formatos MP3 / AAC)'), + ), + migrations.AlterField( + model_name='reuniao', + name='url_video', + field=models.URLField(blank=True, max_length=150, null=True, verbose_name='URL Arquivo Vídeo (Formatos MP4 / FLV / WebM)'), + ), + ] diff --git a/sapl/comissoes/migrations/0007_auto_20180227_1025.py b/sapl/comissoes/migrations/0007_auto_20180227_1025.py new file mode 100644 index 000000000..fb13ba123 --- /dev/null +++ b/sapl/comissoes/migrations/0007_auto_20180227_1025.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-02-27 13:25 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0006_auto_20180227_0842'), + ] + + operations = [ + migrations.AlterField( + model_name='reuniao', + name='hora_fim', + field=models.CharField(max_length=5, verbose_name='Horário de Término (hh:mm)'), + ), + migrations.AlterField( + model_name='reuniao', + name='hora_inicio', + field=models.CharField(max_length=5, verbose_name='Horário de Início (hh:mm)'), + ), + ] diff --git a/sapl/comissoes/migrations/0008_auto_20180227_1111.py b/sapl/comissoes/migrations/0008_auto_20180227_1111.py new file mode 100644 index 000000000..478576a1b --- /dev/null +++ b/sapl/comissoes/migrations/0008_auto_20180227_1111.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-02-27 14:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0007_auto_20180227_1025'), + ] + + operations = [ + migrations.AlterField( + model_name='reuniao', + name='url_audio', + field=models.URLField(blank=True, max_length=150, verbose_name='URL Arquivo Áudio (Formatos MP3 / AAC)'), + ), + migrations.AlterField( + model_name='reuniao', + name='url_video', + field=models.URLField(blank=True, max_length=150, verbose_name='URL Arquivo Vídeo (Formatos MP4 / FLV / WebM)'), + ), + ] diff --git a/sapl/comissoes/migrations/0009_auto_20180301_1011.py b/sapl/comissoes/migrations/0009_auto_20180301_1011.py new file mode 100644 index 000000000..db559de75 --- /dev/null +++ b/sapl/comissoes/migrations/0009_auto_20180301_1011.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-01 13:11 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0008_auto_20180227_1111'), + ] + + operations = [ + migrations.AlterField( + model_name='reuniao', + name='local_reuniao', + field=models.CharField(blank=True, max_length=100, verbose_name='Local da Reunião'), + ), + migrations.AlterField( + model_name='reuniao', + name='observacao', + field=models.TextField(blank=True, max_length=150, verbose_name='Observação'), + ), + migrations.AlterField( + model_name='reuniao', + name='tipo', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='comissoes.TipoComissao', verbose_name='Tipo de Comissão'), + ), + migrations.AlterField( + model_name='reuniao', + name='url_audio', + field=models.URLField(blank=True, max_length=150, verbose_name='URL do Arquivo de Áudio (Formatos MP3 / AAC)'), + ), + migrations.AlterField( + model_name='reuniao', + name='url_video', + field=models.URLField(blank=True, max_length=150, verbose_name='URL do Arquivo de Vídeo (Formatos MP4 / FLV / WebM)'), + ), + ] diff --git a/sapl/comissoes/migrations/0010_auto_20180306_0918.py b/sapl/comissoes/migrations/0010_auto_20180306_0918.py new file mode 100644 index 000000000..b2dd48f87 --- /dev/null +++ b/sapl/comissoes/migrations/0010_auto_20180306_0918.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-06 12:18 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0009_auto_20180301_1011'), + ] + + operations = [ + migrations.AlterField( + model_name='reuniao', + name='hora_fim', + field=models.TimeField(verbose_name='Horário de Término (hh:mm)'), + ), + migrations.AlterField( + model_name='reuniao', + name='hora_inicio', + field=models.TimeField(verbose_name='Horário de Início (hh:mm)'), + ), + ] diff --git a/sapl/comissoes/models.py b/sapl/comissoes/models.py index 078097cab..882fe8638 100644 --- a/sapl/comissoes/models.py +++ b/sapl/comissoes/models.py @@ -1,11 +1,12 @@ - -import reversion from django.db import models from django.utils.translation import ugettext_lazy as _ from model_utils import Choices +import reversion + from sapl.base.models import Autor from sapl.parlamentares.models import Parlamentar -from sapl.utils import YES_NO_CHOICES, SaplGenericRelation +from sapl.utils import (YES_NO_CHOICES, SaplGenericRelation, + restringe_tipos_de_arquivo_txt, texto_upload_path) @reversion.register() @@ -52,22 +53,18 @@ class Comissao(models.Model): secretario = models.CharField( max_length=30, blank=True, verbose_name=_('Secretário')) telefone_reuniao = models.CharField( - max_length=15, - blank=True, + max_length=15, blank=True, verbose_name=_('Tel. Sala Reunião')) endereco_secretaria = models.CharField( - max_length=100, - blank=True, + max_length=100, blank=True, verbose_name=_('Endereço Secretaria')) telefone_secretaria = models.CharField( - max_length=15, - blank=True, + max_length=15, blank=True, verbose_name=_('Tel. Secretaria')) fax_secretaria = models.CharField( max_length=15, blank=True, verbose_name=_('Fax Secretaria')) agenda_reuniao = models.CharField( - max_length=100, - blank=True, + max_length=100, blank=True, verbose_name=_('Data/Hora Reunião')) local_reuniao = models.CharField( max_length=100, blank=True, verbose_name=_('Local Reunião')) @@ -83,7 +80,6 @@ class Comissao(models.Model): default=False, choices=YES_NO_CHOICES, verbose_name=_('Comissão Ativa?')) - autor = SaplGenericRelation(Autor, related_query_name='comissao_set', fields_search=( @@ -170,8 +166,7 @@ class Participacao(models.Model): # ComposicaoComissao null=True, verbose_name=_('Data Desligamento')) motivo_desligamento = models.CharField( - max_length=150, - blank=True, + max_length=150, blank=True, verbose_name=_('Motivo Desligamento')) observacao = models.CharField( max_length=150, blank=True, verbose_name=_('Observação')) @@ -182,3 +177,113 @@ class Participacao(models.Model): # ComposicaoComissao def __str__(self): return '%s : %s' % (self.cargo, self.parlamentar) + + +def get_comissao_media_path(instance, subpath, filename): + return './sapl/comissao/%s/%s/%s' % (instance.numero, subpath, filename) + + +def pauta_upload_path(instance, filename): + return texto_upload_path(instance, filename, subpath='pauta', pk_first=True) + + +def ata_upload_path(instance, filename): + return texto_upload_path(instance, filename, subpath='ata', pk_first=True) + + +def anexo_upload_path(instance, filename): + return texto_upload_path(instance, filename, subpath='anexo', pk_first=True) + + +class Reuniao(models.Model): + periodo = models. ForeignKey( + Periodo, + on_delete=models.PROTECT, + verbose_name=_('Periodo da Composicão da Comissão')) + comissao = models.ForeignKey( + Comissao, + on_delete=models.PROTECT, + verbose_name=_('Comissão')) + tipo = models.ForeignKey( + TipoComissao, + on_delete=models.PROTECT, + verbose_name=_('Tipo de Comissão')) + numero = models.PositiveIntegerField(verbose_name=_('Número')) + nome = models.CharField( + max_length=100, verbose_name=_('Nome da Reunião')) + tema = models.CharField( + max_length=100, verbose_name=_('Tema da Reunião')) + data = models.DateField(verbose_name=_('Data')) + hora_inicio = models.TimeField( + verbose_name=_('Horário de Início (hh:mm)')) + hora_fim = models.TimeField( + verbose_name=_('Horário de Término (hh:mm)')) + local_reuniao = models.CharField( + max_length=100, blank=True, verbose_name=_('Local da Reunião')) + observacao = models.TextField( + max_length=150, blank=True, verbose_name=_('Observação')) + url_audio = models.URLField( + max_length=150, blank=True, + verbose_name=_('URL do Arquivo de Áudio (Formatos MP3 / AAC)')) + url_video = models.URLField( + max_length=150, blank=True, + verbose_name=_('URL do Arquivo de Vídeo (Formatos MP4 / FLV / WebM)')) + upload_pauta = models.FileField( + blank=True, null=True, + upload_to=pauta_upload_path, + verbose_name=_('Pauta da Reunião'), + validators=[restringe_tipos_de_arquivo_txt]) + upload_ata = models.FileField( + blank=True, null=True, + upload_to=ata_upload_path, + verbose_name=_('Ata da Reunião'), + validators=[restringe_tipos_de_arquivo_txt]) + upload_anexo = models.FileField( + blank=True, null=True, + upload_to=anexo_upload_path, + verbose_name=_('Anexo da Reunião')) + + class Meta: + verbose_name = _('Reunião de Comissão') + verbose_name_plural = _('Reuniões de Comissão') + + def __str__(self): + return self.nome + + def delete(self, using=None, keep_parents=False): + if self.upload_pauta: + self.upload_pauta.delete() + + if self.upload_ata: + self.upload_ata.delete() + + if self.upload_anexo: + self.upload_anexo.delete() + + return models.Model.delete( + self, using=using, keep_parents=keep_parents) + + def save(self, force_insert=False, force_update=False, using=None, + update_fields=None): + + if not self.pk and (self.upload_pauta or self.upload_ata or + self.upload_anexo): + upload_pauta = self.upload_pauta + upload_ata = self.upload_ata + upload_anexo = self.upload_anexo + self.upload_pauta = None + self.upload_ata = None + self.upload_anexo = None + models.Model.save(self, force_insert=force_insert, + force_update=force_update, + using=using, + update_fields=update_fields) + + self.upload_pauta = upload_pauta + self.upload_ata = upload_ata + self.upload_anexo = upload_anexo + + return models.Model.save(self, force_insert=force_insert, + force_update=force_update, + using=using, + update_fields=update_fields) diff --git a/sapl/comissoes/urls.py b/sapl/comissoes/urls.py index 128cb7647..47fb0b059 100644 --- a/sapl/comissoes/urls.py +++ b/sapl/comissoes/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import include, url from sapl.comissoes.views import (CargoCrud, ComissaoCrud, ComposicaoCrud, MateriasTramitacaoListView, ParticipacaoCrud, - PeriodoComposicaoCrud, TipoComissaoCrud) + PeriodoComposicaoCrud, ReuniaoCrud, TipoComissaoCrud) from .apps import AppConfig @@ -10,6 +10,7 @@ app_name = AppConfig.name urlpatterns = [ url(r'^comissao/', include(ComissaoCrud.get_urls() + ComposicaoCrud.get_urls() + + ReuniaoCrud.get_urls() + ParticipacaoCrud.get_urls())), url(r'^comissao/(?P\d+)/materias-em-tramitacao$', diff --git a/sapl/comissoes/views.py b/sapl/comissoes/views.py index eb32b977a..0fd5a2c13 100644 --- a/sapl/comissoes/views.py +++ b/sapl/comissoes/views.py @@ -3,13 +3,23 @@ from django.core.urlresolvers import reverse from django.db.models import F from django.views.decorators.clickjacking import xframe_options_exempt from django.views.generic import ListView +from django.views.generic.base import RedirectView +from django.views.generic.detail import DetailView +from django.views.generic.edit import FormMixin + + +from sapl.base.models import AppConfig as AppsAppConfig +from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, + CrudAux, MasterDetailCrud, + PermissionRequiredForAppCrudMixin) from sapl.comissoes.forms import ParticipacaoCreateForm, ParticipacaoEditForm -from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, CrudAux, MasterDetailCrud from sapl.materia.models import MateriaLegislativa, Tramitacao -from .forms import ComissaoForm +from .forms import ReuniaoForm, ComissaoForm + from .models import (CargoComissao, Comissao, Composicao, Participacao, - Periodo, TipoComissao) + Periodo, TipoComissao, Reuniao) +from sapl.comissoes.apps import AppConfig def pegar_url_composicao(pk): @@ -136,3 +146,44 @@ class MateriasTramitacaoListView(ListView): MateriasTramitacaoListView, self).get_context_data(**kwargs) context['object'] = Comissao.objects.get(id=self.kwargs['pk']) return context + +class ReuniaoCrud(MasterDetailCrud): + model = Reuniao + parent_field = 'comissao' + public = [RP_LIST, RP_DETAIL, ] + + class BaseMixin(MasterDetailCrud.BaseMixin): + list_field_names = ['nome', 'tema', 'comissao'] + + @property + def list_url(self): + return '' + + class ListView(MasterDetailCrud.ListView): + paginate_by = 10 + + class UpdateView(MasterDetailCrud.UpdateView): + form_class = ReuniaoForm + + def get_initial(self): + return {'comissao': self.object.comissao} + + + class CreateView(MasterDetailCrud.CreateView): + form_class = ReuniaoForm + + def get_initial(self): + comissao = Comissao.objects.get(id=self.kwargs['pk']) + + return {'comissao': comissao} + + + class DeleteView(MasterDetailCrud.DeleteView): + pass + + + class DetailView(MasterDetailCrud.DetailView): + + @xframe_options_exempt + def get(self, request, *args, **kwargs): + return super().get(request, *args, **kwargs) diff --git a/sapl/crispy_layout_mixin.py b/sapl/crispy_layout_mixin.py index e582ba6c1..0adc1c679 100644 --- a/sapl/crispy_layout_mixin.py +++ b/sapl/crispy_layout_mixin.py @@ -96,7 +96,7 @@ def get_field_display(obj, fieldname): if value is None: display = '' - elif 'date' in str_type_from_value: + elif '.date' in str_type_from_value: display = formats.date_format(value, "SHORT_DATE_FORMAT") elif 'bool' in str_type_from_value: display = _('Sim') if value else _('Não') diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index a8a2baaa1..1eebd5c7d 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -1263,7 +1263,6 @@ class ProposicaoForm(forms.ModelForm): not cd['texto_original'] and \ inst.texto_original: inst.texto_original.delete() - self.gerar_hash(inst, receber_recibo) return super().save(commit) diff --git a/sapl/redireciona_urls/views.py b/sapl/redireciona_urls/views.py index 41d58db33..4f1480a13 100644 --- a/sapl/redireciona_urls/views.py +++ b/sapl/redireciona_urls/views.py @@ -31,6 +31,7 @@ parlamentar_mesa_diretora = (app_parlamentares + ':mesa_diretora') comissao_list = (app_comissoes + ':comissao_list') comissao_detail = (app_comissoes + ':comissao_detail') +reuniao_detail = (app_comissoes + ':reuniao_detail') materialegislativa_detail = (app_materia + ':materialegislativa_detail') materialegislativa_list = (app_materia + ':pesquisar_materia') @@ -633,3 +634,51 @@ class RedirecionaMateriasPorAnoAutorTipo(RedirectView): url = has_iframe(url, self.request) return url + +class RedirecionaReuniao(RedirectView): + permanent = True + + def get_redirect_url(self): + pk_reuniao = self.request.GET.get( + 'cod_comissao', + EMPTY_STRING) + url = EMPTY_STRING + if pk_reuniao: + kwargs = {'pk': pk_reuniao} + try: + url = reverse(reuniao_detail, kwargs=kwargs) + except NoReverseMatch: + raise UnknownUrlNameError(reuniao_detail) + + else: + try: + url = reverse(reuniao_list) + except NoReverseMatch: + raise UnknownUrlNameError(reuniao_list) + + year = self.request.GET.get( + 'ano_reuniao', + EMPTY_STRING) + month = self.request.GET.get( + 'mes_reuniao', + EMPTY_STRING) + day = self.request.GET.get( + 'dia_reuniao', + EMPTY_STRING) + tipo_reuniao = self.request.GET.get( + 'tip_reuniao', + EMPTY_STRING) + + # Remove zeros à esquerda + day = day.lstrip("0") + month = month.lstrip("0") + args = EMPTY_STRING + args += "?data_inicio__year=%s" % (year) + args += "&data_inicio__month=%s" % (month) + args += "&data_inicio__day=%s" % (day) + args += "&tipo=%s&salvar=Pesquisar" % (tipo_reuniao) + url = "%s%s" % (url, args) + + url = has_iframe(url, self.request) + + return url diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index 415c7beca..65d6c0be2 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -89,6 +89,7 @@ rules_group_comissoes = { (comissoes.Composicao, __base__), (comissoes.Participacao, __base__), (materia.Relatoria, __base__), + (comissoes.Reuniao, __base__), ] } diff --git a/sapl/templates/comissoes/layouts.yaml b/sapl/templates/comissoes/layouts.yaml index b5e856b2e..231477a9c 100644 --- a/sapl/templates/comissoes/layouts.yaml +++ b/sapl/templates/comissoes/layouts.yaml @@ -1,4 +1,4 @@ -{% load i18n %} + {% load i18n %} CargoComissao: {% trans 'Período de composição de Comissão' %}: - nome:10 unico @@ -42,4 +42,15 @@ ParticipacaoEdit: - nome_parlamentar cargo titular - data_designacao data_desligamento - motivo_desligamento - - observacao \ No newline at end of file + - observacao + +Reuniao: + {% trans 'Reunião' %}: + - periodo numero tipo + - nome tema local_reuniao + - data hora_inicio hora_fim + - url_video url_audio + - observacao + - upload_pauta upload_ata upload_anexo + - comissao + diff --git a/sapl/templates/comissoes/reunioes.html b/sapl/templates/comissoes/reunioes.html deleted file mode 100644 index 6a78b7881..000000000 --- a/sapl/templates/comissoes/reunioes.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "comissoes/comissao_detail.html" %} -{% load i18n %} - -{% block actions %}{% endblock actions %} - -{% block detail_content %} - TODO ... Reuniões -{% endblock detail_content %} diff --git a/sapl/templates/comissoes/subnav.yaml b/sapl/templates/comissoes/subnav.yaml index 02402bfee..01c10b4f3 100644 --- a/sapl/templates/comissoes/subnav.yaml +++ b/sapl/templates/comissoes/subnav.yaml @@ -6,3 +6,5 @@ urls_extras: participacao_detail participacao_create participacao_edit participacao_delete - title: {% trans 'Matérias em Tramitação' %} url: materias_em_tramitacao +- title: {% trans 'Reunião' %} + url: reuniao_list From cd083f7430e4e88d0d86629aaf3f1948b83373c2 Mon Sep 17 00:00:00 2001 From: Talitha Pumar Date: Tue, 6 Mar 2018 16:23:10 -0300 Subject: [PATCH 3/4] Fix #1735 (#1736) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix protocolação de Materia Legislativa * Fix #1735 * Fix #1735 --- sapl/protocoloadm/forms.py | 2 + sapl/protocoloadm/views.py | 77 +++++++++++++++++++++----------------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py index 7d00d8090..fbac3cf6c 100644 --- a/sapl/protocoloadm/forms.py +++ b/sapl/protocoloadm/forms.py @@ -412,6 +412,8 @@ class ProtocoloMateriaForm(ModelForm): if self.is_valid(): if data['vincular_materia'] == 'True': try: + if not data['ano_materia'] or not data['numero_materia']: + raise ValidationError('Favor informar o número e ano da matéria a ser vinculada') self.materia = MateriaLegislativa.objects.get(ano=data['ano_materia'], numero=data['numero_materia'], tipo=data['tipo_materia']) diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index 47b875753..7c3ea3107 100644 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -277,8 +277,7 @@ class ProtocoloDocumentoView(PermissionRequiredMixin, kwargs={'pk': self.object.id}) def form_valid(self, form): - f = form.save(commit=False) - + protocolo = form.save(commit=False) try: numeracao = sapl.base.models.AppConfig.objects.last( ).sequencia_numeracao @@ -288,30 +287,36 @@ class ProtocoloDocumentoView(PermissionRequiredMixin, messages.add_message(self.request, messages.ERROR, msg) return self.render_to_response(self.get_context_data()) + tipo = form.cleaned_data['tipo_documento'] + if numeracao == 'A': - numero = Protocolo.objects.filter( - ano=timezone.now().year).aggregate(Max('numero')) + numero = DocumentoAdministrativo.objects.filter( + ano=timezone.now().year, tipo=tipo).aggregate(Max('numero')) elif numeracao == 'L': - legislatura = Legislatura.objects.first() + legislatura = Legislatura.objects.filter( + data_inicio__year__lte=timezone.now().year, + data_fim__year__gte=timezone.now().year).first() data_inicio = legislatura.data_inicio data_fim = legislatura.data_fim - numero = Protocolo.objects.filter( - data__gte=data_inicio, data__lte=data_fim).aggregate( - Max('numero')) + numero = DocumentoAdministrativo.objects.filter( + data__gte=data_inicio, + data__lte=data_fim, + tipo=tipo).aggregate( + Max('numero')) elif numeracao == 'U': - numero = Protocolo.objects.all().aggregate(Max('numero')) - - f.tipo_processo = '0' # TODO validar o significado - f.anulado = False - f.numero = (numero['numero__max'] + 1) if numero['numero__max'] else 1 - f.ano = timezone.now().year - f.data = timezone.now() - f.hora = timezone.now().time() - f.timestamp = timezone.now() - f.assunto_ementa = self.request.POST['assunto'] - - f.save() - self.object = f + numero = DocumentoAdministrativo.objects.filter(tipo=tipo).aggregate(Max('numero')) + + protocolo.tipo_processo = '0' # TODO validar o significado + protocolo.anulado = False + protocolo.numero = (numero['numero__max'] + 1) if numero['numero__max'] else 1 + protocolo.ano = timezone.now().year + protocolo.data = timezone.now() + protocolo.hora = timezone.now().time() + protocolo.timestamp = timezone.now() + protocolo.assunto_ementa = self.request.POST['assunto'] + + protocolo.save() + self.object = protocolo return redirect(self.get_success_url()) @@ -414,7 +419,6 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): 'pk': protocolo.pk}) def form_valid(self, form): - try: numeracao = sapl.base.models.AppConfig.objects.last( ).sequencia_numeracao @@ -431,17 +435,21 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): numeracao = tipo.sequencia_numeracao if numeracao == 'A': - numero = Protocolo.objects.filter( - ano=timezone.now().year).aggregate(Max('numero')) + numero = MateriaLegislativa.objects.filter( + ano=timezone.now().year, tipo=tipo).aggregate(Max('numero')) elif numeracao == 'L': - legislatura = Legislatura.objects.first() + legislatura = Legislatura.objects.filter( + data_inicio__year__lte=timezone.now().year, + data_fim__year__gte=timezone.now().year).first() data_inicio = legislatura.data_inicio data_fim = legislatura.data_fim - numero = Protocolo.objects.filter( - data__gte=data_inicio, data__lte=data_fim).aggregate( - Max('numero')) + numero = MateriaLegislativa.objects.filter( + data_apresentacao__gte=data_inicio, + data_apresentacao__lte=data_fim, + tipo=tipo).aggregate( + Max('numero')) elif numeracao == 'U': - numero = Protocolo.objects.all().aggregate(Max('numero')) + numero = MateriaLegislativa.objects.filter(tipo=tipo).aggregate(Max('numero')) if numeracao is None: numero['numero__max'] = 0 @@ -469,11 +477,12 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView): protocolo.save() data = form.cleaned_data - materia = MateriaLegislativa.objects.get(ano=data['ano_materia'], - numero=data['numero_materia'], - tipo=data['tipo_materia']) - materia.numero_protocolo = protocolo.numero - materia.save() + if data['vincular_materia'] == 'True': + materia = MateriaLegislativa.objects.get(ano=data['ano_materia'], + numero=data['numero_materia'], + tipo=data['tipo_materia']) + materia.numero_protocolo = protocolo.numero + materia.save() return redirect(self.get_success_url(protocolo)) From c6d89324df8873a4c5b55f9f5c9a51af757b583f Mon Sep 17 00:00:00 2001 From: Edward Ribeiro Date: Tue, 6 Mar 2018 17:32:00 -0300 Subject: [PATCH 4/4] Release: 3.1.58 --- docker-compose.yml | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5c5341afc..2b9a24183 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ sapldb: ports: - "5432:5432" sapl: - image: interlegis/sapl:3.1.57 + image: interlegis/sapl:3.1.58 restart: always environment: ADMIN_PASSWORD: interlegis diff --git a/setup.py b/setup.py index 5bac3e202..c3db5a9cd 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ install_requires = [ ] setup( name='interlegis-sapl', - version='3.1.57', + version='3.1.58', packages=find_packages(), include_package_data=True, license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007',