Browse Source

Merge master

pull/792/head
Eduardo Calil 9 years ago
parent
commit
b14b11389c
  1. 2
      requirements/requirements.txt
  2. 2
      sapl/api/forms.py
  3. 1
      sapl/api/serializers.py
  4. 6
      sapl/api/urls.py
  5. 9
      sapl/api/views.py
  6. 108
      sapl/base/apps.py
  7. 15
      sapl/base/forms.py
  8. 8
      sapl/base/legacy.yaml
  9. 10
      sapl/base/models.py
  10. 26
      sapl/base/templatetags/common_tags.py
  11. 3
      sapl/base/templatetags/menus.py
  12. 2
      sapl/base/views.py
  13. 28
      sapl/compilacao/forms.py
  14. 19
      sapl/compilacao/migrations/0059_auto_20161027_1323.py
  15. 19
      sapl/compilacao/migrations/0060_auto_20161101_0913.py
  16. 21
      sapl/compilacao/migrations/0061_auto_20161101_1025.py
  17. 19
      sapl/compilacao/migrations/0062_auto_20161101_1221.py
  18. 20
      sapl/compilacao/migrations/0063_tipotextoarticulado_publicacao_func.py
  19. 46
      sapl/compilacao/migrations/0064_auto_20161104_1420.py
  20. 20
      sapl/compilacao/migrations/0065_auto_20161107_1024.py
  21. 20
      sapl/compilacao/migrations/0066_auto_20161107_1028.py
  22. 19
      sapl/compilacao/migrations/0067_auto_20161107_1351.py
  23. 19
      sapl/compilacao/migrations/0068_auto_20161107_1546.py
  24. 20
      sapl/compilacao/migrations/0069_auto_20161107_1932.py
  25. 169
      sapl/compilacao/models.py
  26. 6
      sapl/compilacao/templatetags/compilacao_filters.py
  27. 382
      sapl/compilacao/views.py
  28. 3
      sapl/crispy_layout_mixin.py
  29. 10
      sapl/crud/base.py
  30. 140
      sapl/legacy/migration.py
  31. 206
      sapl/materia/forms.py
  32. 23
      sapl/materia/legacy.yaml
  33. 13
      sapl/materia/models.py
  34. 6
      sapl/materia/tests/test_materia.py
  35. 7
      sapl/materia/urls.py
  36. 188
      sapl/materia/views.py
  37. 40
      sapl/norma/forms.py
  38. 23
      sapl/norma/migrations/0016_auto_20161027_1419.py
  39. 20
      sapl/norma/migrations/0017_auto_20161027_1432.py
  40. 26
      sapl/norma/migrations/0018_auto_20161027_1434.py
  41. 19
      sapl/norma/migrations/0019_auto_20161028_0232.py
  42. 39
      sapl/norma/migrations/0020_auto_20161028_1335.py
  43. 24
      sapl/norma/migrations/0021_auto_20161028_1335.py
  44. 14
      sapl/norma/models.py
  45. 6
      sapl/norma/urls.py
  46. 22
      sapl/norma/views.py
  47. 6
      sapl/painel/views.py
  48. 34
      sapl/parlamentares/views.py
  49. 81
      sapl/protocoloadm/forms.py
  50. 19
      sapl/protocoloadm/migrations/0005_auto_20161027_1741.py
  51. 20
      sapl/protocoloadm/migrations/0006_auto_20161103_1721.py
  52. 11
      sapl/protocoloadm/models.py
  53. 2
      sapl/protocoloadm/tests/test_protocoloadm.py
  54. 48
      sapl/protocoloadm/urls.py
  55. 192
      sapl/protocoloadm/views.py
  56. 2
      sapl/relatorios/views.py
  57. 38
      sapl/rules/__init__.py
  58. 234
      sapl/rules/apps.py
  59. 317
      sapl/rules/map_rules.py
  60. 0
      sapl/rules/migrations/__init__.py
  61. 0
      sapl/rules/models.py
  62. 255
      sapl/rules/tests/test_rules.py
  63. 21
      sapl/sessao/forms.py
  64. 27
      sapl/sessao/views.py
  65. 9
      sapl/settings.py
  66. 9
      sapl/static/styles/compilacao.scss
  67. 3
      sapl/templates/base.html
  68. 49
      sapl/templates/base/appconfig_list.html
  69. 48
      sapl/templates/compilacao/ajax_actions_dinamic_edit.html
  70. 8
      sapl/templates/compilacao/dispositivo_form.html
  71. 2
      sapl/templates/compilacao/publicacao_detail.html
  72. 12
      sapl/templates/compilacao/publicacao_list.html
  73. 20
      sapl/templates/compilacao/text_edit.html
  74. 26
      sapl/templates/compilacao/text_list.html
  75. 21
      sapl/templates/compilacao/text_list__print_version.html
  76. 22
      sapl/templates/compilacao/text_list_bloco.html
  77. 41
      sapl/templates/compilacao/textoarticulado_detail.html
  78. 14
      sapl/templates/compilacao/textoarticulado_list.html
  79. 18
      sapl/templates/compilacao/textoarticulado_menu_config.html
  80. 30
      sapl/templates/compilacao/tipotextoarticulado_detail.html
  81. 12
      sapl/templates/compilacao/tipotextoarticulado_list.html
  82. 53
      sapl/templates/crud/detail.html
  83. 11
      sapl/templates/crud/detail_detail.html
  84. 4
      sapl/templates/materia/confirmar_proposicao.html
  85. 1
      sapl/templates/materia/proposicao_detail.html
  86. 5
      sapl/templates/materia/tipoproposicao_form.html
  87. 2
      sapl/templates/navbar.yaml
  88. 7
      sapl/templates/norma/layouts.yaml
  89. 1
      sapl/templates/norma/list_pesquisa.html
  90. 29
      sapl/templates/norma/normajuridica_form.html
  91. 2
      sapl/templates/norma/subnav.yaml
  92. 4
      sapl/templates/painel/controlador.html
  93. 29
      sapl/templates/protocoloadm/MateriaTemplate.html
  94. 12
      sapl/templates/protocoloadm/anular_protocoloadm.html
  95. 4
      sapl/templates/protocoloadm/comprovante.html
  96. 11
      sapl/templates/protocoloadm/protocolar_documento.html
  97. 10
      sapl/templates/protocoloadm/protocolar_materia.html
  98. 36
      sapl/templates/protocoloadm/protocolo_filter.html
  99. 27
      sapl/templates/protocoloadm/protocolo_mostrar.html
  100. 14
      sapl/templates/protocoloadm/protocoloadm_detail.html

2
requirements/requirements.txt

@ -8,7 +8,7 @@ django-compressor==2.0
django-crispy-forms==1.6.0
django-extensions==1.6.7
django-extra-views==0.8.0
django-filter==0.13.0
django-filter==0.15.3
django-floppyforms==1.6.2
django-model-utils==2.5
django-sass-processor==0.4.6

2
sapl/api/forms.py

@ -1,6 +1,7 @@
from django.db.models import Q
from django_filters.filters import MethodFilter, ModelChoiceFilter
from rest_framework.filters import FilterSet
from sapl.base.models import Autor, TipoAutor
from sapl.utils import generic_relations_for_model
@ -21,7 +22,6 @@ class SaplGenericRelationSearchFilterSet(FilterSet):
order_by = []
for gr in generic_relations_for_model(self._meta.model):
model = gr[0]
sgr = gr[1]
for item in sgr:
if item.related_model != self._meta.model:

1
sapl/api/serializers.py

@ -1,4 +1,5 @@
from rest_framework import serializers
from sapl.base.models import Autor
from sapl.materia.models import MateriaLegislativa

6
sapl/api/urls.py

@ -1,9 +1,9 @@
from django.conf import settings
from django.conf.urls import url, include
from django.conf.urls import include, url
from rest_framework.routers import DefaultRouter
from sapl.api.views import MateriaLegislativaViewSet, AutorListView,\
ModelChoiceView
from sapl.api.views import (AutorListView, MateriaLegislativaViewSet,
ModelChoiceView)
from .apps import AppConfig

9
sapl/api/views.py

@ -5,13 +5,14 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework.filters import DjangoFilterBackend
from rest_framework.generics import ListAPIView
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import GenericViewSet
from sapl.api.forms import AutorChoiceFilterSet
from sapl.api.serializers import ChoiceSerializer, AutorSerializer,\
AutorChoiceSerializer, ModelChoiceSerializer, MateriaLegislativaSerializer
from sapl.api.serializers import (AutorChoiceSerializer, AutorSerializer,
ChoiceSerializer,
MateriaLegislativaSerializer,
ModelChoiceSerializer)
from sapl.base.models import Autor, TipoAutor
from sapl.materia.models import MateriaLegislativa
from sapl.utils import SaplGenericRelation, sapl_logger

108
sapl/base/apps.py

@ -1,115 +1,9 @@
from builtins import LookupError
from django.apps import apps
from django.contrib.auth.management import _get_all_permissions
from django.core import exceptions
from django.db import router
from django.db.models.signals import pre_migrate, post_migrate
from django.db.utils import DEFAULT_DB_ALIAS
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
import django
def create_proxy_permissions(
app_config, verbosity=2, interactive=True,
using=DEFAULT_DB_ALIAS, **kwargs):
if not app_config.models_module:
return
# print(app_config)
try:
Permission = apps.get_model('auth', 'Permission')
except LookupError:
return
if not router.allow_migrate_model(using, Permission):
return
from django.contrib.contenttypes.models import ContentType
permission_name_max_length = Permission._meta.get_field('name').max_length
# This will hold the permissions we're looking for as
# (content_type, (codename, name))
searched_perms = list()
# The codenames and ctypes that should exist.
ctypes = set()
for klass in list(app_config.get_models()):
opts = klass._meta
permissions = (
("list_" + opts.model_name,
string_concat(
_('Visualizaçao da lista de'), ' ',
opts.verbose_name_plural)),
("detail_" + opts.model_name,
string_concat(
_('Visualização dos detalhes de'), ' ',
opts.verbose_name_plural)),
)
opts.permissions = tuple(
set(list(permissions) + list(opts.permissions)))
if opts.proxy:
# Force looking up the content types in the current database
# before creating foreign keys to them.
app_label, model = opts.app_label, opts.model_name
try:
ctype = ContentType.objects.db_manager(
using).get_by_natural_key(app_label, model)
except:
ctype = ContentType.objects.db_manager(
using).create(app_label=app_label, model=model)
else:
ctype = ContentType.objects.db_manager(using).get_for_model(klass)
ctypes.add(ctype)
for perm in _get_all_permissions(klass._meta, ctype):
searched_perms.append((ctype, perm))
# Find all the Permissions that have a content_type for a model we're
# looking for. We don't need to check for codenames since we already have
# a list of the ones we're going to create.
all_perms = set(Permission.objects.using(using).filter(
content_type__in=ctypes,
).values_list(
"content_type", "codename"
))
perms = [
Permission(codename=codename, name=name, content_type=ct)
for ct, (codename, name) in searched_perms
if (ct.pk, codename) not in all_perms
]
# Validate the permissions before bulk_creation to avoid cryptic database
# error when the name is longer than 255 characters
for perm in perms:
if len(perm.name) > permission_name_max_length:
raise exceptions.ValidationError(
'The permission name %s of %s.%s '
'is longer than %s characters' % (
perm.name,
perm.content_type.app_label,
perm.content_type.model,
permission_name_max_length,
)
)
Permission.objects.using(using).bulk_create(perms)
if verbosity >= 2:
for perm in perms:
print("Adding permission '%s'" % perm)
from django.utils.translation import ugettext_lazy as _
class AppConfig(django.apps.AppConfig):
name = 'sapl.base'
label = 'base'
verbose_name = _('Dados Básicos')
def ready(self):
#pre_migrate.connect(run_sql_organizers, self)
post_migrate.connect(
receiver=create_proxy_permissions,
dispatch_uid="django.contrib.auth.management.create_permissions")

15
sapl/base/forms.py

@ -1,22 +1,17 @@
import django_filters
from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row
from crispy_forms.templatetags.crispy_forms_field import css_class
from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import Group
from django.contrib.auth.password_validation import validate_password
from django.contrib.contenttypes.fields import GenericRel
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.forms import ModelForm
from django.utils.translation import ugettext_lazy as _, string_concat
import django_filters
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat
@ -26,11 +21,9 @@ from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
from sapl.materia.models import MateriaLegislativa
from sapl.sessao.models import SessaoPlenaria
from sapl.settings import MAX_IMAGE_UPLOAD_SIZE
from sapl.utils import (RANGE_ANOS, ImageThumbnailFileInput,
RangeWidgetOverride, autor_label, autor_modal,
SaplGenericRelation, models_with_gr_for_model,
ChoiceWithoutValidationField)
from sapl.utils import (RANGE_ANOS, ChoiceWithoutValidationField,
ImageThumbnailFileInput, RangeWidgetOverride,
autor_label, autor_modal, models_with_gr_for_model)
from .models import AppConfig, CasaLegislativa

8
sapl/base/legacy.yaml

@ -0,0 +1,8 @@
TipoAutor:
descricao: des_tipo_autor
Autor:
nome: nom_autor
cargo: des_cargo
tipo: tip_autor
username: col_username

10
sapl/base/models.py

@ -1,18 +1,10 @@
from builtins import LookupError
from django.apps import apps
from django.contrib.auth.management import _get_all_permissions
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core import exceptions
from django.db import models, router
from django.db.utils import DEFAULT_DB_ALIAS
from django.utils.translation import string_concat
from django.db import models
from django.utils.translation import ugettext_lazy as _
from sapl.utils import UF, YES_NO_CHOICES, get_settings_auth_user_model
TIPO_DOCUMENTO_ADMINISTRATIVO = (('O', _('Ostensivo')),
('R', _('Restritivo')))

26
sapl/base/templatetags/common_tags.py

@ -3,7 +3,6 @@ from django import template
from sapl.base.models import AppConfig
from sapl.parlamentares.models import Filiacao
from sapl.utils import permissoes_adm
register = template.Library()
@ -87,26 +86,6 @@ def get_delete_perm(value, arg):
return perm.__contains__(nome_app + can_delete)
@register.filter
def get_doc_adm_template_perms(user):
app_config = AppConfig.objects.last()
if app_config:
if app_config.documentos_administrativos == 'O':
return True
return user.has_perms(permissoes_adm())
@register.filter
def ver_menu_sistema_perm(value):
u = value
if u.groups.filter(name='Operador Geral').exists() or u.is_superuser:
return True
else:
return False
@register.filter
def ultima_filiacao(value):
parlamentar = value
@ -120,11 +99,6 @@ def ultima_filiacao(value):
return None
@register.filter
def get_config_not_exists(user):
return not AppConfig.objects.exists()
@register.filter
def get_config_attr(attribute):
return AppConfig.attr(attribute)

3
sapl/base/templatetags/menus.py

@ -1,11 +1,10 @@
import yaml
from django import template
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
import yaml
from sapl.utils import sapl_logger
register = template.Library()

2
sapl/base/views.py

@ -13,8 +13,8 @@ from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.utils.translation import ugettext_lazy as _
from django.views.generic.base import TemplateView
from django_filters.views import FilterView
from sapl.base.forms import AutorForm, TipoAutorForm, AutorFormForAdmin
from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm
from sapl.base.models import Autor, TipoAutor
from sapl.crud.base import CrudAux
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa

28
sapl/compilacao/forms.py

@ -42,10 +42,18 @@ class TipoTaForm(ModelForm):
label=TipoTextoArticulado._meta.get_field(
'descricao').verbose_name)
participacao_social = forms.NullBooleanField(
participacao_social = forms.ChoiceField(
label=TipoTextoArticulado._meta.get_field(
'participacao_social').verbose_name,
widget=forms.Select(choices=YES_NO_CHOICES),
choices=YES_NO_CHOICES,
widget=forms.RadioSelect(),
required=True)
publicacao_func = forms.ChoiceField(
label=TipoTextoArticulado._meta.get_field(
'publicacao_func').verbose_name,
choices=YES_NO_CHOICES,
widget=forms.RadioSelect(),
required=True)
class Meta:
@ -54,21 +62,27 @@ class TipoTaForm(ModelForm):
'descricao',
'content_type',
'participacao_social',
'publicacao_func'
]
def __init__(self, *args, **kwargs):
row1 = to_row([
('sigla', 2),
('descricao', 4),
('content_type', 3),
('participacao_social', 3),
('sigla', 3),
('descricao', 5),
('content_type', 4),
])
row2 = to_row([
(InlineRadios('participacao_social'), 3),
(InlineRadios('publicacao_func'), 3),
])
self.helper = FormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(_('Identificação Básica'),
row1, css_class="col-md-12"))
row1, css_class="col-md-12"),
Fieldset(_('Funcionalidades'),
row2, css_class="col-md-12"))
super(TipoTaForm, self).__init__(*args, **kwargs)

19
sapl/compilacao/migrations/0059_auto_20161027_1323.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-10-27 13:23
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0058_auto_20161008_0143'),
]
operations = [
migrations.AlterModelOptions(
name='dispositivo',
options={'ordering': ['ta', 'ordem'], 'permissions': (('change_dispositivo_edicao_dinamica', 'Permissão de edição de dispositivos originais via editor dinâmico.'), ('change_dispositivo_edicao_avancada', 'Permissão de edição de dispositivos originais via formulários de edição avançada.'), ('change_dispositivo_registros_compilacao', 'Permissão de registro de compilação via editor dinâmico.'), ('change_dispositivo_notificacoes', 'Permissão de acesso às notificações de pendências.')), 'verbose_name': 'Dispositivo', 'verbose_name_plural': 'Dispositivos'},
),
]

19
sapl/compilacao/migrations/0060_auto_20161101_0913.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-01 09:13
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0059_auto_20161027_1323'),
]
operations = [
migrations.AlterModelOptions(
name='dispositivo',
options={'ordering': ['ta', 'ordem'], 'permissions': (('change_dispositivo_edicao_dinamica', 'Permissão de edição de dispositivos originais via editor dinâmico.'), ('change_dispositivo_edicao_avancada', 'Permissão de edição de dispositivos originais via formulários de edição avançada.'), ('change_dispositivo_registros_compilacao', 'Permissão de registro de compilação via editor dinâmico.'), ('view_dispositivo_notificacoes', 'Permissão de acesso às notificações de pendências.')), 'verbose_name': 'Dispositivo', 'verbose_name_plural': 'Dispositivos'},
),
]

21
sapl/compilacao/migrations/0061_auto_20161101_1025.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-01 10:25
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0060_auto_20161101_0913'),
]
operations = [
migrations.AlterField(
model_name='dispositivo',
name='ta',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dispositivos_set', to='compilacao.TextoArticulado', verbose_name='Texto Articulado'),
),
]

19
sapl/compilacao/migrations/0062_auto_20161101_1221.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-01 12:21
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0061_auto_20161101_1025'),
]
operations = [
migrations.AlterModelOptions(
name='dispositivo',
options={'ordering': ['ta', 'ordem'], 'permissions': (('change_dispositivo_edicao_dinamica', 'Permissão de edição de dispositivos originais via editor dinâmico.'), ('change_dispositivo_edicao_avancada', 'Permissão de edição de dispositivos originais via formulários de edição avançada.'), ('change_dispositivo_registros_compilacao', 'Permissão de registro de compilação via editor dinâmico.'), ('view_dispositivo_notificacoes', 'Permissão de acesso às notificações de pendências.'), ('change_dispositivo_de_vigencia_global', 'Permissão alteração global do dispositivo de vigência')), 'verbose_name': 'Dispositivo', 'verbose_name_plural': 'Dispositivos'},
),
]

20
sapl/compilacao/migrations/0063_tipotextoarticulado_publicacao_func.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-03 11:06
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0062_auto_20161101_1221'),
]
operations = [
migrations.AddField(
model_name='tipotextoarticulado',
name='publicacao_func',
field=models.NullBooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=True, verbose_name='Histórico de Publicação'),
),
]

46
sapl/compilacao/migrations/0064_auto_20161104_1420.py

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-04 14:20
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('compilacao', '0063_tipotextoarticulado_publicacao_func'),
]
operations = [
migrations.AlterModelOptions(
name='textoarticulado',
options={'ordering': ['-data', '-numero'], 'permissions': (('view_restricted_textoarticulado', 'Pode ver qualquer Texto Articulado'),), 'verbose_name': 'Texto Articulado', 'verbose_name_plural': 'Textos Articulados'},
),
migrations.AddField(
model_name='textoarticulado',
name='editable_only_by_owners',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=True, verbose_name='Editável apenas pelos donos do Texto Articulado'),
),
migrations.AddField(
model_name='textoarticulado',
name='editing_locked',
field=models.BooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=True, verbose_name='Texto Articulado em Edição'),
),
migrations.AddField(
model_name='textoarticulado',
name='owners',
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='Donos do Texto Articulado'),
),
migrations.AddField(
model_name='textoarticulado',
name='visibilidade',
field=models.IntegerField(choices=[(99, 'Privado'), (79, 'Restrito'), (89, 'Em Edição'), (0, 'Público')], default=99, verbose_name='Visibilidade'),
),
migrations.AlterField(
model_name='tipotextoarticulado',
name='publicacao_func',
field=models.NullBooleanField(choices=[(True, 'Sim'), (False, 'Não')], default=False, verbose_name='Histórico de Publicação'),
),
]

20
sapl/compilacao/migrations/0065_auto_20161107_1024.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-07 10:24
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0064_auto_20161104_1420'),
]
operations = [
migrations.RenameField(
model_name='textoarticulado',
old_name='visibilidade',
new_name='privacidade'
),
]

20
sapl/compilacao/migrations/0066_auto_20161107_1028.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-07 10:28
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0065_auto_20161107_1024'),
]
operations = [
migrations.AlterField(
model_name='textoarticulado',
name='privacidade',
field=models.IntegerField(choices=[(99, 'Privado'), (79, 'Restrito'), (89, 'Em Edição'), (0, 'Público')], default=99, verbose_name='Privacidade'),
),
]

19
sapl/compilacao/migrations/0067_auto_20161107_1351.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-07 13:51
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0066_auto_20161107_1028'),
]
operations = [
migrations.AlterModelOptions(
name='textoarticulado',
options={'ordering': ['-data', '-numero'], 'permissions': (('view_restricted_textoarticulado', 'Pode ver qualquer Texto Articulado'), ('lock_unlock_textoarticulado', 'Pode bloquear/desbloquear edição de Texto Articulado')), 'verbose_name': 'Texto Articulado', 'verbose_name_plural': 'Textos Articulados'},
),
]

19
sapl/compilacao/migrations/0068_auto_20161107_1546.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-07 15:46
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0067_auto_20161107_1351'),
]
operations = [
migrations.AlterModelOptions(
name='dispositivo',
options={'ordering': ['ta', 'ordem'], 'permissions': (('change_dispositivo_edicao_dinamica', 'Permissão de edição de dispositivos originais via editor dinâmico.'), ('change_your_dispositivo_edicao_dinamica', 'Permissão de edição de dispositivos originais via editor dinâmico desde que seja dono.'), ('change_dispositivo_edicao_avancada', 'Permissão de edição de dispositivos originais via formulários de edição avançada.'), ('change_dispositivo_registros_compilacao', 'Permissão de registro de compilação via editor dinâmico.'), ('view_dispositivo_notificacoes', 'Permissão de acesso às notificações de pendências.'), ('change_dispositivo_de_vigencia_global', 'Permissão alteração global do dispositivo de vigência')), 'verbose_name': 'Dispositivo', 'verbose_name_plural': 'Dispositivos'},
),
]

20
sapl/compilacao/migrations/0069_auto_20161107_1932.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-07 19:32
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('compilacao', '0068_auto_20161107_1546'),
]
operations = [
migrations.AlterField(
model_name='textoarticulado',
name='privacidade',
field=models.IntegerField(choices=[(99, 'Privado'), (79, 'Imotável Restrito'), (69, 'Imutável Público'), (89, 'Em Edição'), (0, 'Público')], default=99, verbose_name='Privacidade'),
),
]

169
sapl/compilacao/models.py

