Browse Source

Merge branch '3.1.x'

pull/1828/merge
Edward Ribeiro 7 years ago
parent
commit
a877df5ac5
  1. 2
      Dockerfile
  2. 36
      create_admin.py
  3. 2
      docker-compose.yml
  4. 7
      sapl/audiencia/views.py
  5. 20
      sapl/base/migrations/0020_auto_20180821_1421.py
  6. 6
      sapl/base/models.py
  7. 43
      sapl/comissoes/forms.py
  8. 5
      sapl/compilacao/models.py
  9. 3
      sapl/compilacao/views.py
  10. 5
      sapl/materia/forms.py
  11. 27
      sapl/materia/models.py
  12. 4
      sapl/materia/urls.py
  13. 53
      sapl/materia/views.py
  14. 10
      sapl/norma/models.py
  15. 15
      sapl/norma/views.py
  16. 2
      sapl/protocoloadm/models.py
  17. 3
      sapl/protocoloadm/urls.py
  18. 21
      sapl/protocoloadm/views.py
  19. 9
      sapl/relatorios/views.py
  20. 5
      sapl/sessao/views.py
  21. 13
      sapl/static/styles/app.scss
  22. 23
      sapl/templates/base.html
  23. 4
      sapl/templates/compilacao/textoarticulado_detail.html
  24. 3
      sapl/templates/compilacao/textoarticulado_menu_config.html
  25. 27
      sapl/templates/materia/proposicao_confirm_return.html
  26. 2
      sapl/templates/materia/proposicao_detail.html
  27. 4
      sapl/templates/norma/normajuridica_detail.html
  28. 1
      sapl/templates/norma/normajuridica_form.html
  29. 31
      sapl/templates/protocoloadm/protocolar_materia.html
  30. 2
      sapl/templates/protocoloadm/protocolo_filter.html
  31. 2
      sapl/templates/protocoloadm/protocolo_mostrar.html
  32. 2
      setup.py

2
Dockerfile

@ -4,6 +4,8 @@ ENV BUILD_PACKAGES postgresql-dev graphviz-dev graphviz build-base git pkgconfig
python3-dev libxml2-dev jpeg-dev libressl-dev libffi-dev libxslt-dev nodejs py3-lxml \ python3-dev libxml2-dev jpeg-dev libressl-dev libffi-dev libxslt-dev nodejs py3-lxml \
py3-magic postgresql-client poppler-utils antiword vim py3-magic postgresql-client poppler-utils antiword vim
RUN apk update && apk upgrade
RUN apk --update add fontconfig ttf-dejavu && fc-cache -fv RUN apk --update add fontconfig ttf-dejavu && fc-cache -fv
RUN apk add --no-cache python3 nginx tzdata && \ RUN apk add --no-cache python3 nginx tzdata && \

36
create_admin.py

@ -7,24 +7,43 @@ import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sapl.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sapl.settings")
def get_enviroment_admin_password(username):
password = os.environ.get('ADMIN_PASSWORD')
if not password:
print(
"[SUPERUSER] Environment variable $ADMIN_PASSWORD"
" for user %s was not set. Leaving..." % username)
sys.exit('MISSING_ADMIN_PASSWORD')
def create_user_interlegis():
from django.contrib.auth.models import User
password = get_enviroment_admin_password('interlegis')
print("[SUPERUSER INTERLEGIS] Creating interlegis superuser...")
user, created = User.objects.get_or_create(username='interlegis')
if not created:
print("[SUPERUSER INTERLEGIS] User interlegis already exists."
" Updating password.")
user.is_superuser = True
user.is_staff = True
user.set_password(password)
user.save()
print("[SUPERUSER INTERLEGIS] Done.")
def create_superuser(): def create_superuser():
from django.contrib.auth.models import User from django.contrib.auth.models import User
username = "admin" username = "admin"
password = os.environ[ email = os.environ.get('ADMIN_EMAIL', '')
'ADMIN_PASSWORD'] if 'ADMIN_PASSWORD' in os.environ else None
email = os.environ['ADMIN_EMAIL'] if 'ADMIN_EMAIL' in os.environ else ''
if User.objects.filter(username=username).exists(): if User.objects.filter(username=username).exists():
print("[SUPERUSER] User %s already exists." print("[SUPERUSER] User %s already exists."
" Exiting without change." % username) " Exiting without change." % username)
sys.exit('ADMIN_USER_EXISTS') sys.exit('ADMIN_USER_EXISTS')
else: else:
if not password: password = get_enviroment_admin_password(username)
print(
"[SUPERUSER] Environment variable $ADMIN_PASSWORD"
" for user %s was not set. Leaving..." % username)
sys.exit('MISSING_ADMIN_PASSWORD')
print("[SUPERUSER] Creating superuser...") print("[SUPERUSER] Creating superuser...")
@ -39,4 +58,5 @@ def create_superuser():
if __name__ == '__main__': if __name__ == '__main__':
django.setup() django.setup()
create_user_interlegis() # must come before create_superuser
create_superuser() create_superuser()

2
docker-compose.yml

@ -11,7 +11,7 @@ sapldb:
ports: ports:
- "5432:5432" - "5432:5432"
sapl: sapl:
image: interlegis/sapl:3.1.107 image: interlegis/sapl:3.1.110
restart: always restart: always
environment: environment:
ADMIN_PASSWORD: interlegis ADMIN_PASSWORD: interlegis

7
sapl/audiencia/views.py

@ -34,9 +34,10 @@ class AudienciaCrud(Crud):
def get_initial(self): def get_initial(self):
initial = super(UpdateView, self).get_initial() initial = super(UpdateView, self).get_initial()
initial['tipo_materia'] = self.object.materia.tipo.id if self.object.materia:
initial['numero_materia'] = self.object.materia.numero initial['tipo_materia'] = self.object.materia.tipo.id
initial['ano_materia'] = self.object.materia.ano initial['numero_materia'] = self.object.materia.numero
initial['ano_materia'] = self.object.materia.ano
return initial return initial
class DeleteView(Crud.DeleteView): class DeleteView(Crud.DeleteView):

