Browse Source

Fix #2252

pull/2257/head
tapumar@gmail.com 7 years ago
parent
commit
5af1f5ab59
  1. 205
      sapl/protocoloadm/email_utils.py
  2. 27
      sapl/protocoloadm/forms.py
  3. 32
      sapl/protocoloadm/migrations/0008_acompanhamentodocumento.py
  4. 27
      sapl/protocoloadm/models.py
  5. 14
      sapl/protocoloadm/urls.py
  6. 151
      sapl/protocoloadm/views.py
  7. 25
      sapl/templates/email/acompanhar_documento.html
  8. 16
      sapl/templates/email/acompanhar_documento.txt
  9. 21
      sapl/templates/protocoloadm/acompanhamento_documento.html
  10. 3
      sapl/templates/protocoloadm/documentoadministrativo_filter.html

205
sapl/protocoloadm/email_utils.py

@ -0,0 +1,205 @@
from datetime import datetime as dt
from django.core.mail import EmailMultiAlternatives, get_connection, send_mail
from django.core.urlresolvers import reverse
from django.template import Context, loader
from django.utils import timezone
from sapl.base.models import CasaLegislativa
from sapl.settings import EMAIL_SEND_USER
from .models import AcompanhamentoDocumento
def load_email_templates(templates, context={}):
emails = []
for t in templates:
tpl = loader.get_template(t)
email = tpl.render(Context(context))
if t.endswith(".html"):
email = email.replace('\n', '').replace('\r', '')
emails.append(email)
return emails
def enviar_emails(sender, recipients, messages):
'''
Recipients is a string list of email addresses
Messages is an array of dicts of the form:
{'recipient': 'address', # useless????
'subject': 'subject text',
'txt_message': 'text message',
'html_message': 'html message'
}
'''
if len(messages) == 1:
# sends an email simultaneously to all recipients
send_mail(messages[0]['subject'],
messages[0]['txt_message'],
sender,
recipients,
html_message=messages[0]['html_message'],
fail_silently=False)
elif len(recipients) > len(messages):
raise ValueError("Message list should have size 1 \
or equal recipient list size. \
recipients: %s, messages: %s" % (recipients, messages)
)
else:
# sends an email simultaneously to all reciepients
for (d, m) in zip(recipients, messages):
send_mail(m['subject'],
m['txt_message'],
sender,
[d],
html_message=m['html_message'],
fail_silently=False)
def criar_email_confirmacao(base_url, casa_legislativa, documento, hash_txt=''):
if not casa_legislativa:
raise ValueError("Casa Legislativa é obrigatória")
if not documento:
raise ValueError("Documento é obrigatório")
# FIXME i18n
casa_nome = (casa_legislativa.nome + ' de ' +
casa_legislativa.municipio + '-' +
casa_legislativa.uf)
documento_url = reverse('sapl.protocoloadm:documentoadministrativo_detail',
kwargs={'pk': documento.id})
confirmacao_url = reverse('sapl.protocoloadm:acompanhar_confirmar',
kwargs={'pk': documento.id})
templates = load_email_templates(['email/acompanhar.txt',
'email/acompanhar.html'],
{"casa_legislativa": casa_nome,
"logotipo": casa_legislativa.logotipo,
"descricao_documento": documento.assunto,
"hash_txt": hash_txt,
"base_url": base_url,
"documento": str(documento),
"documento_url": documento_url,
"confirmacao_url": confirmacao_url, })
return templates
def do_envia_email_confirmacao(base_url, casa, documento, destinatario):
#
# Envia email de confirmacao para atualizações de tramitação
#
sender = EMAIL_SEND_USER
# FIXME i18n
subject = "[SAPL] " + str(documento) + " - Ative o Acompanhamento do Documento"
messages = []
recipients = []
email_texts = criar_email_confirmacao(base_url,
casa,
documento,
destinatario.hash,)
recipients.append(destinatario.email)
messages.append({
'recipient': destinatario.email,
'subject': subject,
'txt_message': email_texts[0],
'html_message': email_texts[1]
})
enviar_emails(sender, recipients, messages)
def criar_email_tramitacao(base_url, casa_legislativa, documento, status,
unidade_destino, hash_txt=''):
if not casa_legislativa:
raise ValueError("Casa Legislativa é obrigatória")
if not documento:
raise ValueError("Documento é obrigatória")
# FIXME i18n
casa_nome = (casa_legislativa.nome + ' de ' +
casa_legislativa.municipio + '-' +
casa_legislativa.uf)
url_documento = reverse('sapl.documento:tramitacao_list',
kwargs={'pk': documento.id})
url_excluir = reverse('sapl.documento:acompanhar_excluir',
kwargs={'pk': documento.id})
tramitacao = documento.tramitacao_set.last()
templates = load_email_templates(['email/tramitacao.txt',
'email/tramitacao.html'],
{"casa_legislativa": casa_nome,
"data_registro": dt.strftime(
timezone.now(),
"%d/%m/%Y"),
"cod_documento": documento.id,
"logotipo": casa_legislativa.logotipo,
"descricao_documento": documento.assunto,
"data": tramitacao.data_tramitacao,
"status": status,
"localizacao": unidade_destino,
"texto_acao": tramitacao.texto,
"hash_txt": hash_txt,
"documento": str(documento),
"base_url": base_url,
"documento_url": url_documento,
"excluir_url": url_excluir})
return templates
def do_envia_email_tramitacao(base_url, documento, status, unidade_destino):
#
# Envia email de tramitacao para usuarios cadastrados
#
destinatarios = AcompanhamentoDocumento.objects.filter(documento=documento,
confirmado=True)
casa = CasaLegislativa.objects.first()
sender = EMAIL_SEND_USER
# FIXME i18n
subject = "[SAPL] " + str(documento) + \
" - Acompanhamento de Documento Administrativo"
connection = get_connection()
connection.open()
for destinatario in destinatarios:
try:
email_texts = criar_email_tramitacao(base_url,
casa,
documento,
status,
unidade_destino,
destinatario.hash,)
email = EmailMultiAlternatives(
subject,
email_texts[0],
sender,
[destinatario.email],
connection=connection)
email.attach_alternative(email_texts[1], "text/html")
email.send()
# Garantia de que, mesmo com o lançamento de qualquer exceção,
# a conexão será fechada
except Exception:
connection.close()
raise Exception(
'Erro ao enviar e-mail de acompanhamento de documento.')
connection.close()

