From 8884eb21848b39829c754e36ade3340d621ecc53 Mon Sep 17 00:00:00 2001 From: Mariana Mendes Date: Thu, 23 Nov 2017 11:08:08 -0200 Subject: [PATCH] =?UTF-8?q?Adiciona=20a=20model,=20forms=20e=20views=20da?= =?UTF-8?q?=20reuni=C3=A3o=20de=20comiss=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/comissoes/forms.py | 46 +++++++++ sapl/comissoes/migrations/0003_reuniao.py | 44 ++++++++ sapl/comissoes/models.py | 118 +++++++++++++++++++++- sapl/comissoes/urls.py | 3 +- sapl/comissoes/views.py | 80 ++++++++++++++- 5 files changed, 286 insertions(+), 5 deletions(-) create mode 100644 sapl/comissoes/migrations/0003_reuniao.py diff --git a/sapl/comissoes/forms.py b/sapl/comissoes/forms.py index 2ce7dcfaa..9d40ad398 100644 --- a/sapl/comissoes/forms.py +++ b/sapl/comissoes/forms.py @@ -3,11 +3,14 @@ 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.parlamentares.models import Legislatura, Mandato, Parlamentar +from .models import Reuniao + class ParticipacaoCreateForm(forms.ModelForm): @@ -142,3 +145,46 @@ class ComissaoForm(forms.ModelForm): nome=nome ) return comissao + + +class ReuniaoForm(ModelForm): + + class Meta: + model = Reuniao + exclude = ['cod_andamento_sessao'] + + def clean(self): + super(ReuniaoForm, self).clean() + + if not self.is_valid(): + return self.cleaned_data + + instance = self.instance + + num = self.cleaned_data['numero'] + com = self.cleaned_data['comissao'] + tipo = self.cleaned_data['tipo'] + periodo = self.cleaned_data['periodo'] + + error = ValidationError( + "Número de Reunião já existente " + "para a Comissão, Período e Tipo informados. " + "Favor escolher um número distinto.") + + reunioes = Reuniao.objects.filter(numero=num, + comissao=com, + periodo=periodo, + tipo=tipo).\ + values_list('id', flat=True) + + qtd_reunioes = len(reunioes) + + if qtd_reunioes > 0: + if instance.pk: # update + if instance.pk not in reunioes or qtd_reunioes > 1: + raise error + else: # create + raise error + + return self.cleaned_data + 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/models.py b/sapl/comissoes/models.py index 078097cab..55776ec8c 100644 --- a/sapl/comissoes/models.py +++ b/sapl/comissoes/models.py @@ -5,7 +5,8 @@ from django.utils.translation import ugettext_lazy as _ from model_utils import Choices 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() @@ -182,3 +183,118 @@ 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')) + 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( + max_length=100, blank=True, verbose_name=_('Local Reunião')) + observacao = models.CharField( + max_length=150, blank=True, verbose_name=_('Observação')) + url_audio = models.URLField( + max_length=150, blank=True, + verbose_name=_('URL Arquivo Áudio (Formatos MP3 / AAC)')) + url_video = models.URLField( + max_length=150, blank=True, + verbose_name=_('URL Arquivo 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..8862fc2ee 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,67 @@ class MateriasTramitacaoListView(ListView): MateriasTramitacaoListView, self).get_context_data(**kwargs) context['object'] = Comissao.objects.get(id=self.kwargs['pk']) return context + +class ReuniaoCrud(Crud): + model = Reuniao + + class BaseMixin(Crud.BaseMixin): + list_field_names = ['data', 'comissao', 'tipo'] + + @property + def list_url(self): + return '' + + @property + def search_url(self): + namespace = self.model._meta.app_config.name + return reverse('%s:%s' % (namespace, 'pesquisar_reuniao')) + + class ListView(Crud.ListView, RedirectView): + + def get_redirect_url(self, *args, **kwargs): + namespace = self.model._meta.app_config.name + return reverse('%s:%s' % (namespace, 'pesquisar_reuniao')) + # arrumar a url + + def get(self, request, *args, **kwargs): + return RedirectView.get(self, request, *args, **kwargs) + + class UpdateView(Crud.UpdateView): + + form_class = ReuniaoForm + + def get_initial(self): + return {'comissao': self.object.comissao} + + class CreateView(Crud.CreateView): + + form_class = ReuniaoForm + + @property + def cancel_url(self): + return self.search_url + + def get_initial(self): + comissao = Comissao.objects.order_by('-data').first() + if comissao: + return { + 'comissao': comissao + } + else: + msg = _('Cadastre alguma comissão antes de adicionar ' + + 'uma reunião!') + messages.add_message(self.request, messagesself.ERROR, msg) + return {} + + class DeleteView(Crud.DeleteView, RedirectView): + + def get_success_url(self): + namespace = self.model._meta.app_config.name + return reverse('%s:%s' % (namespace, 'reuniao_list')) + +class ReuniaoPermissionMixin(PermissionRequiredForAppCrudMixin, + FormMixin, + DetailView): + model = Reuniao + app_label = AppConfig.label,