From 29e94455e3a1f3d0b066090fdd60864834d9d208 Mon Sep 17 00:00:00 2001 From: Mariana Mendes Date: Tue, 13 Mar 2018 08:13:02 -0300 Subject: [PATCH] Fix #1566 (#1750) 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, forms e views da reunião de comissão * Adiciona a model, forms e views da reunião de comissão * Adiciona telas de cadastro de reunião * Adiciona telas de cadastro de reunião * Adiciona layouts e legacy * Adiciona template, modifica o subnav e o ListView * Troca Crud por MasterCrudDetail * Troca Crud por MasterCrudDetail * Troca Crud por MasterCrudDetail * Adiciona template de cadastro de reunião * Corrige Createview e Listview * Ajusta comissoes * Corrige o deleteview e o detailview * Muda o layout do create de reunião * - Retira o campo tipo de cadastro de reunião - Retira a obrigatoriedade do campo tema * Corrige migração * Adiciona Documento Acessorio em cadastro de reunião * Adiciona migração * Adiciona Documento Acessorio no Map Rules * Corrige alguns erros e adiciona mais detalhes no template * Pequena correção * Fix #1566 * Adiciona migração e corrige list view * Ajusta titulo de exibição do documento acessorio --- sapl/comissoes/forms.py | 56 ++++++++--- sapl/comissoes/legacy.yaml | 1 - .../migrations/0010_auto_20180307_1645.py | 29 ++++++ sapl/comissoes/migrations/0011_merge.py | 16 +++ .../migrations/0012_documentoacessorio.py | 36 +++++++ .../migrations/0013_auto_20180312_1533.py | 20 ++++ sapl/comissoes/models.py | 70 +++++++++++-- sapl/comissoes/urls.py | 9 +- sapl/comissoes/views.py | 98 ++++++++++++++----- sapl/materia/forms.py | 1 + sapl/rules/map_rules.py | 1 + .../templates/comissoes/cadastro_reuniao.html | 7 ++ .../comissoes/cadastro_reuniao_edit.html | 7 ++ sapl/templates/comissoes/layouts.yaml | 17 +++- sapl/templates/comissoes/subnav.yaml | 1 + 15 files changed, 319 insertions(+), 50 deletions(-) create mode 100644 sapl/comissoes/migrations/0010_auto_20180307_1645.py create mode 100644 sapl/comissoes/migrations/0011_merge.py create mode 100644 sapl/comissoes/migrations/0012_documentoacessorio.py create mode 100644 sapl/comissoes/migrations/0013_auto_20180312_1533.py create mode 100644 sapl/templates/comissoes/cadastro_reuniao.html create mode 100644 sapl/templates/comissoes/cadastro_reuniao_edit.html diff --git a/sapl/comissoes/forms.py b/sapl/comissoes/forms.py index ea27eab0d..e3d9845c3 100644 --- a/sapl/comissoes/forms.py +++ b/sapl/comissoes/forms.py @@ -7,7 +7,8 @@ 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, Reuniao +from sapl.comissoes.models import (Comissao, Composicao, DocumentoAcessorio, + Participacao, Reuniao) from sapl.parlamentares.models import Legislatura, Mandato, Parlamentar @@ -155,20 +156,53 @@ class ReuniaoForm(ModelForm): 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']: + 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 + +class DocumentoAcessorioCreateForm(forms.ModelForm): + + parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput()) + + class Meta: + model = DocumentoAcessorio + exclude = ['reuniao'] + + def __init__(self, user=None, **kwargs): + super(DocumentoAcessorioCreateForm, self).__init__(**kwargs) + + if self.instance: + reuniao = Reuniao.objects.get(id=self.initial['parent_pk']) + comissao = reuniao.comissao + comissao_pk = comissao.id + documentos = reuniao.documentoacessorio_set.all() + return self.create_documentoacessorio() + - 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) + def create_documentoacessorio(self): + reuniao = Reuniao.objects.get(id=self.initial['parent_pk']) + def clean(self): + super(DocumentoAcessorioCreateForm, self).clean() return self.cleaned_data + + +class DocumentoAcessorioEditForm(forms.ModelForm): + + parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput()) + + class Meta: + model = DocumentoAcessorio + fields = ['nome', 'data', 'autor', 'ementa', + 'indexacao', 'arquivo'] + + def __init__(self, user=None, **kwargs): + super(DocumentoAcessorioEditForm, self).__init__(**kwargs) + diff --git a/sapl/comissoes/legacy.yaml b/sapl/comissoes/legacy.yaml index 2414df885..59aacf503 100644 --- a/sapl/comissoes/legacy.yaml +++ b/sapl/comissoes/legacy.yaml @@ -47,7 +47,6 @@ Participacao (ComposicaoComissao): Reuniao: periodo: periodo_reuniao comissao: cod_comissao - tipo: tipo_comissao numero: num_comissao nome: nom_reuniao tema: tem_reuniao diff --git a/sapl/comissoes/migrations/0010_auto_20180307_1645.py b/sapl/comissoes/migrations/0010_auto_20180307_1645.py new file mode 100644 index 000000000..45b86bec4 --- /dev/null +++ b/sapl/comissoes/migrations/0010_auto_20180307_1645.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-07 19:45 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0009_auto_20180301_1011'), + ] + + operations = [ + migrations.RemoveField( + model_name='reuniao', + name='tipo', + ), + migrations.AlterField( + model_name='reuniao', + name='nome', + field=models.CharField(max_length=150, verbose_name='Nome da Reunião'), + ), + migrations.AlterField( + model_name='reuniao', + name='tema', + field=models.CharField(blank=True, max_length=150, verbose_name='Tema da Reunião'), + ), + ] diff --git a/sapl/comissoes/migrations/0011_merge.py b/sapl/comissoes/migrations/0011_merge.py new file mode 100644 index 000000000..a5a3c7b7a --- /dev/null +++ b/sapl/comissoes/migrations/0011_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-09 10:27 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0010_auto_20180307_1645'), + ('comissoes', '0010_auto_20180306_0918'), + ] + + operations = [ + ] diff --git a/sapl/comissoes/migrations/0012_documentoacessorio.py b/sapl/comissoes/migrations/0012_documentoacessorio.py new file mode 100644 index 000000000..ed296ac21 --- /dev/null +++ b/sapl/comissoes/migrations/0012_documentoacessorio.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-09 12:38 +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', '0011_merge'), + ] + + operations = [ + migrations.CreateModel( + name='DocumentoAcessorio', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nome', models.CharField(max_length=50, verbose_name='Nome')), + ('data', models.DateField(blank=True, default=None, null=True, verbose_name='Data')), + ('autor', models.CharField(blank=True, max_length=50, verbose_name='Autor')), + ('ementa', models.TextField(blank=True, verbose_name='Ementa')), + ('indexacao', models.TextField(blank=True)), + ('arquivo', models.FileField(blank=True, null=True, upload_to=sapl.comissoes.models.anexo_upload_path, validators=[sapl.utils.restringe_tipos_de_arquivo_txt], verbose_name='Texto Integral')), + ('data_ultima_atualizacao', models.DateTimeField(auto_now=True, null=True, verbose_name='Data')), + ('reuniao', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='documentoacessorio_set', to='comissoes.Reuniao')), + ], + options={ + 'verbose_name': 'Documento Acessório', + 'verbose_name_plural': 'Documentos Acessórios', + }, + ), + ] diff --git a/sapl/comissoes/migrations/0013_auto_20180312_1533.py b/sapl/comissoes/migrations/0013_auto_20180312_1533.py new file mode 100644 index 000000000..2210053ff --- /dev/null +++ b/sapl/comissoes/migrations/0013_auto_20180312_1533.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-03-12 18:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0012_documentoacessorio'), + ] + + operations = [ + migrations.AlterField( + model_name='documentoacessorio', + name='autor', + field=models.CharField(max_length=100, verbose_name='Autor'), + ), + ] diff --git a/sapl/comissoes/models.py b/sapl/comissoes/models.py index 2a5bd489c..f0b1b8d8b 100644 --- a/sapl/comissoes/models.py +++ b/sapl/comissoes/models.py @@ -182,15 +182,13 @@ class Participacao(models.Model): # ComposicaoComissao 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) + 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) @@ -204,15 +202,11 @@ class Reuniao(models.Model): 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')) + max_length=150, verbose_name=_('Nome da Reunião')) tema = models.CharField( - max_length=100, verbose_name=_('Tema da Reunião')) + max_length=150, blank=True, 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)')) @@ -287,3 +281,61 @@ class Reuniao(models.Model): force_update=force_update, using=using, update_fields=update_fields) + + +@reversion.register() +class DocumentoAcessorio(models.Model): + reuniao = models.ForeignKey(Reuniao, + related_name='documentoacessorio_set', + on_delete=models.PROTECT) + nome = models.CharField(max_length=50, verbose_name=_('Nome')) + + data = models.DateField(blank=True, null=True, default=None, verbose_name=_('Data')) + autor = models.CharField( + max_length=100, verbose_name=_('Autor')) + ementa = models.TextField(blank=True, verbose_name=_('Ementa')) + indexacao = models.TextField(blank=True) + arquivo = models.FileField( + blank=True, + null=True, + upload_to=anexo_upload_path, + verbose_name=_('Texto Integral'), + validators=[restringe_tipos_de_arquivo_txt]) + + data_ultima_atualizacao = models.DateTimeField( + blank=True, null=True, + auto_now=True, + verbose_name=_('Data')) + + class Meta: + verbose_name = _('Documento Acessório') + verbose_name_plural = _('Documentos Acessórios') + + def __str__(self): + return _('%(nome)s por %(autor)s') % { + 'nome': self.nome, + 'autor': self.autor} + + def delete(self, using=None, keep_parents=False): + if self.arquivo: + self.arquivo.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.arquivo: + arquivo = self.arquivo + self.arquivo = None + models.Model.save(self, force_insert=force_insert, + force_update=force_update, + using=using, + update_fields=update_fields) + self.arquivo = arquivo + + 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 4bf1904aa..72886f1f1 100644 --- a/sapl/comissoes/urls.py +++ b/sapl/comissoes/urls.py @@ -1,9 +1,7 @@ from django.conf.urls import include, url - from sapl.comissoes.views import (CargoCrud, ComissaoCrud, ComposicaoCrud, - MateriasTramitacaoListView, ParticipacaoCrud, - PeriodoComposicaoCrud, ReuniaoCrud, - TipoComissaoCrud) + DocumentoAcessorioCrud, MateriasTramitacaoListView, ParticipacaoCrud, + PeriodoComposicaoCrud, ReuniaoCrud, TipoComissaoCrud) from .apps import AppConfig @@ -13,7 +11,8 @@ urlpatterns = [ url(r'^comissao/', include(ComissaoCrud.get_urls() + ComposicaoCrud.get_urls() + ReuniaoCrud.get_urls() + - ParticipacaoCrud.get_urls())), + ParticipacaoCrud.get_urls() + + DocumentoAcessorioCrud.get_urls())), url(r'^comissao/(?P\d+)/materias-em-tramitacao$', MateriasTramitacaoListView.as_view(), name='materias_em_tramitacao'), diff --git a/sapl/comissoes/views.py b/sapl/comissoes/views.py index 8324f4b6a..428f2bdab 100644 --- a/sapl/comissoes/views.py +++ b/sapl/comissoes/views.py @@ -1,23 +1,27 @@ from django.core.urlresolvers import reverse from django.db.models import F +from django.http.response import HttpResponseRedirect 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.comissoes.apps import AppConfig -from sapl.comissoes.forms import ParticipacaoCreateForm, ParticipacaoEditForm -from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, - MasterDetailCrud, - PermissionRequiredForAppCrudMixin) +from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, + CrudAux, MasterDetailCrud, + PermissionRequiredForAppCrudMixin) +from sapl.comissoes.forms import (ComissaoForm, DocumentoAcessorioCreateForm, + DocumentoAcessorioEditForm, ParticipacaoCreateForm, + ParticipacaoEditForm, ReuniaoForm) from sapl.materia.models import MateriaLegislativa, Tramitacao -from .forms import ComissaoForm, ReuniaoForm -from .models import (CargoComissao, Comissao, Composicao, Participacao, - Periodo, Reuniao, TipoComissao) + +from .models import (CargoComissao, Comissao, Composicao, DocumentoAcessorio, + Participacao, Periodo, TipoComissao, Reuniao) +from sapl.comissoes.apps import AppConfig def pegar_url_composicao(pk): @@ -26,6 +30,11 @@ def pegar_url_composicao(pk): url = reverse('sapl.comissoes:composicao_detail', kwargs={'pk': comp_pk}) return url +def pegar_url_reuniao(pk): + documentoacessorio = DocumentoAcessorio.objects.get(id=pk) + r_pk = documentoacessorio.reuniao.pk + url = reverse('sapl.comissoes:reuniao_detail', kwargs={'pk': r_pk}) + return url CargoCrud = CrudAux.build(CargoComissao, 'cargo_comissao') PeriodoComposicaoCrud = CrudAux.build(Periodo, 'periodo_composicao_comissao') @@ -58,7 +67,6 @@ class ParticipacaoCrud(MasterDetailCrud): form_class = ParticipacaoEditForm class DeleteView(MasterDetailCrud.DeleteView): - def get_success_url(self): composicao_comissao_pk = self.object.composicao.comissao.pk composicao_pk = self.object.composicao.pk @@ -146,22 +154,44 @@ class MateriasTramitacaoListView(ListView): context['object'] = Comissao.objects.get(id=self.kwargs['pk']) return context - class ReuniaoCrud(MasterDetailCrud): model = Reuniao parent_field = 'comissao' + model_set = 'documentoacessorio_set' public = [RP_LIST, RP_DETAIL, ] class BaseMixin(MasterDetailCrud.BaseMixin): - list_field_names = ['nome', 'tema', 'comissao'] - - @property - def list_url(self): - return '' + list_field_names = [ 'nome', 'tema', 'data'] class ListView(MasterDetailCrud.ListView): paginate_by = 10 + def take_reuniao_pk(self): + try: + return int(self.request.GET['pk']) + except: + return 0 + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + reuniao_pk = self.take_reuniao_pk() + + if reuniao_pk == 0: + ultima_reuniao = list(context['reuniao_list']) + if len(ultima_reuniao) > 0: + ultimo = ultima_reuniao[-1] + context['reuniao_pk'] = ultimo.pk + else: + context['reuniao_pk'] = 0 + else: + context['reuniao_pk'] = reuniao_pk + + context['documentoacessorio_set'] = DocumentoAcessorio.objects.filter( + reuniao__pk=context['reuniao_pk'] + ).order_by('id') + return context + class UpdateView(MasterDetailCrud.UpdateView): form_class = ReuniaoForm @@ -172,15 +202,37 @@ class ReuniaoCrud(MasterDetailCrud): form_class = ReuniaoForm def get_initial(self): - comissao = Comissao.objects.get(id=self.kwargs['pk']) + comissao = Comissao.objects.get(id=self.kwargs['pk']) - return {'comissao': comissao} + return {'comissao': comissao} - class DeleteView(MasterDetailCrud.DeleteView): - pass - class DetailView(MasterDetailCrud.DetailView): +class DocumentoAcessorioCrud(MasterDetailCrud): + model = DocumentoAcessorio + parent_field = 'reuniao__comissao' + public = [RP_DETAIL, ] + ListView = None + link_return_to_parent_field = True + + class BaseMixin(MasterDetailCrud.BaseMixin): + list_field_names = ['nome', 'tipo', 'data', 'autor', 'arquivo'] - @xframe_options_exempt - def get(self, request, *args, **kwargs): - return super().get(request, *args, **kwargs) + class CreateView(MasterDetailCrud.CreateView): + form_class = DocumentoAcessorioCreateForm + + def get_initial(self): + initial = super().get_initial() + initial['parent_pk'] = self.kwargs['pk'] + return initial + + class UpdateView(MasterDetailCrud.UpdateView): + layout_key = 'DocumentoAcessorioEdit' + form_class = DocumentoAcessorioEditForm + + class DeleteView(MasterDetailCrud.DeleteView): + def delete(self, *args, **kwargs): + obj = self.get_object() + obj.delete() + return HttpResponseRedirect( + reverse('sapl.comissoes:reuniao_detail', + kwargs={'pk': obj.reuniao.pk})) \ No newline at end of file diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index a7cac7df5..d89f30578 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -1266,6 +1266,7 @@ class ProposicaoForm(forms.ModelForm): inst.texto_original.delete() self.gerar_hash(inst, receber_recibo) + return super().save(commit) inst.ano = timezone.now().year diff --git a/sapl/rules/map_rules.py b/sapl/rules/map_rules.py index 65d6c0be2..d9d199276 100644 --- a/sapl/rules/map_rules.py +++ b/sapl/rules/map_rules.py @@ -90,6 +90,7 @@ rules_group_comissoes = { (comissoes.Participacao, __base__), (materia.Relatoria, __base__), (comissoes.Reuniao, __base__), + (comissoes.DocumentoAcessorio, __base__), ] } diff --git a/sapl/templates/comissoes/cadastro_reuniao.html b/sapl/templates/comissoes/cadastro_reuniao.html new file mode 100644 index 000000000..9682c56d5 --- /dev/null +++ b/sapl/templates/comissoes/cadastro_reuniao.html @@ -0,0 +1,7 @@ +{% extends "crud/detail.html" %} +{% load i18n %} +{% load crispy_forms_tags %} +{% block actions %}{% endblock %} +{% block detail_content %} + {% crispy form %} +{% endblock detail_content %} diff --git a/sapl/templates/comissoes/cadastro_reuniao_edit.html b/sapl/templates/comissoes/cadastro_reuniao_edit.html new file mode 100644 index 000000000..9682c56d5 --- /dev/null +++ b/sapl/templates/comissoes/cadastro_reuniao_edit.html @@ -0,0 +1,7 @@ +{% extends "crud/detail.html" %} +{% load i18n %} +{% load crispy_forms_tags %} +{% block actions %}{% endblock %} +{% block detail_content %} + {% crispy form %} +{% endblock detail_content %} diff --git a/sapl/templates/comissoes/layouts.yaml b/sapl/templates/comissoes/layouts.yaml index 231477a9c..f0a21799c 100644 --- a/sapl/templates/comissoes/layouts.yaml +++ b/sapl/templates/comissoes/layouts.yaml @@ -46,7 +46,7 @@ ParticipacaoEdit: Reuniao: {% trans 'Reunião' %}: - - periodo numero tipo + - periodo numero - nome tema local_reuniao - data hora_inicio hora_fim - url_video url_audio @@ -54,3 +54,18 @@ Reuniao: - upload_pauta upload_ata upload_anexo - comissao +DocumentoAcessorio: + {% trans 'Documento Acessório' %}: + - nome data + - autor arquivo + - indexacao + - ementa + +DocumentoAcessorioEdit: + {% trans 'Documento Acessório' %}: + - nome data + - autor arquivo + - indexacao + - ementa + + diff --git a/sapl/templates/comissoes/subnav.yaml b/sapl/templates/comissoes/subnav.yaml index 01c10b4f3..e7e49837b 100644 --- a/sapl/templates/comissoes/subnav.yaml +++ b/sapl/templates/comissoes/subnav.yaml @@ -8,3 +8,4 @@ url: materias_em_tramitacao - title: {% trans 'Reunião' %} url: reuniao_list +