20
sapl/base/migrations/0020_auto_20180821_1421.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-08-21 17:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0019_auto_20180815_1025'),
]
operations = [
migrations.AlterField(
model_name='appconfig',
name='documentos_administrativos',
field=models.CharField(choices=[('O', 'Ostensiva'), ('R', 'Restritiva')], default='O', max_length=1, verbose_name='Visibilidade dos Documentos Administrativos'),
),
]

6
sapl/base/models.py

@ -9,8 +9,8 @@ from django.utils.translation import ugettext_lazy as _
from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES, from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES,
get_settings_auth_user_model, models_with_gr_for_model) get_settings_auth_user_model, models_with_gr_for_model)
TIPO_DOCUMENTO_ADMINISTRATIVO = (('O', _('Ostensivo')), TIPO_DOCUMENTO_ADMINISTRATIVO = (('O', _('Ostensiva')),
('R', _('Restritivo'))) ('R', _('Restritiva')))
SEQUENCIA_NUMERACAO = (('A', _('Sequencial por ano')), SEQUENCIA_NUMERACAO = (('A', _('Sequencial por ano')),
('L', _('Sequencial por legislatura')), ('L', _('Sequencial por legislatura')),
@ -70,7 +70,7 @@ class AppConfig(models.Model):
documentos_administrativos = models.CharField( documentos_administrativos = models.CharField(
max_length=1, max_length=1,
verbose_name=_('Ostensivo/Restritivo'), verbose_name=_('Visibilidade dos Documentos Administrativos'),
choices=TIPO_DOCUMENTO_ADMINISTRATIVO, default='O') choices=TIPO_DOCUMENTO_ADMINISTRATIVO, default='O')
sequencia_numeracao = models.CharField( sequencia_numeracao = models.CharField(

43
sapl/comissoes/forms.py

@ -45,6 +45,7 @@ class ComposicaoForm(forms.ModelForm):
return cleaned_data return cleaned_data
class PeriodoForm(forms.ModelForm): class PeriodoForm(forms.ModelForm):
class Meta: class Meta:
@ -63,9 +64,20 @@ class PeriodoForm(forms.ModelForm):
if data_fim and data_fim < data_inicio: if data_fim and data_fim < data_inicio:
raise ValidationError('A Data Final não pode ser menor que ' raise ValidationError('A Data Final não pode ser menor que '
'a Data Inicial') 'a Data Inicial')
legislatura = Legislatura.objects.filter(data_inicio__lte=data_inicio,
data_fim__gte=data_fim)
if not legislatura:
raise ValidationError('O período informado '
'deve estar contido em uma única '
'legislatura existente')
return cleaned_data return cleaned_data
class ParticipacaoCreateForm(forms.ModelForm): class ParticipacaoCreateForm(forms.ModelForm):
parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput()) parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput())
@ -245,11 +257,36 @@ class ComissaoForm(forms.ModelForm):
if len(self.cleaned_data['nome']) > 100: if len(self.cleaned_data['nome']) > 100:
msg = _('Nome da Comissão deve ter no máximo 50 caracteres.') msg = _('Nome da Comissão deve ter no máximo 50 caracteres.')
raise ValidationError(msg) raise ValidationError(msg)
if self.cleaned_data['data_extincao']: if (self.cleaned_data['data_extincao'] and
if (self.cleaned_data['data_extincao'] < self.cleaned_data['data_extincao'] <
self.cleaned_data['data_criacao']): self.cleaned_data['data_criacao']):
msg = _('Data de extinção não pode ser menor que a de criação') msg = _('Data de extinção não pode ser menor que a de criação')
raise ValidationError(msg) raise ValidationError(msg)
if (self.cleaned_data['data_final_prevista_temp'] and
self.cleaned_data['data_final_prevista_temp'] <
self.cleaned_data['data_criacao']):
msg = _('Data Prevista para Término não pode ser menor que a de criação')
raise ValidationError(msg)
if (self.cleaned_data['data_prorrogada_temp'] and
self.cleaned_data['data_prorrogada_temp'] <
self.cleaned_data['data_criacao']):
msg = _('Data Novo Prazo não pode ser menor que a de criação')
raise ValidationError(msg)
if (self.cleaned_data['data_instalacao_temp'] and
self.cleaned_data['data_instalacao_temp'] <
self.cleaned_data['data_criacao']):
msg = _('Data de Instalação não pode ser menor que a de criação')
raise ValidationError(msg)
if (self.cleaned_data['data_final_prevista_temp'] and self.cleaned_data['data_instalacao_temp'] and
self.cleaned_data['data_final_prevista_temp'] <
self.cleaned_data['data_instalacao_temp']):
msg = _('Data Prevista para Término não pode ser menor que a de Instalação')
raise ValidationError(msg)
if (self.cleaned_data['data_prorrogada_temp'] and self.cleaned_data['data_instalacao_temp'] and
self.cleaned_data['data_prorrogada_temp'] <
self.cleaned_data['data_instalacao_temp']):
msg = _('Data Novo Prazo não pode ser menor que a de Instalação')
raise ValidationError(msg)
return self.cleaned_data return self.cleaned_data
@transaction.atomic @transaction.atomic

5
sapl/compilacao/models.py

@ -237,7 +237,10 @@ class TextoArticulado(TimestampedMixin):
def __str__(self): def __str__(self):
if self.content_object: if self.content_object:
return str(self.content_object) assert hasattr(self.content_object, 'epigrafe'), _(
'Modelos integrados aos Textos Articulados devem possuir a '
'property "epigrafe"')
return str(self.content_object.epigrafe)
else: else:
return _('%(tipo)s%(numero)s de %(data)s') % { return _('%(tipo)s%(numero)s de %(data)s') % {
'tipo': self.tipo_ta, 'tipo': self.tipo_ta,

3
sapl/compilacao/views.py

@ -137,6 +137,7 @@ class IntegracaoTaView(TemplateView):
'observacao': 'observacao', 'observacao': 'observacao',
'numero': 'numero', 'numero': 'numero',
'ano': 'ano', 'ano': 'ano',
'tipo': 'tipo',
} }
Caso o model de integração não possua um dos campos, Caso o model de integração não possua um dos campos,
@ -562,7 +563,7 @@ class TaDetailView(CompMixin, DetailView):
'Metadados para o Texto Articulado de %s\n' 'Metadados para o Texto Articulado de %s\n'
'<small>%s</small>') % ( '<small>%s</small>') % (
self.get_object().content_object._meta.verbose_name_plural, self.get_object().content_object._meta.verbose_name_plural,
self.get_object().content_object) self.get_object().content_object.epigrafe)
else: else:
return self.get_object() return self.get_object()