@ -1,8 +1,10 @@
from django.contrib import messages
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import F, Q
from django.db.models.aggregates import Max
from django.http.response import Http404
from django.template import defaultfilters
from django.utils.translation import ugettext_lazy as _
@ -81,6 +83,12 @@ class TipoTextoArticulado(models.Model):
choices=YES_NO_CHOICES,
verbose_name=_('Participação Social'))
publicacao_func = models.NullBooleanField(
default=False,
blank=True, null=True,
choices=YES_NO_CHOICES,
verbose_name=_('Histórico de Publicação'))
class Meta:
verbose_name = _('Tipo de Texto Articulado')
verbose_name_plural = _('Tipos de Texto Articulados')
@ -95,6 +103,25 @@ PARTICIPACAO_SOCIAL_CHOICES = [
(False, _('Não'))]
STATUS_TA_PRIVATE = 99 # Só os donos podem ver
STATUS_TA_EDITION = 89
STATUS_TA_IMMUTABLE_RESTRICT = 79
STATUS_TA_IMMUTABLE_PUBLIC = 69
STATUS_TA_PUBLIC = 0
PRIVACIDADE_STATUS = (
(STATUS_TA_PRIVATE, _('Privado')), # só dono ve e edita
# só quem tem permissão para ver
(STATUS_TA_IMMUTABLE_RESTRICT, _('Imotável Restrito')),
# só quem tem permissão para ver
(STATUS_TA_IMMUTABLE_PUBLIC, _('Imutável Público')),
(STATUS_TA_EDITION, _('Em Edição')), # só quem tem permissão para editar
(STATUS_TA_PUBLIC, _('Público')), # visualização pública
)
class TextoArticulado(TimestampedMixin):
data = models.DateField(blank=True, null=True, verbose_name=_('Data'))
ementa = models.TextField(verbose_name=_('Ementa'))
@ -118,10 +145,35 @@ class TextoArticulado(TimestampedMixin):
blank=True, null=True, default=None)
content_object = GenericForeignKey('content_type', 'object_id')
owners = models.ManyToManyField(
get_settings_auth_user_model(),
blank=True, verbose_name=_('Donos do Texto Articulado'))
editable_only_by_owners = models.BooleanField(
choices=YES_NO_CHOICES,
default=True,
verbose_name=_('Editável apenas pelos donos do Texto Articulado'))
editing_locked = models.BooleanField(
choices=YES_NO_CHOICES,
default=True,
verbose_name=_('Texto Articulado em Edição'))
privacidade = models.IntegerField(
_('Privacidade'),
choices=PRIVACIDADE_STATUS,
default=STATUS_TA_PRIVATE)
class Meta:
verbose_name = _('Texto Articulado')
verbose_name_plural = _('Textos Articulados')
ordering = ['-data', '-numero']
permissions = (
('view_restricted_textoarticulado',
_('Pode ver qualquer Texto Articulado')),
('lock_unlock_textoarticulado',
_('Pode bloquear/desbloquear edição de Texto Articulado')),
)
def __str__(self):
if self.content_object:
@ -132,6 +184,102 @@ class TextoArticulado(TimestampedMixin):
'numero': self.numero,
'data': defaultfilters.date(self.data, "d \d\e F \d\e Y")}
def hash(self):
from django.core import serializers
import hashlib
data = serializers.serialize(
"xml", Dispositivo.objects.filter(
Q(ta_id=self.id) | Q(ta_publicado_id=self.id)))
md5 = hashlib.md5()
md5.update(data.encode('utf-8'))
return md5.hexdigest()
def can_use_dynamic_editing(self, user):
return not self.editing_locked and\
(not self.editable_only_by_owners and
user.has_perm(
'compilacao.change_dispositivo_edicao_dinamica') or
self.editable_only_by_owners and user in self.owners.all() and
user.has_perm(
'compilacao.change_your_dispositivo_edicao_dinamica'))
def has_view_permission(self, request):
if self.privacidade in (STATUS_TA_IMMUTABLE_PUBLIC, STATUS_TA_PUBLIC):
return True
if request.user in self.owners.all():
return True
if self.privacidade == STATUS_TA_IMMUTABLE_RESTRICT and\
request.user.has_perm(
'compilacao.view_restricted_textoarticulado'):
return True
elif self.privacidade == STATUS_TA_EDITION:
if request.user.has_perm(
'compilacao.change_dispositivo_edicao_dinamica'):
return True
else:
messages.error(request, _(
'Este Texto Articulado está em edição.'))
elif self.privacidade == STATUS_TA_PRIVATE:
if request.user in self.owners.all():
return True
else:
raise Http404()
return False
def has_edit_permission(self, request):
if self.privacidade == STATUS_TA_PRIVATE:
if request.user not in self.owners.all():
raise Http404()
if not self.can_use_dynamic_editing(request.user):
messages.error(request, _(
'Usuário sem permissão para edição.'))
return False
else:
return True
if self.privacidade == STATUS_TA_IMMUTABLE_RESTRICT:
messages.error(request, _(
'A edição deste Texto Articulado está bloqueada. '
'Este documento é imutável e de acesso é restrito.'))
return False
if self.privacidade == STATUS_TA_IMMUTABLE_PUBLIC:
messages.error(request, _(
'A edição deste Texto Articulado está bloqueada. '
'Este documento é imutável.'))
return False
if self.editing_locked and\
self.privacidade in (STATUS_TA_PUBLIC, STATUS_TA_EDITION) and\
not request.user.has_perm(
'compilacao.lock_unlock_textoarticulado'):
messages.error(request, _(
'A edição deste Texto Articulado está bloqueada. '
'É necessário acessar com usuário que possui '
'permissão de desbloqueio.'))
return False
if not request.user.has_perm(
'compilacao.change_dispositivo_edicao_dinamica'):
messages.error(request, _(
'Usuário sem permissão para edição.'))
return False
if self.editable_only_by_owners and\
request.user not in self.owners.all():
messages.error(request, _(
'Apenas usuários donos do Texto Articulado podem editá-lo.'))
return False
return True
def reagrupar_ordem_de_dispositivos(self):
dpts = Dispositivo.objects.filter(ta=self)
@ -635,7 +783,7 @@ class Dispositivo(BaseModel, TimestampedMixin):
ta = models.ForeignKey(
TextoArticulado,
on_delete=models.PROTECT,
on_delete=models.CASCADE,
related_name='dispositivos_set',
verbose_name=_('Texto Articulado'))
ta_publicado = models.ForeignKey(
@ -693,6 +841,23 @@ class Dispositivo(BaseModel, TimestampedMixin):
'ta_publicado',
'publicacao',),
)
permissions = (
('change_dispositivo_edicao_dinamica', _(
'Permissão de edição de dispositivos originais '
'via editor dinâmico.')),
('change_your_dispositivo_edicao_dinamica', _(
'Permissão de edição de dispositivos originais '
'via editor dinâmico desde que seja dono.')),
('change_dispositivo_edicao_avancada', _(
'Permissão de edição de dispositivos originais '
'via formulários de edição avançada.')),
('change_dispositivo_registros_compilacao', _(
'Permissão de registro de compilação via editor dinâmico.')),
('view_dispositivo_notificacoes', _(
'Permissão de acesso às notificações de pendências.')),
('change_dispositivo_de_vigencia_global', _(
'Permissão alteração global do dispositivo de vigência')),
)
def __str__(self):
return '%(rotulo)s' % {
@ -1248,7 +1413,7 @@ class Dispositivo(BaseModel, TimestampedMixin):
disps[0].get_numero_completo())
# dispositivo.transform_in_next()
else:
dispositivo.set_numero_completo([1, 0, 0, 0, 0, 0, ])
dispositivo.set_numero_completo([0, 0, 0, 0, 0, 0, ])
else:
if ';' in tipo_base.rotulo_prefixo_texto:

6
sapl/compilacao/templatetags/compilacao_filters.py

@ -211,7 +211,7 @@ def heranca(request, d, ignore_ultimo=0, ignore_primeiro=0):
ta_id = str(d.ta_id)
d_pk = str(d.pk)
if ta_id not in ta_dpts_parents or d_pk not in ta_dpts_parents[ta_id]:
print('recarregando estrutura temporaria de heranças')
#print('recarregando estrutura temporaria de heranças')
dpts_parents = {}
ta_dpts_parents[ta_id] = dpts_parents
update_dispositivos_parents(dpts_parents, ta_id)
@ -295,3 +295,7 @@ def urldetail_content_type(obj):
@register.filter
def list(obj):
return [obj, ]
@register.filter
def can_use_dynamic_editing(texto_articulado, user):
return texto_articulado.can_use_dynamic_editing(user)

382
sapl/compilacao/views.py