27
sapl/protocoloadm/forms.py

@ -2,7 +2,7 @@
import django_filters
from crispy_forms.bootstrap import InlineRadios
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button, Fieldset, Layout
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout
from django import forms
from django.core.exceptions import (MultipleObjectsReturned,
ObjectDoesNotExist, ValidationError)
@ -19,7 +19,8 @@ from sapl.materia.models import (MateriaLegislativa, TipoMateriaLegislativa,
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter,
RangeWidgetOverride, autor_label, autor_modal)
from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo,
from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo,
Protocolo, TipoDocumentoAdministrativo,
TramitacaoAdministrativo)
@ -39,6 +40,28 @@ EM_TRAMITACAO = [('', '---------'),
(0, 'Sim'),
(1, 'Não')]
class AcompanhamentoDocumentoForm(ModelForm):
class Meta:
model = AcompanhamentoDocumento
fields = ['email']
def __init__(self, *args, **kwargs):
row1 = to_row([('email', 10)])
row1.append(
Column(form_actions(label='Cadastrar'), css_class='col-md-2')
)
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(
_('Acompanhamento de Documento por e-mail'), row1
)
)
super(AcompanhamentoDocumentoForm, self).__init__(*args, **kwargs)
class ProtocoloFilterSet(django_filters.FilterSet):

32
sapl/protocoloadm/migrations/0008_acompanhamentodocumento.py

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-09-25 15:31
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0007_auto_20180924_1724'),
]
operations = [
migrations.CreateModel(
name='AcompanhamentoDocumento',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('usuario', models.CharField(max_length=50)),
('email', models.EmailField(max_length=100, verbose_name='E-mail')),
('data_cadastro', models.DateField(auto_now_add=True)),
('hash', models.CharField(max_length=8)),
('confirmado', models.BooleanField(default=False)),
('documento', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='protocoloadm.DocumentoAdministrativo')),
],
options={
'verbose_name_plural': 'Acompanhamentos de Documento',
'verbose_name': 'Acompanhamento de Documento',
},
),
]