5
sapl/materia/forms.py

@ -123,13 +123,14 @@ class MateriaSimplificadaForm(ModelForm):
model = MateriaLegislativa model = MateriaLegislativa
fields = ['tipo', 'numero', 'ano', 'data_apresentacao', fields = ['tipo', 'numero', 'ano', 'data_apresentacao',
'numero_protocolo', 'regime_tramitacao', 'numero_protocolo', 'regime_tramitacao',
'em_tramitacao', 'ementa', 'texto_original'] 'em_tramitacao', 'ementa', 'tipo_apresentacao',
'texto_original']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
row1 = to_row([('tipo', 6), ('numero', 3), ('ano', 3)]) row1 = to_row([('tipo', 6), ('numero', 3), ('ano', 3)])
row2 = to_row([('data_apresentacao', 6), ('numero_protocolo', 6)]) row2 = to_row([('data_apresentacao', 6), ('numero_protocolo', 6)])
row3 = to_row([('regime_tramitacao', 6), ('em_tramitacao', 6)]) row3 = to_row([('regime_tramitacao', 6), ('em_tramitacao', 3), ('tipo_apresentacao', 3)])
row4 = to_row([('ementa', 12)]) row4 = to_row([('ementa', 12)])
row5 = to_row([('texto_original', 12)]) row5 = to_row([('texto_original', 12)])

27
sapl/materia/models.py

@ -1,14 +1,15 @@
import reversion
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import models from django.db import models
from django.db.models.functions import Concat from django.db.models.functions import Concat
from django.template import defaultfilters
from django.utils import formats, timezone from django.utils import formats, timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from model_utils import Choices from model_utils import Choices
import reversion
from sapl.base.models import SEQUENCIA_NUMERACAO, Autor from sapl.base.models import SEQUENCIA_NUMERACAO, Autor
from sapl.comissoes.models import Comissao from sapl.comissoes.models import Comissao
@ -19,6 +20,7 @@ from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericForeignKey,
SaplGenericRelation, restringe_tipos_de_arquivo_txt, SaplGenericRelation, restringe_tipos_de_arquivo_txt,
texto_upload_path) texto_upload_path)
EM_TRAMITACAO = [(1, 'Sim'), EM_TRAMITACAO = [(1, 'Sim'),
(0, 'Não')] (0, 'Não')]
@ -242,6 +244,16 @@ class MateriaLegislativa(models.Model):
return _('%(tipo)s%(numero)s de %(ano)s') % { return _('%(tipo)s%(numero)s de %(ano)s') % {
'tipo': self.tipo, 'numero': self.numero, 'ano': self.ano} 'tipo': self.tipo, 'numero': self.numero, 'ano': self.ano}
@property
def epigrafe(self):
return _('%(tipo)s%(numero)s de %(data)s') % {
'tipo': self.tipo,
'numero': self.numero,
'data': defaultfilters.date(
self.data_apresentacao,
"d \d\e F \d\e Y"
)}
def data_entrada_protocolo(self): def data_entrada_protocolo(self):
''' '''
hack: recuperar a data de entrada do protocolo sem gerar hack: recuperar a data de entrada do protocolo sem gerar
@ -425,7 +437,8 @@ class DocumentoAcessorio(models.Model):
verbose_name=_('Tipo')) verbose_name=_('Tipo'))
nome = models.CharField(max_length=50, verbose_name=_('Nome')) nome = models.CharField(max_length=50, verbose_name=_('Nome'))
data = models.DateField(blank=True, null=True, default=None, verbose_name=_('Data')) data = models.DateField(blank=True, null=True,
default=None, verbose_name=_('Data'))
autor = models.CharField( autor = models.CharField(
max_length=50, blank=True, verbose_name=_('Autor')) max_length=50, blank=True, verbose_name=_('Autor'))
ementa = models.TextField(blank=True, verbose_name=_('Ementa')) ementa = models.TextField(blank=True, verbose_name=_('Ementa'))
@ -756,6 +769,16 @@ class Proposicao(models.Model):
self.id, self.id,
descricao) descricao)
@property
def epigrafe(self):
return _('%(tipo)s%(numero)s de %(data)s') % {
'tipo': self.tipo,
'numero': self.numero,
'data': defaultfilters.date(
self.data_envio if self.data_envio else timezone.now(),
"d \d\e F \d\e Y"
)}
def delete(self, using=None, keep_parents=False): def delete(self, using=None, keep_parents=False):
if self.texto_original: if self.texto_original:
self.texto_original.delete() self.texto_original.delete()

4
sapl/materia/urls.py

