diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py
index d0c812958..8f476d443 100644
--- a/sapl/protocoloadm/forms.py
+++ b/sapl/protocoloadm/forms.py
@@ -1,9 +1,9 @@
import logging
-from crispy_forms.bootstrap import InlineRadios
+from crispy_forms.bootstrap import InlineRadios, Alert
from crispy_forms.helper import FormHelper
-from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout
+from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout, Div
from django import forms
from django.core.exceptions import (MultipleObjectsReturned,
ObjectDoesNotExist, ValidationError)
@@ -18,11 +18,12 @@ from sapl.base.models import Autor, TipoAutor
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row
from sapl.materia.models import (MateriaLegislativa, TipoMateriaLegislativa,
UnidadeTramitacao)
+from sapl.protocoloadm.models import Protocolo
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, AnoNumeroOrderingFilter,
RangeWidgetOverride, autor_label, autor_modal,
choice_anos_com_protocolo, choice_force_optional,
choice_anos_com_documentoadministrativo,
- FilterOverridesMetaMixin)
+ FilterOverridesMetaMixin, choice_anos_com_materias)
from .models import (AcompanhamentoDocumento, DocumentoAcessorioAdministrativo,
DocumentoAdministrativo,
@@ -344,6 +345,12 @@ class ProtocoloDocumentForm(ModelForm):
numero = forms.IntegerField(
required=False, label=_('Número de Protocolo (opcional)'))
+ data_hora_manual = forms.ChoiceField(
+ label=_('Informar data e hora manualmente?'),
+ widget=forms.RadioSelect(),
+ choices=YES_NO_CHOICES,
+ initial=False)
+
class Meta:
model = Protocolo
fields = ['tipo_protocolo',
@@ -352,7 +359,9 @@ class ProtocoloDocumentForm(ModelForm):
'assunto',
'interessado',
'observacao',
- 'numero'
+ 'numero',
+ 'data',
+ 'hora',
]
def __init__(self, *args, **kwargs):
@@ -360,30 +369,56 @@ class ProtocoloDocumentForm(ModelForm):
row1 = to_row(
[(InlineRadios('tipo_protocolo'), 12)])
row2 = to_row(
- [('tipo_documento', 6),
- ('numero_paginas', 6)])
- row3 = to_row(
- [('assunto', 12)])
+ [('tipo_documento', 5),
+ ('numero_paginas', 2),
+ (Div(), 1),
+ (InlineRadios('data_hora_manual'), 4),
+ ])
+ row3 = to_row([
+ (Div(), 2),
+ (Alert(
+ """
+ Usuário: {} - {}
+ IP: {} - {}
+
+ """.format(
+ kwargs['initial']['user_data_hora_manual'],
+ Protocolo._meta.get_field(
+ 'user_data_hora_manual').help_text,
+ kwargs['initial']['ip_data_hora_manual'],
+ Protocolo._meta.get_field(
+ 'ip_data_hora_manual').help_text,
+
+ ),
+ dismiss=False,
+ css_class='alert-info'), 6),
+ ('data', 2),
+ ('hora', 2),
+ ])
row4 = to_row(
- [('interessado', 12)])
+ [('assunto', 12)])
row5 = to_row(
- [('observacao', 12)])
+ [('interessado', 12)])
row6 = to_row(
+ [('observacao', 12)])
+ row7 = to_row(
[('numero', 12)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação de Documento'),
row1,
- row2,
+ row2),
+ Fieldset(_('Protocolo com data e hora informados manualmente'),
row3,
- row4,
- row5,
- HTML(" "),
- ),
+ css_id='protocolo_data_hora_manual',
+ css_class='hidden'),
+ row4,
+ row5,
+ HTML(" "),
Fieldset(_('Número do Protocolo (Apenas se quiser que a numeração comece '
'a partir do número a ser informado)'),
- row6,
+ row7,
HTML(" "),
form_actions(label=_('Protocolar Documento'))
)
@@ -419,10 +454,11 @@ class ProtocoloMateriaForm(ModelForm):
ano_materia = forms.CharField(
label=_('Ano matéria'), required=False)
- vincular_materia = forms.ChoiceField(label=_('Vincular a matéria existente?'),
- widget=forms.RadioSelect(),
- choices=YES_NO_CHOICES,
- initial=False)
+ vincular_materia = forms.ChoiceField(
+ label=_('Vincular a matéria existente?'),
+ widget=forms.RadioSelect(),
+ choices=YES_NO_CHOICES,
+ initial=False)
numero_paginas = forms.CharField(label=_('Núm. Páginas'), required=True)
@@ -435,6 +471,12 @@ class ProtocoloMateriaForm(ModelForm):
numero = forms.IntegerField(
required=False, label=_('Número de Protocolo (opcional)'))
+ data_hora_manual = forms.ChoiceField(
+ label=_('Informar data e hora manualmente?'),
+ widget=forms.RadioSelect(),
+ choices=YES_NO_CHOICES,
+ initial=False)
+
class Meta:
model = Protocolo
fields = ['tipo_materia',
@@ -446,7 +488,9 @@ class ProtocoloMateriaForm(ModelForm):
'numero_materia',
'ano_materia',
'vincular_materia',
- 'numero'
+ 'numero',
+ 'data',
+ 'hora',
]
def clean_autor(self):
@@ -506,28 +550,55 @@ class ProtocoloMateriaForm(ModelForm):
('tipo_autor', 3),
('autor', 3)])
row2 = to_row(
- [(InlineRadios('vincular_materia'), 4),
- ('numero_materia', 4),
- ('ano_materia', 4), ])
- row3 = to_row(
- [('assunto_ementa', 12)])
+ [(InlineRadios('vincular_materia'), 3),
+ ('numero_materia', 2),
+ ('ano_materia', 2),
+ (Div(), 1),
+ (InlineRadios('data_hora_manual'), 4),
+ ])
+ row3 = to_row([
+ (Div(), 2),
+ (Alert(
+ """
+ Usuário: {} - {}
+ IP: {} - {}
+
+ """.format(
+ kwargs['initial']['user_data_hora_manual'],
+ Protocolo._meta.get_field(
+ 'user_data_hora_manual').help_text,
+ kwargs['initial']['ip_data_hora_manual'],
+ Protocolo._meta.get_field(
+ 'ip_data_hora_manual').help_text,
+
+ ),
+ dismiss=False,
+ css_class='alert-info'), 6),
+ ('data', 2),
+ ('hora', 2),
+ ])
row4 = to_row(
- [('observacao', 12)])
+ [('assunto_ementa', 12)])
row5 = to_row(
+ [('observacao', 12)])
+ row6 = to_row(
[('numero', 12)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação da Matéria'),
row1,
- row2,
+ row2),
+ Fieldset(_('Protocolo com data e hora informados manualmente'),
row3,
- row4,
- HTML(" "),
- ),
+ css_id='protocolo_data_hora_manual',
+ css_class='hidden'),
+ row4,
+ row5,
+ HTML(" "),
Fieldset(_('Número do Protocolo (Apenas se quiser que a numeração comece'
' a partir do número a ser informado)'),
- row5,
+ row6,
HTML(" "),
form_actions(label=_('Protocolar Matéria')))
)
@@ -855,15 +926,15 @@ class DesvincularDocumentoForm(ModelForm):
logger = logging.getLogger(__name__)
- numero = forms.CharField(required=True,
- label=DocumentoAdministrativo._meta.
- get_field('numero').verbose_name
- )
- ano = forms.ChoiceField(required=True,
- label=DocumentoAdministrativo._meta.
- get_field('ano').verbose_name,
- choices=RANGE_ANOS,
- widget=forms.Select(attrs={'class': 'selector'}))
+ numero = forms.CharField(
+ required=True,
+ label=DocumentoAdministrativo._meta.get_field('numero').verbose_name)
+
+ ano = forms.ChoiceField(
+ required=True,
+ label=DocumentoAdministrativo._meta.get_field('ano').verbose_name,
+ choices=choice_anos_com_documentoadministrativo,
+ widget=forms.Select(attrs={'class': 'selector'}))
def clean(self):
super(DesvincularDocumentoForm, self).clean()
@@ -929,7 +1000,7 @@ class DesvincularMateriaForm(forms.Form):
label=_('Número da Matéria'))
ano = forms.ChoiceField(required=True,
label=_('Ano da Matéria'),
- choices=RANGE_ANOS,
+ choices=choice_anos_com_materias,
widget=forms.Select(attrs={'class': 'selector'}))
tipo = forms.ModelChoiceField(label=_('Tipo de Matéria'),
required=True,
diff --git a/sapl/protocoloadm/migrations/0014_auto_20190110_1300.py b/sapl/protocoloadm/migrations/0014_auto_20190110_1300.py
new file mode 100644
index 000000000..56cdf8d36
--- /dev/null
+++ b/sapl/protocoloadm/migrations/0014_auto_20190110_1300.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.8 on 2019-01-10 15:00
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('protocoloadm', '0013_auto_20190106_1336'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='protocolo',
+ name='ip_data_hora_manual',
+ field=models.CharField(blank=True, help_text='Endereço IP da estação de trabalho do usuário que está realizando Protocolo e informando data e hora manualmente.', max_length=15, verbose_name='IP'),
+ ),
+ migrations.AddField(
+ model_name='protocolo',
+ name='user_data_hora_manual',
+ field=models.CharField(blank=True, help_text='Usuário que está realizando Protocolo e informando data e hora manualmente.', max_length=20, verbose_name='IP'),
+ ),
+ migrations.AlterField(
+ model_name='protocolo',
+ name='data',
+ field=models.DateField(blank=True, help_text='Informado manualmente', null=True, verbose_name='Data do Protocolo'),
+ ),
+ migrations.AlterField(
+ model_name='protocolo',
+ name='hora',
+ field=models.TimeField(blank=True, help_text='Informado manualmente', null=True, verbose_name='Hora do Protocolo'),
+ ),
+ ]
diff --git a/sapl/protocoloadm/migrations/0015_protocolo_timestamp_data_hora_manual.py b/sapl/protocoloadm/migrations/0015_protocolo_timestamp_data_hora_manual.py
new file mode 100644
index 000000000..85554edad
--- /dev/null
+++ b/sapl/protocoloadm/migrations/0015_protocolo_timestamp_data_hora_manual.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.8 on 2019-01-10 15:43
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('protocoloadm', '0014_auto_20190110_1300'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='protocolo',
+ name='timestamp_data_hora_manual',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ ]
diff --git a/sapl/protocoloadm/migrations/0016_auto_20190110_1345.py b/sapl/protocoloadm/migrations/0016_auto_20190110_1345.py
new file mode 100644
index 000000000..71f2b7f7a
--- /dev/null
+++ b/sapl/protocoloadm/migrations/0016_auto_20190110_1345.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.8 on 2019-01-10 15:45
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('protocoloadm', '0015_protocolo_timestamp_data_hora_manual'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='protocolo',
+ name='timestamp',
+ field=models.DateTimeField(blank=True, default=django.utils.timezone.now, null=True),
+ ),
+ ]
diff --git a/sapl/protocoloadm/models.py b/sapl/protocoloadm/models.py
index 6d3d90671..e335a5db1 100644
--- a/sapl/protocoloadm/models.py
+++ b/sapl/protocoloadm/models.py
@@ -57,13 +57,29 @@ class Protocolo(models.Model):
choices=RANGE_ANOS,
verbose_name=_('Ano do Protocolo'))
- # FIXME: https://github.com/interlegis/sapl/issues/2337
- data = models.DateField(null=True, blank=True)
- hora = models.TimeField(null=True, blank=True)
+ data = models.DateField(null=True, blank=True,
+ verbose_name=_('Data do Protocolo'),
+ help_text=_('Informado manualmente'))
+ hora = models.TimeField(null=True, blank=True,
+ verbose_name=_('Hora do Protocolo'),
+ help_text=_('Informado manualmente'))
+ timestamp_data_hora_manual = models.DateTimeField(default=timezone.now)
+ user_data_hora_manual = models.CharField(
+ max_length=20, blank=True,
+ verbose_name=_('IP'),
+ help_text=_('Usuário que está realizando Protocolo e informando '
+ 'data e hora manualmente.'))
+ ip_data_hora_manual = models.CharField(
+ max_length=15, blank=True,
+ verbose_name=_('IP'),
+ help_text=_('Endereço IP da estação de trabalho '
+ 'do usuário que está realizando Protocolo e informando '
+ 'data e hora manualmente.'))
# Não foi utilizado auto_now_add=True em timestamp porque
# ele usa datetime.now que não é timezone aware.
- timestamp = models.DateTimeField(default=timezone.now)
+ timestamp = models.DateTimeField(
+ default=timezone.now, null=True, blank=True)
tipo_protocolo = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Tipo de Protocolo'))
tipo_processo = models.PositiveIntegerField()
diff --git a/sapl/protocoloadm/tests/test_protocoloadm.py b/sapl/protocoloadm/tests/test_protocoloadm.py
index 2a4c9f53c..c7e4bc341 100644
--- a/sapl/protocoloadm/tests/test_protocoloadm.py
+++ b/sapl/protocoloadm/tests/test_protocoloadm.py
@@ -1,4 +1,4 @@
-from datetime import date, timedelta
+from datetime import date, timedelta, datetime
from django.core.urlresolvers import reverse
from django.utils import timezone
@@ -392,29 +392,42 @@ def test_documento_administrativo_protocolo_inexistente():
def test_protocolo_documento_form_invalido():
- form = ProtocoloDocumentForm(data={})
+ form = ProtocoloDocumentForm(
+ data={},
+ initial={
+ 'user_data_hora_manual': '',
+ 'ip_data_hora_manual': '',
+ 'data': timezone.localdate(timezone.now()),
+ 'hora': timezone.localtime(timezone.now())})
assert not form.is_valid()
errors = form.errors
+ assert errors['data_hora_manual'] == [_('Este campo é obrigatório.')]
assert errors['tipo_protocolo'] == [_('Este campo é obrigatório.')]
assert errors['interessado'] == [_('Este campo é obrigatório.')]
assert errors['tipo_documento'] == [_('Este campo é obrigatório.')]
assert errors['numero_paginas'] == [_('Este campo é obrigatório.')]
assert errors['assunto'] == [_('Este campo é obrigatório.')]
- assert len(errors) == 5
+ assert len(errors) == 6
def test_protocolo_materia_invalido():
- form = ProtocoloMateriaForm(data={})
+ form = ProtocoloMateriaForm(data={},
+ initial={
+ 'user_data_hora_manual': '',
+ 'ip_data_hora_manual': '',
+ 'data': timezone.localdate(timezone.now()),
+ 'hora': timezone.localtime(timezone.now())})
assert not form.is_valid()
errors = form.errors
+ assert errors['data_hora_manual'] == [_('Este campo é obrigatório.')]
assert errors['assunto_ementa'] == [_('Este campo é obrigatório.')]
assert errors['tipo_autor'] == [_('Este campo é obrigatório.')]
assert errors['tipo_materia'] == [_('Este campo é obrigatório.')]
@@ -422,4 +435,4 @@ def test_protocolo_materia_invalido():
assert errors['autor'] == [_('Este campo é obrigatório.')]
assert errors['vincular_materia'] == [_('Este campo é obrigatório.')]
- assert len(errors) == 6
+ assert len(errors) == 7
diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py
index 8dd7e9e47..f91ee5227 100755
--- a/sapl/protocoloadm/views.py
+++ b/sapl/protocoloadm/views.py
@@ -492,6 +492,15 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
return reverse('sapl.protocoloadm:protocolo_mostrar',
kwargs={'pk': self.object.id})
+ def get_initial(self):
+ initial = super().get_initial()
+
+ initial['user_data_hora_manual'] = self.request.user.username
+ initial['ip_data_hora_manual'] = get_client_ip(self.request)
+ initial['data'] = timezone.localdate(timezone.now())
+ initial['hora'] = timezone.localtime(timezone.now())
+ return initial
+
def form_valid(self, form):
protocolo = form.save(commit=False)
username = self.request.user.username
@@ -538,6 +547,17 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
return self.render_to_response(self.get_context_data())
protocolo.ano = timezone.now().year
protocolo.assunto_ementa = self.request.POST['assunto']
+
+ if form.cleaned_data['data_hora_manual'] == 'True':
+ protocolo.timestamp = None
+ protocolo.user_data_hora_manual = username
+ protocolo.ip_data_hora_manual = get_client_ip(self.request)
+ else:
+ protocolo.data = None
+ protocolo.hora = None
+ protocolo.user_data_hora_manual = ''
+ protocolo.ip_data_hora_manual = ''
+
protocolo.save()
self.object = protocolo
return redirect(self.get_success_url())
@@ -659,6 +679,15 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
return reverse('sapl.protocoloadm:materia_continuar', kwargs={
'pk': protocolo.pk})
+ def get_initial(self):
+ initial = super().get_initial()
+
+ initial['user_data_hora_manual'] = self.request.user.username
+ initial['ip_data_hora_manual'] = get_client_ip(self.request)
+ initial['data'] = timezone.localdate(timezone.now())
+ initial['hora'] = timezone.localtime(timezone.now())
+ return initial
+
def form_valid(self, form):
protocolo = form.save(commit=False)
username = self.request.user.username
@@ -719,6 +748,16 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
protocolo.observacao = self.request.POST['observacao']
protocolo.assunto_ementa = self.request.POST['assunto_ementa']
+ if form.cleaned_data['data_hora_manual'] == 'True':
+ protocolo.timestamp = None
+ protocolo.user_data_hora_manual = username
+ protocolo.ip_data_hora_manual = get_client_ip(self.request)
+ else:
+ protocolo.data = None
+ protocolo.hora = None
+ protocolo.user_data_hora_manual = ''
+ protocolo.ip_data_hora_manual = ''
+
protocolo.save()
data = form.cleaned_data
if data['vincular_materia'] == 'True':
@@ -1168,4 +1207,4 @@ class FichaSelecionaAdmView(PermissionRequiredMixin, FormView):
context['documento'] = documento
return gerar_pdf_impressos(self.request, context,
- 'materia/impressos/ficha_adm_pdf.html')
\ No newline at end of file
+ 'materia/impressos/ficha_adm_pdf.html')
diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py
index 0c5dfd377..f4bc8cda2 100755
--- a/sapl/relatorios/views.py
+++ b/sapl/relatorios/views.py
@@ -1,7 +1,7 @@
+from datetime import datetime as dt
import html
-import re
import logging
-from datetime import datetime as dt
+import re
from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404, HttpResponse
@@ -581,7 +581,6 @@ def get_sessao_plenaria(sessao, casa):
if dic_expedientes:
lst_expedientes.append(dic_expedientes)
-
# Lista das matérias do Expediente, incluindo o resultado das votacoes
lst_expediente_materia = []
for expediente_materia in ExpedienteMateria.objects.filter(
@@ -612,7 +611,8 @@ def get_sessao_plenaria(sessao, casa):
dic_expediente_materia["nom_autor"] = ''
autoria = materia.autoria_set.all()
- dic_expediente_materia['num_autores'] = 'Autores' if len(autoria) > 1 else 'Autor'
+ dic_expediente_materia['num_autores'] = 'Autores' if len(
+ autoria) > 1 else 'Autor'
if autoria:
for a in autoria:
if a.autor.nome:
@@ -687,7 +687,7 @@ def get_sessao_plenaria(sessao, casa):
numeracao = materia.numeracao_set.first()
if numeracao:
-
+
dic_votacao["des_numeracao"] = (
str(numeracao.numero_materia) +
'/' +
@@ -762,7 +762,6 @@ def get_sessao_plenaria(sessao, casa):
lst_ocorrencias.append(o)
-
return (inf_basicas_dic,
lst_mesa,
lst_presenca_sessao,
@@ -810,10 +809,12 @@ def relatorio_sessao_plenaria(request, pk):
imagem = get_imagem(casa)
try:
- logger.debug("user=" + username + ". Tentando obter SessaoPlenaria com id={}.".format(pk))
+ logger.debug("user=" + username +
+ ". Tentando obter SessaoPlenaria com id={}.".format(pk))
sessao = SessaoPlenaria.objects.get(id=pk)
except ObjectDoesNotExist as e:
- logger.error("user=" + username + ". Essa SessaoPlenaria não existe (pk={}). ".format(pk) + str(e))
+ logger.error("user=" + username +
+ ". Essa SessaoPlenaria não existe (pk={}). ".format(pk) + str(e))
raise Http404('Essa página não existe')
(inf_basicas_dic,
@@ -828,11 +829,10 @@ def relatorio_sessao_plenaria(request, pk):
lst_oradores,
lst_ocorrencias) = get_sessao_plenaria(sessao, casa)
-
for idx in range(len(lst_expedientes)):
txt_expedientes = lst_expedientes[idx]['txt_expediente']
txt_expedientes = TrocaTag(txt_expedientes, '