27
sapl/protocoloadm/models.py

@ -298,3 +298,30 @@ class TramitacaoAdministrativo(models.Model):
return _('%(documento)s - %(status)s') % {
'documento': self.documento, 'status': self.status
}
@reversion.register()
class AcompanhamentoDocumento(models.Model):
usuario = models.CharField(max_length=50)
documento = models.ForeignKey(DocumentoAdministrativo, on_delete=models.CASCADE)
email = models.EmailField(
max_length=100, verbose_name=_('E-mail'))
data_cadastro = models.DateField(auto_now_add=True)
hash = models.CharField(max_length=8)
confirmado = models.BooleanField(default=False)
class Meta:
verbose_name = _('Acompanhamento de Documento')
verbose_name_plural = _('Acompanhamentos de Documento')
def __str__(self):
if self.data_cadastro is None:
return _('%(documento)s - %(email)s') % {
'documento': self.documento,
'email': self.email
}
else:
return _('%(documento)s - %(email)s - Registrado em: %(data)s') % {
'documento': self.documento,
'email': self.email,
'data': str(self.data_cadastro.strftime('%d/%m/%Y'))
}

14
sapl/protocoloadm/urls.py

@ -1,6 +1,9 @@
from django.conf.urls import include, url
from sapl.protocoloadm.views import (AnularProtocoloAdmView,
from sapl.protocoloadm.views import (AcompanhamentoDocumentoView,
AcompanhamentoConfirmarView,
AcompanhamentoExcluirView,
AnularProtocoloAdmView,
ComprovanteProtocoloView,
CriarDocumentoProtocolo,
DocumentoAcessorioAdministrativoCrud,
@ -56,6 +59,15 @@ urlpatterns_protocolo = [
url(r'^protocoloadm/(?P<pk>\d+)/protocolo-mostrar$',
ProtocoloMostrarView.as_view(), name='protocolo_mostrar'),
url(r'^docadm/(?P<pk>\d+)/acompanhar-documento/$',
AcompanhamentoDocumentoView.as_view(), name='acompanhar_documento'),
url(r'^docadm/(?P<pk>\d+)/acompanhar-confirmar$',
AcompanhamentoConfirmarView.as_view(),
name='acompanhar_confirmar'),
url(r'^docadm/(?P<pk>\d+)/acompanhar-excluir$',
AcompanhamentoExcluirView.as_view(),
name='acompanhar_excluir'),
url(r'^protocoloadm/(?P<pk>\d+)/continuar$',

151
sapl/protocoloadm/views.py

@ -1,3 +1,6 @@
from datetime import datetime
from random import choice
from string import ascii_letters, digits
from braces.views import FormValidMessageMixin
from django.contrib import messages
@ -18,24 +21,27 @@ from django.views.generic.edit import FormView
from django_filters.views import FilterView
import sapl
from sapl.base.models import Autor
from sapl.base.models import Autor, CasaLegislativa
from sapl.comissoes.models import Comissao
from sapl.crud.base import Crud, CrudAux, MasterDetailCrud, make_pagination
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.parlamentares.models import Legislatura, Parlamentar
from sapl.protocoloadm.models import Protocolo
from sapl.utils import (create_barcode, get_client_ip,
from sapl.utils import (create_barcode, get_base_url, get_client_ip,
get_mime_type_from_file_extension,
show_results_filter_set)
from .forms import (AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm,
from .email_utils import do_envia_email_confirmacao
from .forms import (AcompanhamentoDocumentoForm, AnularProcoloAdmForm,
DocumentoAcessorioAdministrativoForm,
DocumentoAdministrativoFilterSet,
DocumentoAdministrativoForm, ProtocoloDocumentForm,
ProtocoloFilterSet, ProtocoloMateriaForm,
TramitacaoAdmEditForm, TramitacaoAdmForm, DesvincularDocumentoForm, DesvincularMateriaForm,
filtra_tramitacao_adm_destino_and_status, filtra_tramitacao_adm_destino, filtra_tramitacao_adm_status)
from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo,
StatusTramitacaoAdministrativo,
TramitacaoAdmEditForm, TramitacaoAdmForm,
DesvincularDocumentoForm, DesvincularMateriaForm,
filtra_tramitacao_adm_destino_and_status,
filtra_tramitacao_adm_destino, filtra_tramitacao_adm_status)
from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo, StatusTramitacaoAdministrativo,
TipoDocumentoAdministrativo, TramitacaoAdministrativo)
TipoDocumentoAdministrativoCrud = CrudAux.build(
@ -89,6 +95,135 @@ def doc_texto_integral(request, pk):
return response
raise Http404
class AcompanhamentoConfirmarView(TemplateView):
def get_redirect_url(self, email):
msg = _('Este documento está sendo acompanhado pelo e-mail: %s') % (
email)
messages.add_message(self.request, messages.SUCCESS, msg)
return reverse('sapl.protocoloadm:documentoadministrativo_detail',
kwargs={'pk': self.kwargs['pk']})
def get(self, request, *args, **kwargs):
documento_id = kwargs['pk']
hash_txt = request.GET.get('hash_txt', '')
try:
acompanhar = AcompanhamentoDocumento.objects.get(
documento_id=documento_id,
hash=hash_txt)
except ObjectDoesNotExist:
raise Http404()
# except MultipleObjectsReturned:
# A melhor solução deve ser permitir que a exceção
# (MultipleObjectsReturned) seja lançada e vá para o log,
# pois só poderá ser causada por um erro de desenvolvimente
acompanhar.confirmado = True
acompanhar.save()
return HttpResponseRedirect(self.get_redirect_url(acompanhar.email))
class AcompanhamentoExcluirView(TemplateView):
def get_success_url(self):
msg = _('Você parou de acompanhar este Documento.')
messages.add_message(self.request, messages.INFO, msg)
return reverse('sapl.protocoloadm:documentoadministrativo_detail',
kwargs={'pk': self.kwargs['pk']})
def get(self, request, *args, **kwargs):
materia_id = kwargs['pk']
hash_txt = request.GET.get('hash_txt', '')
try:
AcompanhamentoDocumento.objects.get(documento_id=documento_id,
hash=hash_txt).delete()
except ObjectDoesNotExist:
pass
return HttpResponseRedirect(self.get_success_url())
class AcompanhamentoDocumentoView(CreateView):
template_name = "protocoloadm/acompanhamento_documento.html"
def get_random_chars(self):
s = ascii_letters + digits
return ''.join(choice(s) for i in range(choice([6, 7])))
def get(self, request, *args, **kwargs):
pk = self.kwargs['pk']
documento = DocumentoAdministrativo.objects.get(id=pk)
return self.render_to_response(
{'form': AcompanhamentoDocumentoForm(),
'documento': documento})
def post(self, request, *args, **kwargs):
form = AcompanhamentoDocumentoForm(request.POST)
pk = self.kwargs['pk']
documento = DocumentoAdministrativo.objects.get(id=pk)
if form.is_valid():
email = form.cleaned_data['email']
usuario = request.user
hash_txt = self.get_random_chars()
acompanhar = AcompanhamentoDocumento.objects.get_or_create(
documento=documento,
email=form.data['email'])
# Se o segundo elemento do retorno do get_or_create for True
# quer dizer que o elemento não existia
if acompanhar[1]:
acompanhar = acompanhar[0]
acompanhar.hash = hash_txt
acompanhar.usuario = usuario.username
acompanhar.confirmado = False
acompanhar.save()
base_url = get_base_url(request)
destinatario = AcompanhamentoDocumento.objects.get(
documento=documento,
email=email,
confirmado=False)
casa = CasaLegislativa.objects.first()
do_envia_email_confirmacao(base_url,
casa,
documento,
destinatario)
msg = _('Foi enviado um e-mail de confirmação. Confira sua caixa \
de mensagens e clique no link que nós enviamos para \
confirmar o acompanhamento deste documento.')
messages.add_message(request, messages.SUCCESS, msg)
# Caso esse Acompanhamento já exista
# avisa ao usuário que esse documento já está sendo acompanhado
else:
msg = _('Este e-mail já está acompanhando esse documento.')
messages.add_message(request, messages.INFO, msg)
return self.render_to_response(
{'form': form,
'documento': documento,
'error': _('Esse documento já está\
sendo acompanhada por este e-mail.')})
return HttpResponseRedirect(self.get_success_url())
else:
return self.render_to_response(
{'form': form,
'documento': documento})
def get_success_url(self):
return reverse('sapl.protocoloadm:documentoadministrativo_detail',
kwargs={'pk': self.kwargs['pk']})
class DocumentoAdministrativoMixin:

25
sapl/templates/email/acompanhar_documento.html

@ -0,0 +1,25 @@
{% load i18n %}
{% load static %}
<html><head></head><body bgcolor='#ffffff'>
<h2 align='center'><b>{{casa_legislativa}}</b>
<br/>
Sistema de Apoio ao Processo Legislativo
</h2>
<p>Registramos seu pedido para acompanhamento por e-mail do documento administrativo identificado a seguir:</p>
<a href="{{base_url}}{{documento_url}}">{{documento}}<b> - {{descricao_documento}}</b></a><br/>
{{assunto}}<br/>
</h4>
<p></p>
<p>Para garantia de sua privacidade, solicitamos que ative o recebimento das futuras mensagens clicando no link:</p>
<h4>
<a href="{{base_url}}{{confirmacao_url}}?hash_txt={{hash_txt}}">{{base_url}}{{confirmacao_url}}?hash_txt={{hash_txt}}</a>
</h4>
<br/>
<hr>
<p>Caso não tenha realizado o cadastramento em nosso sistema, favor desconsiderar a presente mensagem<br/>
Esta é uma mensagem automática. Por favor, não responda.</p>
</body>
</html>

16
sapl/templates/email/acompanhar_documento.txt

@ -0,0 +1,16 @@
{{casa_legislativa}}
Sistema de Apoio ao Processo Legislativo
>Registramos seu pedido para acompanhamento por e-mail do documento administrativo identificad a seguir:
{{base_url}}{{documento_url}} - {{documento}} - {{descricao_documento}}
{{assunto}}
Para garantia de sua privacidade, solicitamos que ative o recebimento das futuras mensagens acessando no link:
{{base_url}}{{url_confirmar}}?hash_txt={{hash_txt}}
Caso não tenha realizado o cadastramento em nosso sistema, favor desconsiderar a presente mensagem
Esta é uma mensagem automática. Por favor, não responda.

21
sapl/templates/protocoloadm/acompanhamento_documento.html

@ -0,0 +1,21 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block actions %} {% endblock %}
{% block detail_content %}
<h1>Acompanhamento de Documento</h1>
<hr>
<div class="row">
<div class="col-md-4"><b>Tipo:</b> {{documento.tipo.sigla}} - {{documento.tipo.descricao}}</div>
<div class="col-md-4"><b>Número:</b> {{documento.numero}}</div>
<div class="col-md-4"><b>Ano:</b> {{documento.ano}}</div>
</div>
<div class="row">
<div class="col-md-12"><b>Assunto:</b> {{documento.assunto|safe}}</div>
</div>
{% if error %} <h5 align="center"><font color="#FF0000">{{ error }}</font></h5> {% endif %}
{% crispy form %}
{% endblock %}

3
sapl/templates/protocoloadm/documentoadministrativo_filter.html

@ -63,6 +63,9 @@
{% if d.texto_integral %}
<strong><a href="{{ d.texto_integral.url }}">Texto Integral</a></strong></br>
{% endif %}
{% if d.tramitacao %}
<a href="{% url 'sapl.protocoloadm:acompanhar_documento' d.id %}">Acompanhar Documento</a>
{% endif %}
</td>
</tr>

Loading…
Cancel
Save