@ -24,7 +24,7 @@ from sapl.materia.views import (AcompanhamentoConfirmarView,
TipoProposicaoCrud, TramitacaoCrud, TipoProposicaoCrud, TramitacaoCrud,
TramitacaoEmLoteView, UnidadeTramitacaoCrud, TramitacaoEmLoteView, UnidadeTramitacaoCrud,
proposicao_texto, recuperar_materia, proposicao_texto, recuperar_materia,
ExcluirTramitacaoEmLoteView) ExcluirTramitacaoEmLoteView, RetornarProposicao)
from sapl.norma.views import NormaPesquisaSimplesView from sapl.norma.views import NormaPesquisaSimplesView
from .apps import AppConfig from .apps import AppConfig
@ -120,6 +120,8 @@ urlpatterns_proposicao = [
url(r'^proposicao/texto/(?P<pk>\d+)$', proposicao_texto, url(r'^proposicao/texto/(?P<pk>\d+)$', proposicao_texto,
name='proposicao_texto'), name='proposicao_texto'),
url(r'^proposicao/(?P<pk>\d+)/retornar', RetornarProposicao.as_view(),
name='retornar-proposicao'),
] ]
urlpatterns_sistema = [ urlpatterns_sistema = [

53
sapl/materia/views.py

@ -2,7 +2,6 @@ from datetime import datetime
from random import choice from random import choice
from string import ascii_letters, digits from string import ascii_letters, digits
import weasyprint
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML from crispy_forms.layout import HTML
from django.contrib import messages from django.contrib import messages
@ -17,10 +16,11 @@ from django.shortcuts import get_object_or_404, redirect
from django.template import RequestContext, loader from django.template import RequestContext, loader
from django.utils import formats, timezone from django.utils import formats, timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView, ListView, TemplateView, CreateView, UpdateView from django.views.generic import ListView, TemplateView, CreateView, UpdateView
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django_filters.views import FilterView from django_filters.views import FilterView
import weasyprint
import sapl import sapl
from sapl.base.models import Autor, CasaLegislativa from sapl.base.models import Autor, CasaLegislativa
@ -67,6 +67,7 @@ from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria,
TipoProposicao, Tramitacao, UnidadeTramitacao) TipoProposicao, Tramitacao, UnidadeTramitacao)
from .signals import tramitacao_signal from .signals import tramitacao_signal
AssuntoMateriaCrud = CrudAux.build(AssuntoMateria, 'assunto_materia') AssuntoMateriaCrud = CrudAux.build(AssuntoMateria, 'assunto_materia')
OrigemCrud = CrudAux.build(Origem, '') OrigemCrud = CrudAux.build(Origem, '')
@ -96,9 +97,10 @@ def proposicao_texto(request, pk):
if proposicao.texto_original: if proposicao.texto_original:
if (not proposicao.data_recebimento and if (not proposicao.data_recebimento and
proposicao.autor.user_id != request.user.id): proposicao.autor.user_id != request.user.id):
messages.error(request, _('Você não tem permissão para acessar o texto original.')) messages.error(request, _(
'Você não tem permissão para acessar o texto original.'))
return redirect(reverse('sapl.materia:proposicao_detail', return redirect(reverse('sapl.materia:proposicao_detail',
kwargs={'pk':pk})) kwargs={'pk': pk}))
arquivo = proposicao.texto_original arquivo = proposicao.texto_original
@ -221,6 +223,7 @@ class MateriaTaView(IntegracaoTaView):
'observacao': None, 'observacao': None,
'numero': 'numero', 'numero': 'numero',
'ano': 'ano', 'ano': 'ano',
'tipo': 'tipo',
} }
map_funcs = { map_funcs = {
'publicacao_func': False, 'publicacao_func': False,
@ -251,6 +254,7 @@ class ProposicaoTaView(IntegracaoTaView):
'observacao': None, 'observacao': None,
'numero': 'numero_proposicao', 'numero': 'numero_proposicao',
'ano': 'ano', 'ano': 'ano',
'tipo': 'tipo',
} }
map_funcs = { map_funcs = {
'publicacao_func': False 'publicacao_func': False
@ -509,6 +513,31 @@ class ReceberProposicao(PermissionRequiredForAppCrudMixin, FormView):
return context return context
class RetornarProposicao(UpdateView):
app_label = sapl.protocoloadm.apps.AppConfig.label
template_name = "materia/proposicao_confirm_return.html"
model = Proposicao
fields = ['data_envio', 'descricao' ]
permission_required = ('materia.detail_proposicao_enviada', )
def dispatch(self, request, *args, **kwargs):
try:
p = Proposicao.objects.get(id=kwargs['pk'])
except:
raise Http404()
if p.autor.user != request.user:
messages.error(
request,
'Usuário sem acesso a esta opção.' %
request.user)
return redirect('/')
return super(RetornarProposicao, self).dispatch(
request, *args, **kwargs)
class ConfirmarProposicao(PermissionRequiredForAppCrudMixin, UpdateView): class ConfirmarProposicao(PermissionRequiredForAppCrudMixin, UpdateView):
app_label = sapl.protocoloadm.apps.AppConfig.label app_label = sapl.protocoloadm.apps.AppConfig.label
template_name = "materia/confirmar_proposicao.html" template_name = "materia/confirmar_proposicao.html"
@ -994,7 +1023,8 @@ class RelatoriaCrud(MasterDetailCrud):
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
else: else:
composicao = comissao.composicao_set.last() composicao = comissao.composicao_set.order_by(
'-periodo__data_inicio').first()
participacao = Participacao.objects.filter( participacao = Participacao.objects.filter(
composicao=composicao) composicao=composicao)
@ -1034,7 +1064,7 @@ class TramitacaoCrud(MasterDetailCrud):
if local: if local:
initial['unidade_tramitacao_local' initial['unidade_tramitacao_local'
] = local.unidade_tramitacao_destino.pk ] = local.unidade_tramitacao_destino.pk
else: else:
initial['unidade_tramitacao_local'] = '' initial['unidade_tramitacao_local'] = ''
initial['data_tramitacao'] = timezone.now().date() initial['data_tramitacao'] = timezone.now().date()
@ -2035,10 +2065,13 @@ class ExcluirTramitacaoEmLoteView(PermissionRequiredMixin, FormView):
def form_valid(self, form): def form_valid(self, form):
tramitacao_set = Tramitacao.objects.filter(data_tramitacao=form.cleaned_data['data_tramitacao'], tramitacao_set = Tramitacao.objects.filter(
unidade_tramitacao_local=form.cleaned_data['unidade_tramitacao_local'], data_tramitacao=form.cleaned_data['data_tramitacao'],
unidade_tramitacao_destino=form.cleaned_data['unidade_tramitacao_destino'], unidade_tramitacao_local=form.cleaned_data[
status=form.cleaned_data['status']) 'unidade_tramitacao_local'],
unidade_tramitacao_destino=form.cleaned_data[
'unidade_tramitacao_destino'],
status=form.cleaned_data['status'])
for tramitacao in tramitacao_set: for tramitacao in tramitacao_set:
materia = tramitacao.materia materia = tramitacao.materia
if tramitacao == materia.tramitacao_set.last(): if tramitacao == materia.tramitacao_set.last():

10
sapl/norma/models.py

@ -1,9 +1,9 @@
import reversion
from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.fields import GenericRelation
from django.db import models from django.db import models
from django.template import defaultfilters from django.template import defaultfilters
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from model_utils import Choices from model_utils import Choices
import reversion
from sapl.compilacao.models import TextoArticulado from sapl.compilacao.models import TextoArticulado
from sapl.materia.models import MateriaLegislativa from sapl.materia.models import MateriaLegislativa
@ -146,12 +146,18 @@ class NormaJuridica(models.Model):
norma=self.id) norma=self.id)
return anexos return anexos
def __str__(self): def __str__(self):
return _('%(numero)s de %(data)s') % { return _('%(numero)s de %(data)s') % {
'numero': self.numero, 'numero': self.numero,
'data': defaultfilters.date(self.data, "d \d\e F \d\e Y")} 'data': defaultfilters.date(self.data, "d \d\e F \d\e Y")}
@property
def epigrafe(self):
return _('%(tipo)s%(numero)s de %(data)s') % {
'tipo': self.tipo,
'numero': self.numero,
'data': defaultfilters.date(self.data, "d \d\e F \d\e Y")}
def delete(self, using=None, keep_parents=False): def delete(self, using=None, keep_parents=False):
if self.texto_integral: if self.texto_integral:
self.texto_integral.delete() self.texto_integral.delete()