@ -1,16 +1,16 @@
import logging
import sys
from collections import OrderedDict
from datetime import datetime, timedelta
import logging
import sys
from braces.views import FormMessagesMixin
from django import forms
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.core.signing import Signer
from django.core.urlresolvers import reverse_lazy
from django.core.urlresolvers import reverse_lazy, reverse
from django.db import connection, transaction
from django.db.models import Q
from django.db.utils import IntegrityError
@ -18,10 +18,9 @@ from django.http.response import (HttpResponse, HttpResponseRedirect,
JsonResponse)
from django.shortcuts import get_object_or_404, redirect
from django.utils.dateparse import parse_date
from django.utils.decorators import method_decorator
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _
from django.views.generic.base import TemplateView
from django.views.generic.detail import DetailView
from django.views.generic.edit import (CreateView, DeleteView, FormView,
@ -43,12 +42,14 @@ from sapl.compilacao.models import (Dispositivo, Nota,
Publicacao, TextoArticulado,
TipoDispositivo, TipoNota, TipoPublicacao,
TipoTextoArticulado, TipoVide,
VeiculoPublicacao, Vide)
VeiculoPublicacao, Vide, STATUS_TA_EDITION,
STATUS_TA_PRIVATE, STATUS_TA_PUBLIC)
from sapl.compilacao.utils import (DISPOSITIVO_SELECT_RELATED,
DISPOSITIVO_SELECT_RELATED_EDIT)
from sapl.crud.base import Crud, CrudListView, make_pagination
from sapl.settings import BASE_DIR
TipoNotaCrud = Crud.build(TipoNota, 'tipo_nota')
TipoVideCrud = Crud.build(TipoVide, 'tipo_vide')
TipoPublicacaoCrud = Crud.build(TipoPublicacao, 'tipo_publicacao')
@ -87,12 +88,31 @@ def choice_models_in_extenal_views():
return result
def choice_model_type_foreignkey_in_extenal_views(id_tipo_ta=None):
yield None, '-------------'
if not id_tipo_ta:
return
tipo_ta = TipoTextoArticulado.objects.get(pk=id_tipo_ta)
integrations_view_names = get_integrations_view_names()
for item in integrations_view_names:
if hasattr(item, 'model_type_foreignkey'):
if (tipo_ta.content_type.model == item.model.__name__.lower() and
tipo_ta.content_type.app_label ==
item.model._meta.app_label):
for i in item.model_type_foreignkey.objects.all():
yield i.pk, i
class IntegracaoTaView(TemplateView):
def get_redirect_deactivated(self):
messages.error(
self.request,
_('O modulo de Textos Articulados está desativado.'))
_('O modulo de Textos Articulados para %s está desativado.'
) % self.model._meta.verbose_name_plural)
return redirect('/')
def get(self, request, *args, **kwargs):
@ -100,6 +120,16 @@ class IntegracaoTaView(TemplateView):
try:
if settings.DEBUG or not TipoDispositivo.objects.exists():
self.import_pattern()
if hasattr(self, 'map_funcs'):
tipo_ta = TipoTextoArticulado.objects.get(
content_type=ContentType.objects.get_for_model(
self.model))
for key, value in self.map_funcs.items():
setattr(tipo_ta, key, value)
tipo_ta.save()
except Exception as e:
logger.error(
string_concat(
@ -108,6 +138,26 @@ class IntegracaoTaView(TemplateView):
str(e)))
return self.get_redirect_deactivated()
assert hasattr(self, 'map_fields'), _(
"""
O mapa dos campos não foi definido. Ele deve seguir a estrutura
de chaves abaixo:
map_fields = {
'data': 'data',
'ementa': 'ementa',
'observacao': 'observacao',
'numero': 'numero',
'ano': 'ano',
}
Caso o model de integração não possua um dos campos,
implemente, ou passe `None` para as chaves que são fixas.
""")
map_fields = self.map_fields
ta_values = getattr(self, 'ta_values', {})
item = get_object_or_404(self.model, pk=kwargs['pk'])
related_object_type = ContentType.objects.get_for_model(item)
@ -118,81 +168,71 @@ class IntegracaoTaView(TemplateView):
tipo_ta = TipoTextoArticulado.objects.filter(
content_type=related_object_type)
if not ta.exists():
ta_exists = bool(ta.exists())
if not ta_exists:
ta = TextoArticulado()
tipo_ta = TipoTextoArticulado.objects.filter(
content_type=related_object_type)[:1]
if tipo_ta.exists():
ta.tipo_ta = tipo_ta[0]
ta.content_object = item
else:
ta = ta[0]
if hasattr(item, 'ementa') and item.ementa:
ta.ementa = item.ementa
else:
ta.ementa = _('Integração com %s sem ementa.') % item
ta.privacidade = ta_values.get('privacidade', STATUS_TA_EDITION)
if hasattr(item, 'observacao') and item.observacao:
ta.observacao = item.observacao
else:
ta.observacao = _('Integração com %s sem observacao.') % item
ta.editing_locked = ta_values.get('editing_locked', False)
ta.editable_only_by_owners = ta_values.get(
'editable_only_by_owners', False)
if hasattr(item, 'numero') and item.numero:
ta.numero = item.numero
else:
ta.numero = int('%s%s%s' % (
int(datetime.now().year),
int(datetime.now().month),
int(datetime.now().day)))
if hasattr(item, 'ano') and item.ano:
ta.ano = item.ano
else:
ta.ano = datetime.now().year
ta = ta[0]
if hasattr(item, 'data_apresentacao'):
ta.data = item.data_apresentacao
elif hasattr(item, 'data'):
ta.data = item.data
else:
ta.data = datetime.now()
if not ta.data:
ta.data = getattr(item, map_fields['data']
if map_fields['data'] else 'xxx',
datetime.now())
if not ta.data:
ta.data = datetime.now()
ta.save()
ta.ementa = getattr(
item, map_fields['ementa']
if map_fields['ementa'] else 'xxx', _(
'Integração com %s sem ementa.') % item)
return redirect(to=reverse_lazy('sapl.compilacao:ta_text',
kwargs={'ta_id': ta.pk}))
ta.observacao = getattr(
item, map_fields['observacao']
if map_fields['observacao'] else 'xxx', '')
"""msg = messages.error if not request.user.is_anonymous(
) else messages.info
ta.numero = getattr(
item, map_fields['numero']
if map_fields['numero'] else 'xxx', int('%s%s%s' % (
int(datetime.now().year),
int(datetime.now().month),
int(datetime.now().day))))
msg(request,
_('A funcionalidade de Textos Articulados está desativada.'))
ta.ano = getattr(item, map_fields['ano']
if map_fields['ano'] else 'xxx', datetime.now().year)
if not request.user.is_anonymous():
msg(
request,
_('Para ativá-la, os Tipos de Textos devem ser criados.'))
ta.save()
msg(request,
_('Sua tela foi redirecionada para a tela de '
'cadastro de Textos Articulados.'))
if not ta_exists:
if ta.editable_only_by_owners and\
not self.request.user.is_anonymous():
ta.owners.add(self.request.user)
return redirect(to=reverse_lazy('sapl.compilacao:tipo_ta_list',
kwargs={}))
if not Dispositivo.objects.filter(ta_id=ta.pk).exists() and\
ta.can_use_dynamic_editing(self.request.user):
return redirect(to=reverse_lazy('sapl.compilacao:ta_text_edit',
kwargs={'ta_id': ta.pk}))
else:
return redirect(to=reverse_lazy(
'%s:%s_detail' % (
item._meta.app_config.name, item._meta.model_name),
kwargs={'pk': item.pk}))"""
return redirect(to=reverse_lazy('sapl.compilacao:ta_text',
kwargs={'ta_id': ta.pk}))
def import_pattern(self):
from unipath import Path
compilacao_app = Path(__file__).ancestor(1)
print(compilacao_app)
# print(compilacao_app)
with open(compilacao_app + '/compilacao_data_tables.sql', 'r') as f:
lines = f.readlines()
lines = [line.rstrip('\n') for line in lines]
@ -251,7 +291,20 @@ class IntegracaoTaView(TemplateView):
abstract = True
class CompMixin:
class CompMixin(PermissionRequiredMixin):
permission_required = []
def has_permission(self):
perms = self.get_permission_required()
# Torna a view pública se não possuir conteudo
# no atributo permission_required
return self.request.user.has_perms(perms) if len(perms) else True
@property
def ta(self):
ta = TextoArticulado.objects.get(
pk=self.kwargs.get('ta_id', self.kwargs.get('pk', 0)))
return ta
def get_context_data(self, **kwargs):
context = super(CompMixin, self).get_context_data(**kwargs)
@ -271,6 +324,7 @@ class TipoTaListView(CompMixin, ListView):
model = TipoTextoArticulado
paginate_by = 10
verbose_name = model._meta.verbose_name
permission_required = 'compilacao.list_tipotextoarticulado'
@property
def title(self):
@ -287,6 +341,7 @@ class TipoTaCreateView(CompMixin, FormMessagesMixin, CreateView):
template_name = "crud/form.html"
form_valid_message = _('Registro criado com sucesso!')
form_invalid_message = _('O registro não foi criado.')
permission_required = 'compilacao.add_tipotextoarticulado'
def get(self, request, *args, **kwargs):
self.object = None
@ -308,12 +363,14 @@ class TipoTaCreateView(CompMixin, FormMessagesMixin, CreateView):
class TipoTaDetailView(CompMixin, DetailView):
model = TipoTextoArticulado
permission_required = 'compilacao.detail_tipotextoarticulado'
class TipoTaUpdateView(CompMixin, UpdateView):
model = TipoTextoArticulado
form_class = TipoTaForm
template_name = "crud/form.html"
permission_required = 'compilacao.change_tipotextoarticulado'
def get(self, request, *args, **kwargs):
self.object = self.get_object()
@ -336,6 +393,7 @@ class TipoTaUpdateView(CompMixin, UpdateView):
class TipoTaDeleteView(CompMixin, DeleteView):
model = TipoTextoArticulado
template_name = "crud/confirm_delete.html"
permission_required = 'compilacao.delete_tipotextoarticulado'
@property
def detail_url(self):
@ -350,6 +408,7 @@ class TaListView(CompMixin, ListView):
model = TextoArticulado
paginate_by = 10
verbose_name = model._meta.verbose_name
permission_required = 'compilacao.list_textoarticulado'
@property
def title(self):
@ -367,10 +426,26 @@ class TaListView(CompMixin, ListView):
page_obj.number, paginator.num_pages)
return context
def get_queryset(self):
qs = ListView.get_queryset(self)
qs = qs.exclude(
~Q(owners=self.request.user.id),
privacidade=STATUS_TA_PRIVATE)
return qs
class TaDetailView(CompMixin, DetailView):
model = TextoArticulado
def has_permission(self):
self.object = self.ta
if self.object.has_view_permission(self.request):
return CompMixin.has_permission(self)
else:
return False
@property
def title(self):
if self.get_object().content_object:
@ -389,6 +464,7 @@ class TaCreateView(CompMixin, FormMessagesMixin, CreateView):
template_name = "crud/form.html"
form_valid_message = _('Registro criado com sucesso!')
form_invalid_message = _('O registro não foi criado.')
permission_required = 'compilacao.add_tipotextoarticulado'
def get_success_url(self):
return reverse_lazy('sapl.compilacao:ta_detail',
@ -403,6 +479,7 @@ class TaUpdateView(CompMixin, UpdateView):
model = TextoArticulado
form_class = TaForm
template_name = "crud/form.html"
permission_required = 'compilacao.change_textoarticulado'
def get(self, request, *args, **kwargs):
self.object = self.get_object()
@ -425,6 +502,7 @@ class TaUpdateView(CompMixin, UpdateView):
class TaDeleteView(CompMixin, DeleteView):
model = TextoArticulado
template_name = "crud/confirm_delete.html"
permission_required = 'compilacao.delete_textoarticulado'
@property
def detail_url(self):
@ -464,14 +542,11 @@ class NotaMixin(DispositivoSuccessUrlMixin):
return initial
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(NotaMixin, self).dispatch(*args, **kwargs)
class NotasCreateView(NotaMixin, CreateView):
template_name = 'compilacao/ajax_form.html'
form_class = NotaForm
permission_required = 'compilacao.add_nota'
def get(self, request, *args, **kwargs):
flag_action, modelo_nota = self.get_modelo_nota(request)
@ -506,6 +581,7 @@ class NotasEditView(NotaMixin, UpdateView):
model = Nota
template_name = 'compilacao/ajax_form.html'
form_class = NotaForm
permission_required = 'compilacao.change_nota'
def get(self, request, *args, **kwargs):
flag_action, modelo_nota = self.get_modelo_nota(request)
@ -517,6 +593,8 @@ class NotasEditView(NotaMixin, UpdateView):
class NotasDeleteView(NotaMixin, TemplateView):
permission_required = 'compilacao.delete_nota'
def get(self, request, *args, **kwargs):
nt = Nota.objects.get(pk=self.kwargs['pk'])
nt.delete()
@ -535,59 +613,28 @@ class VideMixin(DispositivoSuccessUrlMixin):
return initial
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(VideMixin, self).dispatch(*args, **kwargs)
def choice_model_type_foreignkey_in_extenal_views(id_tipo_ta=None):
yield None, '-------------'
if not id_tipo_ta:
return
tipo_ta = TipoTextoArticulado.objects.get(pk=id_tipo_ta)
integrations_view_names = get_integrations_view_names()
for item in integrations_view_names:
if hasattr(item, 'model_type_foreignkey'):
if (tipo_ta.content_type.model == item.model.__name__.lower() and
tipo_ta.content_type.app_label ==
item.model._meta.app_label):
for i in item.model_type_foreignkey.objects.all():
yield i.pk, i
class VideCreateView(VideMixin, CreateView):
model = Vide
template_name = 'compilacao/ajax_form.html'
form_class = VideForm
permission_required = 'compilacao.add_vide'
def get(self, request, *args, **kwargs):
self.object = None
form = self.get_form()
return self.render_to_response(self.get_context_data(form=form))
"""
def get_form_kwargs(self):
kwargs = super(VideCreateView, self).get_form_kwargs()
if 'choice_model_type_foreignkey_in_extenal_views' not in kwargs:
kwargs.update({
'choice_model_type_foreignkey_in_extenal_views':
choice_model_type_foreignkey_in_extenal_views
})
return kwargs"""
class VideEditView(VideMixin, UpdateView):
model = Vide
template_name = 'compilacao/ajax_form.html'
form_class = VideForm
permission_required = 'compilacao.change_vide'
class VideDeleteView(VideMixin, TemplateView):
permission_required = 'compilacao.delete_vide'
def get(self, request, *args, **kwargs):
vd = Vide.objects.get(pk=self.kwargs['pk'])
@ -595,9 +642,26 @@ class VideDeleteView(VideMixin, TemplateView):
return HttpResponseRedirect(self.get_success_url())
class PublicacaoListView(CompMixin, ListView):
class PublicacaoMixin(CompMixin):
def dispatch(self, request, *args, **kwargs):
ta = self.ta
if not ta.tipo_ta.publicacao_func:
messages.error(request, _(
'A funcionalidade de %s está desativada para %s.') % (
TipoTextoArticulado._meta.get_field(
'publicacao_func').verbose_name,
ta.tipo_ta.descricao))
return redirect(reverse('sapl.compilacao:ta_text',
kwargs={'ta_id': self.kwargs['ta_id']}))
return PermissionRequiredMixin.dispatch(self, request, *args, **kwargs)
class PublicacaoListView(PublicacaoMixin, ListView):
model = Publicacao
verbose_name = model._meta.verbose_name
permission_required = []
@property
def title(self):
@ -605,11 +669,6 @@ class PublicacaoListView(CompMixin, ListView):
self.model._meta.verbose_name_plural,
self.ta))
@property
def ta(self):
ta = TextoArticulado.objects.get(pk=self.kwargs['ta_id'])
return ta
@property
def create_url(self):
return reverse_lazy(
@ -626,12 +685,13 @@ class PublicacaoListView(CompMixin, ListView):
return context
class PublicacaoCreateView(CompMixin, FormMessagesMixin, CreateView):
class PublicacaoCreateView(PublicacaoMixin, FormMessagesMixin, CreateView):
model = Publicacao
form_class = PublicacaoForm
template_name = "crud/form.html"
form_valid_message = _('Registro criado com sucesso!')
form_invalid_message = _('O registro não foi criado.')
permission_required = 'compilacao.add_publicacao'
def get_success_url(self):
return reverse_lazy(
@ -650,14 +710,16 @@ class PublicacaoCreateView(CompMixin, FormMessagesMixin, CreateView):
return {'ta': self.kwargs['ta_id']}
class PublicacaoDetailView(CompMixin, DetailView):
class PublicacaoDetailView(PublicacaoMixin, DetailView):
model = Publicacao
permission_required = 'compilacao.detail_publicacao'
class PublicacaoUpdateView(CompMixin, UpdateView):
class PublicacaoUpdateView(PublicacaoMixin, UpdateView):
model = Publicacao
form_class = PublicacaoForm
template_name = "crud/form.html"
permission_required = 'compilacao.change_publicacao'
def get(self, request, *args, **kwargs):
self.object = self.get_object()
@ -678,9 +740,10 @@ class PublicacaoUpdateView(CompMixin, UpdateView):
return self.get_success_url()
class PublicacaoDeleteView(CompMixin, DeleteView):
class PublicacaoDeleteView(PublicacaoMixin, DeleteView):
model = Publicacao
template_name = "crud/confirm_delete.html"
permission_required = 'compilacao.delete_publicacao'
@property
def detail_url(self):
@ -708,44 +771,14 @@ class TextView(CompMixin, ListView):
fim_vigencia = None
ta_vigencia = None
def get(self, request, *args, **kwargs):
ta = TextoArticulado.objects.get(pk=self.kwargs['ta_id'])
self.object = ta
if ta.content_object:
item = ta.content_object
self.object = item
if hasattr(item, 'ementa') and item.ementa:
ta.ementa = item.ementa
else:
ta.ementa = _('Integração com %s sem ementa.') % item
if hasattr(item, 'observacao') and item.observacao:
ta.observacao = item.observacao
else:
ta.observacao = _('Integração com %s sem observacao.') % item
if hasattr(item, 'numero') and item.numero:
ta.numero = item.numero
else:
ta.numero = int('%s%s%s' % (
int(datetime.now().year),
int(datetime.now().month),
int(datetime.now().day)))
def has_permission(self):
self.object = self.ta
return self.object.has_view_permission(self.request)
if hasattr(item, 'ano') and item.ano:
ta.ano = item.ano
else:
ta.ano = datetime.now().year
if hasattr(item, 'data_apresentacao'):
ta.data = item.data_apresentacao
elif hasattr(item, 'data'):
ta.data = item.data
else:
ta.data = datetime.now()
ta.save()
return super(TextView, self).get(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
if 'print' in request.GET:
self.template_name = 'compilacao/text_list__print_version.html'
return ListView.get(self, request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(TextView, self).get_context_data(**kwargs)
@ -936,9 +969,45 @@ class DispositivoView(TextView):
return itens
class TextEditView(TemplateView):
class TextEditView(CompMixin, TemplateView):
template_name = 'compilacao/text_edit.html'
def has_permission(self):
self.object = self.ta
return self.object.has_edit_permission(self.request)
def get(self, request, *args, **kwargs):
if self.object.editing_locked:
if 'unlock' not in request.GET:
messages.error(
request, _(
'A edição deste Texto Articulado está bloqueada.'))
return redirect(to=reverse_lazy(
'sapl.compilacao:ta_text', kwargs={
'ta_id': self.object.id}))
else:
# TODO - implementar logging de ação de usuário
self.object.editing_locked = False
self.object.privacidade = STATUS_TA_EDITION
self.object.save()
messages.success(request, _(
'Texto Articulado desbloqueado com sucesso.'))
else:
if 'lock' in request.GET:
# TODO - implementar logging de ação de usuário
self.object.editing_locked = True
self.object.privacidade = STATUS_TA_PUBLIC
self.object.save()
messages.success(request, _(
'Texto Articulado bloqueado com sucesso.'))
return redirect(to=reverse_lazy(
'sapl.compilacao:ta_text', kwargs={
'ta_id': self.object.id}))
return TemplateView.get(self, request, *args, **kwargs)
def get_context_data(self, **kwargs):
dispositivo_id = int(self.kwargs['dispositivo_id']) \
if 'dispositivo_id' in self.kwargs else 0
@ -946,7 +1015,7 @@ class TextEditView(TemplateView):
if dispositivo_id:
self.object = Dispositivo.objects.get(pk=dispositivo_id)
context = super(TextEditView, self).get_context_data(**kwargs)
context = super(TemplateView, self).get_context_data(**kwargs)
if not dispositivo_id:
ta = TextoArticulado.objects.get(pk=self.kwargs['ta_id'])
@ -1721,6 +1790,12 @@ class ActionDispositivoCreateMixin(ActionsCommonsMixin):
for td in otds:
if td.dispositivo_de_alteracao:
if not self.request.user.has_perm(
'compilacao.'
'change_dispositivo_registros_compilacao'):
continue
if paradentro and not td.permitido_inserir_in(
tipb,
include_relative_autos=True,
@ -2417,11 +2492,10 @@ class ActionsEditMixin(ActionDragAndMoveDispositivoAlteradoMixin,
class DispositivoDinamicEditView(
CompMixin, ActionsEditMixin, TextEditView, UpdateView):
ActionsEditMixin, TextEditView, UpdateView):
template_name = 'compilacao/text_edit_bloco.html'
model = Dispositivo
form_class = DispositivoEdicaoBasicaForm
contador = -1
def get_initial(self):
initial = UpdateView.get_initial(self)
@ -2615,7 +2689,6 @@ class DispositivoSearchFragmentFormView(ListView):
def get_queryset(self):
try:
n = 10
if 'max_results' in self.request.GET:
n = int(self.request.GET['max_results'])
@ -2799,6 +2872,8 @@ class DispositivoEdicaoBasicaView(CompMixin, FormMessagesMixin, UpdateView):
form_invalid_message = _('Houve erro em registrar '
'as alterações no Dispositivo')
permission_required = 'compilacao.change_dispositivo_edicao_avancada'
@property
def cancel_url(self):
return reverse_lazy(
@ -2871,6 +2946,8 @@ class DispositivoEdicaoVigenciaView(CompMixin, FormMessagesMixin, UpdateView):
form_invalid_message = _('Houve erro em registrar '
'as alterações no Dispositivo')
permission_required = 'compilacao.change_dispositivo_edicao_avancada'
@property
def cancel_url(self):
return reverse_lazy(
@ -2894,6 +2971,9 @@ class DispositivoDefinidorVigenciaView(CompMixin, FormMessagesMixin, FormView):
form_invalid_message = _('Houve erro em registrar '
'as alterações no Dispositivo')
permission_required = ('compilacao.change_dispositivo_edicao_avancada',
'compilacao.change_dispositivo_de_vigencia_global')
def get_form_kwargs(self):
kwargs = FormView.get_form_kwargs(self)
kwargs.update({
@ -2951,6 +3031,8 @@ class DispositivoEdicaoAlteracaoView(CompMixin, FormMessagesMixin, UpdateView):
form_invalid_message = _('Houve erro em registrar '
'as alterações no Dispositivo')
permission_required = 'compilacao.change_dispositivo_registros_compilacao'
@property
def cancel_url(self):
return reverse_lazy(
@ -2983,6 +3065,8 @@ class TextNotificacoesView(CompMixin, ListView, FormView):
template_name = 'compilacao/text_notificacoes.html'
form_class = TextNotificacoesForm
permission_required = 'compilacao.view_dispositivo_notificacoes'
def get(self, request, *args, **kwargs):
self.object = TextoArticulado.objects.get(pk=self.kwargs['ta_id'])
return super(TextNotificacoesView, self).get(request, *args, **kwargs)

3
sapl/crispy_layout_mixin.py

@ -91,7 +91,8 @@ def get_field_display(obj, fieldname):
else:
display = ''
elif 'ManyRelatedManager' in str(type(value))\
or 'RelatedManager' in str(type(value)):
or 'RelatedManager' in str(type(value))\
or 'GenericRelatedObjectManager' in str(type(value)):
display = '<ul>'
for v in value.all():
display += '<li>%s</li>' % str(v)

10
sapl/crud/base.py

@ -24,6 +24,8 @@ from django.views.generic.base import ContextMixin
from django.views.generic.list import MultipleObjectMixin
from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display
from sapl.rules.map_rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL,
RP_LIST)
from sapl.settings import BASE_DIR
from sapl.utils import normalize
@ -33,10 +35,6 @@ logger = logging.getLogger(BASE_DIR.name)
ACTION_LIST, ACTION_CREATE, ACTION_DETAIL, ACTION_UPDATE, ACTION_DELETE = \
'list', 'create', 'detail', 'update', 'delete'
# RP - Radical das permissões para "..."
RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE =\
'.list_', '.detail_', '.add_', '.change_', '.delete_',
def _form_invalid_message(msg):
return '%s %s' % (_('Formulário inválido.'), msg)
@ -1404,10 +1402,6 @@ class CrudBaseForListAndDetailExternalAppView(MasterDetailCrud):
class BaseMixin(Crud.PublicMixin, MasterDetailCrud.BaseMixin):
@classmethod
def url_name(cls, suffix):
return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix)
def resolve_url(self, suffix, args=None):
obj = self.crud if hasattr(self, 'crud') else self

140
sapl/legacy/migration.py

@ -4,26 +4,31 @@ import pkg_resources
import yaml
from django.apps import apps
from django.apps.config import AppConfig
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.db import connections, models
from django.db.models import CharField, TextField
from django.db.models import CharField, TextField, ProtectedError
from django.db.models.base import ModelBase
from model_mommy import mommy
from model_mommy.mommy import foreign_key_required, make
from sapl.base.models import ProblemaMigracao
from sapl.base.models import Autor, ProblemaMigracao, TipoAutor
from sapl.comissoes.models import Composicao, Participacao
from sapl.materia.models import StatusTramitacao, TipoProposicao, Tramitacao
from sapl.norma.models import AssuntoNormaRelationship, NormaJuridica
from sapl.materia.models import (Proposicao, StatusTramitacao, TipoDocumento,
TipoMateriaLegislativa, TipoProposicao,
Tramitacao)
from sapl.norma.models import AssuntoNorma, NormaJuridica
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import StatusTramitacaoAdministrativo
from sapl.sessao.models import OrdemDia, SessaoPlenaria
from sapl.sessao.models import ExpedienteMateria, OrdemDia, SessaoPlenaria
from sapl.utils import normalize
# BASE ######################################################################
# apps to be migrated, in app dependency order (very important)
appconfs = [apps.get_app_config(n) for n in [
'parlamentares',
'comissoes',
'base',
'materia',
'norma',
'sessao',
@ -31,6 +36,7 @@ appconfs = [apps.get_app_config(n) for n in [
'protocoloadm', ]]
unique_constraints = []
one_to_one_constraints = []
name_sets = [set(m.__name__ for m in ac.get_models()) for ac in appconfs]
@ -118,6 +124,9 @@ def get_fk_related(field, value, label=None):
else:
value = None
else:
if field.model._meta.label == 'sessao.RegistroVotacao' and \
field.name == 'ordem':
return value
value = make_stub(field.related_model, value)
descricao = 'stub criado para entrada orfã!'
warn(msg + ' => ' + descricao)
@ -155,18 +164,33 @@ def delete_constraints(model):
cursor = exec_sql("SELECT conname FROM pg_constraint WHERE conrelid = "
"(SELECT oid FROM pg_class WHERE relname LIKE "
"'%s') and contype = 'u';" % (table))
result = cursor.fetchone()
result = ()
result = cursor.fetchall()
# se existir um resultado, unique constraint será deletado
if result:
warn('Excluindo unique constraint de nome %s' % result)
args = model._meta.unique_together[0]
args_list = list(args)
unique_constraints.append([table, result[0], args_list, model])
for r in result:
if r[0].endswith('key'):
words_list = r[0].split('_')
one_to_one_constraints.append([table, r[0], words_list, model])
else:
args = None
args_list = []
if model._meta.unique_together:
args = model._meta.unique_together[0]
args_list = list(args)
unique_constraints.append([table, r[0], args_list, model])
warn('Excluindo unique constraint de nome %s' % r[0])
exec_sql("ALTER TABLE %s DROP CONSTRAINT %s;" %
(table, result[0]))
(table, r[0]))
def recreate_constraints():
if one_to_one_constraints:
for constraint in one_to_one_constraints:
table, name, args, model = constraint
args_string = ''
args_string = "(" + "_".join(map(str, args[2:-1])) + ")"
exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" %
(table, name, args_string))
if unique_constraints:
for constraint in unique_constraints:
table, name, args, model = constraint
@ -178,6 +202,7 @@ def recreate_constraints():
args_string += "(" + ', '.join(map(str, args)) + ")"
exec_sql("ALTER TABLE %s ADD CONSTRAINT %s UNIQUE %s;" %
(table, name, args_string))
one_to_one_constraints.clear()
unique_constraints.clear()
@ -272,21 +297,58 @@ class DataMigrator:
self.data_mudada.setdefault('nome_campo', []).\
append(field.name)
if field_type == 'CharField' or field_type == 'TextField':
if value is None:
if value is None or value == 'None':
value = ''
if field.model._meta.label == 'sessao.RegistroVotacao' and \
field.name == 'ordem' and \
not isinstance(value, OrdemDia):
try:
new_value = ExpedienteMateria.objects.get(pk=value)
setattr(new, 'expediente', new_value)
setattr(new, field.name, None)
continue
except ObjectDoesNotExist:
msg = 'FK [%s] não encontrada para valor %s ' \
'(em %s %s)' % (
field.name, value,
field.model.__name__, label or '---')
value = make_stub(field.related_model, value)
descricao = 'stub criado para entrada orfã!'
warn(msg + ' => ' + descricao)
save_relation(value, [field.name], msg, descricao,
eh_stub=True)
setattr(new, field.name, value)
elif field.model.__name__ == 'TipoAutor' and \
field.name == 'content_type':
try:
value = field.related_model.objects.get(
model=normalize(new.descricao.lower()).replace(' ',
''))
except ObjectDoesNotExist:
value = None
setattr(new, field.name, value)
def migrate(self, obj=appconfs):
# warning: model/app migration order is of utmost importance
self.to_delete = []
ProblemaMigracao.objects.all().delete()
User.objects.all().delete()
info('Começando migração: %s...' % obj)
self._do_migrate(obj)
# exclude logically deleted in legacy base
info('Deletando models com ind_excluido...')
for obj in self.to_delete:
obj.delete()
try:
obj.delete()
except ProtectedError:
msg = 'A entrada de PK %s da model %s não pode ser excluida' %\
(obj.pk, obj._meta.model_name)
descricao = 'Um ou mais objetos protegidos '
warn(msg + ' => ' + descricao)
save_relation(obj=obj, problema=msg,
descricao=descricao, eh_stub=False)
info('Deletando stubs desnecessários...')
while self.delete_stubs():
pass
@ -299,7 +361,13 @@ class DataMigrator:
if model in self.field_renames)
self._do_migrate(models_to_migrate)
elif isinstance(obj, ModelBase):
self.migrate_model(obj)
# A migração vai pular TipoProposicao e só vai migrar essa model
# antes de migrar Proposicao. Isso deve acontecer por causa da
# GenericRelation existente em TipoProposicao.
if not obj.__name__ == 'TipoProposicao':
if obj.__name__ == 'Proposicao':
self.migrate_model(TipoProposicao)
self.migrate_model(obj)
elif hasattr(obj, '__iter__'):
for item in obj:
self._do_migrate(item)
@ -316,7 +384,11 @@ class DataMigrator:
# Clear all model entries
# They may have been created in a previous migration attempt
model.objects.all().delete()
try:
model.objects.all().delete()
except ProtectedError:
Proposicao.objects.all().delete()
model.objects.all().delete()
delete_constraints(model)
# setup migration strategy for tables with or without a pk
@ -416,13 +488,12 @@ def adjust_sessaoplenaria(new, old):
def adjust_tipoproposicao(new, old):
if new.materia_ou_documento == 'M':
field = TipoProposicao.tipo_materia.field
value = get_fk_related(field=field, value=old.tip_mat_ou_doc)
elif new.materia_ou_documento == 'D':
field = TipoProposicao.tipo_documento.field
value = get_fk_related(field=field, value=old.tip_mat_ou_doc)
setattr(new, field.name, value)
if old.ind_mat_ou_doc == 'M':
new.tipo_conteudo_related = TipoMateriaLegislativa.objects.get(
pk=old.tip_mat_ou_doc)
elif old.ind_mat_ou_doc == 'D':
new.tipo_conteudo_related = TipoDocumento.objects.get(
pk=old.tip_mat_ou_doc)
def adjust_statustramitacao(new, old):
@ -455,15 +526,24 @@ def adjust_normajuridica_antes_salvar(new, old):
def adjust_normajuridica_depois_salvar(new, old):
# Ajusta relação M2M
lista_ids_assunto = old.cod_assunto.split(',')
for id_assunto in lista_ids_assunto:
relacao = AssuntoNormaRelationship()
relacao.assunto_id = int(id_assunto)
relacao.norma_id = new.pk
relacao.save()
lista_pks_assunto = old.cod_assunto.split(',')
for pk_assunto in lista_pks_assunto:
new.assuntos.add(AssuntoNorma.objects.get(pk=pk_assunto))
def adjust_autor(new, old):
new.autor_related = TipoAutor.objects.get(pk=old.tip_autor)
if old.col_username:
if not User.objects.filter(username=old.col_username).exists():
user = User(username=old.col_username, password=12345)
user.save()
new.user = user
else:
new.user = User.objects.filter(username=old.col_username)[0]
AJUSTE_ANTES_SALVAR = {
Autor: adjust_autor,
NormaJuridica: adjust_normajuridica_antes_salvar,
OrdemDia: adjust_ordemdia,
Parlamentar: adjust_parlamentar,

206
sapl/materia/forms.py

@ -1,12 +1,12 @@
from datetime import datetime, date
from datetime import date, datetime
import os
from crispy_forms.bootstrap import Alert, InlineCheckboxes, FormActions,\
InlineRadios
from crispy_forms.bootstrap import (Alert, FormActions, InlineCheckboxes,
InlineRadios)
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout, Row,\
Field, Submit
from crispy_forms.layout import (HTML, Button, Column, Field, Fieldset, Layout,
Submit)
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError
@ -21,21 +21,25 @@ import django_filters
from sapl.base.models import Autor
from sapl.comissoes.models import Comissao
from sapl.crispy_layout_mixin import form_actions, to_row, to_column,\
SaplFormLayout
from sapl.materia.models import TipoProposicao, RegimeTramitacao, TipoDocumento
from sapl.compilacao.models import STATUS_TA_PRIVATE,\
STATUS_TA_IMMUTABLE_PUBLIC, TextoArticulado
from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
to_row)
from sapl.materia.models import TipoProposicao, MateriaLegislativa,\
RegimeTramitacao, TipoDocumento
from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
TipoNormaJuridica)
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import Protocolo
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import (RANGE_ANOS, RangeWidgetOverride, autor_label,
autor_modal, models_with_gr_for_model,
ChoiceWithoutValidationField, YES_NO_CHOICES)
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES,
ChoiceWithoutValidationField,
MateriaPesquisaOrderingFilter, RangeWidgetOverride,
autor_label, autor_modal, models_with_gr_for_model)
import sapl
from .models import (AcompanhamentoMateria, Anexada, Autoria, DespachoInicial,
DocumentoAcessorio, MateriaLegislativa, Numeracao,
DocumentoAcessorio, Numeracao,
Proposicao, Relatoria, TipoMateriaLegislativa, Tramitacao,
UnidadeTramitacao)
@ -65,6 +69,33 @@ class ReceberProposicaoForm(Form):
super(ReceberProposicaoForm, self).__init__(*args, **kwargs)
class MateriaSimplificadaForm(ModelForm):
class Meta:
model = MateriaLegislativa
fields = ['tipo', 'numero', 'ano', 'data_apresentacao',
'numero_protocolo', 'regime_tramitacao',
'em_tramitacao', 'ementa', 'texto_original']
def __init__(self, *args, **kwargs):
row1 = to_row([('tipo', 6), ('numero', 3), ('ano', 3)])
row2 = to_row([('data_apresentacao', 6), ('numero_protocolo', 6)])
row3 = to_row([('regime_tramitacao', 6), ('em_tramitacao', 6)])
row4 = to_row([('ementa', 12)])
row5 = to_row([('texto_original', 12)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(
_('Formulário Simplificado'),
row1, row2, row3, row4, row5,
form_actions(save_label='Salvar')
)
)
super(MateriaSimplificadaForm, self).__init__(*args, **kwargs)
class UnidadeTramitacaoForm(ModelForm):
class Meta:
@ -84,66 +115,6 @@ class UnidadeTramitacaoForm(ModelForm):
return cleaned_data
class ProposicaoOldForm(ModelForm):
tipo_materia = forms.ModelChoiceField(
label=_('Matéria Vinculada'),
required=False,
queryset=TipoMateriaLegislativa.objects.all(),
empty_label='Selecione',
)
numero_materia = forms.CharField(
label='Número', required=False)
ano_materia = forms.CharField(
label='Ano', required=False)
def clean_texto_original(self):
texto_original = self.cleaned_data.get('texto_original', False)
if texto_original:
if texto_original.size > MAX_DOC_UPLOAD_SIZE:
raise ValidationError("Arquivo muito grande. ( > 5mb )")
return texto_original
def clean_data_envio(self):
data_envio = self.cleaned_data.get('data_envio') or None
if (not data_envio) and len(self.initial) > 1:
data_envio = datetime.now()
return data_envio
def clean(self):
cleaned_data = self.cleaned_data
if 'tipo' in cleaned_data:
if cleaned_data['tipo'].descricao == 'Parecer':
if self.instance.materia:
cleaned_data['materia'] = self.instance.materia
else:
try:
materia = MateriaLegislativa.objects.get(
tipo_id=cleaned_data['tipo_materia'],
ano=cleaned_data['ano_materia'],
numero=cleaned_data['numero_materia'])
except ObjectDoesNotExist:
msg = _('Matéria adicionada não existe!')
raise ValidationError(msg)
else:
cleaned_data['materia'] = materia
return cleaned_data
def save(self, commit=False):
proposicao = super(ProposicaoOldForm, self).save(commit)
if 'materia' in self.cleaned_data:
proposicao.materia = self.cleaned_data['materia']
proposicao.save()
return proposicao
class Meta:
model = Proposicao
fields = ['tipo', 'data_envio', 'descricao', 'texto_original', 'autor']
widgets = {'autor': forms.HiddenInput()}
class AcompanhamentoMateriaForm(ModelForm):
class Meta:
@ -504,6 +475,8 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
label=u'Ano da Matéria',
choices=em_tramitacao)
o = MateriaPesquisaOrderingFilter()
class Meta:
model = MateriaLegislativa
fields = ['numero',
@ -513,7 +486,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
'data_apresentacao',
'data_publicacao',
'autoria__autor__tipo',
# 'autoria__autor__partido',
# FIXME 'autoria__autor__partido',
'relatoria__parlamentar_id',
'local_origem_externa',
'tramitacao__unidade_tramitacao_destino',
@ -521,29 +494,6 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
'em_tramitacao',
]
order_by = (
('', 'Selecione'),
('dataC', 'Data, Tipo, Ano, Numero - Ordem Crescente'),
('dataD', 'Data, Tipo, Ano, Numero - Ordem Decrescente'),
('tipoC', 'Tipo, Ano, Numero, Data - Ordem Crescente'),
('tipoD', 'Tipo, Ano, Numero, Data - Ordem Decrescente')
)
order_by_mapping = {
'': [],
'dataC': ['data_apresentacao', 'tipo__sigla', 'ano', 'numero'],
'dataD': ['-data_apresentacao', '-tipo__sigla', '-ano', '-numero'],
'tipoC': ['tipo__sigla', 'ano', 'numero', 'data_apresentacao'],
'tipoD': ['-tipo__sigla', '-ano', '-numero', '-data_apresentacao'],
}
def get_order_by(self, order_value):
if order_value in self.order_by_mapping:
return self.order_by_mapping[order_value]
else:
return super(MateriaLegislativaFilterSet,
self).get_order_by(order_value)
def __init__(self, *args, **kwargs):
super(MateriaLegislativaFilterSet, self).__init__(*args, **kwargs)
@ -829,7 +779,8 @@ class TipoProposicaoForm(ModelForm):
content_type = cd['content_type']
if 'tipo_conteudo_related' not in cd or not cd['tipo_conteudo_related']:
if 'tipo_conteudo_related' not in cd or not cd[
'tipo_conteudo_related']:
raise ValidationError(
_('Seleção de Tipo não definida'))
@ -951,11 +902,14 @@ class ProposicaoForm(forms.ModelForm):
if self.instance.materia_de_vinculo:
self.fields[
'tipo_materia'].initial = self.instance.materia_de_vinculo.tipo
'tipo_materia'
].initial = self.instance.materia_de_vinculo.tipo
self.fields[
'numero_materia'].initial = self.instance.materia_de_vinculo.numero
'numero_materia'
].initial = self.instance.materia_de_vinculo.numero
self.fields[
'ano_materia'].initial = self.instance.materia_de_vinculo.ano
'ano_materia'
].initial = self.instance.materia_de_vinculo.ano
def clean_texto_original(self):
texto_original = self.cleaned_data.get('texto_original', False)
@ -1128,11 +1082,14 @@ class ConfirmarProposicaoForm(ProposicaoForm):
if self.instance.materia_de_vinculo:
self.fields[
'tipo_materia'].initial = self.instance.materia_de_vinculo.tipo
'tipo_materia'
].initial = self.instance.materia_de_vinculo.tipo
self.fields[
'numero_materia'].initial = self.instance.materia_de_vinculo.numero
'numero_materia'
].initial = self.instance.materia_de_vinculo.numero
self.fields[
'ano_materia'].initial = self.instance.materia_de_vinculo.ano
'ano_materia'
].initial = self.instance.materia_de_vinculo.ano
if self.proposicao_incorporacao_obrigatoria == 'C':
self.fields['gerar_protocolo'].initial = True
@ -1148,8 +1105,8 @@ class ConfirmarProposicaoForm(ProposicaoForm):
raise ValidationError(
_('Regimente de Tramitação deve ser informado.'))
elif self.instance.tipo.content_type.model_class() == TipoDocumento\
and not cd['materia_de_vinculo']:
elif self.instance.tipo.content_type.model_class(
) == TipoDocumento and not cd['materia_de_vinculo']:
raise ValidationError(
_('Documentos não podem ser incorporados sem definir '
@ -1179,6 +1136,12 @@ class ConfirmarProposicaoForm(ProposicaoForm):
self.instance.data_envio = None
self.instance.save()
if self.instance.texto_articulado.exists():
ta = self.instance.texto_articulado.first()
ta.privacidade = STATUS_TA_PRIVATE
ta.editing_locked = False
ta.save()
self.instance.results = {
'messages': {
'success': [_('Devolução efetuada com sucesso.'), ]
@ -1192,6 +1155,12 @@ class ConfirmarProposicaoForm(ProposicaoForm):
self.instance.data_devolucao = None
self.instance.data_recebimento = datetime.now()
if self.instance.texto_articulado.exists():
ta = self.instance.texto_articulado.first()
ta.privacidade = STATUS_TA_IMMUTABLE_PUBLIC
ta.editing_locked = True
ta.save()
self.instance.save()
"""
@ -1216,7 +1185,8 @@ class ConfirmarProposicaoForm(ProposicaoForm):
proposicao = self.instance
conteudo_gerado = None
if self.instance.tipo.content_type.model_class() == TipoMateriaLegislativa:
if self.instance.tipo.content_type.model_class(
) == TipoMateriaLegislativa:
numero__max = MateriaLegislativa.objects.filter(
tipo=proposicao.tipo.tipo_conteudo_related,
ano=datetime.now().year).aggregate(Max('numero'))
@ -1231,13 +1201,26 @@ class ConfirmarProposicaoForm(ProposicaoForm):
materia.data_apresentacao = datetime.now()
materia.em_tramitacao = True
materia.regime_tramitacao = cd['regime_tramitacao']
materia.texto_original = File(
proposicao.texto_original,
os.path.basename(proposicao.texto_original.path))
materia.texto_articulo = proposicao.texto_articulado
if proposicao.texto_original:
materia.texto_original = File(
proposicao.texto_original,
os.path.basename(proposicao.texto_original.path))
materia.save()
conteudo_gerado = materia
if proposicao.texto_articulado.exists():
ta = proposicao.texto_articulado.first()
ta.id = None
ta.content_object = materia
ta.save()
pass
# FIXME - gerar texto_articulado da materia com base na prop.
# materia.texto_articulo = proposicao.texto_articulado
self.instance.results['messages']['success'].append(_(
'Matéria Legislativa registrada com sucesso (%s)'
) % str(materia))
@ -1349,7 +1332,8 @@ class ConfirmarProposicaoForm(ProposicaoForm):
protocolo.numero_paginas = cd['numero_de_paginas']
protocolo.anulado = False
if self.instance.tipo.content_type.model_class() == TipoMateriaLegislativa:
if self.instance.tipo.content_type.model_class(
) == TipoMateriaLegislativa:
protocolo.tipo_materia = proposicao.tipo.tipo_conteudo_related
elif self.instance.tipo.content_type.model_class() == TipoDocumento:
protocolo.tipo_documento = proposicao.tipo.tipo_conteudo_related

23
sapl/materia/legacy.yaml

@ -37,6 +37,11 @@ MateriaLegislativa:
tipo: tip_id_basica
tipo_origem_externa: tip_origem_externa
Autoria:
autor: cod_autor
materia: cod_materia
primeiro_autor: ind_primeiro_autor
AcompanhamentoMateria (AcompMateria):
email: end_email
hash: txt_hash
@ -52,24 +57,6 @@ AssuntoMateria:
assunto: des_assunto
dispositivo: des_dispositivo
TipoAutor:
descricao: des_tipo_autor
Autor:
cargo: des_cargo
comissao: cod_comissao
nome: nom_autor
parlamentar: cod_parlamentar
partido: cod_partido
tipo: tip_autor
username: col_username
user:
Autoria:
autor: cod_autor
materia: cod_materia
primeiro_autor: ind_primeiro_autor
DespachoInicial:
comissao: cod_comissao
materia: cod_materia

13
sapl/materia/models.py

@ -1,9 +1,5 @@
import datetime
import re
from django.contrib.auth.models import Group
from django.contrib.contenttypes.fields import GenericForeignKey,\
GenericRelation
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models.deletion import PROTECT
@ -14,10 +10,9 @@ 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,
restringe_tipos_de_arquivo_txt, SaplGenericRelation,
SaplGenericForeignKey, texto_upload_path)
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericForeignKey,
SaplGenericRelation, restringe_tipos_de_arquivo_txt,
texto_upload_path)
EM_TRAMITACAO = [(1, 'Sim'),
(0, 'Não')]

6
sapl/materia/tests/test_materia.py

@ -1,9 +1,9 @@
import pytest
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.urlresolvers import reverse
from model_mommy import mommy
import pytest
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Comissao, TipoComissao
@ -452,7 +452,7 @@ def test_proposicao_submit(admin_client):
*models_with_gr_for_model(TipoProposicao))
for pk, mct in enumerate(mcts):
tipo_conteudo_related = mommy.make(mct, pk=pk)
tipo_conteudo_related = mommy.make(mct, pk=pk + 1)
response = admin_client.post(
reverse('sapl.materia:proposicao_create'),
@ -475,7 +475,7 @@ def test_proposicao_submit(admin_client):
assert proposicao is not None
assert proposicao.descricao == 'Teste proposição'
assert proposicao.tipo.pk == 3
assert proposicao.tipo.tipo_conteudo_related.pk == pk
assert proposicao.tipo.tipo_conteudo_related.pk == pk + 1
@pytest.mark.django_db(transaction=False)

7
sapl/materia/urls.py

@ -4,7 +4,8 @@ from sapl.materia.views import (AcompanhamentoConfirmarView,
AcompanhamentoExcluirView,
AcompanhamentoMateriaView, AnexadaCrud,
AutoriaCrud, ConfirmarProposicao,
DespachoInicialCrud, DocumentoAcessorioCrud,
CriarProtocoloMateriaView, DespachoInicialCrud,
DocumentoAcessorioCrud,
DocumentoAcessorioEmLoteView,
LegislacaoCitadaCrud, MateriaLegislativaCrud,
MateriaLegislativaPesquisaView, MateriaTaView,
@ -34,6 +35,10 @@ urlpatterns_materia = [
TramitacaoCrud.get_urls() +
RelatoriaCrud.get_urls() +
DocumentoAcessorioCrud.get_urls())),
url(r'^materia/(?P<pk>[0-9]+)/create_simplificado$',
CriarProtocoloMateriaView.as_view(),
name='materia_create_simplificado'),
url(r'^materia/recuperar-materia', recuperar_materia),
url(r'^materia/(?P<pk>[0-9]+)/ta$',
MateriaTaView.as_view(), name='materia_ta'),

188
sapl/materia/views.py

@ -5,48 +5,45 @@ from string import ascii_letters, digits
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML
from django.contrib import messages
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist,\
PermissionDenied
from django.core.exceptions import ObjectDoesNotExist
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, Http404
from django.shortcuts import redirect
from django.http.response import Http404, HttpResponseRedirect
from django.shortcuts import redirect, get_object_or_404
from django.template import Context, loader
from django.utils import dateformat, formats
from django.utils.http import urlsafe_base64_decode
from django.utils import formats
from django.utils.translation import ugettext_lazy as _
from django.views.generic import CreateView, ListView, TemplateView, UpdateView
from django.views.generic.base import RedirectView
from django.views.generic.edit import FormView
from django_filters.views import FilterView
from sapl.base.models import Autor, CasaLegislativa, TipoAutor
from sapl.base.models import Autor, CasaLegislativa
from sapl.compilacao.models import STATUS_TA_PRIVATE, STATUS_TA_EDITION,\
STATUS_TA_IMMUTABLE_RESTRICT
from sapl.compilacao.views import IntegracaoTaView
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions
from sapl.crud.base import (ACTION_CREATE, ACTION_DELETE, ACTION_DETAIL,
ACTION_LIST, ACTION_UPDATE, RP_DETAIL, RP_LIST,
Crud, CrudAux, CrudDetailView, MasterDetailCrud,
make_pagination, PermissionRequiredForAppCrudMixin)
from sapl.materia import apps
from sapl.materia.forms import AnexadaForm, LegislacaoCitadaForm,\
TipoProposicaoForm, ProposicaoForm, ConfirmarProposicaoForm
Crud, CrudAux, MasterDetailCrud,
PermissionRequiredForAppCrudMixin, make_pagination)
from sapl.materia.forms import (AnexadaForm, ConfirmarProposicaoForm,
LegislacaoCitadaForm, ProposicaoForm,
TipoProposicaoForm)
from sapl.norma.models import LegislacaoCitada
from sapl.protocoloadm.models import Protocolo
from sapl.utils import (TURNO_TRAMITACAO_CHOICES, YES_NO_CHOICES, autor_label,
autor_modal, gerar_hash_arquivo, get_base_url,
montar_row_autor, permission_required_for_app,
permissoes_autor, permissoes_materia,
permissoes_protocoloadm, permission_required_for_app,
montar_row_autor)
import sapl
from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
DocumentoAcessorioForm,
MateriaLegislativaFilterSet,
PrimeiraTramitacaoEmLoteFilterSet, ProposicaoOldForm,
DocumentoAcessorioForm, MateriaLegislativaFilterSet,
MateriaSimplificadaForm, PrimeiraTramitacaoEmLoteFilterSet,
ReceberProposicaoForm, TramitacaoEmLoteFilterSet,
filtra_tramitacao_destino,
filtra_tramitacao_destino_and_status,
@ -74,9 +71,52 @@ TipoFimRelatoriaCrud = CrudAux.build(
TipoFimRelatoria, 'fim_relatoria')
class CriarProtocoloMateriaView(CreateView):
template_name = "crud/form.html"
form_class = MateriaSimplificadaForm
form_valid_message = _('Matéria cadastrada com sucesso!')
def get_success_url(self, materia):
return reverse('sapl.materia:materialegislativa_detail', kwargs={
'pk': materia.pk})
def get_context_data(self, **kwargs):
context = super(
CriarProtocoloMateriaView, self).get_context_data(**kwargs)
protocolo = Protocolo.objects.get(pk=self.kwargs['pk'])
context['form'].fields['tipo'].initial = protocolo.tipo_materia
context['form'].fields['numero'].initial = protocolo.numero
context['form'].fields['ano'].initial = protocolo.ano
context['form'].fields['data_apresentacao'].initial = protocolo.data
context['form'].fields['numero_protocolo'].initial = protocolo.numero
context['form'].fields['ementa'].initial = protocolo.observacao
return context
def form_valid(self, form):
materia = form.save()
return redirect(self.get_success_url(materia))
class MateriaTaView(IntegracaoTaView):
model = MateriaLegislativa
model_type_foreignkey = TipoMateriaLegislativa
map_fields = {
'data': 'data_apresentacao',
'ementa': 'ementa',
'observacao': None,
'numero': 'numero',
'ano': 'ano',
}
map_funcs = {
'publicacao_func': False,
}
ta_values = {
'editable_only_by_owners': False,
'editing_locked': False,
}
def get(self, request, *args, **kwargs):
"""
@ -93,8 +133,20 @@ class MateriaTaView(IntegracaoTaView):
class ProposicaoTaView(IntegracaoTaView):
model = Proposicao
model_type_foreignkey = TipoProposicao
# TODO implmentar o mapa de fields e utiliza-lo em IntegracaoTaView
fields = {
map_fields = {
'data': 'data_envio',
'ementa': 'descricao',
'observacao': None,
'numero': 'numero_proposicao',
'ano': 'ano',
}
map_funcs = {
'publicacao_func': False
}
ta_values = {
'editable_only_by_owners': True,
'editing_locked': False,
'privacidade': STATUS_TA_PRIVATE
}
def get(self, request, *args, **kwargs):
@ -104,12 +156,19 @@ class ProposicaoTaView(IntegracaoTaView):
de usuário.
"""
if sapl.base.models.AppConfig.attr('texto_articulado_proposicao'):
proposicao = get_object_or_404(self.model, pk=kwargs['pk'])
if not proposicao.data_envio and\
request.user != proposicao.autor.user:
raise Http404()
return IntegracaoTaView.get(self, request, *args, **kwargs)
else:
return self.get_redirect_deactivated()
@permission_required_for_app(app_label=apps.AppConfig.label)
@permission_required('materia.detail_materialegislativa')
def recuperar_materia(request):
tipo = TipoMateriaLegislativa.objects.get(pk=request.GET['tipo'])
ano = request.GET.get('ano', '')
@ -192,7 +251,7 @@ class ProposicaoDevolvida(PermissionRequiredMixin, ListView):
model = Proposicao
ordering = ['data_envio']
paginate_by = 10
permission_required = permissoes_protocoloadm()
permission_required = ('materia.list_proposicao', )
def get_queryset(self):
return Proposicao.objects.filter(
@ -216,7 +275,7 @@ class ProposicaoPendente(PermissionRequiredMixin, ListView):
model = Proposicao
ordering = ['data_envio', 'autor', 'tipo', 'descricao']
paginate_by = 10
permission_required = permissoes_protocoloadm()
permission_required = ('materia.list_proposicao', )
def get_queryset(self):
return Proposicao.objects.filter(
@ -241,7 +300,7 @@ class ProposicaoRecebida(PermissionRequiredMixin, ListView):
model = Proposicao
ordering = ['data_envio']
paginate_by = 10
permission_required = permissoes_protocoloadm()
permission_required = ('materia.list_proposicao', )
def get_queryset(self):
return Proposicao.objects.filter(
@ -274,10 +333,14 @@ class ReceberProposicao(PermissionRequiredForAppCrudMixin, FormView):
data_envio__isnull=False, data_recebimento__isnull=True)
for proposicao in proposicoes:
# FIXME implementar hash para texto eletrônico
hasher = gerar_hash_arquivo(
proposicao.texto_original.path,
str(proposicao.pk)) if proposicao.texto_original else None
if proposicao.texto_articulado.exists():
ta = proposicao.texto_articulado.first()
# FIXME hash para textos articulados
hasher = 'P' + ta.hash() + '/' + str(proposicao.id)
else:
hasher = gerar_hash_arquivo(
proposicao.texto_original.path,
str(proposicao.pk)) if proposicao.texto_original else None
if hasher == form.cleaned_data['cod_hash']:
return HttpResponseRedirect(
reverse('sapl.materia:proposicao-confirmar',
@ -304,9 +367,6 @@ class ConfirmarProposicao(PermissionRequiredForAppCrudMixin, UpdateView):
form_class = ConfirmarProposicaoForm
def get_success_url(self):
# FIXME redirecionamento trival,
# ainda por implementar se será para protocolo ou para doc resultante
msgs = self.object.results['messages']
for key, value in msgs.items():
@ -326,9 +386,15 @@ class ConfirmarProposicao(PermissionRequiredForAppCrudMixin, UpdateView):
data_recebimento__isnull=True)
self.object = None
# FIXME implementar hash para texto eletrônico
hasher = gerar_hash_arquivo(
proposicao.texto_original.path,
str(proposicao.pk)) if proposicao.texto_original else None
if proposicao.texto_articulado.exists():
ta = proposicao.texto_articulado.first()
# FIXME hash para textos articulados
hasher = 'P' + ta.hash() + '/' + str(proposicao.id)
else:
hasher = gerar_hash_arquivo(
proposicao.texto_original.path,
str(proposicao.pk)) if proposicao.texto_original else None
if hasher == 'P%s/%s' % (self.kwargs['hash'], proposicao.pk):
self.object = proposicao
@ -435,6 +501,13 @@ class ProposicaoCrud(Crud):
p.data_devolucao = None
p.data_envio = datetime.now()
p.save()
if p.texto_articulado.exists():
ta = p.texto_articulado.first()
ta.privacidade = STATUS_TA_IMMUTABLE_RESTRICT
ta.editing_locked = True
ta.save()
messages.success(request, _(
'Proposição enviada com sucesso.'))
@ -447,6 +520,11 @@ class ProposicaoCrud(Crud):
else:
p.data_envio = None
p.save()
if p.texto_articulado.exists():
ta = p.texto_articulado.first()
ta.privacidade = STATUS_TA_PRIVATE
ta.editing_locked = False
ta.save()
messages.success(request, _(
'Proposição Retornada com sucesso.'))
@ -508,9 +586,8 @@ class ProposicaoCrud(Crud):
messages.info(self.request,
_('Sempre que uma Proposição é inclusa ou '
'alterada e a opção "Texto Articulado " for '
'marcada, você será redirecionado para o '
'Texto Eletrônico. Use a opção "Editar Texto" '
'para construir seu texto.'))
'marcada, você será redirecionado para a '
'edição do Texto Eletrônico.'))
return reverse('sapl.materia:proposicao_ta',
kwargs={'pk': self.object.pk})
else:
@ -564,7 +641,7 @@ class ProposicaoCrud(Crud):
class ReciboProposicaoView(TemplateView):
template_name = "materia/recibo_proposicao.html"
permission_required = permissoes_autor()
permission_required = ('materia.detail_proposicao', )
def has_permission(self):
perms = self.get_permission_required()
@ -579,10 +656,18 @@ class ReciboProposicaoView(TemplateView):
context = super(ReciboProposicaoView, self).get_context_data(
**kwargs)
proposicao = Proposicao.objects.get(pk=self.kwargs['pk'])
if proposicao.texto_original:
_hash = gerar_hash_arquivo(
proposicao.texto_original.path,
self.kwargs['pk'])
elif proposicao.texto_articulado.exists():
ta = proposicao.texto_articulado.first()
# FIXME hash para textos articulados
_hash = 'P' + ta.hash() + '/' + str(proposicao.id)
context.update({'proposicao': proposicao,
'hash': gerar_hash_arquivo(
proposicao.texto_original.path,
self.kwargs['pk'])})
'hash': _hash})
return context
def get(self, request, *args, **kwargs):
@ -592,7 +677,7 @@ class ReciboProposicaoView(TemplateView):
return TemplateView.get(self, request, *args, **kwargs)
if not proposicao.data_envio and not proposicao.data_devolucao:
messages.error(request, _('Não é possível gerar recebo para uma '
messages.error(request, _('Não é possível gerar recibo para uma '
'Proposição ainda não enviada.'))
elif proposicao.data_devolucao:
messages.error(request, _('Não é possível gerar recibo.'))
@ -916,7 +1001,7 @@ class MateriaLegislativaCrud(Crud):
class DocumentoAcessorioView(PermissionRequiredMixin, CreateView):
template_name = "materia/documento_acessorio.html"
form_class = DocumentoAcessorioForm
permission_required = permissoes_materia()
permission_required = ('materia.add_documentoacessorio', )
def get(self, request, *args, **kwargs):
materia = MateriaLegislativa.objects.get(id=kwargs['pk'])
@ -950,8 +1035,7 @@ class DocumentoAcessorioView(PermissionRequiredMixin, CreateView):
return reverse('sapl.materia:documento_acessorio', kwargs={'pk': pk})
class AcompanhamentoConfirmarView(PermissionRequiredMixin, TemplateView):
permission_required = permissoes_materia()
class AcompanhamentoConfirmarView(TemplateView):
def get_redirect_url(self):
return reverse('sapl.sessao:list_pauta_sessao')
@ -968,8 +1052,7 @@ class AcompanhamentoConfirmarView(PermissionRequiredMixin, TemplateView):
return HttpResponseRedirect(self.get_redirect_url())
class AcompanhamentoExcluirView(PermissionRequiredMixin, TemplateView):
permission_required = permissoes_materia()
class AcompanhamentoExcluirView(TemplateView):
def get_redirect_url(self):
return reverse('sapl.sessao:list_pauta_sessao')
@ -1045,9 +1128,8 @@ class MateriaLegislativaPesquisaView(FilterView):
return context
class AcompanhamentoMateriaView(PermissionRequiredMixin, CreateView):
class AcompanhamentoMateriaView(CreateView):
template_name = "materia/acompanhamento_materia.html"
permission_required = permissoes_materia()
def get_random_chars(self):
s = ascii_letters + digits
@ -1303,7 +1385,7 @@ def do_envia_email_tramitacao(request, materia):
class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = AcessorioEmLoteFilterSet
template_name = 'materia/em_lote/acessorio.html'
permission_required = permissoes_materia()
permission_required = ('materia.add_documentoacessorio',)
def get_context_data(self, **kwargs):
context = super(DocumentoAcessorioEmLoteView,
@ -1347,7 +1429,7 @@ class DocumentoAcessorioEmLoteView(PermissionRequiredMixin, FilterView):
class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
filterset_class = PrimeiraTramitacaoEmLoteFilterSet
template_name = 'materia/em_lote/tramitacao.html'
permission_required = permissoes_materia()
permission_required = ('materia.add_tramitacao', )
def get_context_data(self, **kwargs):
context = super(PrimeiraTramitacaoEmLoteView,

40
sapl/norma/forms.py

@ -4,7 +4,7 @@ from crispy_forms.helper import FormHelper
from crispy_forms.layout import Fieldset, Layout
from django import forms
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.forms import ModelForm
from django.forms import ModelForm, widgets
from django.utils.translation import ugettext_lazy as _
from sapl.crispy_layout_mixin import form_actions, to_row
@ -12,7 +12,7 @@ from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.settings import MAX_DOC_UPLOAD_SIZE
from sapl.utils import RANGE_ANOS
from .models import NormaJuridica
from .models import AssuntoNorma, NormaJuridica
def get_esferas():
@ -64,7 +64,7 @@ class NormaJuridicaPesquisaForm(ModelForm):
ano = forms.ModelChoiceField(
label='Ano',
required=False,
queryset=NormaJuridica.objects.order_by('ano').values_list(
queryset=NormaJuridica.objects.order_by('-ano').values_list(
'ano', flat=True).distinct(),
empty_label='Selecione'
)
@ -81,6 +81,13 @@ class NormaJuridicaPesquisaForm(ModelForm):
numero = forms.IntegerField(required=False)
assunto = forms.ModelChoiceField(
label='Assunto',
required=False,
queryset=AssuntoNorma.objects.all(),
empty_label='Selecione'
)
class Meta:
model = NormaJuridica
fields = ['tipo',
@ -89,25 +96,20 @@ class NormaJuridicaPesquisaForm(ModelForm):
'periodo_inicial',
'periodo_final',
'publicacao_inicial',
'publicacao_final']
'publicacao_final',
'assunto']
def __init__(self, *args, **kwargs):
row1 = to_row(
[('tipo', 12)])
row1 = to_row([('tipo', 12)])
row2 = to_row(
[('numero', 6), ('ano', 6)])
row2 = to_row([('numero', 6), ('ano', 6)])
row3 = to_row(
[('periodo_inicial', 6), ('periodo_final', 6)])
row3 = to_row([('periodo_inicial', 6), ('periodo_final', 6)])
row4 = to_row(
[('publicacao_inicial', 6), ('publicacao_final', 6)])
row4 = to_row([('publicacao_inicial', 6), ('publicacao_final', 6)])
row5 = to_row(
[('em_vigencia', 6),
('ordenacao', 6)])
row5 = to_row([('em_vigencia', 4), ('ordenacao', 4), ('assunto', 4)])
self.helper = FormHelper()
self.helper.layout = Layout(
@ -155,7 +157,9 @@ class NormaJuridicaForm(ModelForm):
'ementa',
'indexacao',
'observacao',
'texto_integral']
'texto_integral',
'assuntos']
widgets = {'assuntos': widgets.CheckboxSelectMultiple}
def clean(self):
cleaned_data = self.cleaned_data
@ -186,8 +190,8 @@ class NormaJuridicaForm(ModelForm):
return texto_integral
def save(self, commit=False):
norma = super(NormaJuridicaForm, self).save(commit)
norma = self.instance
norma.timestamp = datetime.now()
norma.materia = self.cleaned_data['materia']
norma.save()
norma = super(NormaJuridicaForm, self).save(commit=True)
return norma

23
sapl/norma/migrations/0016_auto_20161027_1419.py

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-10-27 14:19
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('norma', '0015_auto_20160929_1635'),
]
operations = [
migrations.AlterModelOptions(
name='assuntonormarelationship',
options={'verbose_name': 'Assunto', 'verbose_name_plural': 'Assuntos'},
),
migrations.AlterUniqueTogether(
name='assuntonormarelationship',
unique_together=set([]),
),
]

20
sapl/norma/migrations/0017_auto_20161027_1432.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-10-27 14:32
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0016_auto_20161027_1419'),
]
operations = [
migrations.AlterField(
model_name='normajuridica',
name='assuntos',
field=models.ManyToManyField(through='norma.AssuntoNormaRelationship', to='norma.AssuntoNorma', verbose_name='Assuntos'),
),
]

26
sapl/norma/migrations/0018_auto_20161027_1434.py

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-10-27 14:34
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('norma', '0017_auto_20161027_1432'),
]
operations = [
migrations.AlterField(
model_name='assuntonormarelationship',
name='assunto',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='norma.AssuntoNorma', verbose_name='Assunto'),
),
migrations.AlterField(
model_name='assuntonormarelationship',
name='norma',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='norma.NormaJuridica', verbose_name='Norma'),
),
]

19
sapl/norma/migrations/0019_auto_20161028_0232.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-10-28 02:32
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('norma', '0018_auto_20161027_1434'),
]
operations = [
migrations.AlterUniqueTogether(
name='assuntonormarelationship',
unique_together=set([('assunto', 'norma')]),
),
]

39
sapl/norma/migrations/0020_auto_20161028_1335.py

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-10-28 13:35
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0019_auto_20161028_0232'),
]
operations = [
migrations.AlterUniqueTogether(
name='assuntonormarelationship',
unique_together=set([]),
),
migrations.RemoveField(
model_name='assuntonormarelationship',
name='assunto',
),
migrations.RemoveField(
model_name='assuntonormarelationship',
name='norma',
),
migrations.RemoveField(
model_name='normajuridica',
name='assuntos',
),
migrations.AddField(
model_name='normajuridica',
name='assuntos',
field=models.TextField(blank=True, null=True),
),
migrations.DeleteModel(
name='AssuntoNormaRelationship',
),
]

24
sapl/norma/migrations/0021_auto_20161028_1335.py

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-10-28 13:35
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('norma', '0020_auto_20161028_1335'),
]
operations = [
migrations.RemoveField(
model_name='normajuridica',
name='assuntos',
),
migrations.AddField(
model_name='normajuridica',
name='assuntos',
field=models.ManyToManyField(blank=True, to='norma.AssuntoNorma', verbose_name='Assuntos'),
),
]

14
sapl/norma/models.py

@ -97,8 +97,8 @@ class NormaJuridica(models.Model):
choices=YES_NO_CHOICES)
# XXX was a CharField (attention on migrate)
assuntos = models.ManyToManyField(
AssuntoNorma,
through='AssuntoNormaRelationship')
AssuntoNorma, blank=True,
verbose_name=_('Assuntos'))
data_vigencia = models.DateField(blank=True, null=True)
timestamp = models.DateTimeField()
@ -134,16 +134,6 @@ class NormaJuridica(models.Model):
update_fields=update_fields)
class AssuntoNormaRelationship(models.Model):
assunto = models.ForeignKey(AssuntoNorma)
norma = models.ForeignKey(NormaJuridica)
class Meta:
unique_together = (
('assunto', 'norma'),
)
class LegislacaoCitada(models.Model):
materia = models.ForeignKey(MateriaLegislativa)
norma = models.ForeignKey(NormaJuridica)

6
sapl/norma/urls.py

@ -1,8 +1,8 @@
from django.conf.urls import include, url
from sapl.norma.views import (AssuntoNormaCrud, NormaCrud, NormaPesquisaView,
NormaTaView, PesquisaNormaListView,
TipoNormaCrud)
from sapl.norma.views import (AssuntoNormaCrud,
NormaCrud, NormaPesquisaView, NormaTaView,
PesquisaNormaListView, TipoNormaCrud)
from .apps import AppConfig

22
sapl/norma/views.py

@ -7,11 +7,13 @@ from django.views.generic.base import RedirectView
from sapl.base.models import AppConfig
from sapl.compilacao.views import IntegracaoTaView
from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, CrudAux, make_pagination
from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
MasterDetailCrud, make_pagination)
from sapl.norma.forms import NormaJuridicaForm
from .forms import NormaJuridicaPesquisaForm
from .models import AssuntoNorma, NormaJuridica, TipoNormaJuridica
from .models import (AssuntoNorma, NormaJuridica,
TipoNormaJuridica)
# LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '')
AssuntoNormaCrud = CrudAux.build(AssuntoNorma, 'assunto_norma_juridica',
@ -26,6 +28,17 @@ TipoNormaCrud = CrudAux.build(
class NormaTaView(IntegracaoTaView):
model = NormaJuridica
model_type_foreignkey = TipoNormaJuridica
map_fields = {
'data': 'data',
'ementa': 'ementa',
'observacao': 'observacao',
'numero': 'numero',
'ano': 'ano',
}
map_funcs = {
'publicacao_func': True
}
def get(self, request, *args, **kwargs):
"""
@ -116,6 +129,8 @@ class NormaPesquisaView(FormView):
kwargs['ordenacao'] = form.data['ordenacao']
if form.data['em_vigencia']:
kwargs['em_vigencia'] = form.data['em_vigencia']
if form.data['assunto']:
kwargs['assunto'] = form.data['assunto']
request.session['kwargs'] = kwargs
return redirect('sapl.norma:list_pesquisa_norma')
@ -183,6 +198,9 @@ class PesquisaNormaListView(ListView):
if 'ano' in kwargs:
normas = normas.filter(ano=kwargs['ano'])
if 'assunto' in kwargs:
normas = normas.filter(assuntos=kwargs['assunto'])
return normas
def get_context_data(self, **kwargs):

6
sapl/painel/views.py

@ -7,20 +7,22 @@ from django.shortcuts import render
from django.utils.translation import ugettext_lazy as _
from sapl.crud.base import Crud
from sapl.painel.apps import AppConfig
from sapl.painel.models import Painel
from sapl.parlamentares.models import Filiacao
from sapl.sessao.models import (ExpedienteMateria, OrdemDia, PresencaOrdemDia,
RegistroVotacao, SessaoPlenaria,
SessaoPlenariaPresenca, VotoParlamentar)
from sapl.utils import permissoes_painel
from .models import Cronometro
CronometroPainelCrud = Crud.build(Cronometro, '')
# FIXME mudar lógica
def check_permission(user):
return user.has_perms(permissoes_painel())
return user.has_module_perms(AppConfig.label)
@user_passes_test(check_permission)

34
sapl/parlamentares/views.py

@ -54,6 +54,12 @@ class RelatoriaParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
help_path = 'relatoria_parlamentar'
namespace = AppConfig.name
class BaseMixin(CrudBaseForListAndDetailExternalAppView.BaseMixin):
@classmethod
def url_name(cls, suffix):
return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix)
class ProposicaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
model = Proposicao
@ -61,11 +67,31 @@ class ProposicaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
parent_field = 'autor__parlamentar_set'
namespace = AppConfig.name
class BaseMixin(CrudBaseForListAndDetailExternalAppView.BaseMixin):
@classmethod
def url_name(cls, suffix):
return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix)
class ListView(CrudBaseForListAndDetailExternalAppView.ListView):
def get_queryset(self):
return super().get_queryset().filter(
data_envio__isnull=False)
data_envio__isnull=False,
data_recebimento__isnull=False)
class DetailView(CrudBaseForListAndDetailExternalAppView.DetailView):
@property
def extras_url(self):
if self.object.texto_articulado.exists():
ta = self.object.texto_articulado.first()
yield (str(reverse_lazy(
'sapl.compilacao:ta_text',
kwargs={'ta_id': ta.pk})) + '?back_type=history',
'btn-success',
_('Texto Eletrônico'))
class ParticipacaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
@ -75,6 +101,12 @@ class ParticipacaoParlamentarCrud(CrudBaseForListAndDetailExternalAppView):
list_field_names = ['composicao__comissao__nome', 'cargo__nome', (
'composicao__periodo__data_inicio', 'composicao__periodo__data_fim')]
class BaseMixin(CrudBaseForListAndDetailExternalAppView.BaseMixin):
@classmethod
def url_name(cls, suffix):
return '%s_parlamentar_%s' % (cls.model._meta.model_name, suffix)
class ListView(CrudBaseForListAndDetailExternalAppView.ListView):
ordering = ('-composicao__periodo')

81
sapl/protocoloadm/forms.py

@ -13,14 +13,15 @@ from django.utils.translation import ugettext_lazy as _
from sapl.base.models import Autor
from sapl.crispy_layout_mixin import form_actions, to_row
from sapl.materia.models import UnidadeTramitacao
from sapl.utils import (RANGE_ANOS, RangeWidgetOverride, autor_label,
autor_modal)
from sapl.utils import (RANGE_ANOS, AnoNumeroOrderingFilter,
RangeWidgetOverride, autor_label, autor_modal)
from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo,
Protocolo, TipoDocumentoAdministrativo,
TramitacaoAdministrativo)
TIPOS_PROTOCOLO = [('0', 'Enviado'), ('1', 'Recebido'), ('', 'Ambos')]
TIPOS_PROTOCOLO_CREATE = [('0', 'Enviado'), ('1', 'Recebido')]
NATUREZA_PROCESSO = [('', 'Ambos'),
('0', 'Administrativo'),
@ -68,6 +69,8 @@ class ProtocoloFilterSet(django_filters.FilterSet):
widget=forms.Select(
attrs={'class': 'selector'}))
o = AnoNumeroOrderingFilter()
class Meta:
model = Protocolo
fields = ['numero',
@ -76,25 +79,6 @@ class ProtocoloFilterSet(django_filters.FilterSet):
'tipo_materia',
]
order_by = (
('', 'Selecione'),
('CRE', 'Ordem Crescente'),
('DEC', 'Ordem Decrescente'),
)
order_by_mapping = {
'': [],
'CRE': ['ano', 'numero'],
'DEC': ['-ano', '-numero'],
}
def get_order_by(self, order_value):
if order_value in self.order_by_mapping:
return self.order_by_mapping[order_value]
else:
return super(ProtocoloFilterSet,
self).get_order_by(order_value)
def __init__(self, *args, **kwargs):
super(ProtocoloFilterSet, self).__init__(*args, **kwargs)
@ -131,7 +115,7 @@ class ProtocoloFilterSet(django_filters.FilterSet):
self.form.helper = FormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisar Protocolo'),
Fieldset('',
row1, row2,
row3,
HTML(autor_label),
@ -162,6 +146,8 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
interessado = django_filters.CharFilter(lookup_expr='icontains')
o = AnoNumeroOrderingFilter()
class Meta:
model = DocumentoAdministrativo
fields = ['tipo',
@ -171,25 +157,6 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
'tramitacaoadministrativo__unidade_tramitacao_destino',
'tramitacaoadministrativo__status']
order_by = (
('', 'Selecione'),
('CRE', 'Ordem Crescente'),
('DEC', 'Ordem Decrescente'),
)
order_by_mapping = {
'': [],
'CRE': ['ano', 'numero'],
'DEC': ['-ano', '-numero'],
}
def get_order_by(self, order_value):
if order_value in self.order_by_mapping:
return self.order_by_mapping[order_value]
else:
return super(DocumentoAdministrativoFilterSet,
self).get_order_by(order_value)
def __init__(self, *args, **kwargs):
super(DocumentoAdministrativoFilterSet, self).__init__(*args, **kwargs)
@ -304,7 +271,7 @@ class ProtocoloDocumentForm(ModelForm):
tipo_protocolo = forms.ChoiceField(required=True,
label=_('Tipo de Protocolo'),
choices=TIPOS_PROTOCOLO,)
choices=TIPOS_PROTOCOLO_CREATE,)
tipo_documento = forms.ModelChoiceField(
label=_('Tipo de Documento'),
@ -313,7 +280,7 @@ class ProtocoloDocumentForm(ModelForm):
empty_label='Selecione',
)
num_paginas = forms.CharField(label=_('Núm. Páginas'), required=True)
numero_paginas = forms.CharField(label=_('Núm. Páginas'), required=True)
assunto = forms.CharField(
widget=forms.Textarea, label='Assunto', required=True)
@ -327,7 +294,7 @@ class ProtocoloDocumentForm(ModelForm):
model = Protocolo
fields = ['tipo_protocolo',
'tipo_documento',
'num_paginas',
'numero_paginas',
'assunto',
'interessado',
'observacao',
@ -339,7 +306,7 @@ class ProtocoloDocumentForm(ModelForm):
[(InlineRadios('tipo_protocolo'), 12)])
row2 = to_row(
[('tipo_documento', 6),
('num_paginas', 6)])
('numero_paginas', 6)])
row3 = to_row(
[('assunto', 12)])
row4 = to_row(
@ -364,11 +331,6 @@ class ProtocoloDocumentForm(ModelForm):
class ProtocoloMateriaForm(ModelForm):
tipo_protocolo = forms.ChoiceField(required=True,
label='Tipo de Protocolo',
choices=TIPOS_PROTOCOLO,)
autor = forms.IntegerField(widget=forms.HiddenInput(), required=False)
def clean_autor(self):
@ -383,8 +345,7 @@ class ProtocoloMateriaForm(ModelForm):
class Meta:
model = Protocolo
fields = ['tipo_protocolo',
'tipo_materia',
fields = ['tipo_materia',
'numero_paginas',
'autor',
'observacao']
@ -392,11 +353,9 @@ class ProtocoloMateriaForm(ModelForm):
def __init__(self, *args, **kwargs):
row1 = to_row(
[(InlineRadios('tipo_protocolo'), 12)])
row2 = to_row(
[('tipo_materia', 4),
('numero_paginas', 4)])
row3 = to_row(
row2 = to_row(
[('autor', 0),
(Button('pesquisar',
'Pesquisar Autor',
@ -404,25 +363,23 @@ class ProtocoloMateriaForm(ModelForm):
(Button('limpar',
'limpar Autor',
css_class='btn btn-primary btn-sm'), 10)])
row4 = to_row(
row3 = to_row(
[('observacao', 12)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação da Matéria'),
row1,
HTML(autor_label),
HTML(autor_modal),
row2,
HTML(autor_label),
HTML(autor_modal),
row3,
row4,
form_actions(save_label='Protocolar Matéria')
)
)
super(ProtocoloMateriaForm, self).__init__(
*args, **kwargs)
self.fields['tipo_protocolo'].inline_class = True
class DocumentoAcessorioAdministrativoForm(ModelForm):
@ -587,6 +544,10 @@ class DocumentoAdministrativoForm(ModelForm):
'texto_integral',
]
def save(self, commit=True):
documento = super(DocumentoAdministrativoForm, self).save(commit)
return documento
def __init__(self, *args, **kwargs):
row1 = to_row(

19
sapl/protocoloadm/migrations/0005_auto_20161027_1741.py

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-10-27 17:41
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0004_auto_20161023_1444'),
]
operations = [
migrations.AlterModelOptions(
name='protocolo',
options={'permissions': (('action_anular_protocolo', 'Permissão para Anular Protocolo'),), 'verbose_name': 'Protocolo', 'verbose_name_plural': 'Protocolos'},
),
]

20
sapl/protocoloadm/migrations/0006_auto_20161103_1721.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-11-03 17:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('protocoloadm', '0005_auto_20161027_1741'),
]
operations = [
migrations.AlterField(
model_name='protocolo',
name='tipo_protocolo',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Tipo de Protocolo'),
),
]

11
sapl/protocoloadm/models.py

@ -1,13 +1,9 @@
from uuid import uuid4
from django.db import models
from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
from sapl.base.models import Autor
from sapl.materia.models import (TipoMateriaLegislativa,
UnidadeTramitacao)
from sapl.materia.models import TipoMateriaLegislativa, UnidadeTramitacao
from sapl.utils import RANGE_ANOS, YES_NO_CHOICES, texto_upload_path
@ -123,7 +119,7 @@ class Protocolo(models.Model):
# TODO transformar campo timestamp em auto_now_add
timestamp = models.DateTimeField()
tipo_protocolo = models.PositiveIntegerField(
verbose_name=_('Tipo de Protocolo'))
blank=True, null=True, verbose_name=_('Tipo de Protocolo'))
tipo_processo = models.PositiveIntegerField()
interessado = models.CharField(
max_length=60, blank=True, verbose_name=_('Interessado'))
@ -153,6 +149,9 @@ class Protocolo(models.Model):
class Meta:
verbose_name = _('Protocolo')
verbose_name_plural = _('Protocolos')
permissions = (
('action_anular_protocolo', _('Permissão para Anular Protocolo')),
)
class StatusTramitacaoAdministrativo(models.Model):

2
sapl/protocoloadm/tests/test_protocoloadm.py

@ -1,10 +1,10 @@
import datetime
import pytest
from django.core.urlresolvers import reverse
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from model_mommy import mommy
import pytest
from sapl.materia.models import UnidadeTramitacao
from sapl.protocoloadm.forms import AnularProcoloAdmForm

48
sapl/protocoloadm/urls.py

@ -9,9 +9,8 @@ from sapl.protocoloadm.views import (AnularProtocoloAdmView,
DocumentoAcessorioAdministrativoView,
DocumentoAdministrativoCrud,
PesquisarDocumentoAdministrativoView,
ProtocoloDocumentoCrud,
ProtocoloDocumentoView, ProtocoloListView,
ProtocoloMateriaCrud,
ProtocoloDocumentoView,
ProtocoloMateriaTemplateView,
ProtocoloMateriaView,
ProtocoloMostrarView,
ProtocoloPesquisaView,
@ -42,31 +41,40 @@ urlpatterns_documento_administrativo = [
]
urlpatterns_protocolo = [
url(r'^protocoloadm/protocolo-doc/',
include(ProtocoloDocumentoCrud.get_urls())),
url(r'^protocoloadm/protocolo-mat/',
include(ProtocoloMateriaCrud.get_urls()), name='protocolomat'),
url(r'^protocoloadm/protocolo$',
# url(r'^protocoloadm/protocolo-doc/',
# include(ProtocoloDocumentoCrud.get_urls())),
# url(r'^protocoloadm/protocolo-mat/',
# include(ProtocoloMateriaCrud.get_urls()), name='protocolomat'),
# url(r'^protocoloadm/protocolo-list$',
# ProtocoloListView.as_view(), name='protocolo_list'),
url(r'^protocoloadm/$',
ProtocoloPesquisaView.as_view(), name='protocolo'),
url(r'^protocoloadm/protocolo-list$',
ProtocoloListView.as_view(), name='protocolo_list'),
url(r'^protocoloadm/anular-protocolo',
AnularProtocoloAdmView.as_view(), name='anular_protocolo'),
url(r'^protocoloadm/protocolar-doc',
ProtocoloDocumentoView.as_view(), name='protocolar_doc'),
url(r'^protocoloadm/(?P<pk>\d+)/protocolo-mostrar$',
ProtocoloMostrarView.as_view(), name='protocolo_mostrar'),
url(r'^protocoloadm/(?P<pk>\d+)/continuar$',
ProtocoloMateriaTemplateView.as_view(), name='materia_continuar'),
url(r'^protocoloadm/anular-protocolo',
AnularProtocoloAdmView.as_view(), name='anular_protocolo'),
url(r'^protocoloadm/protocolar-mat',
ProtocoloMateriaView.as_view(), name='protocolar_mat'),
# FIXME estas urls com pk e ano não fazem sentido
# se vai buscar por pk não precisa de nenhuma outra informação
# mas veja, apesar de chamar de pk aqui nas urls
# usou-se dentro da view como paramentro para ano.
url(r'^protocoloadm/(?P<pk>\d+)/(?P<ano>\d+)/protocolo-mostrar$',
ProtocoloMostrarView.as_view(), name='protocolo_mostrar'),
url(r'^protocoloadm/(?P<pk>\d+)/(?P<ano>\d+)/comprovante$',
url(r'^protocoloadm/(?P<pk>\d+)/comprovante$',
ComprovanteProtocoloView.as_view(), name='comprovante_protocolo'),
url(r'^protocoloadm/(?P<pk>\d+)/(?P<ano>\d+)/criar-documento$',
url(r'^protocoloadm/(?P<pk>\d+)/criar-documento$',
CriarDocumentoProtocolo.as_view(), name='criar_documento'),
]
urlpatterns_sistema = [

192
sapl/protocoloadm/views.py

@ -3,6 +3,7 @@ from datetime import date, datetime
from braces.views import FormValidMessageMixin
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.db.models import Max
from django.http import HttpResponseRedirect
@ -12,12 +13,10 @@ from django.views.generic import CreateView, DetailView, FormView, ListView
from django.views.generic.base import TemplateView
from django_filters.views import FilterView
from sapl.base.apps import AppConfig as AppsAppConfig
from sapl.base.models import AppConfig
import sapl
from sapl.crud.base import Crud, CrudAux, MasterDetailCrud, make_pagination
from sapl.materia.models import TipoMateriaLegislativa
from sapl.utils import (create_barcode, get_client_ip, permissoes_adm,
permissoes_protocoloadm)
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.utils import create_barcode, get_client_ip
from .forms import (AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm,
DocumentoAdministrativoFilterSet,
@ -32,9 +31,9 @@ TipoDocumentoAdministrativoCrud = CrudAux.build(
TipoDocumentoAdministrativo, '')
ProtocoloDocumentoCrud = Crud.build(Protocolo, '')
# ProtocoloDocumentoCrud = Crud.build(Protocolo, '')
# FIXME precisa de uma chave diferente para o layout
ProtocoloMateriaCrud = Crud.build(Protocolo, '')
# ProtocoloMateriaCrud = Crud.build(Protocolo, '')
DocumentoAcessorioAdministrativoCrud = Crud.build(
@ -44,11 +43,11 @@ DocumentoAcessorioAdministrativoCrud = Crud.build(
class DocumentoAdministrativoMixin:
def has_permission(self):
app_config = AppConfig.objects.last()
app_config = sapl.base.models.AppConfig.objects.last()
if app_config and app_config.documentos_administrativos == 'O':
return True
return self.request.user.has_module_perms(AppsAppConfig.label)
return super().has_permission()
class DocumentoAdministrativoCrud(Crud):
@ -60,10 +59,10 @@ class DocumentoAdministrativoCrud(Crud):
'numero_protocolo', 'assunto',
'interessado', 'tramitacao', 'texto_integral']
class ListView(Crud.ListView, DocumentoAdministrativoMixin):
class ListView(DocumentoAdministrativoMixin, Crud.ListView):
pass
class DetailView(Crud.DetailView, DocumentoAdministrativoMixin):
class DetailView(DocumentoAdministrativoMixin, Crud.DetailView):
pass
@ -82,7 +81,7 @@ class ProtocoloPesquisaView(PermissionRequiredMixin, FilterView):
model = Protocolo
filterset_class = ProtocoloFilterSet
paginate_by = 10
permission_required = permissoes_protocoloadm()
permission_required = ('protocoloadm.list_protocolo',)
def get_filterset_kwargs(self, filterset_class):
super(ProtocoloPesquisaView,
@ -109,6 +108,8 @@ class ProtocoloPesquisaView(PermissionRequiredMixin, FilterView):
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context['title'] = _('Pesquisa de Protocolos')
return context
def get(self, request, *args, **kwargs):
@ -143,7 +144,7 @@ class ProtocoloListView(PermissionRequiredMixin, ListView):
context_object_name = 'protocolos'
model = Protocolo
paginate_by = 10
permission_required = permissoes_protocoloadm()
permission_required = ('protocoloadm.list_protocolo',)
def get_queryset(self):
kwargs = self.request.session['kwargs']
@ -166,7 +167,7 @@ class AnularProtocoloAdmView(PermissionRequiredMixin, CreateView):
template_name = 'protocoloadm/anular_protocoloadm.html'
form_class = AnularProcoloAdmForm
form_valid_message = _('Protocolo anulado com sucesso!')
permission_required = permissoes_protocoloadm()
permission_required = ('protocoloadm.action_anular_protocolo', )
def get_success_url(self):
return reverse('sapl.protocoloadm:protocolo')
@ -195,16 +196,18 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
template_name = "protocoloadm/protocolar_documento.html"
form_class = ProtocoloDocumentForm
form_valid_message = _('Protocolo cadastrado com sucesso!')
permission_required = permissoes_protocoloadm()
permission_required = ('protocoloadm.add_protocolo', )
def get_success_url(self):
return reverse('sapl.protocoloadm:protocolo')
return reverse('sapl.protocoloadm:protocolo_mostrar',
kwargs={'pk': self.object.id})
def form_valid(self, form):
f = form.save(commit=False)
try:
numeracao = AppConfig.objects.last().sequencia_numeracao
numeracao = sapl.base.models.AppConfig.objects.last(
).sequencia_numeracao
except AttributeError:
msg = _('É preciso definir a sequencia de ' +
'numeração na tabelas auxiliares!')
@ -230,31 +233,28 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
f.assunto_ementa = self.request.POST['assunto']
f.save()
self.object = f
return redirect(self.get_success_url())
class CriarDocumentoProtocolo(PermissionRequiredMixin, CreateView):
template_name = "protocoloadm/criar_documento.html"
form_class = DocumentoAdministrativoForm
permission_required = permissoes_protocoloadm()
permission_required = ('protocoloadm.add_documentoadministrativo',)
def get_initial(self):
numero = self.kwargs['pk']
ano = self.kwargs['ano']
protocolo = Protocolo.objects.get(ano=ano, numero=numero)
protocolo = Protocolo.objects.get(pk=self.kwargs['pk'])
return self.criar_documento(protocolo)
def get_success_url(self):
return reverse('sapl.protocoloadm:protocolo_mostrar',
kwargs={'pk': self.kwargs['pk'],
'ano': self.kwargs['ano']})
kwargs={'pk': self.kwargs['pk']})
def criar_documento(self, protocolo):
numero = Protocolo.objects.filter(
tipo_documento=protocolo.tipo_documento,
ano=protocolo.ano,
anulado=False).aggregate(Max('numero'))
numero_max = DocumentoAdministrativo.objects.filter(
tipo=protocolo.tipo_documento
).aggregate(Max('numero'))['numero__max']
doc = {}
doc['tipo'] = protocolo.tipo_documento
@ -263,24 +263,36 @@ class CriarDocumentoProtocolo(PermissionRequiredMixin, CreateView):
doc['numero_protocolo'] = protocolo.numero
doc['assunto'] = protocolo.assunto_ementa
doc['interessado'] = protocolo.interessado
doc['numero'] = numero['numero__max']
if doc['numero'] is None:
doc['numero'] = 1
else:
doc['numero'] = doc['numero'] + 1
doc['numero'] = numero_max + 1 if numero_max else 1
return doc
class ProtocoloMostrarView(PermissionRequiredMixin, TemplateView):
template_name = "protocoloadm/protocolo_mostrar.html"
permission_required = permissoes_protocoloadm()
permission_required = ('protocoloadm.detail_protocolo', )
def get_context_data(self, **kwargs):
context = super(ProtocoloMostrarView, self).get_context_data(**kwargs)
numero = self.kwargs['pk']
ano = self.kwargs['ano']
protocolo = Protocolo.objects.get(ano=ano, numero=numero)
protocolo = Protocolo.objects.get(pk=self.kwargs['pk'])
if protocolo.tipo_materia:
try:
materia = MateriaLegislativa.objects.get(
numero_protocolo=protocolo.numero, ano=protocolo.ano)
except ObjectDoesNotExist:
context['materia'] = None
else:
context['materia'] = materia
elif protocolo.tipo_documento:
try:
documento = DocumentoAdministrativo.objects.get(
numero_protocolo=protocolo.numero, ano=protocolo.ano)
except ObjectDoesNotExist:
context['documento'] = None
else:
context['documento'] = documento
context['protocolo'] = protocolo
return context
@ -288,16 +300,14 @@ class ProtocoloMostrarView(PermissionRequiredMixin, TemplateView):
class ComprovanteProtocoloView(PermissionRequiredMixin, TemplateView):
template_name = "protocoloadm/comprovante.html"
permission_required = permissoes_protocoloadm()
permission_required = ('protocoloadm.detail_protocolo', )
def get_context_data(self, **kwargs):
context = super(ComprovanteProtocoloView, self).get_context_data(
**kwargs)
numero = self.kwargs['pk']
ano = self.kwargs['ano']
protocolo = Protocolo.objects.get(ano=ano, numero=numero)
protocolo = Protocolo.objects.get(pk=self.kwargs['pk'])
# numero is string, padd with zeros left via .zfill()
base64_data = create_barcode(numero.zfill(6))
base64_data = create_barcode(str(protocolo.numero).zfill(6))
barcode = 'data:image/png;base64,{0}'.format(base64_data)
autenticacao = _("** NULO **")
@ -319,14 +329,16 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
template_name = "protocoloadm/protocolar_materia.html"
form_class = ProtocoloMateriaForm
form_valid_message = _('Matéria cadastrada com sucesso!')
permission_required = permissoes_protocoloadm()
permission_required = ('protocoloadm.add_protocolo',)
def get_success_url(self):
return reverse('sapl.protocoloadm:protocolo')
def get_success_url(self, protocolo):
return reverse('sapl.protocoloadm:materia_continuar', kwargs={
'pk': protocolo.pk})
def form_valid(self, form):
try:
numeracao = AppConfig.objects.last().sequencia_numeracao
numeracao = sapl.base.models.AppConfig.objects.last(
).sequencia_numeracao
except AttributeError:
msg = _('É preciso definir a sequencia de ' +
'numeração na tabelas auxiliares!')
@ -349,7 +361,6 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
protocolo.data = datetime.now().strftime("%Y-%m-%d")
protocolo.hora = datetime.now().strftime("%H:%M")
protocolo.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
protocolo.tipo_protocolo = self.request.POST['tipo_protocolo']
protocolo.tipo_processo = '0' # TODO validar o significado
if form.cleaned_data['autor']:
protocolo.autor = form.cleaned_data['autor']
@ -359,16 +370,29 @@ class ProtocoloMateriaView(PermissionRequiredMixin, CreateView):
protocolo.numero_paginas = self.request.POST['numero_paginas']
protocolo.observacao = self.request.POST['observacao']
protocolo.save()
return redirect(self.get_success_url())
return redirect(self.get_success_url(protocolo))
class PesquisarDocumentoAdministrativoView(PermissionRequiredMixin,
FilterView,
DocumentoAdministrativoMixin):
class ProtocoloMateriaTemplateView(PermissionRequiredMixin, TemplateView):
template_name = "protocoloadm/MateriaTemplate.html"
permission_required = ('protocoloadm.detail_protocolo', )
def get_context_data(self, **kwargs):
context = super(ProtocoloMateriaTemplateView, self).get_context_data(
**kwargs)
protocolo = Protocolo.objects.get(pk=self.kwargs['pk'])
context.update({'protocolo': protocolo})
return context
class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
PermissionRequiredMixin,
FilterView):
model = DocumentoAdministrativo
filterset_class = DocumentoAdministrativoFilterSet
paginate_by = 10
permission_required = permissoes_adm()
permission_required = ('protocoloadm.list_documentoadministrativo', )
def get_filterset_kwargs(self, filterset_class):
super(PesquisarDocumentoAdministrativoView,
@ -426,7 +450,7 @@ class PesquisarDocumentoAdministrativoView(PermissionRequiredMixin,
class DetailDocumentoAdministrativo(PermissionRequiredMixin, DetailView):
template_name = "protocoloadm/detail_doc_adm.html"
permission_required = permissoes_adm()
permission_required = ('protocoloadm.detail_documentoadministrativo', )
def get(self, request, *args, **kwargs):
documento = DocumentoAdministrativo.objects.get(
@ -468,7 +492,8 @@ class DetailDocumentoAdministrativo(PermissionRequiredMixin, DetailView):
class DocumentoAcessorioAdministrativoEditView(PermissionRequiredMixin,
FormView):
template_name = "protocoloadm/documento_acessorio_administrativo_edit.html"
permission_required = permissoes_adm()
permission_required = (
'protocoloadm.change_documentoacessorioadministrativo', )
def get(self, request, *args, **kwargs):
doc = DocumentoAdministrativo.objects.get(
@ -516,7 +541,8 @@ class DocumentoAcessorioAdministrativoEditView(PermissionRequiredMixin,
class DocumentoAcessorioAdministrativoView(PermissionRequiredMixin, FormView):
template_name = "protocoloadm/documento_acessorio_administrativo.html"
permission_required = permissoes_adm()
permission_required = (
'protocoloadm.add_documentoacessorioadministrativo', )
def get(self, request, *args, **kwargs):
form = DocumentoAcessorioAdministrativoForm()
@ -569,65 +595,13 @@ class TramitacaoAdmCrud(MasterDetailCrud):
class UpdateView(MasterDetailCrud.UpdateView):
form_class = TramitacaoAdmEditForm
class ListView(MasterDetailCrud.ListView, DocumentoAdministrativoMixin):
class ListView(DocumentoAdministrativoMixin, MasterDetailCrud.ListView):
def get_queryset(self):
qs = super(MasterDetailCrud.ListView, self).get_queryset()
kwargs = {self.crud.parent_field: self.kwargs['pk']}
return qs.filter(**kwargs).order_by('-data_tramitacao', '-id')
class DetailView(MasterDetailCrud.DetailView,
DocumentoAdministrativoMixin):
class DetailView(DocumentoAdministrativoMixin,
MasterDetailCrud.DetailView):
pass
"""
def get_nome_autor(request):
nome_autor = ''
if request.method == 'GET':
id = request.GET.get('id', '')
try:
autor = Autor.objects.get(pk=id)
if autor.parlamentar:
nome_autor = autor.parlamentar.nome_parlamentar
elif autor.comissao:
nome_autor = autor.comissao.nome
except ObjectDoesNotExist:
pass
return HttpResponse("{\"nome\":\"" + nome_autor + "\"}",
content_type="application/json; charset=utf-8")"""
"""
def pesquisa_autores(request):
q = ''
if request.method == 'GET':
q = request.GET.get('q', '')
autor = Autor.objects.filter(
Q(nome__icontains=q) |
Q(parlamentar__nome_parlamentar__icontains=q) |
Q(comissao__nome__icontains=q)
)
autor = Autor.objects.filter(nome__icontains=q)
autores = []
for a in autor:
nome = ''
if a.nome:
nome = a.nome
elif a.parlamentar:
nome = a.parlamentar.nome_parlamentar
elif a.comissao:
nome = a.comissao.nome
autores.append((a.id, nome))
autores = sorted(autores, key=lambda x: x[1])
return HttpResponse(json.dumps(autores,
sort_keys=True,
ensure_ascii=False),
content_type="application/json; charset=utf-8")
"""

2
sapl/relatorios/views.py

@ -19,7 +19,6 @@ from sapl.sessao.models import (ExpedienteMateria, ExpedienteSessao, Orador,
SessaoPlenariaPresenca, TipoExpediente)
from sapl.settings import STATIC_ROOT
from sapl.utils import UF
import sapl
from .templates import (pdf_capa_processo_gerar,
pdf_documento_administrativo_gerar, pdf_espelho_gerar,
@ -27,7 +26,6 @@ from .templates import (pdf_capa_processo_gerar,
pdf_ordem_dia_gerar, pdf_pauta_sessao_gerar,
pdf_protocolo_gerar, pdf_sessao_plenaria_gerar)
uf_dic = dict(UF)

38
sapl/rules/__init__.py

@ -0,0 +1,38 @@
from django.utils.translation import ugettext_lazy as _
default_app_config = 'sapl.rules.apps.AppConfig'
SAPL_GROUP_ADMINISTRATIVO = _("Operador Administrativo")
SAPL_GROUP_PROTOCOLO = _("Operador de Protocolo Administrativo")
SAPL_GROUP_COMISSOES = _("Operador de Comissões")
SAPL_GROUP_MATERIA = _("Operador de Matéria")
SAPL_GROUP_NORMA = _("Operador de Norma Jurídica")
SAPL_GROUP_SESSAO = _("Operador de Sessão Plenária")
SAPL_GROUP_PAINEL = _("Operador de Painel Eletrônico")
SAPL_GROUP_GERAL = _("Operador Geral")
SAPL_GROUP_AUTOR = _("Autor")
SAPL_GROUP_PARLAMENTAR = _("Parlamentar")
# TODO - funcionalidade ainda não existe mas está aqui para efeito de anotação
SAPL_GROUP_LOGIN_SOCIAL = _("Usuários com Login Social")
# ANONYMOUS não é um grupo mas é uma variável usadas nas rules para anotar
# explicitamente models que podem ter ação de usuários anônimos
# como por exemplo AcompanhamentoMateria
SAPL_GROUP_ANONYMOUS = ''
SAPL_GROUPS = [
SAPL_GROUP_ADMINISTRATIVO,
SAPL_GROUP_PROTOCOLO,
SAPL_GROUP_COMISSOES,
SAPL_GROUP_MATERIA,
SAPL_GROUP_NORMA,
SAPL_GROUP_SESSAO,
SAPL_GROUP_PAINEL,
SAPL_GROUP_GERAL,
SAPL_GROUP_AUTOR,
SAPL_GROUP_PARLAMENTAR,
SAPL_GROUP_LOGIN_SOCIAL,
SAPL_GROUP_ANONYMOUS,
]

234
sapl/rules/apps.py

@ -0,0 +1,234 @@
from builtins import LookupError
import django
from django.apps import apps
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.management import _get_all_permissions
from django.core import exceptions
from django.db import models, router
from django.db.utils import DEFAULT_DB_ALIAS
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat
from sapl.rules import (SAPL_GROUP_ADMINISTRATIVO, SAPL_GROUP_COMISSOES,
SAPL_GROUP_GERAL, SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA,
SAPL_GROUP_PAINEL, SAPL_GROUP_PROTOCOLO,
SAPL_GROUP_SESSAO)
class AppConfig(django.apps.AppConfig):
name = 'sapl.rules'
label = 'rules'
verbose_name = _('Regras de Acesso')
def create_proxy_permissions(
app_config, verbosity=2, interactive=True,
using=DEFAULT_DB_ALIAS, **kwargs):
if not app_config.models_module:
return
# print(app_config)
try:
Permission = apps.get_model('auth', 'Permission')
except LookupError:
return
if not router.allow_migrate_model(using, Permission):
return
from django.contrib.contenttypes.models import ContentType
permission_name_max_length = Permission._meta.get_field('name').max_length
# This will hold the permissions we're looking for as
# (content_type, (codename, name))
searched_perms = list()
# The codenames and ctypes that should exist.
ctypes = set()
for klass in list(app_config.get_models()):
opts = klass._meta
permissions = (
("list_" + opts.model_name,
string_concat(
_('Visualizaçao da lista de'), ' ',
opts.verbose_name_plural)),
("detail_" + opts.model_name,
string_concat(
_('Visualização dos detalhes de'), ' ',
opts.verbose_name_plural)),
)
opts.permissions = tuple(
set(list(permissions) + list(opts.permissions)))
if opts.proxy:
# Force looking up the content types in the current database
# before creating foreign keys to them.
app_label, model = opts.app_label, opts.model_name
try:
ctype = ContentType.objects.db_manager(
using).get_by_natural_key(app_label, model)
except:
ctype = ContentType.objects.db_manager(
using).create(app_label=app_label, model=model)
else:
ctype = ContentType.objects.db_manager(using).get_for_model(klass)
ctypes.add(ctype)
for perm in _get_all_permissions(klass._meta, ctype):
searched_perms.append((ctype, perm))
# Find all the Permissions that have a content_type for a model we're
# looking for. We don't need to check for codenames since we already have
# a list of the ones we're going to create.
all_perms = set(Permission.objects.using(using).filter(
content_type__in=ctypes,
).values_list(
"content_type", "codename"
))
perms = [
Permission(codename=codename, name=name, content_type=ct)
for ct, (codename, name) in searched_perms
if (ct.pk, codename) not in all_perms
]
# Validate the permissions before bulk_creation to avoid cryptic database
# error when the name is longer than 255 characters
for perm in perms:
if len(perm.name) > permission_name_max_length:
raise exceptions.ValidationError(
'The permission name %s of %s.%s '
'is longer than %s characters' % (
perm.name,
perm.content_type.app_label,
perm.content_type.model,
permission_name_max_length,
)
)
Permission.objects.using(using).bulk_create(perms)
if verbosity >= 2:
for perm in perms:
print("Adding permission '%s'" % perm)
def update_groups(app_config, verbosity=2, interactive=True,
using=DEFAULT_DB_ALIAS, **kwargs):
if app_config != AppConfig and not isinstance(app_config, AppConfig):
return
from sapl.rules.map_rules import rules_patterns
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
class Rules:
def __init__(self, rules_patterns):
self.rules_patterns = rules_patterns
def associar(self, g, model, tipo):
for t in tipo:
content_type = ContentType.objects.get_by_natural_key(
app_label=model._meta.app_label,
model=model._meta.model_name)
codename = (t[1:] + model._meta.model_name)\
if t[0] == '.' and t[-1] == '_' else t
p = Permission.objects.get(
content_type=content_type,
codename=codename)
g.permissions.add(p)
g.save()
def _config_group(self, group_name, rules_list):
if not group_name:
return
g = Group.objects.get_or_create(name=group_name)
if not isinstance(g, Group):
g = g[0]
g.permissions.clear()
try:
print(' ', group_name)
for model, perms in rules_list:
self.associar(g, model, perms)
except Exception as e:
print(group_name, e)
if settings.DEBUG:
user = ''
if group_name == SAPL_GROUP_ADMINISTRATIVO:
user = 'operador_administrativo'
elif group_name == SAPL_GROUP_PROTOCOLO:
user = 'operador_protocoloadm'
elif group_name == SAPL_GROUP_COMISSOES:
user = 'operador_comissoes'
elif group_name == SAPL_GROUP_MATERIA:
user = 'operador_materia'
elif group_name == SAPL_GROUP_NORMA:
user = 'operador_norma'
elif group_name == SAPL_GROUP_SESSAO:
user = 'operador_sessao'
elif group_name == SAPL_GROUP_PAINEL:
user = 'operador_painel'
elif group_name == SAPL_GROUP_GERAL:
user = 'operador_geral'
if user:
self.cria_usuario(user, g)
def groups_add_user(self, user, groups_name):
if not isinstance(groups_name, list):
groups_name = [groups_name, ]
for group_name in groups_name:
if not group_name or user.groups.filter(
name=group_name).exists():
continue
g = Group.objects.get_or_create(name=group_name)[0]
user.groups.add(g)
def groups_remove_user(self, user, groups_name):
if not isinstance(groups_name, list):
groups_name = [groups_name, ]
for group_name in groups_name:
if not group_name or not user.groups.filter(
name=group_name).exists():
continue
g = Group.objects.get_or_create(name=group_name)[0]
user.groups.remove(g)
def cria_usuario(self, nome, grupo):
nome_usuario = nome
usuario = get_user_model().objects.get_or_create(
username=nome_usuario)[0]
usuario.set_password('interlegis')
usuario.save()
grupo.user_set.add(usuario)
def update_groups(self):
print('')
print(string_concat('\033[93m\033[1m',
_('Atualizando grupos:'),
'\033[0m'))
for rules_group in self.rules_patterns:
group_name = rules_group['group']
rules_list = rules_group['rules']
self._config_group(group_name, rules_list)
rules = Rules(rules_patterns)
rules.update_groups()
models.signals.post_migrate.connect(
receiver=update_groups)
models.signals.post_migrate.connect(
receiver=create_proxy_permissions,
dispatch_uid="django.contrib.auth.management.create_permissions")

317
sapl/rules/map_rules.py

@ -0,0 +1,317 @@
from sapl.base import models as base
from sapl.comissoes import models as comissoes
from sapl.compilacao import models as compilacao
from sapl.lexml import models as lexml
from sapl.materia import models as materia
from sapl.norma import models as norma
from sapl.painel import models as painel
from sapl.parlamentares import models as parlamentares
from sapl.protocoloadm import models as protocoloadm
from sapl.rules import (SAPL_GROUP_ADMINISTRATIVO, SAPL_GROUP_ANONYMOUS,
SAPL_GROUP_AUTOR, SAPL_GROUP_COMISSOES,
SAPL_GROUP_GERAL, SAPL_GROUP_LOGIN_SOCIAL,
SAPL_GROUP_MATERIA, SAPL_GROUP_NORMA,
SAPL_GROUP_PAINEL, SAPL_GROUP_PARLAMENTAR,
SAPL_GROUP_PROTOCOLO, SAPL_GROUP_SESSAO)
from sapl.sessao import models as sessao
"""
Todas as permissões do django framework seguem o padrão
[app_label].[radical_de_permissao]_[model]
ou seja, em sapl.norma.NormaJuridica, por exemplo, o django framework cria
três permissões registadas na classe Permission:
definição uso
- add_normajuridica norma.add_normajuridica
- change_normajuridica norma.change_normajuridica
- delete_normajuridica norma.delete_normajuridica
No SAPL foram acrescidas em todos os models as duas regras abaixo, adicionadas
com o Signal post_migrate `create_proxy_permissions`
localizado em sapl.rules.apps.py.
- list_normajuridica norma.list_normajuridica
- detail_normajuridica norma.detail_normajuridica
Tanto o Crud implementado em sapl.crud.base.py quanto o Signal post_migrate
`update_groups` que é responsável por ler o mapa deste
arquivo (sapl.rules.map_rules.py) e criar os grupos definidos na regra de
negócio trabalham com os cinco radiais de permissão
e com qualquer outro tipo de permissão customizada, nesta ordem de precedência.
Os cinco radicais de permissão completa são:
RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE =\
'.list_', '.detail_', '.add_', '.change_', '.delete_',
Tanto a app crud quanto a app rules estão sempre ligadas a um model. Ao lidar
com permissões, sempre é analisado se é apenas um radical ou permissão
completa, sendo apenas um radical, a permissão completa é montada com base
no model associado.
"""
RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE =\
'.list_', '.detail_', '.add_', '.change_', '.delete_',
__base__ = [RP_LIST, RP_DETAIL, RP_ADD, RP_CHANGE, RP_DELETE]
__listdetailchange__ = [RP_LIST, RP_DETAIL, RP_CHANGE]
rules_group_administrativo = {
'group': SAPL_GROUP_ADMINISTRATIVO,
'rules': [
(protocoloadm.DocumentoAdministrativo, __base__),
(protocoloadm.DocumentoAcessorioAdministrativo, __base__),
(protocoloadm.TramitacaoAdministrativo, __base__),
]
}
rules_group_protocolo = {
'group': SAPL_GROUP_PROTOCOLO,
'rules': [
(protocoloadm.Protocolo, __base__ + [
'action_anular_protocolo']),
(protocoloadm.DocumentoAdministrativo,
[RP_ADD] + __listdetailchange__),
(protocoloadm.DocumentoAcessorioAdministrativo, __listdetailchange__),
(materia.MateriaLegislativa, __listdetailchange__),
(materia.DocumentoAcessorio, __listdetailchange__),
(materia.Anexada, __base__),
(materia.Autoria, __base__),
(materia.Proposicao, __listdetailchange__),
(compilacao.TextoArticulado, ['view_restricted_textoarticulado'])
]
}
rules_group_comissoes = {
'group': SAPL_GROUP_COMISSOES,
'rules': [
(comissoes.Comissao, __base__),
(comissoes.Composicao, __base__),
(comissoes.Participacao, __base__),
]
}
rules_group_materia = {
'group': SAPL_GROUP_MATERIA,
'rules': [
(materia.Anexada, __base__),
(materia.Autoria, __base__),
(materia.DespachoInicial, __base__),
(materia.DocumentoAcessorio, __base__),
(materia.MateriaLegislativa, __base__),
(materia.Numeracao, __base__),
(materia.Relatoria, __base__),
(materia.Tramitacao, __base__),
(materia.Relatoria, __base__),
(norma.LegislacaoCitada, __base__),
(compilacao.Dispositivo, __base__ + [
'change_dispositivo_edicao_dinamica',
# TODO: adicionar 'change_dispositivo_registros_compilacao'
# quando testes forem feitos para permtir que matérias possam
# ser vinculadas a outras matérias via registro de compilação.
# Normalmente emendas e/ou projetos substitutivos podem alterar
# uma matéria original. Fazer esse registro de compilação ofereceria
# um autografo eletrônico pronto para ser convertido em Norma.
])
]
}
rules_group_norma = {
'group': SAPL_GROUP_NORMA,
'rules': [
(norma.NormaJuridica, __base__),
(norma.VinculoNormaJuridica, __base__),
# Publicacao está com permissão apenas para norma e não para matéria
# e proposições apenas por análise do contexto, não é uma limitação
# da ferramenta.
(compilacao.Publicacao, __base__),
(compilacao.Vide, __base__),
(compilacao.Nota, __base__),
(compilacao.Dispositivo, __base__ + [
'view_dispositivo_notificacoes',
'change_dispositivo_edicao_dinamica',
'change_dispositivo_edicao_avancada',
'change_dispositivo_registros_compilacao',
'change_dispositivo_de_vigencia_global'
])
]
}
rules_group_sessao = {
'group': SAPL_GROUP_SESSAO,
'rules': [
(sessao.SessaoPlenaria, __base__),
(sessao.SessaoPlenariaPresenca, __base__),
(sessao.ExpedienteMateria, __base__),
(sessao.IntegranteMesa, __base__),
(sessao.ExpedienteSessao, __base__),
(sessao.Orador, __base__),
(sessao.OradorExpediente, __base__),
(sessao.OrdemDia, __base__),
(sessao.PresencaOrdemDia, __base__),
(sessao.RegistroVotacao, __base__),
(sessao.VotoParlamentar, __base__),
]
}
rules_group_painel = {
'group': SAPL_GROUP_PAINEL,
'rules': [
(painel.Painel, __base__),
(painel.Cronometro, __base__),
]
}
rules_group_autor = {
'group': SAPL_GROUP_AUTOR,
'rules': [
(materia.Proposicao, __base__),
(compilacao.Dispositivo, __base__ + [
'change_your_dispositivo_edicao_dinamica',
])
]
}
rules_group_parlamentar = {
'group': SAPL_GROUP_PARLAMENTAR,
'rules': []
}
rules_group_geral = {
'group': SAPL_GROUP_GERAL,
'rules': [
(base.AppConfig, __base__ + [
'menu_sistemas',
'view_tabelas_auxiliares'
]),
(base.CasaLegislativa, __listdetailchange__),
(base.ProblemaMigracao, []),
(base.TipoAutor, __base__),
(base.Autor, __base__),
(protocoloadm.StatusTramitacaoAdministrativo, __base__),
(protocoloadm.TipoDocumentoAdministrativo, __base__),
(comissoes.CargoComissao, __base__),
(comissoes.TipoComissao, __base__),
(comissoes.Periodo, __base__),
(materia.AssuntoMateria, __base__), # não há implementação
(materia.MateriaAssunto, __base__), # não há implementação
(materia.TipoProposicao, __base__),
(materia.TipoMateriaLegislativa, __base__),
(materia.RegimeTramitacao, __base__),
(materia.Origem, __base__),
(materia.TipoDocumento, __base__),
(materia.Orgao, __base__),
(materia.TipoFimRelatoria, __base__),
(materia.Parecer, __base__),
(materia.StatusTramitacao, __base__),
(materia.UnidadeTramitacao, __base__),
(norma.AssuntoNorma, __base__),
(norma.TipoNormaJuridica, __base__),
(parlamentares.Legislatura, __base__),
(parlamentares.SessaoLegislativa, __base__),
(parlamentares.Coligacao, __base__),
(parlamentares.ComposicaoColigacao, __base__),
(parlamentares.Partido, __base__),
(parlamentares.Municipio, __base__),
(parlamentares.NivelInstrucao, __base__),
(parlamentares.SituacaoMilitar, __base__),
(parlamentares.Parlamentar, __base__),
(parlamentares.TipoDependente, __base__),
(parlamentares.Dependente, __base__),
(parlamentares.Filiacao, __base__),
(parlamentares.TipoAfastamento, __base__),
(parlamentares.Mandato, __base__),
(parlamentares.CargoMesa, __base__),
(parlamentares.ComposicaoMesa, __base__),
(parlamentares.Frente, __base__),
(sessao.CargoBancada, __base__),
(sessao.Bancada, __base__),
(sessao.TipoSessaoPlenaria, __base__),
(sessao.TipoResultadoVotacao, __base__),
(sessao.TipoExpediente, __base__),
(sessao.Bloco, __base__),
(lexml.LexmlProvedor, __base__),
(lexml.LexmlPublicador, __base__),
(compilacao.VeiculoPublicacao, __base__),
(compilacao.TipoTextoArticulado, __base__),
(compilacao.TipoNota, __base__),
(compilacao.TipoVide, __base__),
(compilacao.TipoPublicacao, __base__),
# este model é um espelho do model integrado e sua edição pode
# confundir Autores, operadores de matéria e/ou norma.
# Por isso está adicionado apenas para o operador geral
(compilacao.TextoArticulado, __base__ + ['lock_unlock_textoarticulado']),
# estes tres models são complexos e a principio apenas o admin tem perm
(compilacao.TipoDispositivo, []),
(compilacao.TipoDispositivoRelationship, []),
(compilacao.PerfilEstruturalTextoArticulado, []),
]
}
# não possui efeito e é usada nos testes que verificam se todos os models estão
# neste arquivo rules.py
rules_group_anonymous = {
'group': SAPL_GROUP_ANONYMOUS,
'rules': [
(materia.AcompanhamentoMateria, [RP_ADD, RP_DELETE]),
]
}
rules_group_login_social = {
'group': SAPL_GROUP_LOGIN_SOCIAL,
'rules': []
}
rules_group_geral['rules'] = (rules_group_geral['rules'] +
rules_group_administrativo['rules'] +
rules_group_protocolo['rules'] +
rules_group_comissoes['rules'] +
rules_group_materia['rules'] +
rules_group_norma['rules'] +
rules_group_sessao['rules'] +
rules_group_painel['rules'] +
rules_group_login_social['rules'])
rules_patterns = [
rules_group_administrativo,
rules_group_protocolo,
rules_group_comissoes,
rules_group_materia,
rules_group_norma,
rules_group_sessao,
rules_group_painel,
rules_group_geral,
rules_group_autor,
rules_group_parlamentar,
rules_group_anonymous, # anotação para validação do teste de rules
rules_group_login_social # TODO não implementado
]

0
sapl/rules/migrations/__init__.py

0
sapl/rules/models.py

255
sapl/rules/tests/test_rules.py

@ -0,0 +1,255 @@
import pytest
from django.apps import apps
from django.conf import settings
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.utils import six
from django.utils.translation import ugettext_lazy as _
from sapl.base.models import CasaLegislativa, ProblemaMigracao
from sapl.compilacao.models import (PerfilEstruturalTextoArticulado,
TipoDispositivo,
TipoDispositivoRelationship)
from sapl.materia.models import AcompanhamentoMateria
from sapl.rules import SAPL_GROUPS, map_rules
from sapl.test_urls import create_perms_post_migrate
from scripts.lista_permissions_in_decorators import \
lista_permissions_in_decorators
from scripts.lista_urls import lista_urls
sapl_appconfs = [apps.get_app_config(n[5:]) for n in settings.SAPL_APPS]
sapl_models = []
for app in sapl_appconfs:
sapl_models.extend(app.get_models())
sapl_models.reverse()
@pytest.mark.parametrize('group_item', SAPL_GROUPS)
def test_groups_in_rules_patterns(group_item):
test = False
for rules_group in map_rules.rules_patterns:
if rules_group['group'] == group_item:
test = True
assert test, _('O grupo (%s) não foi a rules_patterns.') % (group_item)
@pytest.mark.parametrize('model_item', sapl_models)
def test_models_in_rules_patterns(model_item):
test = False
for rules_group in map_rules.rules_patterns:
rules_model = rules_group['rules']
for rm in rules_model:
if rm[0] == model_item:
test = True
break
assert test, _('O model %s (%s) não foi adicionado em nenhum '
'grupo padrão para regras de acesso.') % (
str(model_item),
model_item._meta.verbose_name)
# __falsos_positivos__
__fp__in__test_permission_of_models_in_rules_patterns = {
map_rules.RP_ADD: [CasaLegislativa,
ProblemaMigracao,
TipoDispositivo,
TipoDispositivoRelationship,
PerfilEstruturalTextoArticulado],
map_rules.RP_CHANGE: [ProblemaMigracao,
AcompanhamentoMateria,
TipoDispositivo,
TipoDispositivoRelationship,
PerfilEstruturalTextoArticulado],
map_rules.RP_DELETE: [CasaLegislativa,
ProblemaMigracao,
TipoDispositivo,
TipoDispositivoRelationship,
PerfilEstruturalTextoArticulado],
map_rules.RP_LIST: [ProblemaMigracao,
AcompanhamentoMateria,
TipoDispositivo,
TipoDispositivoRelationship,
PerfilEstruturalTextoArticulado],
map_rules.RP_DETAIL: [ProblemaMigracao,
AcompanhamentoMateria,
TipoDispositivo,
TipoDispositivoRelationship,
PerfilEstruturalTextoArticulado]
}
@pytest.mark.django_db(transaction=False)
@pytest.mark.parametrize('model_item', sapl_models)
def test_permission_of_models_in_rules_patterns(model_item):
create_perms_post_migrate(model_item._meta.app_config)
permissions = map_rules.__base__ + list(
filter(
lambda perm: not perm.startswith(
'detail_') and not perm.startswith('list_'),
map(lambda x: x[0],
model_item._meta.permissions))
)
__fp__ = __fp__in__test_permission_of_models_in_rules_patterns
for perm in permissions:
if perm in __fp__ and model_item in __fp__[perm]:
continue
test = False
for rules_group in map_rules.rules_patterns:
rules_model = rules_group['rules']
for rm in rules_model:
model = rm[0]
rules = rm[1]
if model == model_item:
if perm in rules:
test = True
break
assert test, _('A permissão (%s) do model (%s) não foi adicionado em '
'nenhum grupo padrão para regras de acesso.') % (
perm,
str(model_item))
@pytest.mark.django_db(transaction=False)
@pytest.mark.parametrize('model_item', sapl_models)
def test_permission_of_rules_exists(model_item):
print(model_item)
create_perms_post_migrate(model_item._meta.app_config)
for rules_group in map_rules.rules_patterns:
rules_model = rules_group['rules']
for rm in rules_model:
model = rm[0]
rules = rm[1]
if model != model_item:
continue
for r in rules:
content_type = ContentType.objects.get_by_natural_key(
app_label=model._meta.app_label,
model=model._meta.model_name)
codename = (r[1:] + model._meta.model_name)\
if r[0] == '.' and r[-1] == '_' else r
p = Permission.objects.filter(
content_type=content_type,
codename=codename).exists()
assert p, _('Permissão (%s) associada ao model (%s) '
'não está em _meta.permissions.') % (
codename,
model_item)
_lista_urls = lista_urls()
@pytest.mark.django_db(transaction=False)
@pytest.mark.parametrize('url_item', _lista_urls)
def test_permission_required_of_views_exists(url_item):
"""
testa se, nas views que possuem atributo permission_required,
as permissões fixas escritas manualmente realmente exitem em Permission
Obs: isso não testa permissões escritas em anotações de método ou classe
"""
for app in sapl_appconfs:
# readequa permissões dos models adicionando
# list e detail permissions
create_perms_post_migrate(app)
key, url, var, app_name = url_item
url = '/' + (url % {v: 1 for v in var})
assert '\n' not in url, """
A url (%s) da app (%s) está mal formada.
""" % (app_name, url)
view = None
if hasattr(key, 'view_class'):
view = key.view_class
if hasattr(view, 'permission_required'):
if isinstance(view.permission_required, six.string_types):
perms = (view.permission_required, )
else:
perms = view.permission_required
if not perms:
return
for perm in perms:
if perm[0] == '.' and perm[-1] == '_':
model = None
if hasattr(view, 'model') and view.model:
model = view.model
elif hasattr(view, 'filterset_class'):
model = view.fielterset_class._meta.model
elif hasattr(view, 'form_class'):
model = view.form_class._meta.model
assert model, _('model %s não localizado em %s'
) % (model, view)
codename = perm[1:] + view.model._meta.model_name
else:
codename = perm
codename = codename.split('.')
if len(codename) == 1:
content_type = ContentType.objects.get_by_natural_key(
app_label=model._meta.app_label,
model=model._meta.model_name)
p = Permission.objects.filter(
content_type=content_type,
codename=codename[0]).exists()
elif len(codename) == 2:
p = Permission.objects.filter(
content_type__app_label=codename[0],
codename=codename[1]).exists()
assert p, _('Permissão (%s) na view (%s) não existe.') % (
codename,
view)
_lista_permissions_in_decorators = lista_permissions_in_decorators()
@pytest.mark.django_db(transaction=False)
@pytest.mark.parametrize('permission', _lista_permissions_in_decorators)
def test_permission_required_of_decorators(permission):
"""
testa se, nos decorators permission_required com ou sem method_decorator
as permissões fixas escritas manualmente realmente exitem em Permission
"""
for app in sapl_appconfs:
# readequa permissões dos models adicionando
# list e detail permissions
create_perms_post_migrate(app)
codename = permission[0].split('.')
p = Permission.objects.filter(
content_type__app_label=codename[0],
codename=codename[1]).exists()
assert p, _('Permissão (%s) na view (%s) não existe.') % (
permission[0],
permission[1])

21
sapl/sessao/forms.py

@ -12,7 +12,9 @@ from sapl.crispy_layout_mixin import form_actions, to_row
from sapl.materia.forms import MateriaLegislativaFilterSet
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.parlamentares.models import Parlamentar
from sapl.utils import RANGE_DIAS_MES, RANGE_MESES, autor_label, autor_modal
from sapl.utils import (RANGE_DIAS_MES, RANGE_MESES,
MateriaPesquisaOrderingFilter, autor_label,
autor_modal)
from .models import (Bancada, ExpedienteMateria, Orador, OradorExpediente,
OrdemDia, SessaoPlenaria, SessaoPlenariaPresenca)
@ -21,7 +23,10 @@ from .models import (Bancada, ExpedienteMateria, Orador, OradorExpediente,
def recupera_anos():
try:
anos_list = SessaoPlenaria.objects.all().dates('data_inicio', 'year')
anos = [(k.year, k.year) for k in anos_list]
# a listagem deve ser em ordem descrescente, mas por algum motivo
# a adicao de .order_by acima depois do all() nao surte efeito
# apos a adicao do .dates(), por isso o reversed() abaixo
anos = [(k.year, k.year) for k in reversed(anos_list)]
return anos
except:
return []
@ -203,6 +208,8 @@ class SessaoPlenariaFilterSet(django_filters.FilterSet):
class AdicionarVariasMateriasFilterSet(MateriaLegislativaFilterSet):
o = MateriaPesquisaOrderingFilter()
class Meta:
model = MateriaLegislativa
fields = ['numero',
@ -212,20 +219,12 @@ class AdicionarVariasMateriasFilterSet(MateriaLegislativaFilterSet):
'data_apresentacao',
'data_publicacao',
'autoria__autor__tipo',
# 'autoria__autor__partido',
# FIXME 'autoria__autor__partido',
'relatoria__parlamentar_id',
'local_origem_externa',
'em_tramitacao',
]
order_by = (
('', 'Selecione'),
('dataC', 'Data, Tipo, Ano, Numero - Ordem Crescente'),
('dataD', 'Data, Tipo, Ano, Numero - Ordem Decrescente'),
('tipoC', 'Tipo, Ano, Numero, Data - Ordem Crescente'),
('tipoD', 'Tipo, Ano, Numero, Data - Ordem Decrescente')
)
def __init__(self, *args, **kwargs):
super(MateriaLegislativaFilterSet, self).__init__(*args, **kwargs)

27
sapl/sessao/views.py

@ -2,6 +2,7 @@ from datetime import datetime
from re import sub
from django.contrib import messages
from django.contrib.auth.decorators import permission_required
from django.core.exceptions import (ObjectDoesNotExist, MultipleObjectsReturned,
ValidationError)
from django.core.urlresolvers import reverse
@ -61,11 +62,9 @@ TipoResultadoVotacaoCrud = CrudAux.build(
def reordernar_materias_expediente(request, pk):
expedientes = ExpedienteMateria.objects.filter(
sessao_plenaria_id=pk)
exp_num = 1
for e in expedientes:
for exp_num, e in enumerate(expedientes, 1):
e.numero_ordem = exp_num
e.save()
exp_num += 1
return HttpResponseRedirect(
reverse('sapl.sessao:expedientemateria_list', kwargs={'pk': pk}))
@ -74,17 +73,15 @@ def reordernar_materias_expediente(request, pk):
def reordernar_materias_ordem(request, pk):
ordens = OrdemDia.objects.filter(
sessao_plenaria_id=pk)
ordem_num = 1
for o in ordens:
for ordem_num, o in enumerate(ordens, 1):
o.numero_ordem = ordem_num
o.save()
ordem_num += 1
return HttpResponseRedirect(
reverse('sapl.sessao:ordemdia_list', kwargs={'pk': pk}))
@permission_required_for_app(app_label=apps.AppConfig.label)
@permission_required('sessao.change_expedientemateria')
def abrir_votacao_expediente_view(request, pk, spk):
existe_votacao_aberta = ExpedienteMateria.objects.filter(
sessao_plenaria_id=spk, votacao_aberta=True
@ -101,7 +98,7 @@ def abrir_votacao_expediente_view(request, pk, spk):
reverse('sapl.sessao:expedientemateria_list', kwargs={'pk': spk}))
@permission_required_for_app(app_label=apps.AppConfig.label)
@permission_required('sessao.change_ordemdia')
def abrir_votacao_ordem_view(request, pk, spk):
existe_votacao_aberta = OrdemDia.objects.filter(
sessao_plenaria_id=spk, votacao_aberta=True
@ -524,14 +521,12 @@ class PresencaView(FormMixin, PresencaMixin, DetailView):
_('Presença'), self.object)
return context
@method_decorator(permission_required_for_app(AppConfig.label))
@method_decorator(permission_required(
'sessao.add_sessaoplenariapresenca'))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if not self.request.user.has_module_perms(AppConfig.label):
return self.form_invalid(form)
if form.is_valid():
# Pegar os presentes salvos no banco
presentes_banco = SessaoPlenariaPresenca.objects.filter(
@ -604,7 +599,7 @@ class PresencaOrdemDiaView(FormMixin, PresencaMixin, DetailView):
_('Presença Ordem do Dia'), self.object)
return context
@method_decorator(permission_required_for_app(AppConfig.label))
@method_decorator(permission_required('sessao.add_presencaordemdia'))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
@ -684,7 +679,7 @@ class ListMateriaOrdemDiaView(FormMixin, DetailView):
return self.render_to_response(context)
@method_decorator(permission_required_for_app(AppConfig.label))
@method_decorator(permission_required('sessao.change_ordemdia'))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
@ -786,7 +781,7 @@ class MesaView(FormMixin, DetailView):
_('Mesa Diretora'), self.object)
return context
@method_decorator(permission_required_for_app(AppConfig.label))
@method_decorator(permission_required('sessao.change_integrantemesa'))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = MesaForm(request.POST)
@ -1041,7 +1036,7 @@ class ExpedienteView(FormMixin, DetailView):
_('Expediente Diversos'), self.object)
return context
@method_decorator(permission_required_for_app(AppConfig.label))
@method_decorator(permission_required('sessao.add_expedientesessao'))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = ExpedienteForm(request.POST)

9
sapl/settings.py

@ -53,7 +53,10 @@ SAPL_APPS = (
'sapl.painel',
'sapl.protocoloadm',
'sapl.compilacao',
'sapl.api'
'sapl.api',
'sapl.rules'
)
INSTALLED_APPS = (
@ -129,8 +132,8 @@ TEMPLATES = [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
"django.core.context_processors.media",
"django.core.context_processors.static",
"django.template.context_processors.media",
"django.template.context_processors.static",
'django.contrib.messages.context_processors.messages',
'sapl.context_processors.parliament_info',
],

9
sapl/static/styles/compilacao.scss

@ -1513,8 +1513,13 @@ a:link:after, a:visited:after {
}
@media print {
.cp .vigencias, .toggle-topbar, .menu-icon, .button {
.cp .vigencias, .toggle-topbar, .menu-icon, .button, .tipo-vigencias, .dne {
display:none;
}
#btn_font_menos, #btn_font_mais {
display:none;
}
.container {
width: 100%;
}
}

3
sapl/templates/base.html

@ -146,6 +146,8 @@
{% endblock content_container %}
{% block footer_container %}
<footer id="footer" class="footer page__row">
<div class="container">
@ -196,6 +198,7 @@
</div>
</footer>
</div>
{% endblock footer_container %}
{% block foot_js %}
<!-- Bootstrap core JavaScript ================================================== -->

49
sapl/templates/base/appconfig_list.html

@ -1,49 +0,0 @@
{% extends "crud/list.html" %}
{% load i18n %}
{% load common_tags %}
{% block base_content %}
<div class="actions btn-group pull-right" role="group">
{% if user|get_config_not_exists %}
<a href="{{ view.create_url }}" class="btn btn-default">
{% blocktrans with verbose_name=view.verbose_name %} Adicionar {{ verbose_name }} {% endblocktrans %}
</a>
{% endif %}
{% block more_buttons %}{% endblock more_buttons %}
</div>
<br/><br/>
{% block extra_content %} {% endblock %}
{% if not rows %}
<p>{{ NO_ENTRIES_MSG }}</p>
{% else %}
<table class="table table-striped table-hover">
<thead>
<tr>
{% for name in headers %}
<th>{{ name }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for value_list in rows %}
<tr>
{% for value, href in value_list %}
<td>
{% if href %}
<a href="{{ href }}">{{ value }}</a>
{% else %}
{{ value|safe }}
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% include "paginacao.html" %}
{% endblock %}

48
sapl/templates/compilacao/ajax_actions_dinamic_edit.html

@ -1,21 +1,23 @@
{% load i18n %}
<div class="btn-toolbar pull-right" role="toolbar" >
{% if perms.compilacao.change_dispositivo_registros_compilacao %}
{% if object.tipo_dispositivo.dispositivo_de_articulacao and object.tipo_dispositivo.dispositivo_de_alteracao %}
<div class="btn-group " role="group">
<button type="button" class="btn btn-default btn-sm btn-compila" pk="{{object.pk}}" action="get_form_revogacao" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{% trans "Registrar Revogação" %}
{% if object.tipo_dispositivo.dispositivo_de_articulacao and object.tipo_dispositivo.dispositivo_de_alteracao %}
<div class="btn-group " role="group">
<button type="button" class="btn btn-default btn-sm btn-compila" pk="{{object.pk}}" action="get_form_revogacao" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
Registrar Revogação
</button>
<button type="button" class="btn btn-default btn-sm btn-compila" pk="{{object.pk}}" action="get_form_alteracao" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
Registrar Alteração
</button>
<button type="button" class="btn btn-default btn-sm btn-compila" pk="{{object.pk}}" action="get_form_inclusao" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
Registrar Inclusão
</button>
</button>
<button type="button" class="btn btn-default btn-sm btn-compila" pk="{{object.pk}}" action="get_form_alteracao" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{% trans "Registrar Alteração" %}
</button>
<button type="button" class="btn btn-default btn-sm btn-compila" pk="{{object.pk}}" action="get_form_inclusao" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{% trans "Registrar Inclusão" %}
</button>
</div>
{%endif%}
</div>
{%endif%}
{% endif %}
<div class="btn-group " role="group">
<button type="button" class="btn btn-default btn-sm radius-right" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-edit fa-lg"></i>
@ -25,7 +27,9 @@
<li><a class="btn-editor-type" editortype="construct">Construtor</a></li>
<li><a class="btn-editor-type" editortype="textarea">Editor Simples</a></li>
<li><a class="btn-editor-type" editortype="tinymce">Editor Tinymce</a></li>
<li><a class="btn-editor-avancado" href="{% url 'sapl.compilacao:dispositivo_edit' object.ta_id object.pk %}" >Editor Avançado</a></li>
{% if perms.compilacao.change_dispositivo_edicao_avancada %}
<li><a class="btn-editor-avancado" href="{% url 'sapl.compilacao:dispositivo_edit' object.ta_id object.pk %}" >Editor Avançado</a></li>
{% endif %}
</ul>
</div>
</div>
@ -61,11 +65,13 @@
{% endfor %}
{%endif%}
</div>
{% if not object.ta_publicado and not object.dispositivo_subsequente and not object.tipo_dispositivo.dispositivo_de_alteracao%}
<div class="btn-group " role="group">
<button type="button" class="btn-action btn btn-xs radius-right {% if object.pk == object.dispositivo_vigencia_id %}btn-primary{%else%}btn-default{%endif%}" pk="{{object.pk}}" action="json_set_dvt" title="{% if object.pk == object.dispositivo_vigencia_id %}{% trans 'Dispositivo de Vigência Atual'%}{%else%}{% trans 'Tornar este o Dispositivo de Vigência de todo o Texto Articulado.'%}{%endif%}">
DVt
</button>
</div>
{%endif%}
{% if perms.compilacao.change_dispositivo_de_vigencia_global %}
{% if not object.ta_publicado and not object.dispositivo_subsequente and not object.tipo_dispositivo.dispositivo_de_alteracao%}
<div class="btn-group " role="group">
<button type="button" class="btn-action btn btn-xs radius-right {% if object.pk == object.dispositivo_vigencia_id %}btn-primary{%else%}btn-default{%endif%}" pk="{{object.pk}}" action="json_set_dvt" title="{% if object.pk == object.dispositivo_vigencia_id %}{% trans 'Dispositivo de Vigência Atual'%}{%else%}{% trans 'Tornar este o Dispositivo de Vigência de todo o Texto Articulado.'%}{%endif%}">
DVt
</button>
</div>
{% endif %}
{% endif %}
</div>

8
sapl/templates/compilacao/dispositivo_form.html

@ -10,10 +10,10 @@
{% url 'sapl.compilacao:dispositivo_edit_vigencia' object.ta_id object.pk as edit_vigencia_url %}
{% url 'sapl.compilacao:dispositivo_edit_alteracao' object.ta_id object.pk as edit_alteracao_url %}
{% url 'sapl.compilacao:dispositivo_edit_definidor_vigencia' object.ta_id object.pk as edit_definidor_vigencia_url %}
<li {% if request.get_full_path == edit_url %}class="active"{%endif%}><a class="btn-warning" href="{{ edit_url }}">{% trans 'Dados Básicos' %}</a></li>
<li {% if request.get_full_path == edit_vigencia_url %}class="active"{%endif%}><a class="btn-warning" href="{{ edit_vigencia_url }}" >{% trans 'Vigência' %}</a></li>
<li {% if request.get_full_path == edit_definidor_vigencia_url %}class="active"{%endif%}><a class="btn-danger" href="{{ edit_definidor_vigencia_url }}" >{% trans 'Definidor de Vigência' %}</a></li>
<li {% if request.get_full_path == edit_alteracao_url %}class="active"{%endif%}><a class="btn-danger" href="{{ edit_alteracao_url }}" >{% trans 'Alteração' %}</a></li>
{% if perms.compilacao.change_dispositivo_edicao_avancada %}<li {% if request.get_full_path == edit_url %}class="active"{%endif%}><a class="btn-warning" href="{{ edit_url }}">{% trans 'Dados Básicos' %}</a></li>{% endif %}
{% if perms.compilacao.change_dispositivo_edicao_avancada %}<li {% if request.get_full_path == edit_vigencia_url %}class="active"{%endif%}><a class="btn-warning" href="{{ edit_vigencia_url }}" >{% trans 'Vigência' %}</a></li>{% endif %}
{% if perms.compilacao.change_dispositivo_de_vigencia_global %}<li {% if request.get_full_path == edit_definidor_vigencia_url %}class="active"{%endif%}><a class="btn-danger" href="{{ edit_definidor_vigencia_url }}" >{% trans 'Definidor de Vigência' %}</a></li>{% endif %}
{% if perms.compilacao.change_dispositivo_registros_compilacao %}<li {% if request.get_full_path == edit_alteracao_url %}class="active"{%endif%}><a class="btn-danger" href="{{ edit_alteracao_url }}" >{% trans 'Alteração' %}</a></li>{% endif %}
</ul>
{% endblock sections_nav %}{% trans '' %}

2
sapl/templates/compilacao/publicacao_detail.html

@ -8,7 +8,7 @@
{% block actions %}
<div class="actions btn-group pull-right" role="group">
<a href="{% url 'sapl.compilacao:ta_pub_edit' object.ta.pk object.pk %}" class="btn btn-default">{% trans 'Editar' %}</a>
<a href="{% url 'sapl.compilacao:ta_pub_delete' object.ta.pk object.pk %}" class="btn btn-default">{% trans 'Excluir' %}</a>
<a href="{% url 'sapl.compilacao:ta_pub_delete' object.ta.pk object.pk %}" class="btn btn-default btn-excluir">{% trans 'Excluir' %}</a>
</div>
{% endblock actions %}
</div>

12
sapl/templates/compilacao/publicacao_list.html

@ -5,11 +5,13 @@
{% block base_content %}
<div class="actions btn-group pull-right" role="group">
<a href="{{ view.create_url }}" class="btn btn-default">
{% trans 'Adicionar'%} {%model_verbose_name 'sapl.compilacao.models.Publicacao'%}
</a>
</div>
{% if perms.compilacao.add_publicacao %}
<div class="actions btn-group pull-right" role="group">
<a href="{{ view.create_url }}" class="btn btn-default">
{% trans 'Adicionar'%} {%model_verbose_name 'sapl.compilacao.models.Publicacao'%}
</a>
</div>
{% endif %}
{% if not object_list %}
<p>{{ NO_ENTRIES_MSG }}</p>

20
sapl/templates/compilacao/text_edit.html

@ -11,18 +11,24 @@
{% endblock %}
{% block title%}
<h1>{{object }}. <small><i>{% trans 'Texto Multivigente em Edição' %}</i></small></h1>
<h1 class="page-header">{{object }}. <small><i>{% trans 'Texto Multivigente em Edição' %}</i></small></h1>
{% endblock %}
{% block actions %}
<div class="clearfix">
<div class="actions btn-toolbar pull-right" role="toolbar">
<div class="actions btn-group" role="group">
<a href="{% url 'sapl.compilacao:ta_edit' object.pk %}" class="btn btn-default">{% trans 'Editar Metadados do Texto Articulado' %}</a>
{% include 'compilacao/textoarticulado_menu_config.html' %}
{% if perms.compilacao.change_textoarticulado %}
<div class="clearfix">
<div class="actions btn-toolbar pull-right" role="toolbar">
<div class="actions btn-group" role="group">
{% if perms.compilacao.lock_unlock_textoarticulado and not object.editable_only_by_owners%}
<a href="{% url 'sapl.compilacao:ta_text_edit' object.pk %}?{% if object.editing_locked %}unlock{%else%}lock{% endif %}" class="btn btn-default btn-excluir">{% if object.editing_locked %}{% trans 'Desbloquear Edição' %}{%else%}{% trans 'Bloquear Edição' %}{% endif %}</a>
{% endif %}
<a href="{% url 'sapl.compilacao:ta_edit' object.pk %}" class="btn btn-default">{% trans 'Editar Metadados do Texto Articulado' %}</a>
{% include 'compilacao/textoarticulado_menu_config.html' %}
</div>
</div>
</div>
</div>
{% endif %}
{% endblock actions %}
{% block base_content %}{{block.super}}

26
sapl/templates/compilacao/text_list.html

@ -9,17 +9,27 @@
{{block.super}}
<link rel="stylesheet" href="{% sass_src 'styles/compilacao.scss' %}" type="text/css">
{% endblock %}
{% block extra_sections_nav %}
<li><a href="{% url 'sapl.compilacao:ta_text' object.pk %}?print">{% trans 'Versão para Impressão' %}</a></li>
{% endblock %}
{% block base_content %}
{% block actions %}
<div class="clearfix">
<div class="actions btn-group pull-right" role="group">
{% if user.is_authenticated %}
<a href="{% url 'sapl.compilacao:ta_text_edit' object.pk %}" class="btn btn-default">{% trans 'Editar Texto' %}</a>
{% endif %}
{{block.super}}
{% endblock %}
{% comment %}
{% if perms.compilacao.change_dispositivo_edicao_dinamica %}
{% block actions %}
<div class="clearfix">
<div class="actions btn-group pull-right" role="group">
<a href="{% url 'sapl.compilacao:ta_text_edit' object.pk %}" class="btn btn-default">{% trans 'Editar Texto' %}</a>
</div>
</div>
</div>
{% endblock actions %}
{% endblock actions %}
{% endif %}
{% endcomment %}
{% block detail_content %}
{{block.super}}

21
sapl/templates/compilacao/text_list__print_version.html

@ -0,0 +1,21 @@
{% extends "compilacao/text_list.html" %}
{% load i18n %}
{% load compilacao_filters %}
{% load common_tags %}
{% load staticfiles %}
{% load sass_tags %}
{% block navigation %}{% endblock %}
{% block sections_nav %}{% endblock %}
{% block extra_sections_nav %}{% endblock %}
{% block actions %}{% endblock %}
{% block dsp_actions %}{% endblock %}
{% block detail_content %}{% endblock %}
{% block footer_container %}{% endblock %}
{% block foot_js %}{{block.super}}
<script type="text/javascript" src="{% static 'js/compilacao.js' %}"></script>
<script type="text/javascript" src="{% static 'js/compilacao_view.js' %}"></script>
{% endblock %}

22
sapl/templates/compilacao/text_list_bloco.html

@ -44,18 +44,18 @@
{% endif %}
</a>
{% endif %}
{% if user.is_authenticated and not dpt.tipo_dispositivo.dispositivo_de_articulacao%}
{% if perms.compilacao.add_nota or perms.compilacao.add_vide or perms.compilacao.change_dispositivo%}
<div class="dne" id="dne{{dpt.pk}}" pk="{{dpt.pk}}">
<ul class="btns-action">
{% if perms.compilacao.change_dispositivo %}<li><a href = "{% url 'sapl.compilacao:ta_text_edit' dpt.ta.pk%}#{{dpt.pk}}" class="btn-action" title="{% trans 'Editar Dispositivo'%}">Ed</a></li>{% endif %}
{% if perms.compilacao.add_nota %}<li><a class="btn-action btn-nota-create" model="nota" pk="{{dpt.pk}}" title="{% trans 'Adicionar Nota'%}">N</a></li>{% endif %}
{% if perms.compilacao.add_vide %}<li><a class="btn-action btn-vide-create" model="vide" pk="{{dpt.pk}}" title="{% trans 'Adicionar Vide'%}">V</a></li>{% endif %}
</ul>
<div class="dne-form clearfix"></div>
</div>
{% if user.is_authenticated and not dpt.tipo_dispositivo.dispositivo_de_articulacao%}
{% if perms.compilacao.add_nota or perms.compilacao.add_vide or perms.compilacao.change_dispositivo%}
<div class="dne" id="dne{{dpt.pk}}" pk="{{dpt.pk}}">
<ul class="btns-action">
{% if perms.compilacao.change_dispositivo %}<li><a href = "{% url 'sapl.compilacao:ta_text_edit' dpt.ta.pk%}#{{dpt.pk}}" class="btn-action" title="{% trans 'Editar Dispositivo'%}">Ed</a></li>{% endif %}
{% if perms.compilacao.add_nota %}<li><a class="btn-action btn-nota-create" model="nota" pk="{{dpt.pk}}" title="{% trans 'Adicionar Nota'%}">N</a></li>{% endif %}
{% if perms.compilacao.add_vide %}<li><a class="btn-action btn-vide-create" model="vide" pk="{{dpt.pk}}" title="{% trans 'Adicionar Vide'%}">V</a></li>{% endif %}
</ul>
<div class="dne-form clearfix"></div>
</div>
{% endif %}
{% endif %}
{% endif %}
</div>
{% if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}

41
sapl/templates/compilacao/textoarticulado_detail.html

@ -7,15 +7,23 @@
<ul class="nav nav-pills navbar-right">
{%if object %}
<li>
{% if object.content_object%}
<a href="{% url object|urldetail_content_type object.content_object.pk %}" title="{% trans 'Ir para '%}{{object.content_object}}">Início</a>
{% 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>
{% elif object.content_object%}
<a href="{% url object|urldetail_content_type object.content_object.pk %}" title="{% trans 'Voltar para '%}{{object.content_object}}">{% trans 'Voltar para '%}{{object.content_object}}</a>
{%else%}
<a href="{% url 'sapl.compilacao:ta_detail' object.pk %}">{% trans 'Início' %}</a>
{%endif%}
</li>
<li><a href="{% url 'sapl.compilacao:ta_pub_list' object.pk %}">{% model_verbose_name_plural 'sapl.compilacao.models.Publicacao' %}</a></li>
<li><a href="{% url 'sapl.compilacao:ta_text_notificacoes' object.pk %}">{% trans 'Notificações' %}</a></li>
<li><a href="{% url 'sapl.compilacao:ta_text' object.pk %}">{% trans 'Texto' %}</a></li>
{% if object.tipo_ta.publicacao_func %}
<li><a href="{% url 'sapl.compilacao:ta_pub_list' object.pk %}">{% model_verbose_name_plural 'sapl.compilacao.models.Publicacao' %}</a></li>
{% endif %}
{% if perms.compilacao.view_dispositivo_notificacoes %}
<li><a href="{% url 'sapl.compilacao:ta_text_notificacoes' object.pk %}">{% trans 'Notificações' %}</a></li>
{% endif %}
{% block extra_sections_nav %}
<li><a href="{% url 'sapl.compilacao:ta_text' object.pk %}">{% trans 'Texto' %}</a></li>
{% endblock %}
{% endif %}
</ul>
{% endblock %}
@ -24,11 +32,18 @@
{% block actions %}
<div class="clearfix">
<div class="actions btn-group pull-right" role="group">
{% if user.is_authenticated %}
{% if perms.compilacao.lock_unlock_textoarticulado and not object.editable_only_by_owners%}
<a href="{% url 'sapl.compilacao:ta_text_edit' object.pk %}?{% if object.editing_locked %}unlock{%else%}lock{% endif %}" class="btn btn-default btn-excluir">{% if object.editing_locked %}{% trans 'Desbloquear Edição' %}{%else%}{% trans 'Bloquear Edição' %}{% endif %}</a>
{% endif %}
{% if perms.compilacao.change_textoarticulado and object|can_use_dynamic_editing:user %}
<a href="{% url 'sapl.compilacao:ta_edit' object.pk %}" class="btn btn-default">{% trans 'Editar Metadados do Texto Articulado' %}</a>
<a href="{% url 'sapl.compilacao:ta_text_edit' object.pk %}" class="btn btn-default">{% trans 'Editar Texto' %}</a>
{% endif %}
{% if object|can_use_dynamic_editing:user %}
<a href="{% url 'sapl.compilacao:ta_text_edit' object.pk %}" class="btn btn-default">{% trans 'Editar Texto' %}</a>
{% endif %}
</div>
</div>
{% endblock actions %}
@ -74,14 +89,14 @@
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="div_id_ementa" class="holder">
<label>{% field_verbose_name object 'ementa' %}</label>
<p>{{ object.ementa|safe}}</p>
</div>
<div class="row">
<div class="col-md-12">
<div id="div_id_ementa" class="holder">
<label>{% field_verbose_name object 'ementa' %}</label>
<p>{{ object.ementa|safe}}</p>
</div>
</div>
</div>
</fieldset>
{% endblock detail_content %}
{% endblock base_content %}

14
sapl/templates/compilacao/textoarticulado_list.html

@ -8,12 +8,14 @@
{% endblock detail_content %}
{% block actions %}
<div class="actions btn-group pull-right clearfix" role="group">
<a href="{{ view.create_url }}" class="btn btn-default">
{% trans 'Adicionar'%} {%model_verbose_name 'sapl.compilacao.models.TextoArticulado'%}
</a>
{% include 'compilacao/textoarticulado_menu_config.html' %}
</div>
{% if perms.compilacao.add_textoarticulado %}
<div class="actions btn-group pull-right clearfix" role="group">
<a href="{{ view.create_url }}" class="btn btn-default">
{% trans 'Adicionar'%} {%model_verbose_name 'sapl.compilacao.models.TextoArticulado'%}
</a>
{% include 'compilacao/textoarticulado_menu_config.html' %}
</div>
{% endif %}
{% endblock actions %}
<table class="table table-striped table-hover">

18
sapl/templates/compilacao/textoarticulado_menu_config.html

@ -4,11 +4,15 @@
<i class="fa fa-cog fa-1x fa-fw"></i>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="{% url 'sapl.compilacao:tipo_ta_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoTextoArticulado'%}</a></li>
<li><a href="{% url 'sapl.compilacao:tipopublicacao_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoPublicacao'%}</a></li>
<li><a href="{% url 'sapl.compilacao:veiculopublicacao_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.VeiculoPublicacao'%}</a></li>
<li><a href="{% url 'sapl.compilacao:tiponota_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoNota'%}</a></li>
<li><a href="{% url 'sapl.compilacao:tipovide_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoVide'%}</a></li>
<li><a href="{% url 'sapl.compilacao:tipodispositivo_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoDispositivo'%}</a></li>
<li><a href="#">TODO: Perfil Estrutural de Textos Articulados</a></li>
{% if perms.compilacao.list_tipotextoarticulado %}<li><a href="{% url 'sapl.compilacao:tipo_ta_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoTextoArticulado'%}</a></li>{% endif %}
{% if perms.compilacao.list_tipo_publicacao %}<li><a href="{% url 'sapl.compilacao:tipopublicacao_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoPublicacao'%}</a></li>{% endif %}
{% if perms.compilacao.list_veiculopublicacao %}<li><a href="{% url 'sapl.compilacao:veiculopublicacao_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.VeiculoPublicacao'%}</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 user.is_superuser %}
<li><a href="{% url 'sapl.compilacao:tipodispositivo_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoDispositivo'%}</a></li>
<li><a href="#">TODO: Perfil Estrutural de Textos Articulados</a></li>
{% endif %}
</ul>

30
sapl/templates/compilacao/tipotextoarticulado_detail.html

@ -9,8 +9,8 @@
<div class="clearfix">
{% block actions %}
<div class="actions btn-group pull-right" role="group">
<a class="btn btn-default" href="{% url 'sapl.compilacao:tipo_ta_edit' object.pk %}">{% trans 'Editar' %}</a>
<a class="btn btn-default" href="{% url 'sapl.compilacao:tipo_ta_delete' object.pk %}">{% trans 'Excluir' %}</a>
{% if perms.compilacao.change_tipotextoarticulado %}<a class="btn btn-default" href="{% url 'sapl.compilacao:tipo_ta_edit' object.pk %}">{% trans 'Editar' %}</a>{% endif %}
{% if perms.compilacao.delete_tipotextoarticulado %}<a class="btn btn-default btn-excluir" href="{% url 'sapl.compilacao:tipo_ta_delete' object.pk %}">{% trans 'Excluir' %}</a>{% endif %}
</div>
{% endblock actions %}
</div>
@ -19,33 +19,43 @@
<fieldset>
<legend>{%trans 'Identificação Básica'%}</legend>
<div class="row">
<div class="col-md-2">
<div class="col-md-3">
<div id="div_id_tipo" class="holder">
<label>{% field_verbose_name object 'sigla' %}</label>
<p>{{ object.sigla}}</p>
</div>
</div>
<div class="col-md-4">
<div class="col-md-5">
<div id="div_id_numero" class="holder">
<label>{% field_verbose_name object 'descricao' %}</label>
<p>{{ object.descricao}}</p>
</div>
</div>
<div class="col-md-3">
<div class="col-md-4">
<div id="div_id_ano" class="holder">
<label>{% field_verbose_name object 'content_type' %}</label>
<p>{{ object.content_type|default:""}}</p>
</div>
</div>
<div class="col-md-3">
<div id="div_id_ano" class="holder">
<label>{% field_verbose_name object 'participacao_social' %}</label>
<p>{{ object.get_participacao_social_display}}</p>
</div>
</fieldset>
<fieldset>
<legend>{%trans 'Funcionalidades'%}</legend>
<div class="row">
<div class="col-md-3">
<div id="div_id_ano" class="holder">
<label>{% field_verbose_name object 'participacao_social' %}</label>
<p>{{ object.get_participacao_social_display}}</p>
</div>
</div>
<div class="col-md-3">
<div id="div_id_ano" class="holder">
<label>{% field_verbose_name object 'publicacao_func' %}</label>
<p>{{ object.get_publicacao_func_display}}</p>
</div>
</div>
</div>
</fieldset>
{% endblock detail_content %}
{% endblock base_content %}

12
sapl/templates/compilacao/tipotextoarticulado_list.html

@ -5,11 +5,13 @@
{% block base_content %}
{% block actions %}
<div class="actions btn-group pull-right" role="group">
<a href="{{ view.create_url }}" class="btn btn-default">
{% trans 'Adicionar'%} {%model_verbose_name 'sapl.compilacao.models.TipoTextoArticulado'%}
</a>
</div>
{% if perms.compilacao.add_tipotextoarticulado %}
<div class="actions btn-group pull-right" role="group">
<a href="{{ view.create_url }}" class="btn btn-default">
{% trans 'Adicionar'%} {%model_verbose_name 'sapl.compilacao.models.TipoTextoArticulado'%}
</a>
</div>
{% endif %}
{% endblock actions %}
{% if not object_list %}

53
sapl/templates/crud/detail.html

@ -21,6 +21,15 @@
</a>
{% endif %}
</div>
{% if view.extras_url %}
<div class="actions btn-group btn-group-sm" role="group">
{% for url, css_class, text in view.extras_url %}
<a href="{{url}}" class="btn btn-default {{css_class}}">
{{text}}
</a>
{% endfor %}
</div>
{% endif %}
{% endblock sub_actions %}
<div class="editons pull-right">
@ -41,30 +50,32 @@
{% endblock actions %}
</div>
{% block detail_content %}
{% for fieldset in view.layout_display %}
<h2 class="legend">{{ fieldset.legend }}</h2>
{% for row in fieldset.rows %}
<div class="row-fluid">
{% for column in row %}
<div class="col-sm-{{ column.span }}">
<div id="div_id_{{ column.id }}" class="form-group">
<p class="control-label">{{ column.verbose_name }}</p>
<div class="controls">
{% comment %}TODO Transformar os links em URLs diretamente no CRUD{% endcomment %}
{% if column.text|url %}
<div class="form-control-static"><a href="{{ column.text|safe }}"> {{ column.text|safe }} </a></div>
{% else %}
<div class="form-control-static">{{ column.text|safe }}</div>
{% endif %}
<div class="container-detail clearfix">
{% block detail_content %}
{% for fieldset in view.layout_display %}
<h2 class="legend">{{ fieldset.legend }}</h2>
{% for row in fieldset.rows %}
<div class="row-fluid">
{% for column in row %}
<div class="col-sm-{{ column.span }}">
<div id="div_id_{{ column.id }}" class="form-group">
<p class="control-label">{{ column.verbose_name }}</p>
<div class="controls">
{% comment %}TODO Transformar os links em URLs diretamente no CRUD{% endcomment %}
{% if column.text|url %}
<div class="form-control-static"><a href="{{ column.text|safe }}"> {{ column.text|safe }} </a></div>
{% else %}
<div class="form-control-static">{{ column.text|safe }}</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endfor %}
{% endfor %}
{% endfor %}
{% endblock detail_content %}
{% endblock detail_content %}
</div>
{% block table_content %}
<div class="container-table">

11
sapl/templates/crud/detail_detail.html

@ -18,6 +18,17 @@
</a>
{% endif %}
</div>
{% if view.extras_url %}
<div class="actions btn-group btn-group-sm" role="group">
{% for url, css_class, text in view.extras_url %}
<a href="{{url}}" class="btn btn-default {{css_class}}">
{{text}}
</a>
{% endfor %}
</div>
{% endif %}
{% if view.update_url or view.delete_url %}
<div class="actions btn-group pull-right " role="group">
{% if view.update_url %}

4
sapl/templates/materia/confirmar_proposicao.html

@ -7,7 +7,9 @@
{% block actions %}{{block.super}}
<div class="actions btn-group btn-group-sm pull-right" role="group">
{% if object.texto_articulado.exists %}
<a class="btn btn-default" href="{% url 'sapl.materia:proposicao_ta' object.pk%}">{% trans "Texto Eletrônico da Proposição" %}</a>
<a class="btn btn-default" href="{% url 'sapl.compilacao:ta_text' object.texto_articulado.first.pk%}?back_type=history">{% trans "Texto Eletrônico da Proposição" %}</a>
{% endif %}
{% if object.texto_original %}
<a class="btn btn-default" href="{{ object.texto_original.url }}">{% trans "Texto Original da Proposição" %}</a>

1
sapl/templates/materia/proposicao_detail.html

@ -123,5 +123,4 @@
</div>
</div>
{% endif %}
{% endblock detail_content %}

5
sapl/templates/materia/tipoproposicao_form.html

@ -22,13 +22,10 @@ $(document).ready(function(){
initial_select='';
radios.append(html_radio);
});
});
});
$('#id_content_type').trigger('change');
$("#div_id_tipo_conteudo_related_radio .controls").addClass('controls-radio-checkbox');
$('#id_content_type').trigger('change');
});

2
sapl/templates/navbar.yaml

@ -13,7 +13,7 @@
url: sapl.parlamentares:parlamentar_list
- title: {% trans 'Protocolo' %}
check_permission: protolcoloadm.list_protocolo
check_permission: protocoloadm.list_protocolo
children:
- title: {% trans 'Pesquisar Protocolo' %}
url: sapl.protocoloadm:protocolo

7
sapl/templates/norma/layouts.yaml

@ -1,4 +1,9 @@
{% load i18n %}
AssuntoNormaRelationship:
{% trans 'Assunto Norma Jurídica' %}:
- assunto
AssuntoNorma:
{% trans 'Assunto Norma Jurídica' %}:
- assunto descricao
@ -17,6 +22,7 @@ NormaJuridica:
- ementa
- indexacao
- observacao
- assuntos
NormaJuridicaCreate:
{% trans 'Identificação Básica' %}:
@ -28,6 +34,7 @@ NormaJuridicaCreate:
- ementa
- indexacao
- observacao
- assuntos
LegislacaoCitada:
{% trans 'Legislação Citada' %}:

1
sapl/templates/norma/list_pesquisa.html

@ -32,7 +32,6 @@
</tr>
{% endfor %}
</table>
{% include "paginacao.html" %}
{% else %}
<h2>Nenhum Registro recuperado</h2>
{% endif %}

29
sapl/templates/norma/normajuridica_form.html

@ -0,0 +1,29 @@
{% extends "crud/form.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load common_tags %}
{% block extra_js %}
<script language="Javascript">
function recuperar_materia() {
var tipo_materia = $("#id_tipo_materia").val()
var numero_materia = $("#id_numero_materia").val()
var ano_materia = $("#id_ano_materia").val()
if (tipo_materia && numero_materia && ano_materia) {
$.get("/sessao/recuperar-materia",{tipo_materia: tipo_materia,
numero_materia: numero_materia,
ano_materia: ano_materia},
function(data, status) {
$("#id_ementa").val(data.ementa);
});
}
}
var fields = ["#id_tipo_materia", "#id_numero_materia", "#id_ano_materia"]
for (i = 0; i < fields.length; i++) {
$(fields[i]).change(recuperar_materia);
}
</script>
{% endblock %}

2
sapl/templates/norma/subnav.yaml

@ -1,4 +1,5 @@
{% load i18n common_tags%}
- title: {% trans 'Início' %}
url: normajuridica_detail
@ -6,6 +7,7 @@
# para integração foram necessárias apenas criar a url norma_ta em urls.py
# e a view NormaTaView(IntegracaoTaView) em views.py
# Em nada mais a integração interfere em NormaJuridica
{% if 'texto_articulado_norma'|get_config_attr %}
- title: {% trans 'Texto' %}
url: norma_ta

4
sapl/templates/painel/controlador.html

@ -21,4 +21,8 @@ FECHADO
<input type="submit" name="stop-painel" value="Fechar Painel" class="button primary">
<input type="submit" name="save-painel" value="Salvar" class="button primary">
</form>
</br>
</br>
<a class="btn btn-primary" href="{{request.META.HTTP_REFERER}}">Voltar</a>
{% endblock %}

29
sapl/templates/protocoloadm/MateriaTemplate.html

@ -0,0 +1,29 @@
{% extends "base.html" %}
{% load i18n common_tags%}
{% block base_content %}
<div class="alert alert-success alert-dismissible fade in" role="alert">
<p align="center"><b><font color="green">Matéria procololada com sucesso!</font></b></p>
</div>
<div align="center">
<div class="row" style="width:50%;">
<div class="col-md-6">
<a onclick="window.open('{% url 'sapl.relatorios:relatorio_etiqueta_protocolo' protocolo.numero protocolo.ano %}','Comprovante','width=400, height=200')"class="btn btn-secondary">Imprimir Etiqueta</a>
</div>
<div class="col-md-6">
<a target="popup" class="btn btn-secondary" onclick="window.open('{% url 'sapl.protocoloadm:comprovante_protocolo' protocolo.pk %}','Comprovante','width=800, height=700')">Imprimir Comprovante</a>
</div>
</div>
<div class="row" style="width:50%;">
<div class="col-md-6">
<a href="{% url 'sapl.materia:materia_create_simplificado' protocolo.pk %}" class="btn btn-warning">Criar Matéria</a>
</div>
<div class="col-md-6">
<a href="{% url 'sapl.protocoloadm:protocolo_mostrar' protocolo.pk %}" class="btn btn-primary">Continuar</a>
</div>
</div>
</div>
{% endblock base_content %}

12
sapl/templates/protocoloadm/anular_protocoloadm.html

@ -1,7 +1,17 @@
{% extends "protocoloadm/protocoloadm_detail.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block actions %}
{{ block.super }}
<div class="actions btn-group pull-right grid-gutter-width-right " role="group">
<a href="{% url 'protocoloadm:protocolo' %}" class="btn btn-default">{% trans 'Fazer nova pesquisa' %}</a>
</div>
{% endblock %}
{% block detail_content %}
<div>{{ message }} </div>
{% crispy form %}
{% endblock detail_content %}
{% endblock detail_content %}

4
sapl/templates/protocoloadm/comprovante.html

@ -83,9 +83,5 @@
<th>Número Páginas</th>
<td>{{ protocolo.numero_paginas }}</td>
</tr>
<tr>
<th>Número Páginas</th>
<td>{{ protocolo.numero_paginas }}</td>
</tr>
</table>
{% endblock detail_content %}

11
sapl/templates/protocoloadm/protocolar_documento.html

@ -2,6 +2,15 @@
{% load i18n %}
{% load crispy_forms_tags %}
{% block actions %}
{{ block.super }}
<div class="actions btn-group pull-right grid-gutter-width-right " role="group">
<a href="{% url 'protocoloadm:protocolo' %}" class="btn btn-default">{% trans 'Fazer nova pesquisa' %}</a>
</div>
{% endblock %}
{% block detail_content %}
{% crispy form %}
{% endblock detail_content %}
{% endblock detail_content %}

10
sapl/templates/protocoloadm/protocolar_materia.html

@ -2,6 +2,14 @@
{% load i18n %}
{% load crispy_forms_tags %}
{% block actions %}
{{ block.super }}
<div class="actions btn-group pull-right grid-gutter-width-right " role="group">
<a href="{% url 'protocoloadm:protocolo' %}" class="btn btn-default">{% trans 'Fazer nova pesquisa' %}</a>
</div>
{% endblock %}
{% block detail_content %}
{% crispy form %}
{% endblock detail_content %}
{% endblock detail_content %}

36
sapl/templates/protocoloadm/protocolo_filter.html

@ -3,20 +3,25 @@
{% load crispy_forms_tags %}
{% load static %}
{% block sections_nav %} {% endblock %}
{% block base_content %}
<h1><b>Pesquisa de Protocolo</b></h1>
<br></br>
{% if filter_url %}
<div class="actions btn-group pull-right" role="group">
<a href="{% url 'protocoloadm:protocolo' %}" class="btn btn-default">{% trans 'Fazer nova pesquisa' %}</a>
</div>
{% endif %}
{% block actions %}
{% if not filter_url %}
{% crispy filter.form %}
{% endif %}
{{ block.super }}
{% if filter_url %}
<div class="actions btn-group pull-right grid-gutter-width-right " role="group">
<a href="{% url 'protocoloadm:protocolo' %}" class="btn btn-default">{% trans 'Fazer nova pesquisa' %}</a>
</div>
{% endif %}
{% endblock %}
{% block detail_content %}
<br>
{% if not filter_url %}
{% crispy filter.form %}
{% endif %}
{% if filter_url %}
<table class="table table-striped table-bordered">
@ -33,7 +38,7 @@
<tr>
<td>
<strong>Protocolo:
<a href="{% url 'protocoloadm:protocolo_mostrar' p.numero p.ano %}">{{ p.numero|stringformat:'06d' }}/{{ p.ano }}</a></strong>&nbsp;&nbsp;<strong>-</strong>&nbsp;&nbsp;
<a href="{% url 'protocoloadm:protocolo_mostrar' p.pk %}">{{ p.numero|stringformat:'06d' }}/{{ p.ano }}</a></strong>&nbsp;&nbsp;<strong>-</strong>&nbsp;&nbsp;
<a href="{% url 'relatorios:relatorio_etiqueta_protocolo' p.numero p.ano %}"><img src="{% static 'img/etiqueta.png' %}" alt="Etiqueta Individual"></a></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.hora|date:"G:i:s" }}</br>
@ -50,4 +55,7 @@
</table>
{% include "paginacao.html" %}
{% endif %}
{% endblock base_content %}
{% endblock detail_content %}
{% block table_content %}
{% endblock table_content %}

27
sapl/templates/protocoloadm/protocolo_mostrar.html

@ -15,9 +15,28 @@
<br />
<strong>Documento Vinculado:</strong></br>
<a href="{% url 'sapl.protocoloadm:criar_documento' protocolo.numero protocolo.ano %}" class="btn btn-primary">Criar Documento</a>
<strong>Documento Vinculado:</strong>
{% if protocolo.tipo_documento %}
{% if documento %}
<a href="{% url 'sapl.protocoloadm:documentoadministrativo_detail' documento.pk %}"> {{documento}} </a>
</br>
{% else %}
<br />
<a href="{% url 'sapl.protocoloadm:criar_documento' protocolo.pk %}" class="btn btn-primary">Criar Documento</a>
{% endif %}
{% elif protocolo.tipo_materia %}
{% if materia %}
<a href="{% url 'sapl.materia:materialegislativa_detail' materia.pk %}"> {{materia}} </a>
</br>
{% else %}
<br />
<a href="{% url 'sapl.materia:materia_create_simplificado' protocolo.pk %}" class="btn btn-primary">Criar Matéria</a>
{% endif %}
{% endif %}
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="{% url 'sapl.protocoloadm:comprovante_protocolo' protocolo.numero protocolo.ano %}" target="popup" class="btn btn-primary" onclick="window.open('{% url 'sapl.protocoloadm:comprovante_protocolo' protocolo.numero protocolo.ano %}','Comprovante','width=800, height=600')">Comprovante
<a target="popup" class="btn btn-primary" onclick="window.open('{% url 'sapl.protocoloadm:comprovante_protocolo' protocolo.pk%}','Comprovante','width=800, height=600')">Comprovante
</a>
{% endblock detail_content %}
{% endblock detail_content %}

14
sapl/templates/protocoloadm/protocoloadm_detail.html

@ -1,9 +1,9 @@
{% extends "crud/detail.html" %}
{% load i18n %}
{% block actions %}
<div class="actions btn-group pull-right" role="group">
<a href="{% url 'protocoloadm:protocolar_doc' %}" class="btn btn-default">{% trans 'Protocolar Documento' %}</a>
<a href="{% url 'protocoloadm:protocolar_mat' %}" class="btn btn-default">{% trans 'Protocolar Matéria' %}</a>
<a href="{% url 'protocoloadm:anular_protocolo' %}" class="btn btn-default">{% trans 'Anular Protocolo' %}</a>
</div>
{% endblock actions %}
{% block editions %}
<div class="actions btn-group pull-right" role="group">
<a href="{% url 'protocoloadm:protocolar_doc' %}" class="btn btn-default">{% trans 'Protocolar Documento' %}</a>
<a href="{% url 'protocoloadm:protocolar_mat' %}" class="btn btn-default">{% trans 'Protocolar Matéria' %}</a>
<a href="{% url 'protocoloadm:anular_protocolo' %}" class="btn btn-default btn-excluir">{% trans 'Anular Protocolo' %}</a>
</div>
{% endblock editions %}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save