diff --git a/sapl/base/views.py b/sapl/base/views.py index db6b9a1f7..9d41fe106 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -77,36 +77,33 @@ class AutorCrud(CrudAux): # FIXME try except - testar envio de emails pk_autor = self.object.id - try: - kwargs = {} - user = self.object.user - - if user.is_active: - return reverse('sapl.base:autor_detail', - kwargs={'pk': pk_autor}) - - kwargs['token'] = default_token_generator.make_token(user) - kwargs['uidb64'] = urlsafe_base64_encode(force_bytes(user.pk)) - assunto = "SAPL - Confirmação de Conta" - full_url = self.request.get_raw_uri() - url_base = full_url[:full_url.find('sistema') - 1] - - mensagem = ( - "Este e-mail foi utilizado para fazer cadastro no " + - "SAPL com o perfil de Autor. Agora você pode " + - "criar/editar/enviar Proposições.\n" + - "Seu nome de usuário é: " + - self.request.POST['username'] + "\n" - "Caso você não tenha feito este cadastro, por favor " + - "ignore esta mensagem. Caso tenha, clique " + - "no link abaixo\n" + url_base + - reverse('sapl.materia:confirmar_email', kwargs=kwargs)) - remetente = [settings.EMAIL_SEND_USER] - destinatario = [user.email] - send_mail(assunto, mensagem, remetente, destinatario, - fail_silently=False) - except: - pass + kwargs = {} + user = self.object.user + + """if user.is_active: + return reverse('sapl.base:autor_detail', + kwargs={'pk': pk_autor})""" + + kwargs['token'] = default_token_generator.make_token(user) + kwargs['uidb64'] = urlsafe_base64_encode(force_bytes(user.pk)) + assunto = "SAPL - Confirmação de Conta" + full_url = self.request.get_raw_uri() + url_base = full_url[:full_url.find('sistema') - 1] + + mensagem = ( + "Este e-mail foi utilizado para fazer cadastro no " + + "SAPL com o perfil de Autor. Agora você pode " + + "criar/editar/enviar Proposições.\n" + + "Seu nome de usuário é: " + + self.request.POST['username'] + "\n" + "Caso você não tenha feito este cadastro, por favor " + + "ignore esta mensagem. Caso tenha, clique " + + "no link abaixo\n" + url_base + + reverse('sapl.materia:confirmar_email', kwargs=kwargs)) + remetente = [settings.EMAIL_SEND_USER] + destinatario = [user.email] + send_mail(assunto, mensagem, remetente, destinatario, + fail_silently=False) return reverse('sapl.base:autor_detail', kwargs={'pk': pk_autor}) @@ -121,32 +118,29 @@ class AutorCrud(CrudAux): def get_success_url(self): pk_autor = self.object.id - try: - # FIXME try except - testar envio de emails - kwargs = {} - user = self.object.user - kwargs['token'] = default_token_generator.make_token(user) - kwargs['uidb64'] = urlsafe_base64_encode(force_bytes(user.pk)) - assunto = "SAPL - Confirmação de Conta" - full_url = self.request.get_raw_uri() - url_base = full_url[:full_url.find('sistema') - 1] - - mensagem = ( - "Este e-mail foi utilizado para fazer cadastro no " + - "SAPL com o perfil de Autor. Agora você pode " + - "criar/editar/enviar Proposições.\n" + - "Seu nome de usuário é: " + - self.request.POST['username'] + "\n" - "Caso você não tenha feito este cadastro, por favor " + - "ignore esta mensagem. Caso tenha, clique " + - "no link abaixo\n" + url_base + - reverse('sapl.materia:confirmar_email', kwargs=kwargs)) - remetente = settings.EMAIL_SEND_USER - destinatario = [user.email] - send_mail(assunto, mensagem, remetente, destinatario, - fail_silently=False) - except: - pass + # FIXME try except - testar envio de emails + kwargs = {} + user = self.object.user + kwargs['token'] = default_token_generator.make_token(user) + kwargs['uidb64'] = urlsafe_base64_encode(force_bytes(user.pk)) + assunto = "SAPL - Confirmação de Conta" + full_url = self.request.get_raw_uri() + url_base = full_url[:full_url.find('sistema') - 1] + + mensagem = ( + "Este e-mail foi utilizado para fazer cadastro no " + + "SAPL com o perfil de Autor. Agora você pode " + + "criar/editar/enviar Proposições.\n" + + "Seu nome de usuário é: " + + self.request.POST['username'] + "\n" + "Caso você não tenha feito este cadastro, por favor " + + "ignore esta mensagem. Caso tenha, clique " + + "no link abaixo\n" + url_base + + reverse('sapl.materia:confirmar_email', kwargs=kwargs)) + remetente = settings.EMAIL_SEND_USER + destinatario = [user.email] + send_mail(assunto, mensagem, remetente, destinatario, + fail_silently=False) return reverse('sapl.base:autor_detail', kwargs={'pk': pk_autor}) diff --git a/sapl/crud/base.py b/sapl/crud/base.py index 03c02a7c6..77ec08e4d 100644 --- a/sapl/crud/base.py +++ b/sapl/crud/base.py @@ -200,7 +200,6 @@ class PermissionRequiredContainerCrudMixin(PermissionRequiredMixin): container = self.container_field.split('__') if len(container) > 1: - # TODO: implementar caso o user for o próprio o container container_model = getattr( self.model, container[0]).field.related_model @@ -211,9 +210,12 @@ class PermissionRequiredContainerCrudMixin(PermissionRequiredMixin): if not container_model.objects.filter(**params).exists(): messages.error( request, - 'O Usuário (%s) não possui registro de %s.' % ( + 'O Usuário (%s) não está registrado como (%s).' % ( request.user, container_model._meta.verbose_name)) return redirect('/') + else: + # TODO: implementar caso o user for o próprio o container + pass return super(PermissionRequiredMixin, self).dispatch( request, *args, **kwargs) diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index c37e73e8e..e9467d9fd 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -858,7 +858,7 @@ class TipoProposicaoForm(ModelForm): return tipo_proposicao -class ProposicaoCreateForm(forms.ModelForm): +class ProposicaoForm(forms.ModelForm): TIPO_TEXTO_CHOICE = [ ('D', _('Arquivo Digital')), @@ -930,7 +930,15 @@ class ProposicaoCreateForm(forms.ModelForm): self.helper = FormHelper() self.helper.layout = SaplFormLayout(*fields) - super(ProposicaoCreateForm, self).__init__(*args, **kwargs) + super(ProposicaoForm, self).__init__(*args, **kwargs) + + if self.instance.pk: + if self.texto_articulado_proposicao: + self.fields['tipo_texto'].initial = [] + if self.instance.texto_original: + self.fields['tipo_texto'].initial.append('D') + if self.instance.texto_articulado: + self.fields['tipo_texto'].initial.append('T') def clean_texto_original(self): texto_original = self.cleaned_data.get('texto_original', False) diff --git a/sapl/materia/models.py b/sapl/materia/models.py index 20e7a8bd7..68d0da596 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -1,5 +1,6 @@ from django.contrib.auth.models import Group -from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.fields import GenericForeignKey,\ + GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -7,6 +8,7 @@ from model_utils import Choices from sapl.base.models import Autor from sapl.comissoes.models import Comissao +from sapl.compilacao.models import TextoArticulado from sapl.parlamentares.models import Parlamentar from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, get_settings_auth_user_model, @@ -495,6 +497,9 @@ class Proposicao(models.Model): verbose_name=_('Texto Original'), validators=[restringe_tipos_de_arquivo_txt]) + texto_articulado = GenericRelation( + TextoArticulado, related_query_name='texto_articulado') + class Meta: verbose_name = _('Proposição') verbose_name_plural = _('Proposições') diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 28eebe9b2..587759356 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -7,14 +7,16 @@ from crispy_forms.layout import HTML from django.contrib import messages from django.contrib.auth import get_user_model from django.contrib.auth.mixins import PermissionRequiredMixin -from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist +from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist,\ + PermissionDenied from django.core.mail import send_mail from django.core.urlresolvers import reverse from django.db.models import Q from django.http import JsonResponse -from django.http.response import HttpResponseRedirect +from django.http.response import HttpResponseRedirect, Http404 from django.shortcuts import redirect from django.template import Context, loader +from django.utils import dateformat, formats from django.utils.http import urlsafe_base64_decode from django.utils.translation import ugettext_lazy as _ from django.views.generic import CreateView, ListView, TemplateView, UpdateView @@ -30,7 +32,7 @@ from sapl.crud.base import (ACTION_CREATE, ACTION_DELETE, ACTION_DETAIL, make_pagination) from sapl.materia import apps from sapl.materia.forms import AnexadaForm, LegislacaoCitadaForm,\ - TipoProposicaoForm, ProposicaoCreateForm + TipoProposicaoForm, ProposicaoForm from sapl.norma.models import LegislacaoCitada from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label, autor_modal, gerar_hash_arquivo, get_base_url, @@ -38,7 +40,6 @@ from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label, permissoes_protocoloadm, permission_required_for_app, montar_row_autor) - from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm, ConfirmarProposicaoForm, DocumentoAcessorioForm, MateriaLegislativaFilterSet, @@ -54,6 +55,7 @@ from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial, TipoMateriaLegislativa, TipoProposicao, Tramitacao, UnidadeTramitacao) + OrigemCrud = Crud.build(Origem, '') TipoMateriaCrud = CrudAux.build( @@ -326,145 +328,144 @@ class ConfirmarProposicao(PermissionRequiredMixin, CreateView): class ProposicaoCrud(Crud): - """ - TODO: Entre outros comportamento gerais, mesmo que um usuário tenha - Perfil de Autor o Crud de proposição não deverá permitir acesso a - proposições. O acesso só deve ser permitido se existe um Autor registrado - e vinculado ao usuário. Essa tarefa deve ser realizada nas Tabelas Aux. - """ model = Proposicao help_path = '' container_field = 'autor__user' class BaseMixin(Crud.BaseMixin): - list_field_names = ['data_envio', 'descricao', - 'tipo', 'data_recebimento'] - - class ListView(Crud.ListView): - ordering = ['-data_envio', 'descricao'] + list_field_names = ['data_envio', 'data_recebimento', 'descricao', + 'tipo'] - def get_rows(self, object_list): - - for obj in object_list: - if obj.data_envio is None: - obj.data_envio = 'Em elaboração...' - else: - obj.data_envio = obj.data_envio.strftime("%d/%m/%Y %H:%M") - if obj.data_recebimento is None: - obj.data_recebimento = 'Não recebida' - else: - obj.data_recebimento = obj.data_recebimento.strftime( - "%d/%m/%Y %H:%M") - - return [self._as_row(obj) for obj in object_list] - - class CreateView(Crud.CreateView): - form_class = ProposicaoCreateForm + class BaseLocalMixin: + form_class = ProposicaoForm layout_key = None - def get_success_url(self): - - tipo_texto = self.request.POST.get('tipo_texto', '') - - if tipo_texto != 'T': - return Crud.CreateView.get_success_url(self) - else: - return reverse('sapl.materia:proposicao_ta', - kwargs={'pk': self.object.pk}) + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['subnav_template_name'] = '' + return context + def get(self, request, *args, **kwargs): + """if not Proposicao.objects.filter( + pk=kwargs.get('pk'), autor__user=self.autor.user).exists(): + raise Http404()""" -class ProposicaoOldCrud(Crud): - """ - TODO: Entre outros comportamento gerais, mesmo que um usuário tenha - Perfil de Autor o Crud de proposição não deverá permitir acesso a - proposições. O acesso só deve ser permitido se existe um Autor registrado - e vinculado ao usuário. Essa tarefa deve ser realizada nas Tabelas Aux. - """ - model = Proposicao - help_path = '' + if not self._action_is_valid(request, *args, **kwargs): + return redirect(reverse('sapl.materia:proposicao_detail', + kwargs={'pk': kwargs['pk']})) + return super().get(self, request, *args, **kwargs) - class BaseMixin(Crud.BaseMixin): - list_field_names = ['data_envio', 'descricao', - 'tipo', 'data_recebimento'] + def post(self, request, *args, **kwargs): + """if not Proposicao.objects.filter( + pk=kwargs.get('pk'), autor__user=self.autor.user).exists(): + raise Http404()""" - class CreateView(Crud.CreateView): - form_class = ProposicaoOldForm + if not self._action_is_valid(request, *args, **kwargs): + return redirect(reverse('sapl.materia:proposicao_detail', + kwargs={'pk': kwargs['pk']})) + return super().post(self, request, *args, **kwargs) - @property - def layout_key(self): - return 'ProposicaoCreate' + class DetailView(BaseLocalMixin, Crud.DetailView): + layout_key = 'Proposicao' - def get_initial(self): - try: - autor_id = Autor.objects.get(user=self.request.user).id - except MultipleObjectsReturned: - msg = _('Este usuário está relacionado a mais de um autor. ' + - 'Operação cancelada') - messages.add_message(self.request, messages.ERROR, msg) - return redirect(self.get_success_url()) - except ObjectDoesNotExist: - # FIXME: Pensar em uma melhor forma - tipo = TipoAutor.objects.get(name='Externo') - - autor_id = Autor.objects.create( - user=self.request.user, - nome=str(self.request.user), - tipo=tipo).id - return {'autor': autor_id} - else: - return {'autor': autor_id} + def get(self, request, *args, **kwargs): + action = request.GET.get('action', '') + + if not action: + return Crud.DetailView.get(self, request, *args, **kwargs) + + p = Proposicao.objects.get(id=kwargs['pk']) + + msg_error = '' + if p: + if action == 'send': + if p.data_envio and p.data_recebimento: + msg_error = _('Proposição já foi enviada e recebida.') + elif p.data_envio: + msg_error = _('Proposição já foi enviada.') + else: + p.data_envio = datetime.now() + p.save() + messages.success(request, _( + 'Proposição enviada com sucesso.')) + + elif action == 'return': + if not p.data_envio: + msg_error = _('Proposição ainda não foi enviada.') + elif p.data_recebimento: + msg_error = _('Proposição já foi recebida, não é ' + 'possível retorná-la.') + else: + p.data_envio = None + p.save() + messages.success(request, _( + 'Proposição Retornada com sucesso.')) + + if msg_error: + messages.error(request, msg_error) + + return Crud.DetailView.get(self, request, *args, **kwargs) + + class DeleteView(BaseLocalMixin, Crud.DeleteView): + + def _action_is_valid(self, request, *args, **kwargs): + proposicao = Proposicao.objects.filter( + id=kwargs['pk']).values_list( + 'data_envio', 'data_recebimento') + + if proposicao: + if proposicao[0][0] and proposicao[0][1]: + msg = _('Proposição já foi enviada e recebida.' + 'Não pode mais ser excluida.') + elif proposicao[0][0] and not proposicao[0][1]: + msg = _('Proposição já foi enviada mas ainda não recebida ' + 'pelo protocolo. Use a opção Recuperar Proposição ' + 'para depois excluí-la.') + + if proposicao[0][0] or proposicao[0][1]: + messages.error(request, msg) + return False + return True - class UpdateView(Crud.UpdateView): - form_class = ProposicaoOldForm + class UpdateView(BaseLocalMixin, Crud.UpdateView): - def get_initial(self): - initial = self.initial.copy() - if self.object.materia: - initial['tipo_materia'] = self.object.materia.tipo.id - initial['numero_materia'] = self.object.materia.numero - initial['ano_materia'] = self.object.materia.ano - return initial + def _action_is_valid(self, request, *args, **kwargs): - @property - def layout_key(self): - return 'ProposicaoCreate' + proposicao = Proposicao.objects.filter( + id=kwargs['pk']).values_list( + 'data_envio', 'data_recebimento') - def has_permission(self): - perms = self.get_permission_required() - if not self.request.user.has_perms(perms): - return False - - if (Proposicao.objects.filter( - id=self.kwargs['pk'], - autor__user_id=self.request.user.id).exists()): - proposicao = Proposicao.objects.get( - id=self.kwargs['pk'], - autor__user_id=self.request.user.id) - if (not proposicao.data_recebimento or - proposicao.data_devolucao): - return True - else: - msg = _('Essa proposição já foi recebida. ' + + if proposicao: + if proposicao[0][0] and proposicao[0][1]: + msg = _('Proposição já foi enviada e recebida.' 'Não pode mais ser editada') - messages.add_message(self.request, messages.ERROR, msg) - return False - - class DetailView(Crud.DetailView): + elif proposicao[0][0] and not proposicao[0][1]: + msg = _('Proposição já foi enviada mas ainda não recebida ' + 'pelo protocolo. Use a opção Recuperar Proposição ' + 'para voltar para edição.') - def has_permission(self): - perms = self.get_permission_required() - if not self.request.user.has_perms(perms): - return False + if proposicao[0][0] or proposicao[0][1]: + messages.error(request, msg) + return False + return True - return (Proposicao.objects.filter( - id=self.kwargs['pk'], - autor__user_id=self.request.user.id).exists()) + class CreateView(Crud.CreateView): def get_context_data(self, **kwargs): - context = CrudDetailView.get_context_data(self, **kwargs) + context = super().get_context_data(**kwargs) context['subnav_template_name'] = '' return context + def get_success_url(self): + + tipo_texto = self.request.POST.get('tipo_texto', '') + + if tipo_texto == 'T': + return reverse('sapl.materia:proposicao_ta', + kwargs={'pk': self.object.pk}) + else: + return Crud.CreateView.get_success_url(self) + class ListView(Crud.ListView): ordering = ['-data_envio', 'descricao'] @@ -474,60 +475,17 @@ class ProposicaoOldCrud(Crud): if obj.data_envio is None: obj.data_envio = 'Em elaboração...' else: - obj.data_envio = obj.data_envio.strftime("%d/%m/%Y %H:%M") + obj.data_envio = formats.date_format( + obj.data_envio, "DATE_FORMAT") + if obj.data_recebimento is None: obj.data_recebimento = 'Não recebida' else: - obj.data_recebimento = obj.data_recebimento.strftime( - "%d/%m/%Y %H:%M") + obj.data_envio = formats.date_format( + obj.data_recebimento, "DATE_FORMAT") return [self._as_row(obj) for obj in object_list] - def get_queryset(self): - # Só tem acesso as Proposicoes criadas por ele que ainda nao foram - # recebidas ou foram devolvidas - lista = Proposicao.objects.filter( - autor__user_id=self.request.user.id) - lista = lista.filter( - Q(data_recebimento__isnull=True) | - Q(data_devolucao__isnull=False)) - - return lista - - class DeleteView(Crud.DeleteView): - - def has_permission(self): - perms = self.get_permission_required() - if not self.request.user.has_perms(perms): - return False - - return (Proposicao.objects.filter( - id=self.kwargs['pk'], - autor__user_id=self.request.user.id).exists()) - - def delete(self, request, *args, **kwargs): - proposicao = Proposicao.objects.get(id=self.kwargs['pk']) - - if not proposicao.data_envio or proposicao.data_devolucao: - proposicao.delete() - return HttpResponseRedirect( - reverse('sapl.materia:proposicao_list')) - - elif not proposicao.data_recebimento: - proposicao.data_envio = None - proposicao.save() - return HttpResponseRedirect( - reverse('sapl.materia:proposicao_detail', - kwargs={'pk': proposicao.pk})) - - else: - msg = _('Essa proposição já foi recebida. ' + - 'Não pode mais ser excluída/recuperada') - messages.add_message(self.request, messages.ERROR, msg) - return HttpResponseRedirect( - reverse('sapl.materia:proposicao_detail', - kwargs={'pk': proposicao.pk})) - class ReciboProposicaoView(TemplateView): template_name = "materia/recibo_proposicao.html" diff --git a/sapl/settings.py b/sapl/settings.py index 608fa71d8..42a4248c7 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -179,7 +179,7 @@ USE_I18N = True USE_L10N = False USE_TZ = True # DATE_FORMAT = 'N j, Y' -DATE_FORMAT = 'd/m/Y' +DATE_FORMAT = 'd/m/Y H:i' SHORT_DATE_FORMAT = 'd/m/Y' DATE_INPUT_FORMATS = ('%d/%m/%Y', '%m-%d-%Y', '%Y-%m-%d') diff --git a/sapl/static/js/app.js b/sapl/static/js/app.js index c6c3f1db6..9b7f2297d 100644 --- a/sapl/static/js/app.js +++ b/sapl/static/js/app.js @@ -150,13 +150,62 @@ function autorModal() { get_nome_autor("#id_autoria__autor");*/ } +function OptionalCustomFrontEnd() { + // Adaptações opcionais de layout com a presença de JS. + // Não implementar customizações que a funcionalidade que fique dependente. + var instance; + if (!(this instanceof OptionalCustomFrontEnd)) { + if (!instance) { + instance = new OptionalCustomFrontEnd(); + } + return instance; + } + instance = this; + OptionalCustomFrontEnd = function() { + return instance; + } + instance.customCheckBoxAndRadio = function() { + $('[type=radio], [type=checkbox]').each(function() { + var _this = $(this) + var _controls = _this.closest('.controls'); + _controls && _controls.find(':file').length == 0 && !_controls.hasClass('controls-radio-checkbox') && _controls.addClass('controls-radio-checkbox'); + _controls.find(':file').length > 0 && _controls.addClass('controls-file'); + }); + } + instance.customCheckBoxAndRadioWithoutLabel = function() { -var customsFront = function() { - $('[type=radio], [type=checkbox]').each(function() { - var $controls = $(this).closest('.controls') - $controls && !$controls.hasClass('controls-radio-checkbox') && $controls.addClass('controls-radio-checkbox') - }); + $('[type=radio], [type=checkbox]').each(function() { + var _this = $(this); + var _label = _this.closest('label'); + + if (_label.length) + return; + if (this.id) + _label = $('label[for='+this.id+']'); + else { + _label = $('