15
sapl/norma/views.py

@ -1,6 +1,6 @@
import re import re
import weasyprint
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@ -12,6 +12,7 @@ from django.views.generic import TemplateView, UpdateView
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django_filters.views import FilterView from django_filters.views import FilterView
import weasyprint
from sapl.base.models import AppConfig from sapl.base.models import AppConfig
from sapl.compilacao.views import IntegracaoTaView from sapl.compilacao.views import IntegracaoTaView
@ -24,6 +25,7 @@ from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm,
from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada, from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica, TipoVinculoNormaJuridica) TipoNormaJuridica, TipoVinculoNormaJuridica)
# LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '') # LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '')
AssuntoNormaCrud = CrudAux.build(AssuntoNorma, 'assunto_norma_juridica', AssuntoNormaCrud = CrudAux.build(AssuntoNorma, 'assunto_norma_juridica',
list_field_names=['assunto', 'descricao']) list_field_names=['assunto', 'descricao'])
@ -72,7 +74,10 @@ class NormaPesquisaView(FilterView):
def get_queryset(self): def get_queryset(self):
qs = super().get_queryset() qs = super().get_queryset()
qs = qs.extra({'norma_i': "CAST(regexp_replace(numero,'[^0-9]','', 'g') AS INTEGER)", 'norma_letra': "regexp_replace(numero,'[^a-zA-Z]','', 'g')"}).order_by('-data', '-norma_i', '-norma_letra') qs = qs.extra({
'nm_i': "CAST(regexp_replace(numero,'[^0-9]','', 'g') AS INTEGER)",
'norma_letra': "regexp_replace(numero,'[^a-zA-Z]','', 'g')"
}).order_by('-data', '-nm_i', '-norma_letra')
return qs return qs
@ -98,6 +103,7 @@ class NormaPesquisaView(FilterView):
return context return context
class AnexoNormaJuridicaCrud(MasterDetailCrud): class AnexoNormaJuridicaCrud(MasterDetailCrud):
model = AnexoNormaJuridica model = AnexoNormaJuridica
parent_field = 'norma' parent_field = 'norma'
@ -105,7 +111,7 @@ class AnexoNormaJuridicaCrud(MasterDetailCrud):
public = [RP_LIST, RP_DETAIL] public = [RP_LIST, RP_DETAIL]
class BaseMixin(MasterDetailCrud.BaseMixin): class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['id','anexo_arquivo'] list_field_names = ['id', 'anexo_arquivo']
class CreateView(MasterDetailCrud.CreateView): class CreateView(MasterDetailCrud.CreateView):
form_class = AnexoNormaJuridicaForm form_class = AnexoNormaJuridicaForm
@ -141,6 +147,7 @@ class NormaTaView(IntegracaoTaView):
'observacao': 'observacao', 'observacao': 'observacao',
'numero': 'numero', 'numero': 'numero',
'ano': 'ano', 'ano': 'ano',
'tipo': 'tipo',
} }
map_funcs = { map_funcs = {
@ -238,7 +245,7 @@ def recuperar_numero_norma(request):
param = {'tipo': tipo} param = {'tipo': tipo}
param['ano'] = ano if ano else timezone.now().year param['ano'] = ano if ano else timezone.now().year
norma = NormaJuridica.objects.filter(**param).order_by( norma = NormaJuridica.objects.filter(**param).order_by(
'tipo', 'ano', 'numero').values_list('numero', 'ano').last() 'tipo', 'ano', 'numero').values_list('numero', 'ano').last()
if norma: if norma:
response = JsonResponse({'numero': int(re.sub("[^0-9].*", '', norma[0])) + 1, response = JsonResponse({'numero': int(re.sub("[^0-9].*", '', norma[0])) + 1,
'ano': norma[1]}) 'ano': norma[1]})

2
sapl/protocoloadm/models.py

@ -55,6 +55,8 @@ class Protocolo(models.Model):
null=False, null=False,
choices=RANGE_ANOS, choices=RANGE_ANOS,
verbose_name=_('Ano do Protocolo')) verbose_name=_('Ano do Protocolo'))
# TODO: Remover esses dois campos após migração,
# TODO: pois timestamp supre a necessidade
data = models.DateField() data = models.DateField()
hora = models.TimeField() hora = models.TimeField()
# TODO transformar campo timestamp em auto_now_add # TODO transformar campo timestamp em auto_now_add

3
sapl/protocoloadm/urls.py

@ -12,6 +12,7 @@ from sapl.protocoloadm.views import (AnularProtocoloAdmView,
ProtocoloMostrarView, ProtocoloMostrarView,
ProtocoloPesquisaView, ProtocoloPesquisaView,
StatusTramitacaoAdministrativoCrud, StatusTramitacaoAdministrativoCrud,
recuperar_materia_protocolo,
TipoDocumentoAdministrativoCrud, TipoDocumentoAdministrativoCrud,
TramitacaoAdmCrud, TramitacaoAdmCrud,
atualizar_numero_documento, atualizar_numero_documento,
@ -77,6 +78,8 @@ urlpatterns_protocolo = [
url(r'^protocoloadm/atualizar_numero_documento$', url(r'^protocoloadm/atualizar_numero_documento$',
atualizar_numero_documento, name='atualizar_numero_documento'), atualizar_numero_documento, name='atualizar_numero_documento'),
url(r'^protocoloadm/recuperar-materia',
recuperar_materia_protocolo, name='recuperar_materia_protocolo'),
] ]

21
sapl/protocoloadm/views.py

@ -1,6 +1,7 @@
from braces.views import FormValidMessageMixin from braces.views import FormValidMessageMixin
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
@ -45,6 +46,24 @@ TipoDocumentoAdministrativoCrud = CrudAux.build(
# FIXME precisa de uma chave diferente para o layout # FIXME precisa de uma chave diferente para o layout
# ProtocoloMateriaCrud = Crud.build(Protocolo, '') # ProtocoloMateriaCrud = Crud.build(Protocolo, '')
@permission_required('protocoloadm.add_protocolo')
def recuperar_materia_protocolo(request):
tipo = request.GET.get('tipo')
ano = request.GET.get('ano')
numero = request.GET.get('numero')
try:
materia = MateriaLegislativa.objects.get(
tipo=tipo, ano=ano,numero=numero)
autoria = materia.autoria_set.first()
content = {'ementa': materia.ementa.strip(),
'ano':materia.ano, 'numero':materia.numero}
if autoria:
content.update({'autor': autoria.autor.pk,
'tipo_autor':autoria.autor.tipo.pk})
response = JsonResponse(content)
except Exception as e:
response = JsonResponse({'error':e})
return response
def doc_texto_integral(request, pk): def doc_texto_integral(request, pk):
can_see = True can_see = True
@ -782,4 +801,4 @@ class DesvincularMateriaView(PermissionRequiredMixin, FormView):
tipo=form.cleaned_data['tipo']) tipo=form.cleaned_data['tipo'])
materia.numero_protocolo = None materia.numero_protocolo = None
materia.save() materia.save()
return redirect(self.get_success_url()) return redirect(self.get_success_url())

9
sapl/relatorios/views.py

@ -826,9 +826,10 @@ def get_protocolos(prots):
dic['titulo'] = str(protocolo.numero) + '/' + str(protocolo.ano) dic['titulo'] = str(protocolo.numero) + '/' + str(protocolo.ano)
dic['data'] = protocolo.data.strftime( ts = timezone.localtime(protocolo.timestamp)
"%d/%m/%Y") + ' - <b>Horário:</b>' + protocolo.hora.strftime(
"%H:%m") dic['data'] = ts.strftime("%d/%m/%Y") + ' - <b>Horário:</b>' + \
ts.strftime("%H:%m")
dic['txt_assunto'] = protocolo.assunto_ementa dic['txt_assunto'] = protocolo.assunto_ementa
@ -941,7 +942,7 @@ def get_etiqueta_protocolos(prots):
tz_hora = timezone.localtime(p.timestamp) tz_hora = timezone.localtime(p.timestamp)
dic['data'] = '<b>Data: </b>' + p.data.strftime( dic['data'] = '<b>Data: </b>' + tz_hora.strftime(
"%d/%m/%Y") + ' - <b>Horário: </b>' + tz_hora.strftime("%H:%M") "%d/%m/%Y") + ' - <b>Horário: </b>' + tz_hora.strftime("%H:%M")
dic['txt_assunto'] = p.assunto_ementa dic['txt_assunto'] = p.assunto_ementa
dic['txt_interessado'] = p.interessado dic['txt_interessado'] = p.interessado

5
sapl/sessao/views.py

@ -460,9 +460,10 @@ def recuperar_materia(request):
ano=ano, ano=ano,
numero=numero) numero=numero)
response = JsonResponse({'ementa': materia.ementa, response = JsonResponse({'ementa': materia.ementa,
'id': materia.id}) 'id': materia.id,
'indexacao': materia.indexacao})
except ObjectDoesNotExist: except ObjectDoesNotExist:
response = JsonResponse({'ementa': '', 'id': 0}) response = JsonResponse({'ementa': '', 'id': 0, 'indexacao':''})
return response return response

13
sapl/static/styles/app.scss

@ -558,6 +558,19 @@ p {
} }
} }
.btn-cancel-iframe {
position: relative;
text-align: right;
opacity: 0.5;
&:hover {
opacity: 1;
}
a {
padding: 10px;
display: inline-block;
}
}
@media (max-width: 1199px) { @media (max-width: 1199px) {
.masthead { .masthead {

23
sapl/templates/base.html

@ -29,6 +29,7 @@
<body> <body>
<div class="page fadein"> <div class="page fadein">
{% if not request|has_iframe %} {% if not request|has_iframe %}
{% block navigation %} {% block navigation %}
<nav class="navbar navbar-inverse navbar-static-top"> <nav class="navbar navbar-inverse navbar-static-top">
@ -108,6 +109,9 @@
</header> </header>
{% endblock main_header %} {% endblock main_header %}
{% else %} {% else %}
<div class="btn-cancel-iframe">
<a href="?iframe=0" target="_blank"><i class="fa fa-2x fa-arrows-alt"></i></a>
</div>
<header class="masthead"> <header class="masthead">
<div class="container"> <div class="container">
<div class="hidden-print"> <div class="hidden-print">
@ -245,6 +249,25 @@
{% block extra_js %}{% endblock %} {% block extra_js %}{% endblock %}
<script type="text/javascript" >
function inIframe () {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
$(document).ready(function(){
let iframe_set_backend = {{ request|has_iframe|lower }}
if (iframe_set_backend && !inIframe() ) {
let href = location.href
location.href = href + '?iframe=0'
}
});
</script>
{% endblock foot_js %} {% endblock foot_js %}
</body> </body>
</html> </html>

4
sapl/templates/compilacao/textoarticulado_detail.html

@ -8,9 +8,9 @@
{%if object %} {%if object %}
<li> <li>
{% if request.GET.back_type == 'history' and object.content_object %} {% if request.GET.back_type == 'history' and object.content_object %}
<a href="javascript:window.history.back()" title="{% trans 'Voltar para '%}{{object.content_object}}">{% trans 'Voltar para '%}{{object.content_object}}</a> <a href="javascript:window.history.back()" title="{% trans 'Voltar para '%}{{object}}">{% trans 'Voltar para '%}{{object}}</a>
{% elif object.content_object%} {% elif object.content_object%}
<a href="{% url object|urldetail_content_type:object.content_object object.content_object.pk %}" title="{% trans 'Voltar para '%}{{object.content_object}}">{% trans 'Voltar para '%}{{object.content_object}}</a> <a href="{% url object|urldetail_content_type:object.content_object object.content_object.pk %}" title="{% trans 'Voltar para '%}{{object}}">{% trans 'Voltar para '%}{{object}}</a>
{%else%} {%else%}
<a href="{% url 'sapl.compilacao:ta_detail' object.pk %}">{% trans 'Início' %}</a> <a href="{% url 'sapl.compilacao:ta_detail' object.pk %}">{% trans 'Início' %}</a>
{%endif%} {%endif%}

3
sapl/templates/compilacao/textoarticulado_menu_config.html

@ -13,8 +13,5 @@
{% if perms.compilacao.list_tiponota %}<li><a href="{% url 'sapl.compilacao:tiponota_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoNota'%}</a></li>{% endif %} {% if perms.compilacao.list_tiponota %}<li><a href="{% url 'sapl.compilacao:tiponota_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoNota'%}</a></li>{% endif %}
{% if perms.compilacao.list_tipovide %}<li><a href="{% url 'sapl.compilacao:tipovide_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoVide'%}</a></li>{% endif %} {% if perms.compilacao.list_tipovide %}<li><a href="{% url 'sapl.compilacao:tipovide_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoVide'%}</a></li>{% endif %}
{% if user.is_superuser %}
<li><a href="/admin/compilacao/tipodispositivorelationship/">Relacionamento entre Dispositivos</a></li>
{% endif %}
</ul> </ul>
{% endif %} {% endif %}

27
sapl/templates/materia/proposicao_confirm_return.html

@ -0,0 +1,27 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% block sections_nav %}{% endblock sections_nav %}
{% block title %}
<h1 class="page-header">
{{ object|safe|linebreaksbr }}
</h1>
{% endblock %}
{% block base_content %}
<form action="" method="post">{% csrf_token %}
<br>
<div class="panel panel-danger">
<div class="panel-heading text-center">
{% blocktrans %}
ATENÇÃO: Retornar a proposição cancela o envio da mesma para protocolo e invalida o recibo já emitido.<br />
Será necessário novo envio e impressão de novo recibo para recebimento pela Casa Legislativa.<br />
Confirma retorno da proposição para o Parlamentar/Gabinete?
{% endblocktrans %}<br>
</div>
<div class="panel-body text-center">
<a href="{% url 'sapl.materia:proposicao_detail' object.pk %}" class="btn btn-inverse">{% trans 'Cancelar' %}</a>
<a href="{% url 'sapl.materia:proposicao_detail' object.pk %}?action=return" class="btn btn-default btn-excluir">{% trans 'Confirmar' %}</a>
</div>
</div>
</form>
{% endblock %}

2
sapl/templates/materia/proposicao_detail.html

@ -21,7 +21,7 @@
<div class="actions btn-group" role="group"> <div class="actions btn-group" role="group">
<a class="btn btn-default" onclick="window.open('{% url 'sapl.materia:recibo-proposicao' object.pk %}','Recibo','width=1100, height=600, scrollbars=yes')">{% trans "Recibo de Envio" %}</a> <a class="btn btn-default" onclick="window.open('{% url 'sapl.materia:recibo-proposicao' object.pk %}','Recibo','width=1100, height=600, scrollbars=yes')">{% trans "Recibo de Envio" %}</a>
{% if not object.data_recebimento %} {% if not object.data_recebimento %}
<a href="{{ view.detail_url }}?action=return" class="btn btn-default btn-excluir">{% trans 'Retornar Proposição Enviada' %}</a> <a href="{% url 'sapl.materia:retornar-proposicao' object.pk %}" class="btn btn-default btn-excluir">{% trans 'Retornar Proposição Enviada' %}</a>
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock %}

4
sapl/templates/norma/normajuridica_detail.html

@ -42,7 +42,7 @@
<div class="form-control-static"> <div class="form-control-static">
<b>{{ p.tipo_vinculo.descricao_ativa }}</b>&nbsp; <b>{{ p.tipo_vinculo.descricao_ativa }}</b>&nbsp;
<a href="{% url 'sapl.norma:normajuridica_detail' p.norma_relacionada.pk %}"> <a href="{% url 'sapl.norma:normajuridica_detail' p.norma_relacionada.pk %}">
{{ p.norma_relacionada }} {{ p.norma_relacionada.epigrafe }}
</a> </a>
</div> </div>
{% endfor %} {% endfor %}
@ -53,7 +53,7 @@
<div class="form-control-static"> <div class="form-control-static">
<b>{{ r.tipo_vinculo.descricao_passiva }}</b>&nbsp; <b>{{ r.tipo_vinculo.descricao_passiva }}</b>&nbsp;
<a href="{% url 'sapl.norma:normajuridica_detail' r.norma_principal.pk %}"> <a href="{% url 'sapl.norma:normajuridica_detail' r.norma_principal.pk %}">
{{ r.norma_principal }} {{ r.norma_principal.epigrafe }}
</a> </a>
</div> </div>
{% endfor %} {% endfor %}

1
sapl/templates/norma/normajuridica_form.html

@ -17,6 +17,7 @@
ano_materia: ano_materia}, ano_materia: ano_materia},
function(data, status) { function(data, status) {
$("#id_ementa").val(data.ementa); $("#id_ementa").val(data.ementa);
$("#id_indexacao").val(data.indexacao);
}); });
} }
} }

31
sapl/templates/protocoloadm/protocolar_materia.html

@ -26,6 +26,36 @@
} }
$(document).ready(function() { $(document).ready(function() {
function busca_ementa() {
var vincular_materia = $("#id_vincular_materia_1").prop("checked");
var ano_materia = $("#id_ano_materia").val();
var numero_materia = $("#id_numero_materia").val();
var tipo_materia = $("#id_tipo_materia").val();
var json_data = {
ano : ano_materia,
numero : numero_materia,
tipo : tipo_materia
}
if (vincular_materia === true && ano_materia !== undefined &&
numero_materia !== undefined && numero_materia !== "") {
$.getJSON("/protocoloadm/recuperar-materia", json_data, function(data){
if (data) {
if (data['error'] === undefined){
$('#id_assunto_ementa').val(data['ementa']);
if (data['autor'] !== undefined) {
$('#id_autor').val(data['autor']);
$('#id_tipo_autor').val(data['tipo_autor']);
}
}
}
})
}
};
$("#id_ano_materia").blur(busca_ementa);
$("#id_numero_materia").blur(busca_ementa);
$("#id_tipo_materia").change(busca_ementa);
$("#id_tipo_autor").change(function() { $("#id_tipo_autor").change(function() {
var tipo_selecionado = $("#id_tipo_autor").val(); var tipo_selecionado = $("#id_tipo_autor").val();
var autor_selecionado = $("#id_autor").val(); var autor_selecionado = $("#id_autor").val();
@ -51,6 +81,7 @@
}); });
} }
}); });
}); });
</script> </script>
{% endblock %} {% endblock %}

2
sapl/templates/protocoloadm/protocolo_filter.html

@ -44,7 +44,7 @@
{% if p.anulado %}<strong><font color="red">&nbsp;&nbsp;** NULO **</font></strong>{% endif %} {% if p.anulado %}<strong><font color="red">&nbsp;&nbsp;** NULO **</font></strong>{% endif %}
</br> </br>
<strong>Assunto:</strong> {{ p.assunto_ementa|default_if_none:"Não informado"}}</br> <strong>Assunto:</strong> {{ p.assunto_ementa|default_if_none:"Não informado"}}</br>
<strong>Data Protocolo:</strong> {{ p.data|date:"d/m/Y"|default_if_none:"Não informado" }} - Horário: {{ p.timestamp|localtime|date:"G:i:s" }}</br> <strong>Data Protocolo:</strong> {{ p.timestamp|localtime|date:"d/m/Y"|default_if_none:"Não informado" }} - Horário: {{ p.timestamp|localtime|date:"G:i:s" }}</br>
{% if p.tipo_processo == 0 %} {% if p.tipo_processo == 0 %}
<strong>Interessado:</strong> {{ p.interessado|default_if_none:"Não informado" }}</br> <strong>Interessado:</strong> {{ p.interessado|default_if_none:"Não informado" }}</br>

2
sapl/templates/protocoloadm/protocolo_mostrar.html

@ -9,7 +9,7 @@
<a href="{% url 'sapl.relatorios:relatorio_etiqueta_protocolo' protocolo.numero protocolo.ano %}"><img src="{% static 'img/etiqueta.png' %}" alt="Etiqueta Individual"></a></br> <a href="{% url 'sapl.relatorios:relatorio_etiqueta_protocolo' protocolo.numero protocolo.ano %}"><img src="{% static 'img/etiqueta.png' %}" alt="Etiqueta Individual"></a></br>
<strong>Assunto: </strong> {{ protocolo.assunto_ementa|default:"Não informado" }}</br> <strong>Assunto: </strong> {{ protocolo.assunto_ementa|default:"Não informado" }}</br>
<strong>Data Protocolo: </strong> {{ protocolo.data|date:"d/m/Y" }} - Horário: {{ protocolo.timestamp|localtime|date:"H:i" }}</br> <strong>Data Protocolo: </strong> {{ protocolo.timestamp|localtime|date:"d/m/Y" }} - Horário: {{ protocolo.timestamp|localtime|date:"G:i:s" }}</br>
{% if protocolo.tipo_processo == 0 %} {% if protocolo.tipo_processo == 0 %}
<strong>Interessado:</strong> {{ protocolo.interessado|default_if_none:"Não informado" }}</br> <strong>Interessado:</strong> {{ protocolo.interessado|default_if_none:"Não informado" }}</br>

2
setup.py

@ -49,7 +49,7 @@ install_requires = [
] ]
setup( setup(
name='interlegis-sapl', name='interlegis-sapl',
version='3.1.107', version='3.1.110',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007', license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007',

Loading…
Cancel
Save