Browse Source

Reformata código com isort e black

pull/3789/head
Edward Ribeiro 3 months ago
parent
commit
6eb348aa6a
  1. 9
      conftest.py
  2. 31
      docker/startup_scripts/create_admin.py
  3. 13
      docker/startup_scripts/genkey.py
  4. 1
      docker/startup_scripts/gunicorn.conf.py
  5. 152
      docker/startup_scripts/solr_cli.py
  6. 205
      drfautoapi/drfautoapi.py
  7. 2
      sapl/api/__init__.py
  8. 6
      sapl/api/apps.py
  9. 6
      sapl/api/deprecated.py
  10. 132
      sapl/api/forms.py
  11. 131
      sapl/api/pagination.py
  12. 51
      sapl/api/permissions.py
  13. 215
      sapl/api/serializers.py
  14. 46
      sapl/api/urls.py
  15. 40
      sapl/api/views.py
  16. 12
      sapl/api/views_audiencia.py
  17. 70
      sapl/api/views_base.py
  18. 20
      sapl/api/views_comissoes.py
  19. 15
      sapl/api/views_compilacao.py
  20. 62
      sapl/api/views_materia.py
  21. 15
      sapl/api/views_norma.py
  22. 14
      sapl/api/views_painel.py
  23. 68
      sapl/api/views_parlamentares.py
  24. 45
      sapl/api/views_protocoloadm.py
  25. 25
      sapl/api/views_sessao.py
  26. 2
      sapl/audiencia/__init__.py
  27. 6
      sapl/audiencia/apps.py
  28. 233
      sapl/audiencia/forms.py
  29. 185
      sapl/audiencia/models.py
  30. 82
      sapl/audiencia/tests/test_audiencia.py
  31. 8
      sapl/audiencia/urls.py
  32. 71
      sapl/audiencia/views.py
  33. 2
      sapl/base/__init__.py
  34. 4
      sapl/base/admin.py
  35. 6
      sapl/base/apps.py
  36. 177
      sapl/base/email_utils.py
  37. 1251
      sapl/base/forms.py
  38. 10
      sapl/base/management/commands/backfill_auditlog.py
  39. 493
      sapl/base/models.py
  40. 250
      sapl/base/receivers.py
  41. 136
      sapl/base/search_indexes.py
  42. 5
      sapl/base/templatetags/base_tags.py
  43. 112
      sapl/base/templatetags/common_tags.py
  44. 128
      sapl/base/templatetags/menus.py
  45. 28
      sapl/base/tests/test_base.py
  46. 43
      sapl/base/tests/test_form.py
  47. 56
      sapl/base/tests/test_login.py
  48. 393
      sapl/base/tests/test_view_base.py
  49. 4
      sapl/base/tests/teststub_urls.py
  50. 281
      sapl/base/urls.py
  51. 1226
      sapl/base/views.py
  52. 2
      sapl/comissoes/__init__.py
  53. 6
      sapl/comissoes/apps.py
  54. 455
      sapl/comissoes/forms.py
  55. 389
      sapl/comissoes/models.py
  56. 160
      sapl/comissoes/tests/test_comissoes.py
  57. 69
      sapl/comissoes/urls.py
  58. 328
      sapl/comissoes/views.py
  59. 2
      sapl/compilacao/__init__.py
  60. 8
      sapl/compilacao/admin.py
  61. 55
      sapl/compilacao/apps.py
  62. 1781
      sapl/compilacao/forms.py
  63. 1459
      sapl/compilacao/models.py
  64. 179
      sapl/compilacao/templatetags/compilacao_filters.py
  65. 78
      sapl/compilacao/tests/test_compilacao.py
  66. 43
      sapl/compilacao/tests/test_tipo_texto_articulado_form.py
  67. 237
      sapl/compilacao/urls.py
  68. 57
      sapl/compilacao/utils.py
  69. 3087
      sapl/compilacao/views.py
  70. 39
      sapl/context_processors.py
  71. 273
      sapl/crispy_layout_mixin.py
  72. 1079
      sapl/crud/base.py
  73. 68
      sapl/crud/tests/settings.py
  74. 6
      sapl/crud/tests/stub_app/models.py
  75. 5
      sapl/crud/tests/stub_app/urls.py
  76. 4
      sapl/crud/tests/stub_app/views.py
  77. 2
      sapl/crud/tests/test_base.py
  78. 17
      sapl/crud/tests/test_masterdetail.py
  79. 2
      sapl/crud/urls.py
  80. 23
      sapl/decorators.py
  81. 33
      sapl/endpoint_restriction_middleware.py
  82. 4
      sapl/hashers.py
  83. 332
      sapl/lexml/OAIServer.py
  84. 2
      sapl/lexml/__init__.py
  85. 6
      sapl/lexml/apps.py
  86. 24
      sapl/lexml/forms.py
  87. 60
      sapl/lexml/models.py
  88. 22
      sapl/lexml/urls.py
  89. 21
      sapl/lexml/views.py
  90. 2
      sapl/materia/__init__.py
  91. 6
      sapl/materia/admin.py
  92. 6
      sapl/materia/apps.py
  93. 2959
      sapl/materia/forms.py
  94. 1183
      sapl/materia/models.py
  95. 69
      sapl/materia/tests/test_email_templates.py
  96. 953
      sapl/materia/tests/test_materia.py
  97. 151
      sapl/materia/tests/test_materia_form.py
  98. 23
      sapl/materia/tests/test_materia_urls.py
  99. 387
      sapl/materia/urls.py
  100. 2683
      sapl/materia/views.py

9
conftest.py

@ -3,18 +3,17 @@ from django_webtest import DjangoTestApp, WebTestMixin
class OurTestApp(DjangoTestApp):
def __init__(self, *args, **kwargs):
self.default_user = kwargs.pop('default_user', None)
self.default_user = kwargs.pop("default_user", None)
super(OurTestApp, self).__init__(*args, **kwargs)
def get(self, *args, **kwargs):
kwargs.setdefault('user', self.default_user)
kwargs.setdefault('auto_follow', True)
kwargs.setdefault("user", self.default_user)
kwargs.setdefault("auto_follow", True)
return super(OurTestApp, self).get(*args, **kwargs)
@pytest.fixture(scope='function')
@pytest.fixture(scope="function")
def app(request, admin_user):
"""WebTest's TestApp.

31
docker/startup_scripts/create_admin.py

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

13
docker/startup_scripts/genkey.py

@ -2,10 +2,15 @@ import random
def generate_secret():
return (''.join([random.SystemRandom().choice(
'abcdefghijklmnopqrst'
'uvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]))
return "".join(
[
random.SystemRandom().choice(
"abcdefghijklmnopqrst" "uvwxyz0123456789!@#$%^&*(-_=+)"
)
for i in range(50)
]
)
if __name__ == '__main__':
if __name__ == "__main__":
print(generate_secret())

1
docker/startup_scripts/gunicorn.conf.py

@ -74,6 +74,7 @@ def on_starting(server):
def post_fork(server, worker):
try:
from django import db
db.connections.close_all()
except Exception:
# Django not initialized yet or not available

152
docker/startup_scripts/solr_cli.py

@ -40,7 +40,7 @@ SECURITY_FILE_TEMPLATE = """
}
"""
URL_PATTERN = 'https?://(([a-zA-Z0-9]+):([a-zA-Z0-9]+)@)?([a-zA-Z0-9.-]+)(:[0-9]{4})?'
URL_PATTERN = "https?://(([a-zA-Z0-9]+):([a-zA-Z0-9]+)@)?([a-zA-Z0-9.-]+)(:[0-9]{4})?"
def solr_hash_password(password: str, salt: str = None):
@ -57,15 +57,15 @@ def solr_hash_password(password: str, salt: str = None):
salt = secrets.token_bytes(32)
else:
salt = b64decode(salt)
m.update(salt + password.encode('utf-8'))
m.update(salt + password.encode("utf-8"))
digest = m.digest()
m = sha256()
m.update(digest)
digest = m.digest()
cypher = b64encode(digest).decode('utf-8')
salt = b64encode(salt).decode('utf-8')
cypher = b64encode(digest).decode("utf-8")
salt = b64encode(salt).decode("utf-8")
return cypher, salt
@ -81,18 +81,18 @@ def upload_security_file(zk_host):
zk_port = 9983 # embedded ZK port
logger.info(f"Uploading security file to Solr, ZK server={zk_host}:{zk_port}...")
try:
with open('security.json', 'r') as f:
with open("security.json", "r") as f:
data = f.read()
zk = KazooClient(hosts=f"{zk_host}:{zk_port}")
zk.start()
logger.info("Uploading security.json file...")
if zk.exists('/security.json'):
if zk.exists("/security.json"):
zk.set("/security.json", str.encode(data))
else:
zk.create("/security.json", str.encode(data))
data, stat = zk.get('/security.json')
data, stat = zk.get("/security.json")
logger.info("file uploaded!")
logger.info(data.decode('utf-8'))
logger.info(data.decode("utf-8"))
zk.stop()
except Exception as e:
logger.error(e)
@ -103,14 +103,17 @@ class SolrClient:
LIST_CONFIGSETS = "{}/solr/admin/configs?action=LIST&omitHeader=true&wt=json"
UPLOAD_CONFIGSET = "{}/solr/admin/configs?action=UPLOAD&name={}&wt=json"
LIST_COLLECTIONS = "{}/solr/admin/collections?action=LIST&wt=json"
STATUS_COLLECTION = "{}/solr/admin/collections?action=CLUSTERSTATUS" \
"&collection={}&wt=json"
STATUS_COLLECTION = (
"{}/solr/admin/collections?action=CLUSTERSTATUS" "&collection={}&wt=json"
)
STATUS_CORE = "{}/admin/cores?action=STATUS&name={}"
EXISTS_COLLECTION = "{}/solr/{}/admin/ping?wt=json"
OPTIMIZE_COLLECTION = "{}/solr/{}/update?optimize=true&wt=json"
CREATE_COLLECTION = "{}/solr/admin/collections?action=CREATE&name={}" \
"&collection.configName={}&numShards={}" \
CREATE_COLLECTION = (
"{}/solr/admin/collections?action=CREATE&name={}"
"&collection.configName={}&numShards={}"
"&replicationFactor={}&maxShardsPerNode={}&wt=json"
)
DELETE_COLLECTION = "{}/solr/admin/collections?action=DELETE&name={}&wt=json"
DELETE_DATA = "{}/solr/{}/update?commitWithin=1000&overwrite=true&wt=json"
QUERY_DATA = "{}/solr/{}/select?q=*:*"
@ -130,7 +133,7 @@ class SolrClient:
dic = res.json()
return dic["response"]["numFound"]
except Exception as e:
print(F"Erro no get_num_docs. Erro: {e}")
print(f"Erro no get_num_docs. Erro: {e}")
print(res.content)
return 0
@ -140,9 +143,9 @@ class SolrClient:
res = requests.get(req_url)
try:
dic = res.json()
return dic['collections']
return dic["collections"]
except Exception as e:
print(F"Erro no list_collections. Erro: {e}")
print(f"Erro no list_collections. Erro: {e}")
print(res.content)
return 0
@ -156,8 +159,8 @@ class SolrClient:
# zip files in memory
_zipfile = BytesIO()
with zipfile.ZipFile(_zipfile, 'w', zipfile.ZIP_DEFLATED) as zipf:
for file in base_path.rglob('*'):
with zipfile.ZipFile(_zipfile, "w", zipfile.ZIP_DEFLATED) as zipf:
for file in base_path.rglob("*"):
zipf.write(file, file.relative_to(base_path))
return _zipfile
except Exception as e:
@ -169,23 +172,26 @@ class SolrClient:
res = requests.get(req_url)
try:
dic = res.json()
configsets = dic['configSets']
configsets = dic["configSets"]
except Exception as e:
print(F"Erro ao configurar configsets. Erro: {e}")
print(f"Erro ao configurar configsets. Erro: {e}")
print(res.content)
# UPLOAD configset
if not self.CONFIGSET_NAME in configsets or force:
# GENERATE in memory configset
configset_zip = self.zip_configset()
data = configset_zip.getvalue()
configset_zip.close()
files = {'file': ('saplconfigset.zip',
files = {
"file": (
"saplconfigset.zip",
data,
'application/octet-stream',
{'Expires': '0'})}
"application/octet-stream",
{"Expires": "0"},
)
}
req_url = self.UPLOAD_CONFIGSET.format(self.url, self.CONFIGSET_NAME)
@ -193,16 +199,20 @@ class SolrClient:
print(resp.content)
else:
print('O %s já presente no servidor, NÃO enviando.' % self.CONFIGSET_NAME)
print("O %s já presente no servidor, NÃO enviando." % self.CONFIGSET_NAME)
def create_collection(self, collection_name, shards=1, replication_factor=1, max_shards_per_node=1):
def create_collection(
self, collection_name, shards=1, replication_factor=1, max_shards_per_node=1
):
self.maybe_upload_configset()
req_url = self.CREATE_COLLECTION.format(self.url,
req_url = self.CREATE_COLLECTION.format(
self.url,
collection_name,
self.CONFIGSET_NAME,
shards,
replication_factor,
max_shards_per_node)
max_shards_per_node,
)
res = requests.post(req_url)
if res.ok:
print("Collection '%s' created succesfully" % collection_name)
@ -210,15 +220,15 @@ class SolrClient:
print("Error creating collection '%s'" % collection_name)
try:
as_json = res.json()
print("Error %s: %s" % (res.status_code, as_json['error']['msg']))
print("Error %s: %s" % (res.status_code, as_json["error"]["msg"]))
except Exception as e:
print(F"Erro ao verificar erro na resposta. Erro: {e}")
print(f"Erro ao verificar erro na resposta. Erro: {e}")
print(res.content)
return False
return True
def delete_collection(self, collection_name):
if collection_name == '*':
if collection_name == "*":
collections = self.list_collections()
else:
collections = [collection_name]
@ -234,9 +244,11 @@ class SolrClient:
def delete_index_data(self, collection_name):
req_url = self.DELETE_DATA.format(self.url, collection_name)
res = requests.post(req_url,
data='<delete><query>*:*</query></delete>',
headers={'Content-Type': 'application/xml'})
res = requests.post(
req_url,
data="<delete><query>*:*</query></delete>",
headers={"Content-Type": "application/xml"},
)
if not res.ok:
print("Error deleting index for collection '%s'", collection_name)
print("Code {}: {}".format(res.status_code, res.text))
@ -257,33 +269,67 @@ def setup_embedded_zk(solr_url):
create_security_file(solr_user, solr_pwd)
upload_security_file(solr_host)
else:
print(f"Missing Solr's username, password, and host: {solr_user}/{solr_pwd}/{solr_host}")
print(
f"Missing Solr's username, password, and host: {solr_user}/{solr_pwd}/{solr_host}"
)
sys.exit(-1)
else:
print(f"Solr URL path doesn't match the required format: {solr_url}")
sys.exit(-1)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Cria uma collection no Solr')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Cria uma collection no Solr")
# required arguments
parser.add_argument('-u', type=str, metavar='URL', nargs=1, dest='url',
required=True, help='Endereço do servidor Solr na forma http(s)://<address>[:port]')
parser.add_argument('-c', type=str, metavar='COLLECTION', dest='collection', nargs=1,
required=True, help='Collection Solr a ser criada')
parser.add_argument(
"-u",
type=str,
metavar="URL",
nargs=1,
dest="url",
required=True,
help="Endereço do servidor Solr na forma http(s)://<address>[:port]",
)
parser.add_argument(
"-c",
type=str,
metavar="COLLECTION",
dest="collection",
nargs=1,
required=True,
help="Collection Solr a ser criada",
)
# optional arguments
parser.add_argument('-s', type=int, dest='shards', nargs='?',
help='Number of shards (default=1)', default=1)
parser.add_argument('-rf', type=int, dest='replication_factor', nargs='?',
help='Replication factor (default=1)', default=1)
parser.add_argument('-ms', type=int, dest='max_shards_per_node', nargs='?',
help='Max shards per node (default=1)', default=1)
parser.add_argument("--embedded_zk", default=False, action="store_true",
help="Embedded ZooKeeper")
parser.add_argument(
"-s",
type=int,
dest="shards",
nargs="?",
help="Number of shards (default=1)",
default=1,
)
parser.add_argument(
"-rf",
type=int,
dest="replication_factor",
nargs="?",
help="Replication factor (default=1)",
default=1,
)
parser.add_argument(
"-ms",
type=int,
dest="max_shards_per_node",
nargs="?",
help="Max shards per node (default=1)",
default=1,
)
parser.add_argument(
"--embedded_zk", default=False, action="store_true", help="Embedded ZooKeeper"
)
try:
args = parser.parse_args()
@ -305,10 +351,12 @@ if __name__ == '__main__':
## Add --clean option to clean uploadconfig and collection
if not client.exists_collection(collection):
print("Collection '%s' doesn't exists. Creating a new one..." % collection)
created = client.create_collection(collection,
created = client.create_collection(
collection,
shards=args.shards,
replication_factor=args.replication_factor,
max_shards_per_node=args.max_shards_per_node)
max_shards_per_node=args.max_shards_per_node,
)
if not created:
sys.exit(-1)
else:

205
drfautoapi/drfautoapi.py

@ -36,25 +36,22 @@ class SplitStringCharFilter(django_filters.CharFilter):
return qs
if self.distinct:
qs = qs.distinct()
lookup = '%s__%s' % (self.field_name, self.lookup_expr)
lookup = "%s__%s" % (self.field_name, self.lookup_expr)
values = [value]
if self.lookup_expr == 'icontains':
if self.lookup_expr == "icontains":
if not '"' in value:
values = value.split(' ')
values = value.split(" ")
else:
values = list(
filter(
lambda x: x and x != ' ' and x[0] != '"',
self._re.findall(value)
lambda x: x and x != " " and x[0] != '"',
self._re.findall(value),
)
) + list(
map(
lambda x: x[1:-1],
filter(
lambda x: x and x[0] == '"',
self._re.findall(value)
)
filter(lambda x: x and x[0] == '"', self._re.findall(value)),
)
)
@ -66,36 +63,34 @@ class SplitStringCharFilter(django_filters.CharFilter):
class ApiFilterSetMixin(FilterSet):
o = CharFilter(method='filter_o')
o = CharFilter(method="filter_o")
class Meta:
fields = '__all__'
fields = "__all__"
filter_overrides = {
FileField: {
'filter_class': django_filters.CharFilter,
'extra': lambda f: {
'lookup_expr': 'exact',
"filter_class": django_filters.CharFilter,
"extra": lambda f: {
"lookup_expr": "exact",
},
},
CharField: {
'filter_class': SplitStringCharFilter,
"filter_class": SplitStringCharFilter,
},
TextField: {
'filter_class': SplitStringCharFilter,
"filter_class": SplitStringCharFilter,
},
JSONField: {
'filter_class': django_filters.CharFilter,
'extra': lambda f: {
'lookup_expr': 'exact',
"filter_class": django_filters.CharFilter,
"extra": lambda f: {
"lookup_expr": "exact",
},
},
}
def filter_o(self, queryset, name, value):
try:
return queryset.order_by(
*map(str.strip, value.split(',')))
return queryset.order_by(*map(str.strip, value.split(",")))
except:
return queryset
@ -113,29 +108,34 @@ class ApiFilterSetMixin(FilterSet):
for f_str in fields_model:
if f_str not in fields:
f = model._meta.get_field(f_str)
if f.many_to_many:
fields[f_str] = ['exact']
fields[f_str] = ["exact"]
continue
fields[f_str] = ['exact']
fields[f_str] = ["exact"]
def get_keys_lookups(cl, sub_f):
r = []
for lk, lv in cl.items():
if lk in ('contained_by', 'trigram_similar', 'unaccent', 'search'):
if lk in (
"contained_by",
"trigram_similar",
"unaccent",
"search",
):
continue
sflk = f'{sub_f}{"__" if sub_f else ""}{lk}'
r.append(sflk)
if hasattr(lv, 'get_lookups'):
if hasattr(lv, "get_lookups"):
r += get_keys_lookups(lv.get_lookups(), sflk)
if hasattr(lv, 'output_field') and hasattr(lv, 'output_field.get_lookups'):
if hasattr(lv, "output_field") and hasattr(
lv, "output_field.get_lookups"
):
r.append(f'{sflk}{"__" if sflk else ""}range')
r += get_keys_lookups(lv.output_field.class_lookups, sflk)
@ -143,31 +143,30 @@ class ApiFilterSetMixin(FilterSet):
return r
fields[f_str] = list(
set(fields[f_str] + get_keys_lookups(f.get_lookups(), '')))
set(fields[f_str] + get_keys_lookups(f.get_lookups(), ""))
)
# Remove excluded fields
exclude = exclude or []
fields = [(f, lookups)
for f, lookups in fields.items() if f not in exclude]
fields = [(f, lookups) for f, lookups in fields.items() if f not in exclude]
return OrderedDict(fields)
@classmethod
def filter_for_field(cls, f, name, lookup_expr='exact'):
def filter_for_field(cls, f, name, lookup_expr="exact"):
# Redefine método estático para ignorar filtro para
# fields que não possuam lookup_expr informado
f, lookup_type = resolve_field(f, lookup_expr)
default = {
'field_name': name,
'label': capfirst(f.verbose_name),
'lookup_expr': lookup_expr
"field_name": name,
"label": capfirst(f.verbose_name),
"lookup_expr": lookup_expr,
}
filter_class, params = cls.filter_for_lookup(
f, lookup_type)
filter_class, params = cls.filter_for_lookup(f, lookup_type)
default.update(params)
if filter_class is not None:
return filter_class(**default)
@ -175,7 +174,7 @@ class ApiFilterSetMixin(FilterSet):
class BusinessRulesNotImplementedMixin:
http_method_names = ['get', 'head', 'options', 'trace']
http_method_names = ["get", "head", "options", "trace"]
def create(self, request, *args, **kwargs):
raise Exception(_("POST Create não implementado"))
@ -187,8 +186,7 @@ class BusinessRulesNotImplementedMixin:
raise Exception(_("DELETE Delete não implementado"))
class ApiViewSetConstrutor():
class ApiViewSetConstrutor:
_built_sets = {}
class ApiViewSet(ModelViewSet):
@ -212,13 +210,11 @@ class ApiViewSetConstrutor():
router = router_class()
for app, built_sets in cls._built_sets.items():
for model, viewset in built_sets.items():
router.register(
f'{app.label}/{model._meta.model_name}', viewset)
router.register(f"{app.label}/{model._meta.model_name}", viewset)
return router
@classmethod
def build_class(cls, apps_or_models):
DRFAUTOAPI = settings.DRFAUTOAPI
serializers_classes = {}
@ -229,35 +225,38 @@ class ApiViewSetConstrutor():
try:
if DRFAUTOAPI:
if 'DEFAULT_SERIALIZER_MODULE' in DRFAUTOAPI:
if "DEFAULT_SERIALIZER_MODULE" in DRFAUTOAPI:
serializers = importlib.import_module(
DRFAUTOAPI['DEFAULT_SERIALIZER_MODULE']
DRFAUTOAPI["DEFAULT_SERIALIZER_MODULE"]
)
serializers_classes = inspect.getmembers(serializers)
serializers_classes = {i[0]: i[1] for i in filter(
lambda x: x[0].endswith('Serializer'),
serializers_classes
)}
serializers_classes = {
i[0]: i[1]
for i in filter(
lambda x: x[0].endswith("Serializer"), serializers_classes
)
}
if 'DEFAULT_FILTER_MODULE' in DRFAUTOAPI:
if "DEFAULT_FILTER_MODULE" in DRFAUTOAPI:
filters = importlib.import_module(
DRFAUTOAPI['DEFAULT_FILTER_MODULE']
DRFAUTOAPI["DEFAULT_FILTER_MODULE"]
)
filters_classes = inspect.getmembers(filters)
filters_classes = {i[0]: i[1] for i in filter(
lambda x: x[0].endswith('FilterSet'),
filters_classes
)}
if 'GLOBAL_SERIALIZER_MIXIN' in DRFAUTOAPI:
cs = DRFAUTOAPI['GLOBAL_SERIALIZER_MIXIN'].split('.')
module = importlib.import_module(
'.'.join(cs[0:-1]))
filters_classes = {
i[0]: i[1]
for i in filter(
lambda x: x[0].endswith("FilterSet"), filters_classes
)
}
if "GLOBAL_SERIALIZER_MIXIN" in DRFAUTOAPI:
cs = DRFAUTOAPI["GLOBAL_SERIALIZER_MIXIN"].split(".")
module = importlib.import_module(".".join(cs[0:-1]))
global_serializer_mixin = getattr(module, cs[-1])
if 'GLOBAL_FILTERSET_MIXIN' in DRFAUTOAPI:
cs = DRFAUTOAPI['GLOBAL_FILTERSET_MIXIN'].split('.')
m = importlib.import_module('.'.join(cs[0:-1]))
if "GLOBAL_FILTERSET_MIXIN" in DRFAUTOAPI:
cs = DRFAUTOAPI["GLOBAL_FILTERSET_MIXIN"].split(".")
m = importlib.import_module(".".join(cs[0:-1]))
global_filter_class = getattr(m, cs[-1])
except Exception as e:
@ -268,45 +267,50 @@ class ApiViewSetConstrutor():
def build(_model):
object_name = _model._meta.object_name
serializer_name = f'{object_name}Serializer'
serializer_name = f"{object_name}Serializer"
_serializer_class = serializers_classes.get(
serializer_name, global_serializer_mixin)
serializer_name, global_serializer_mixin
)
filter_name = f'{object_name}FilterSet'
_filterset_class = filters_classes.get(
filter_name, global_filter_class)
filter_name = f"{object_name}FilterSet"
_filterset_class = filters_classes.get(filter_name, global_filter_class)
def create_class():
_meta_serializer = object if not hasattr(
_serializer_class, 'Meta') else _serializer_class.Meta
_meta_serializer = (
object
if not hasattr(_serializer_class, "Meta")
else _serializer_class.Meta
)
class ApiSerializer(_serializer_class):
class Meta(_meta_serializer):
if not hasattr(_meta_serializer, 'ref_name'):
ref_name = f'{object_name}Serializer'
if not hasattr(_meta_serializer, "ref_name"):
ref_name = f"{object_name}Serializer"
if not hasattr(_meta_serializer, 'model'):
if not hasattr(_meta_serializer, "model"):
model = _model
if hasattr(_meta_serializer, 'exclude'):
if hasattr(_meta_serializer, "exclude"):
exclude = _meta_serializer.exclude
else:
if not hasattr(_meta_serializer, 'fields'):
fields = '__all__'
elif _meta_serializer.fields != '__all__':
if not hasattr(_meta_serializer, "fields"):
fields = "__all__"
elif _meta_serializer.fields != "__all__":
fields = list(_meta_serializer.fields)
else:
fields = _meta_serializer.fields
_meta_filterset = object if not hasattr(
_filterset_class, 'Meta') else _filterset_class.Meta
_meta_filterset = (
object
if not hasattr(_filterset_class, "Meta")
else _filterset_class.Meta
)
class ApiFilterSet(_filterset_class):
class Meta(_meta_filterset, ):
if not hasattr(_meta_filterset, 'model'):
class Meta(
_meta_filterset,
):
if not hasattr(_meta_filterset, "model"):
model = _model
class ModelApiViewSet(ApiViewSetConstrutor.ApiViewSet):
@ -317,11 +321,10 @@ class ApiViewSetConstrutor():
return ModelApiViewSet
viewset = create_class()
viewset.__name__ = '%sModelViewSet' % _model.__name__
viewset.__name__ = "%sModelViewSet" % _model.__name__
return viewset
for am in apps_or_models:
if isinstance(am, ModelBase):
app = am._meta.app_config
else:
@ -351,17 +354,16 @@ class ApiViewSetConstrutor():
class wrapper_queryset_response_for_drf_action(object):
def __init__(self, model):
self.model = model
def __call__(self, cls):
def wrapper(instance_view, *args, **kwargs):
# recupera a viewset do model anotado
iv = instance_view
viewset_from_model = ApiViewSetConstrutor._built_sets[
self.model._meta.app_config][self.model]
self.model._meta.app_config
][self.model]
# apossa da instancia da viewset mae do action
# em uma viewset que processa dados do model passado no decorator
@ -369,41 +371,38 @@ class wrapper_queryset_response_for_drf_action(object):
iv.serializer_class = viewset_from_model.serializer_class
iv.filterset_class = viewset_from_model.filterset_class
iv.queryset = instance_view.filter_queryset(
iv.get_queryset())
iv.queryset = instance_view.filter_queryset(iv.get_queryset())
# chama efetivamente o metodo anotado que deve devolver um queryset
# com os filtros específicos definido pelo programador customizador
qs = cls(instance_view, *args, **kwargs)
page = iv.paginate_queryset(qs)
data = iv.get_serializer(
page if page is not None else qs, many=True).data
data = iv.get_serializer(page if page is not None else qs, many=True).data
return iv.get_paginated_response(
data) if page is not None else Response(data)
return (
iv.get_paginated_response(data) if page is not None else Response(data)
)
return wrapper
# decorator para recuperar e transformar o default
class customize(object):
def __init__(self, model):
self.model = model
def __call__(self, cls):
class _ApiViewSet(
cls,
ApiViewSetConstrutor._built_sets[
self.model._meta.app_config][self.model]
ApiViewSetConstrutor._built_sets[self.model._meta.app_config][self.model],
):
pass
if hasattr(_ApiViewSet, 'build'):
if hasattr(_ApiViewSet, "build"):
_ApiViewSet = _ApiViewSet.build()
ApiViewSetConstrutor._built_sets[
self.model._meta.app_config][self.model] = _ApiViewSet
ApiViewSetConstrutor._built_sets[self.model._meta.app_config][
self.model
] = _ApiViewSet
return _ApiViewSet

2
sapl/api/__init__.py

@ -1 +1 @@
default_app_config = 'sapl.api.apps.AppConfig'
default_app_config = "sapl.api.apps.AppConfig"

6
sapl/api/apps.py

@ -3,9 +3,9 @@ from django.utils.translation import ugettext_lazy as _
class AppConfig(apps.AppConfig):
name = 'sapl.api'
label = 'api'
verbose_name = _('API Rest')
name = "sapl.api"
label = "api"
verbose_name = _("API Rest")
def ready(self):
from . import signals

6
sapl/api/deprecated.py

@ -7,9 +7,7 @@ from sapl.api.serializers import SessaoPlenariaECidadaniaSerializer
from sapl.sessao.models import SessaoPlenaria
class SessaoPlenariaViewSet(ListModelMixin,
RetrieveModelMixin,
GenericViewSet):
class SessaoPlenariaViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
"""
Deprecated - Será eliminado na versão 3.2
@ -24,4 +22,4 @@ class SessaoPlenariaViewSet(ListModelMixin,
serializer_class = SessaoPlenariaECidadaniaSerializer
queryset = SessaoPlenaria.objects.all()
filter_backends = (DjangoFilterBackend,)
filter_fields = ('data_inicio', 'data_fim', 'interativa')
filter_fields = ("data_inicio", "data_fim", "interativa")

132
sapl/api/forms.py

@ -8,7 +8,7 @@ from django_filters.filterset import FilterSet
from rest_framework import serializers
from drfautoapi.drfautoapi import ApiFilterSetMixin
from sapl.base.models import TipoAutor, Autor
from sapl.base.models import Autor, TipoAutor
from sapl.parlamentares.models import Legislatura
from sapl.utils import generic_relations_for_model
@ -20,19 +20,17 @@ class SaplFilterSetMixin(ApiFilterSetMixin):
class AutorFilterSet(SaplFilterSetMixin):
q = CharFilter(method='filter_q')
q = CharFilter(method="filter_q")
tipo = ModelChoiceFilter(queryset=TipoAutor.objects.all())
def filter_q(self, queryset, name, value):
query = value.split(' ')
query = value.split(" ")
if query:
q = Q()
for qtext in query:
if not qtext:
continue
q_fs = Q(nome__icontains=qtext) | Q(
tipo__descricao__icontains=qtext)
q_fs = Q(nome__icontains=qtext) | Q(tipo__descricao__icontains=qtext)
order_by = []
@ -45,17 +43,23 @@ class AutorFilterSet(SaplFilterSetMixin):
for field in item.fields_search:
if flag_order_by:
flag_order_by = False
order_by.append('%s__%s' % (
item.related_query_name(),
field[0])
order_by.append(
"%s__%s" % (item.related_query_name(), field[0])
)
# if len(field) == 3 and field[2](qtext) is not
# None:
q_fs = q_fs | Q(**{'%s__%s%s' % (
q_fs = q_fs | Q(
**{
"%s__%s%s"
% (
item.related_query_name(),
field[0],
field[1]): qtext if len(field) == 2
else field[2](qtext)})
field[1],
): qtext
if len(field) == 2
else field[2](qtext)
}
)
q = q & q_fs
@ -66,25 +70,28 @@ class AutorFilterSet(SaplFilterSetMixin):
class AutoresPossiveisFilterSet(SaplFilterSetMixin):
data_relativa = DateFilter(method='filter_data_relativa')
tipo = CharFilter(method='filter_tipo')
data_relativa = DateFilter(method="filter_data_relativa")
tipo = CharFilter(method="filter_tipo")
class Meta:
model = Autor
fields = ['data_relativa', 'tipo', ]
fields = [
"data_relativa",
"tipo",
]
def filter_data_relativa(self, queryset, name, value):
return queryset
def filter_tipo(self, queryset, name, value):
try:
logger.debug(
"Tentando obter TipoAutor correspondente à pk {}.".format(value))
"Tentando obter TipoAutor correspondente à pk {}.".format(value)
)
tipo = TipoAutor.objects.get(pk=value)
except:
logger.error("TipoAutor(pk={}) inexistente.".format(value))
raise serializers.ValidationError(_('Tipo de Autor inexistente.'))
raise serializers.ValidationError(_("Tipo de Autor inexistente."))
qs = queryset.filter(tipo=tipo)
@ -94,11 +101,15 @@ class AutoresPossiveisFilterSet(SaplFilterSetMixin):
def qs(self):
qs = super().qs
data_relativa = self.form.cleaned_data['data_relativa'] \
if 'data_relativa' in self.form.cleaned_data else None
data_relativa = (
self.form.cleaned_data["data_relativa"]
if "data_relativa" in self.form.cleaned_data
else None
)
tipo = self.form.cleaned_data['tipo'] \
if 'tipo' in self.form.cleaned_data else None
tipo = (
self.form.cleaned_data["tipo"] if "tipo" in self.form.cleaned_data else None
)
if not tipo:
return qs
@ -107,7 +118,7 @@ class AutoresPossiveisFilterSet(SaplFilterSetMixin):
if not tipo.content_type:
return qs
filter_for_model = 'filter_%s' % tipo.content_type.model
filter_for_model = "filter_%s" % tipo.content_type.model
if not hasattr(self, filter_for_model):
return qs
@ -120,62 +131,79 @@ class AutoresPossiveisFilterSet(SaplFilterSetMixin):
def filter_parlamentar(self, queryset, data_relativa):
# não leva em conta afastamentos
legislatura_relativa = Legislatura.objects.filter(
data_inicio__lte=data_relativa,
data_fim__gte=data_relativa).first()
data_inicio__lte=data_relativa, data_fim__gte=data_relativa
).first()
q = Q(
parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa,
parlamentar_set__mandato__data_fim_mandato__isnull=True) | Q(
parlamentar_set__mandato__data_fim_mandato__isnull=True,
) | Q(
parlamentar_set__mandato__data_inicio_mandato__lte=data_relativa,
parlamentar_set__mandato__data_fim_mandato__gte=data_relativa)
parlamentar_set__mandato__data_fim_mandato__gte=data_relativa,
)
if legislatura_relativa.atual():
q = q & Q(parlamentar_set__ativo=True)
legislatura_anterior = self.request.GET.get(
'legislatura_anterior', 'False')
if legislatura_anterior.lower() == 'true':
legislatura_anterior = self.request.GET.get("legislatura_anterior", "False")
if legislatura_anterior.lower() == "true":
legislaturas = Legislatura.objects.filter(
data_fim__lte=data_relativa).order_by('-data_fim')[:2]
data_fim__lte=data_relativa
).order_by("-data_fim")[:2]
if len(legislaturas) == 2:
_, leg_anterior = legislaturas
q = q | Q(
parlamentar_set__mandato__data_inicio_mandato__gte=leg_anterior.data_inicio)
parlamentar_set__mandato__data_inicio_mandato__gte=leg_anterior.data_inicio
)
qs = queryset.filter(q)
return qs
def filter_comissao(self, queryset, data_relativa):
return queryset.filter(
Q(comissao_set__data_extincao__isnull=True,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__isnull=True) |
Q(comissao_set__data_extincao__isnull=True,
comissao_set__data_fim_comissao__gte=data_relativa) |
Q(comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__gte=data_relativa),
comissao_set__data_criacao__lte=data_relativa)
Q(
comissao_set__data_extincao__isnull=True,
comissao_set__data_fim_comissao__isnull=True,
)
| Q(
comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__isnull=True,
)
| Q(
comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__isnull=True,
)
| Q(
comissao_set__data_extincao__isnull=True,
comissao_set__data_fim_comissao__gte=data_relativa,
)
| Q(
comissao_set__data_extincao__gte=data_relativa,
comissao_set__data_fim_comissao__gte=data_relativa,
),
comissao_set__data_criacao__lte=data_relativa,
)
def filter_frente(self, queryset, data_relativa):
return queryset.filter(
Q(frente_set__data_extincao__isnull=True) |
Q(frente_set__data_extincao__gte=data_relativa),
frente_set__data_criacao__lte=data_relativa)
Q(frente_set__data_extincao__isnull=True)
| Q(frente_set__data_extincao__gte=data_relativa),
frente_set__data_criacao__lte=data_relativa,
)
def filter_bancada(self, queryset, data_relativa):
return queryset.filter(
Q(bancada_set__data_extincao__isnull=True) |
Q(bancada_set__data_extincao__gte=data_relativa),
bancada_set__data_criacao__lte=data_relativa)
Q(bancada_set__data_extincao__isnull=True)
| Q(bancada_set__data_extincao__gte=data_relativa),
bancada_set__data_criacao__lte=data_relativa,
)
def filter_bloco(self, queryset, data_relativa):
return queryset.filter(
Q(bloco_set__data_extincao__isnull=True) |
Q(bloco_set__data_extincao__gte=data_relativa),
bloco_set__data_criacao__lte=data_relativa)
Q(bloco_set__data_extincao__isnull=True)
| Q(bloco_set__data_extincao__gte=data_relativa),
bloco_set__data_criacao__lte=data_relativa,
)
def filter_orgao(self, queryset, data_relativa):
# na implementação, não havia regras a implementar para orgao

131
sapl/api/pagination.py

@ -5,71 +5,73 @@ from rest_framework.response import Response
class StandardPagination(pagination.PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
page_size_query_param = "page_size"
max_page_size = 100
def paginate_queryset(self, queryset, request, view=None):
if request.query_params.get('get_all', '').lower() == 'true':
if request.query_params.get("get_all", "").lower() == "true":
return None
return super().paginate_queryset(queryset, request, view=view)
def get_paginated_response_schema(self, schema):
r = {
'type': 'object',
'properties': {
'pagination': {
'type': 'object',
'properties': {
'links': {
'type': 'object',
'properties': {
'next': {
'type': 'string',
'nullable': True,
'format': 'uri',
'example': 'http://api.example.org/accounts/?{page_query_param}=4'.format(
page_query_param=self.page_query_param)
},
'previous': {
'type': 'string',
'nullable': True,
'format': 'uri',
'example': 'http://api.example.org/accounts/?{page_query_param}=2'.format(
page_query_param=self.page_query_param)
"type": "object",
"properties": {
"pagination": {
"type": "object",
"properties": {
"links": {
"type": "object",
"properties": {
"next": {
"type": "string",
"nullable": True,
"format": "uri",
"example": "http://api.example.org/accounts/?{page_query_param}=4".format(
page_query_param=self.page_query_param
),
},
}
"previous": {
"type": "string",
"nullable": True,
"format": "uri",
"example": "http://api.example.org/accounts/?{page_query_param}=2".format(
page_query_param=self.page_query_param
),
},
'previous_page': {
'type': 'integer',
'example': 123,
},
'next_page': {
'type': 'integer',
'example': 123,
},
'start_index': {
'type': 'integer',
'example': 123,
"previous_page": {
"type": "integer",
"example": 123,
},
'end_index': {
'type': 'integer',
'example': 123,
"next_page": {
"type": "integer",
"example": 123,
},
'total_entries': {
'type': 'integer',
'example': 123,
"start_index": {
"type": "integer",
"example": 123,
},
'total_pages': {
'type': 'integer',
'example': 123,
"end_index": {
"type": "integer",
"example": 123,
},
"total_entries": {
"type": "integer",
"example": 123,
},
"total_pages": {
"type": "integer",
"example": 123,
},
"page": {
"type": "integer",
"example": 123,
},
'page': {
'type': 'integer',
'example': 123,
},
}
},
'results': schema,
"results": schema,
},
}
return r
@ -85,20 +87,21 @@ class StandardPagination(pagination.PageNumberPagination):
except EmptyPage:
next_page_number = None
return Response({
'pagination': {
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link(),
},
'previous_page': previous_page_number,
'next_page': next_page_number,
'start_index': self.page.start_index(),
'end_index': self.page.end_index(),
'total_entries': self.page.paginator.count,
'total_pages': self.page.paginator.num_pages,
'page': self.page.number,
},
'results': data,
})
return Response(
{
"pagination": {
"links": {
"next": self.get_next_link(),
"previous": self.get_previous_link(),
},
"previous_page": previous_page_number,
"next_page": next_page_number,
"start_index": self.page.start_index(),
"end_index": self.page.end_index(),
"total_entries": self.page.paginator.count,
"total_pages": self.page.paginator.num_pages,
"page": self.page.number,
},
"results": data,
}
)

51
sapl/api/permissions.py

@ -1,42 +1,47 @@
from rest_framework.permissions import DjangoModelPermissions
from sapl.rules.map_rules import rules_patterns_public
class SaplModelPermissions(DjangoModelPermissions):
perms_map = {
'GET': ['%(app_label)s.list_%(model_name)s',
'%(app_label)s.detail_%(model_name)s'],
'OPTIONS': ['%(app_label)s.list_%(model_name)s',
'%(app_label)s.detail_%(model_name)s'],
'HEAD': ['%(app_label)s.list_%(model_name)s',
'%(app_label)s.detail_%(model_name)s'],
'POST': ['%(app_label)s.add_%(model_name)s'],
'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
"GET": [
"%(app_label)s.list_%(model_name)s",
"%(app_label)s.detail_%(model_name)s",
],
"OPTIONS": [
"%(app_label)s.list_%(model_name)s",
"%(app_label)s.detail_%(model_name)s",
],
"HEAD": [
"%(app_label)s.list_%(model_name)s",
"%(app_label)s.detail_%(model_name)s",
],
"POST": ["%(app_label)s.add_%(model_name)s"],
"PUT": ["%(app_label)s.change_%(model_name)s"],
"PATCH": ["%(app_label)s.change_%(model_name)s"],
"DELETE": ["%(app_label)s.delete_%(model_name)s"],
}
def has_permission(self, request, view):
if getattr(view, '_ignore_model_permissions', False):
if getattr(view, "_ignore_model_permissions", False):
return True
if hasattr(view, 'get_queryset'):
if hasattr(view, "get_queryset"):
queryset = view.get_queryset()
else:
queryset = getattr(view, 'queryset', None)
queryset = getattr(view, "queryset", None)
assert queryset is not None, (
'Cannot apply DjangoModelPermissions on a view that '
'does not set `.queryset` or have a `.get_queryset()` method.'
"Cannot apply DjangoModelPermissions on a view that "
"does not set `.queryset` or have a `.get_queryset()` method."
)
perms = self.get_required_permissions(request.method, queryset.model)
key = '{}:{}'.format(
queryset.model._meta.app_label,
queryset.model._meta.model_name)
key = "{}:{}".format(
queryset.model._meta.app_label, queryset.model._meta.model_name
)
if key in rules_patterns_public:
perms = set(perms)
@ -47,7 +52,7 @@ class SaplModelPermissions(DjangoModelPermissions):
return True
return (
request.user and
(request.user.is_authenticated or not self.authenticated_users_only) and
request.user.has_perms(perms)
request.user
and (request.user.is_authenticated or not self.authenticated_users_only)
and request.user.has_perms(perms)
)

215
sapl/api/serializers.py

@ -9,7 +9,7 @@ from rest_framework import serializers
from rest_framework.fields import SerializerMethodField
from sapl.base.models import Autor, CasaLegislativa, Metadata
from sapl.parlamentares.models import Parlamentar, Mandato, Legislatura
from sapl.parlamentares.models import Legislatura, Mandato, Parlamentar
from sapl.sessao.models import OrdemDia, SessaoPlenaria
@ -18,7 +18,7 @@ class SaplSerializerMixin(serializers.ModelSerializer):
metadata = SerializerMethodField()
class Meta:
fields = '__all__'
fields = "__all__"
def get___str__(self, obj) -> str:
return str(obj)
@ -26,9 +26,8 @@ class SaplSerializerMixin(serializers.ModelSerializer):
def get_metadata(self, obj) -> dict:
try:
metadata = Metadata.objects.get(
content_type=ContentType.objects.get_for_model(
obj._meta.model),
object_id=obj.id
content_type=ContentType.objects.get_for_model(obj._meta.model),
object_id=obj.id,
).metadata
except:
metadata = {}
@ -48,7 +47,6 @@ class ChoiceSerializer(serializers.Serializer):
class ModelChoiceSerializer(ChoiceSerializer):
def get_text(self, obj):
return str(obj)
@ -57,18 +55,16 @@ class ModelChoiceSerializer(ChoiceSerializer):
class ModelChoiceObjectRelatedField(serializers.RelatedField):
def to_representation(self, value):
return ModelChoiceSerializer(value).data
class AutorSerializer(SaplSerializerMixin):
autor_related = ModelChoiceObjectRelatedField(read_only=True)
class Meta:
model = Autor
fields = '__all__'
fields = "__all__"
class CasaLegislativaSerializer(SaplSerializerMixin):
@ -79,46 +75,57 @@ class CasaLegislativaSerializer(SaplSerializerMixin):
class Meta:
model = CasaLegislativa
fields = '__all__'
fields = "__all__"
class ParlamentarSerializerPublic(SaplSerializerMixin):
class Meta:
model = Parlamentar
exclude = ["cpf", "rg", "fax", "data_nascimento",
"endereco_residencia", "municipio_residencia",
"uf_residencia", "cep_residencia", "situacao_militar",
"telefone_residencia", "titulo_eleitor", "fax_residencia"]
exclude = [
"cpf",
"rg",
"fax",
"data_nascimento",
"endereco_residencia",
"municipio_residencia",
"uf_residencia",
"cep_residencia",
"situacao_militar",
"telefone_residencia",
"titulo_eleitor",
"fax_residencia",
]
class ParlamentarSerializerVerbose(SaplSerializerMixin):
titular = serializers.SerializerMethodField('check_titular')
partido = serializers.SerializerMethodField('check_partido')
fotografia_cropped = serializers.SerializerMethodField('crop_fotografia')
titular = serializers.SerializerMethodField("check_titular")
partido = serializers.SerializerMethodField("check_partido")
fotografia_cropped = serializers.SerializerMethodField("crop_fotografia")
logger = logging.getLogger(__name__)
def crop_fotografia(self, obj):
thumbnail_url = ""
try:
import os
if not obj.fotografia or not os.path.exists(obj.fotografia.path):
return thumbnail_url
self.logger.warning(f"Iniciando cropping da imagem {obj.fotografia}")
thumbnail_url = get_backend().get_thumbnail_url(
obj.fotografia,
{
'size': (128, 128),
'box': obj.cropping,
'crop': True,
'detail': True,
}
"size": (128, 128),
"box": obj.cropping,
"crop": True,
"detail": True,
},
)
self.logger.warning(
f"Cropping da imagem {obj.fotografia} realizado com sucesso"
)
self.logger.warning(f"Cropping da imagem {obj.fotografia} realizado com sucesso")
except Exception as e:
self.logger.error(e)
self.logger.error('erro processando arquivo: %s' %
obj.fotografia.path)
self.logger.error("erro processando arquivo: %s" % obj.fotografia.path)
return thumbnail_url
@ -129,19 +136,22 @@ class ParlamentarSerializerVerbose(SaplSerializerMixin):
return ""
try:
legislatura = Legislatura.objects.get(
id=self.context.get('legislatura'))
legislatura = Legislatura.objects.get(id=self.context.get("legislatura"))
except ObjectDoesNotExist:
legislatura = Legislatura.objects.first()
mandato = Mandato.objects.filter(
mandato = (
Mandato.objects.filter(
parlamentar=obj,
data_inicio_mandato__gte=legislatura.data_inicio,
data_fim_mandato__lte=legislatura.data_fim
).order_by('-data_inicio_mandato').first()
data_fim_mandato__lte=legislatura.data_fim,
)
.order_by("-data_inicio_mandato")
.first()
)
if mandato:
is_titular = 'Sim' if mandato.titular else 'Não'
is_titular = "Sim" if mandato.titular else "Não"
else:
is_titular = '-'
is_titular = "-"
return is_titular
def check_partido(self, obj):
@ -151,76 +161,95 @@ class ParlamentarSerializerVerbose(SaplSerializerMixin):
# da legislatura e data de desfiliação deve nula, ou maior,
# ou igual a data de fim da legislatura
username = self.context['request'].user.username
username = self.context["request"].user.username
if not Legislatura.objects.exists():
self.logger.error("Não há legislaturas cadastradas.")
return ""
try:
legislatura = Legislatura.objects.get(
id=self.context.get('legislatura'))
legislatura = Legislatura.objects.get(id=self.context.get("legislatura"))
except ObjectDoesNotExist:
legislatura = Legislatura.objects.first()
try:
self.logger.debug("user=" + username + ". Tentando obter filiação do parlamentar com (data<={} e data_desfiliacao>={}) "
"ou (data<={} e data_desfiliacao=Null))."
.format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim))
filiacao = obj.filiacao_set.get(Q(
data__lte=legislatura.data_fim,
data_desfiliacao__gte=legislatura.data_fim) | Q(
self.logger.debug(
"user="
+ username
+ ". Tentando obter filiação do parlamentar com (data<={} e data_desfiliacao>={}) "
"ou (data<={} e data_desfiliacao=Null)).".format(
legislatura.data_fim, legislatura.data_fim, legislatura.data_fim
)
)
filiacao = obj.filiacao_set.get(
Q(
data__lte=legislatura.data_fim,
data_desfiliacao__isnull=True))
data_desfiliacao__gte=legislatura.data_fim,
)
| Q(data__lte=legislatura.data_fim, data_desfiliacao__isnull=True)
)
# Caso não exista filiação com essas condições
except ObjectDoesNotExist:
self.logger.warning("user=" + username + ". Parlamentar com (data<={} e data_desfiliacao>={}) "
"ou (data<={} e data_desfiliacao=Null)) não possui filiação."
.format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim))
filiacao = 'Não possui filiação'
self.logger.warning(
"user="
+ username
+ ". Parlamentar com (data<={} e data_desfiliacao>={}) "
"ou (data<={} e data_desfiliacao=Null)) não possui filiação.".format(
legislatura.data_fim, legislatura.data_fim, legislatura.data_fim
)
)
filiacao = "Não possui filiação"
# Caso exista mais de uma filiação nesse intervalo
# Entretanto, NÃO DEVE OCORRER
except MultipleObjectsReturned:
self.logger.error("user=" + username + ". O Parlamentar com (data<={} e data_desfiliacao>={}) "
"ou (data<={} e data_desfiliacao=Null)) possui duas filiações conflitantes"
.format(legislatura.data_fim, legislatura.data_fim, legislatura.data_fim))
filiacao = 'O Parlamentar possui duas filiações conflitantes'
self.logger.error(
"user="
+ username
+ ". O Parlamentar com (data<={} e data_desfiliacao>={}) "
"ou (data<={} e data_desfiliacao=Null)) possui duas filiações conflitantes".format(
legislatura.data_fim, legislatura.data_fim, legislatura.data_fim
)
)
filiacao = "O Parlamentar possui duas filiações conflitantes"
# Caso encontre UMA filiação nessas condições
else:
self.logger.debug("user=" + username +
". Filiação encontrada com sucesso.")
self.logger.debug("user=" + username + ". Filiação encontrada com sucesso.")
filiacao = filiacao.partido.sigla
return filiacao
class Meta:
model = Parlamentar
fields = ['id', 'nome_parlamentar', 'fotografia_cropped',
'fotografia', 'ativo', 'partido', 'titular', ]
fields = [
"id",
"nome_parlamentar",
"fotografia_cropped",
"fotografia",
"ativo",
"partido",
"titular",
]
class SessaoPlenariaECidadaniaSerializer(serializers.ModelSerializer):
codReuniao = serializers.SerializerMethodField('get_pk_sessao')
codReuniaoPrincipal = serializers.SerializerMethodField('get_pk_sessao')
txtTituloReuniao = serializers.SerializerMethodField('get_name')
txtSiglaOrgao = serializers.SerializerMethodField('get_sigla_orgao')
txtApelido = serializers.SerializerMethodField('get_name')
txtNomeOrgao = serializers.SerializerMethodField('get_nome_orgao')
codEstadoReuniao = serializers.SerializerMethodField(
'get_estadoSessaoPlenaria')
txtTipoReuniao = serializers.SerializerMethodField('get_tipo_sessao')
txtObjeto = serializers.SerializerMethodField('get_assunto_sessao')
txtLocal = serializers.SerializerMethodField('get_endereco_orgao')
bolReuniaoConjunta = serializers.SerializerMethodField(
'get_reuniao_conjunta')
bolHabilitarEventoInterativo = serializers.SerializerMethodField(
'get_iterativo')
idYoutube = serializers.SerializerMethodField('get_url')
codReuniao = serializers.SerializerMethodField("get_pk_sessao")
codReuniaoPrincipal = serializers.SerializerMethodField("get_pk_sessao")
txtTituloReuniao = serializers.SerializerMethodField("get_name")
txtSiglaOrgao = serializers.SerializerMethodField("get_sigla_orgao")
txtApelido = serializers.SerializerMethodField("get_name")
txtNomeOrgao = serializers.SerializerMethodField("get_nome_orgao")
codEstadoReuniao = serializers.SerializerMethodField("get_estadoSessaoPlenaria")
txtTipoReuniao = serializers.SerializerMethodField("get_tipo_sessao")
txtObjeto = serializers.SerializerMethodField("get_assunto_sessao")
txtLocal = serializers.SerializerMethodField("get_endereco_orgao")
bolReuniaoConjunta = serializers.SerializerMethodField("get_reuniao_conjunta")
bolHabilitarEventoInterativo = serializers.SerializerMethodField("get_iterativo")
idYoutube = serializers.SerializerMethodField("get_url")
codEstadoTransmissaoYoutube = serializers.SerializerMethodField(
'get_estadoTransmissaoYoutube')
datReuniaoString = serializers.SerializerMethodField('get_date')
"get_estadoTransmissaoYoutube"
)
datReuniaoString = serializers.SerializerMethodField("get_date")
# Constantes SessaoPlenaria (de 1-9) (apenas 3 serão usados)
SESSAO_FINALIZADA = 4
@ -235,21 +264,21 @@ class SessaoPlenariaECidadaniaSerializer(serializers.ModelSerializer):
class Meta:
model = SessaoPlenaria
fields = (
'codReuniao',
'codReuniaoPrincipal',
'txtTituloReuniao',
'txtSiglaOrgao',
'txtApelido',
'txtNomeOrgao',
'codEstadoReuniao',
'txtTipoReuniao',
'txtObjeto',
'txtLocal',
'bolReuniaoConjunta',
'bolHabilitarEventoInterativo',
'idYoutube',
'codEstadoTransmissaoYoutube',
'datReuniaoString'
"codReuniao",
"codReuniaoPrincipal",
"txtTituloReuniao",
"txtSiglaOrgao",
"txtApelido",
"txtNomeOrgao",
"codEstadoReuniao",
"txtTipoReuniao",
"txtObjeto",
"txtLocal",
"bolReuniaoConjunta",
"bolHabilitarEventoInterativo",
"idYoutube",
"codEstadoTransmissaoYoutube",
"datReuniaoString",
)
def get_pk_sessao(self, obj):
@ -277,9 +306,7 @@ class SessaoPlenariaECidadaniaSerializer(serializers.ModelSerializer):
def get_date(self, obj):
return "{} {}{}".format(
obj.data_inicio.strftime("%d/%m/%Y"),
obj.hora_inicio,
":00"
obj.data_inicio.strftime("%d/%m/%Y"), obj.hora_inicio, ":00"
)
def get_estadoTransmissaoYoutube(self, obj):
@ -292,9 +319,9 @@ class SessaoPlenariaECidadaniaSerializer(serializers.ModelSerializer):
return self.SEM_TRANSMISSAO
def get_assunto_sessao(self, obj):
pauta_sessao = ''
pauta_sessao = ""
ordem_dia = OrdemDia.objects.filter(sessao_plenaria=obj.pk)
pauta_sessao = ', '.join([i.materia.__str__() for i in ordem_dia])
pauta_sessao = ", ".join([i.materia.__str__() for i in ordem_dia])
return str(pauta_sessao)

46
sapl/api/urls.py

@ -1,16 +1,16 @@
from django.conf.urls import include, url
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, \
SpectacularRedocView
from drf_spectacular.views import (
SpectacularAPIView,
SpectacularRedocView,
SpectacularSwaggerView,
)
from rest_framework.authtoken.views import obtain_auth_token
from sapl.api.deprecated import SessaoPlenariaViewSet
from sapl.api.views import AppVersionView, recria_token,\
SaplApiViewSetConstrutor
from sapl.api.views import AppVersionView, SaplApiViewSetConstrutor, recria_token
from .apps import AppConfig
app_name = AppConfig.name
router = SaplApiViewSetConstrutor.router()
@ -19,26 +19,30 @@ router = SaplApiViewSetConstrutor.router()
# verificar se ainda permanece necessidade desses endpoint's
# /api/sessao-planaria -> /api/sessao/sessaoplenaria/ecidadania
# /api/sessao-planaria/{pk} -> /api/sessao/sessaoplenaria/{pk}/ecidadania
router.register(r'sessao-plenaria', SessaoPlenariaViewSet,
basename='sessao_plenaria_old')
router.register(
r"sessao-plenaria", SessaoPlenariaViewSet, basename="sessao_plenaria_old"
)
urlpatterns_router = router.urls
urlpatterns_api_doc = [
url('^schema/swagger-ui/',
SpectacularSwaggerView.as_view(url_name='sapl.api:schema_api'),
name='swagger_ui_schema_api'),
url('^schema/redoc/',
SpectacularRedocView.as_view(url_name='sapl.api:schema_api'),
name='redoc_schema_api'),
url('^schema/', SpectacularAPIView.as_view(), name='schema_api'),
url(
"^schema/swagger-ui/",
SpectacularSwaggerView.as_view(url_name="sapl.api:schema_api"),
name="swagger_ui_schema_api",
),
url(
"^schema/redoc/",
SpectacularRedocView.as_view(url_name="sapl.api:schema_api"),
name="redoc_schema_api",
),
url("^schema/", SpectacularAPIView.as_view(), name="schema_api"),
]
urlpatterns = [
url(r'^api/', include(urlpatterns_api_doc)),
url(r'^api/', include(urlpatterns_router)),
url(r'^api/version', AppVersionView.as_view()),
url(r'^api/auth/token$', obtain_auth_token),
url(r'^api/recriar-token/(?P<pk>\d*)$', recria_token, name="recria_token"),
url(r"^api/", include(urlpatterns_api_doc)),
url(r"^api/", include(urlpatterns_router)),
url(r"^api/version", AppVersionView.as_view()),
url(r"^api/auth/token$", obtain_auth_token),
url(r"^api/recriar-token/(?P<pk>\d*)$", recria_token, name="recria_token"),
]

40
sapl/api/views.py

@ -3,7 +3,7 @@ import logging
from django.conf import settings
from rest_framework.authtoken.models import Token
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
@ -12,7 +12,7 @@ from drfautoapi.drfautoapi import ApiViewSetConstrutor
logger = logging.getLogger(__name__)
@api_view(['POST'])
@api_view(["POST"])
@permission_classes([IsAdminUser])
def recria_token(request, pk):
Token.objects.filter(user_id=pk).delete()
@ -26,28 +26,30 @@ class AppVersionView(APIView):
def get(self, request):
content = {
'name': 'SAPL',
'description': 'Sistema de Apoio ao Processo Legislativo',
'version': settings.SAPL_VERSION,
'user': request.user.username,
'is_authenticated': request.user.is_authenticated,
"name": "SAPL",
"description": "Sistema de Apoio ao Processo Legislativo",
"version": settings.SAPL_VERSION,
"user": request.user.username,
"is_authenticated": request.user.is_authenticated,
}
return Response(content)
SaplApiViewSetConstrutor = ApiViewSetConstrutor
SaplApiViewSetConstrutor.import_modules([
'sapl.api.views_audiencia',
'sapl.api.views_base',
'sapl.api.views_comissoes',
'sapl.api.views_compilacao',
'sapl.api.views_materia',
'sapl.api.views_norma',
'sapl.api.views_painel',
'sapl.api.views_parlamentares',
'sapl.api.views_protocoloadm',
'sapl.api.views_sessao',
])
SaplApiViewSetConstrutor.import_modules(
[
"sapl.api.views_audiencia",
"sapl.api.views_base",
"sapl.api.views_comissoes",
"sapl.api.views_compilacao",
"sapl.api.views_materia",
"sapl.api.views_norma",
"sapl.api.views_painel",
"sapl.api.views_parlamentares",
"sapl.api.views_protocoloadm",
"sapl.api.views_sessao",
]
)
"""

12
sapl/api/views_audiencia.py

@ -1,11 +1,11 @@
from django.apps.registry import apps
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
from drfautoapi.drfautoapi import (
ApiViewSetConstrutor,
customize,
wrapper_queryset_response_for_drf_action,
)
AudienciaApiViewSetConstrutor = ApiViewSetConstrutor.build_class(
[
apps.get_app_config('audiencia')
]
[apps.get_app_config("audiencia")]
)

70
sapl/api/views_base.py

@ -11,22 +11,18 @@ from drfautoapi.drfautoapi import ApiViewSetConstrutor, customize
from sapl.api.forms import AutoresPossiveisFilterSet
from sapl.api.serializers import ChoiceSerializer
from sapl.base.models import Autor, TipoAutor
from sapl.utils import models_with_gr_for_model, SaplGenericRelation
from sapl.utils import SaplGenericRelation, models_with_gr_for_model
logger = logging.getLogger(__name__)
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('contenttypes'),
apps.get_app_config('base')
]
[apps.get_app_config("contenttypes"), apps.get_app_config("base")]
)
@customize(ContentType)
class _ContentTypeSet:
http_method_names = ['get', 'head', 'options', 'trace']
http_method_names = ["get", "head", "options", "trace"]
@customize(Autor)
@ -74,7 +70,6 @@ class _AutorViewSet:
@classmethod
def build(cls):
models_with_gr_for_autor = models_with_gr_for_model(Autor)
for _model in models_with_gr_for_autor:
@ -87,10 +82,10 @@ class _AutorViewSet:
return self.list_for_content_type(content_type)
func = actionclass
func.mapping['get'] = func.kwargs['name']
func.url_name = func.kwargs['name']
func.url_path = func.kwargs['name']
func.__name__ = func.kwargs['name']
func.mapping["get"] = func.kwargs["name"]
func.url_name = func.kwargs["name"]
func.url_path = func.kwargs["name"]
func.__name__ = func.kwargs["name"]
func.__model = _model
setattr(cls, _model._meta.model_name, func)
@ -103,7 +98,6 @@ class _AutorViewSet:
@action(detail=False)
def provaveis(self, request, *args, **kwargs):
self.get_queryset = self.provaveis__get_queryset
self.filter_backends = []
@ -112,15 +106,15 @@ class _AutorViewSet:
return self.list(request, *args, **kwargs)
def provaveis__get_queryset(self):
params = {'content_type__isnull': False}
params = {"content_type__isnull": False}
username = self.request.user.username
tipo = ''
tipo = ""
try:
tipo = int(self.request.GET.get('tipo', ''))
tipo = int(self.request.GET.get("tipo", ""))
if tipo:
params['id'] = tipo
params["id"] = tipo
except Exception as e:
logger.error('user= ' + username + '. ' + str(e))
logger.error("user= " + username + ". " + str(e))
pass
tipos = TipoAutor.objects.filter(**params)
@ -130,14 +124,17 @@ class _AutorViewSet:
r = []
for tipo in tipos:
q = self.request.GET.get('q', '').strip()
q = self.request.GET.get("q", "").strip()
model_class = tipo.content_type.model_class()
fields = list(filter(
lambda field: isinstance(field, SaplGenericRelation) and
field.related_model == Autor,
model_class._meta.get_fields(include_hidden=True)))
fields = list(
filter(
lambda field: isinstance(field, SaplGenericRelation)
and field.related_model == Autor,
model_class._meta.get_fields(include_hidden=True),
)
)
"""
fields - é um array de SaplGenericRelation que deve possuir o
@ -145,11 +142,13 @@ class _AutorViewSet:
a estrutura de fields_search.
"""
assert len(fields) >= 1, (_(
'Não foi encontrado em %(model)s um atributo do tipo '
'SaplGenericRelation que use o model %(model_autor)s') % {
'model': model_class._meta.verbose_name,
'model_autor': Autor._meta.verbose_name})
assert len(fields) >= 1, _(
"Não foi encontrado em %(model)s um atributo do tipo "
"SaplGenericRelation que use o model %(model_autor)s"
) % {
"model": model_class._meta.verbose_name,
"model_autor": Autor._meta.verbose_name,
}
qs = model_class.objects.all()
@ -160,19 +159,18 @@ class _AutorViewSet:
continue
q_fs = Q()
for field in item.fields_search:
q_fs = q_fs | Q(**{'%s%s' % (
field[0],
field[1]): q})
q_fs = q_fs | Q(**{"%s%s" % (field[0], field[1]): q})
q_filter = q_filter & q_fs
qs = qs.filter(q_filter).distinct(
fields[0].fields_search[0][0]).order_by(
fields[0].fields_search[0][0])
qs = (
qs.filter(q_filter)
.distinct(fields[0].fields_search[0][0])
.order_by(fields[0].fields_search[0][0])
)
else:
qs = qs.order_by(fields[0].fields_search[0][0])
qs = qs.values_list(
'id', fields[0].fields_search[0][0])
qs = qs.values_list("id", fields[0].fields_search[0][0])
r += list(qs)
if tipos.count() > 1:

20
sapl/api/views_comissoes.py

@ -1,23 +1,19 @@
from django.apps.registry import apps
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
from sapl.comissoes.models import Comissao
from rest_framework.decorators import action
from drfautoapi.drfautoapi import (
ApiViewSetConstrutor,
customize,
wrapper_queryset_response_for_drf_action,
)
from sapl.comissoes.models import Comissao
from sapl.materia.models import MateriaEmTramitacao
ApiViewSetConstrutor.build_class([apps.get_app_config("comissoes")])
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('comissoes')
]
)
@customize(Comissao)
class _ComissaoViewSet:
@action(detail=True)
def materiaemtramitacao(self, request, *args, **kwargs):
return self.get_materiaemtramitacao(**kwargs)
@ -25,5 +21,5 @@ class _ComissaoViewSet:
@wrapper_queryset_response_for_drf_action(model=MateriaEmTramitacao)
def get_materiaemtramitacao(self, **kwargs):
return self.get_queryset().filter(
unidade_tramitacao_atual__comissao=kwargs['pk'],
unidade_tramitacao_atual__comissao=kwargs["pk"],
)

15
sapl/api/views_compilacao.py

@ -1,12 +1,9 @@
from django.apps.registry import apps
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('compilacao')
]
from drfautoapi.drfautoapi import (
ApiViewSetConstrutor,
customize,
wrapper_queryset_response_for_drf_action,
)
ApiViewSetConstrutor.build_class([apps.get_app_config("compilacao")])

62
sapl/api/views_materia.py

@ -1,22 +1,23 @@
from django.apps.registry import apps
from django.db.models import Q
from rest_framework.decorators import action
from rest_framework.response import Response
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
from drfautoapi.drfautoapi import (
ApiViewSetConstrutor,
customize,
wrapper_queryset_response_for_drf_action,
)
from sapl.api.permissions import SaplModelPermissions
from sapl.materia.models import TipoMateriaLegislativa, Tramitacao,\
MateriaLegislativa, Proposicao
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('materia')
]
from sapl.materia.models import (
MateriaLegislativa,
Proposicao,
TipoMateriaLegislativa,
Tramitacao,
)
ApiViewSetConstrutor.build_class([apps.get_app_config("materia")])
@customize(Proposicao)
class _ProposicaoViewSet:
@ -46,9 +47,8 @@ class _ProposicaoViewSet:
"""
class ProposicaoPermission(SaplModelPermissions):
def has_permission(self, request, view):
if request.method == 'GET':
if request.method == "GET":
return True
# se a solicitação é list ou detail, libera o teste de permissão
# e deixa o get_queryset filtrar de acordo com a regra de
@ -68,7 +68,6 @@ class _ProposicaoViewSet:
q = Q(data_recebimento__isnull=False, object_id__isnull=False)
if not self.request.user.is_anonymous:
autor_do_usuario_logado = self.request.user.autor_set.first()
# se usuário logado é operador de algum autor
@ -76,9 +75,8 @@ class _ProposicaoViewSet:
q = Q(autor=autor_do_usuario_logado)
# se é operador de protocolo, ve qualquer coisa enviada
if self.request.user.has_perm('protocoloadm.list_protocolo'):
q = Q(data_envio__isnull=False) | Q(
data_devolucao__isnull=False)
if self.request.user.has_perm("protocoloadm.list_protocolo"):
q = Q(data_envio__isnull=False) | Q(data_devolucao__isnull=False)
qs = qs.filter(q)
return qs
@ -86,26 +84,26 @@ class _ProposicaoViewSet:
@customize(MateriaLegislativa)
class _MateriaLegislativaViewSet:
class Meta:
ordering = ['-ano', 'tipo', 'numero']
ordering = ["-ano", "tipo", "numero"]
@action(detail=True, methods=['GET'])
@action(detail=True, methods=["GET"])
def ultima_tramitacao(self, request, *args, **kwargs):
materia = self.get_object()
if not materia.tramitacao_set.exists():
return Response({})
ultima_tramitacao = materia.tramitacao_set.order_by(
'-data_tramitacao', '-id').first()
"-data_tramitacao", "-id"
).first()
serializer_class = ApiViewSetConstrutor.get_viewset_for_model(
Tramitacao).serializer_class(ultima_tramitacao)
Tramitacao
).serializer_class(ultima_tramitacao)
return Response(serializer_class.data)
@action(detail=True, methods=['GET'])
@action(detail=True, methods=["GET"])
def anexadas(self, request, *args, **kwargs):
self.queryset = self.get_object().anexadas.all()
return self.list(request, *args, **kwargs)
@ -113,17 +111,13 @@ class _MateriaLegislativaViewSet:
@customize(TipoMateriaLegislativa)
class _TipoMateriaLegislativaViewSet:
@action(detail=True, methods=['POST'])
@action(detail=True, methods=["POST"])
def change_position(self, request, *args, **kwargs):
result = {
'status': 200,
'message': 'OK'
}
result = {"status": 200, "message": "OK"}
d = request.data
if 'pos_ini' in d and 'pos_fim' in d:
if d['pos_ini'] != d['pos_fim']:
pk = kwargs['pk']
TipoMateriaLegislativa.objects.reposicione(pk, d['pos_fim'])
if "pos_ini" in d and "pos_fim" in d:
if d["pos_ini"] != d["pos_fim"]:
pk = kwargs["pk"]
TipoMateriaLegislativa.objects.reposicione(pk, d["pos_fim"])
return Response(result)

15
sapl/api/views_norma.py

@ -1,14 +1,11 @@
from django.apps.registry import apps
from rest_framework.decorators import action
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
from drfautoapi.drfautoapi import (
ApiViewSetConstrutor,
customize,
wrapper_queryset_response_for_drf_action,
)
from sapl.norma.models import NormaJuridica
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('norma')
]
)
ApiViewSetConstrutor.build_class([apps.get_app_config("norma")])

14
sapl/api/views_painel.py

@ -1,11 +1,9 @@
from django.apps.registry import apps
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('painel')
]
from drfautoapi.drfautoapi import (
ApiViewSetConstrutor,
customize,
wrapper_queryset_response_for_drf_action,
)
ApiViewSetConstrutor.build_class([apps.get_app_config("painel")])

68
sapl/api/views_parlamentares.py

@ -1,34 +1,30 @@
from django.apps.registry import apps
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.decorators import action
from rest_framework.response import Response
from drfautoapi.drfautoapi import customize, ApiViewSetConstrutor, \
wrapper_queryset_response_for_drf_action
from drfautoapi.drfautoapi import (
ApiViewSetConstrutor,
customize,
wrapper_queryset_response_for_drf_action,
)
from sapl.api.permissions import SaplModelPermissions
from sapl.api.serializers import ParlamentarSerializerVerbose, \
ParlamentarSerializerPublic
from sapl.api.serializers import (
ParlamentarSerializerPublic,
ParlamentarSerializerVerbose,
)
from sapl.materia.models import Proposicao
from sapl.parlamentares.models import Mandato, Legislatura
from sapl.parlamentares.models import Parlamentar
from sapl.parlamentares.models import Legislatura, Mandato, Parlamentar
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('parlamentares')
]
)
ApiViewSetConstrutor.build_class([apps.get_app_config("parlamentares")])
@customize(Parlamentar)
class _ParlamentarViewSet:
class ParlamentarPermission(SaplModelPermissions):
def has_permission(self, request, view):
if request.method == 'GET':
if request.method == "GET":
return True
else:
perm = super().has_permission(request, view)
@ -37,7 +33,7 @@ class _ParlamentarViewSet:
permission_classes = (ParlamentarPermission,)
def get_serializer(self, *args, **kwargs):
if not self.request.user.has_perm('parlamentares.add_parlamentar'):
if not self.request.user.has_perm("parlamentares.add_parlamentar"):
self.serializer_class = ParlamentarSerializerPublic
return super().get_serializer(*args, **kwargs)
@ -58,35 +54,30 @@ class _ParlamentarViewSet:
@wrapper_queryset_response_for_drf_action(model=Proposicao)
def get_proposicoes(self, **kwargs):
return self.get_queryset().filter(
data_envio__isnull=False,
data_recebimento__isnull=False,
cancelado=False,
autor__object_id=kwargs['pk'],
autor__content_type=ContentType.objects.get_for_model(Parlamentar)
autor__object_id=kwargs["pk"],
autor__content_type=ContentType.objects.get_for_model(Parlamentar),
)
@action(detail=False, methods=['GET'])
@action(detail=False, methods=["GET"])
def search_parlamentares(self, request, *args, **kwargs):
nome = request.query_params.get('nome_parlamentar', '')
parlamentares = Parlamentar.objects.filter(
nome_parlamentar__icontains=nome)
nome = request.query_params.get("nome_parlamentar", "")
parlamentares = Parlamentar.objects.filter(nome_parlamentar__icontains=nome)
serializer_class = ParlamentarSerializerVerbose(
parlamentares, many=True, context={'request': request})
parlamentares, many=True, context={"request": request}
)
return Response(serializer_class.data)
@customize(Legislatura)
class _LegislaturaViewSet:
@action(detail=True)
def parlamentares(self, request, *args, **kwargs):
def get_serializer_context():
return {
'request': self.request, 'legislatura': kwargs['pk']
}
return {"request": self.request, "legislatura": kwargs["pk"]}
def get_serializer_class():
return ParlamentarSerializerVerbose
@ -98,22 +89,21 @@ class _LegislaturaViewSet:
@wrapper_queryset_response_for_drf_action(model=Parlamentar)
def get_parlamentares(self):
try:
legislatura = Legislatura.objects.get(pk=self.kwargs['pk'])
legislatura = Legislatura.objects.get(pk=self.kwargs["pk"])
except ObjectDoesNotExist:
return Response("")
filter_params = {
'legislatura': legislatura,
'data_inicio_mandato__gte': legislatura.data_inicio,
'data_fim_mandato__lte': legislatura.data_fim,
"legislatura": legislatura,
"data_inicio_mandato__gte": legislatura.data_inicio,
"data_fim_mandato__lte": legislatura.data_fim,
}
mandatos = Mandato.objects.filter(
**filter_params).order_by('-data_inicio_mandato')
mandatos = Mandato.objects.filter(**filter_params).order_by(
"-data_inicio_mandato"
)
parlamentares = self.get_queryset().filter(
mandato__in=mandatos).distinct()
parlamentares = self.get_queryset().filter(mandato__in=mandatos).distinct()
return parlamentares

45
sapl/api/views_protocoloadm.py

@ -1,29 +1,28 @@
from django.apps.registry import apps
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
from drfautoapi.drfautoapi import (
ApiViewSetConstrutor,
customize,
wrapper_queryset_response_for_drf_action,
)
from sapl.api.permissions import SaplModelPermissions
from sapl.base.models import AppConfig, DOC_ADM_OSTENSIVO
from sapl.protocoloadm.models import DocumentoAdministrativo, \
DocumentoAcessorioAdministrativo, TramitacaoAdministrativo, Anexado
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('protocoloadm')
]
from sapl.base.models import DOC_ADM_OSTENSIVO, AppConfig
from sapl.protocoloadm.models import (
Anexado,
DocumentoAcessorioAdministrativo,
DocumentoAdministrativo,
TramitacaoAdministrativo,
)
ApiViewSetConstrutor.build_class([apps.get_app_config("protocoloadm")])
@customize(DocumentoAdministrativo)
class _DocumentoAdministrativoViewSet:
class DocumentoAdministrativoPermission(SaplModelPermissions):
def has_permission(self, request, view):
if request.method == 'GET':
comportamento = AppConfig.attr('documentos_administrativos')
if request.method == "GET":
comportamento = AppConfig.attr("documentos_administrativos")
if comportamento == DOC_ADM_OSTENSIVO:
return True
"""
@ -54,9 +53,9 @@ class _DocumentoAdministrativoViewSet:
@customize(DocumentoAcessorioAdministrativo)
class _DocumentoAcessorioAdministrativoViewSet:
permission_classes = (
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission,)
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission,
)
def get_queryset(self):
qs = super().get_queryset()
@ -72,10 +71,11 @@ class _TramitacaoAdministrativoViewSet:
# tramitacação de adm possui regras previstas de limitação de origem
# destino
http_method_names = ['get', 'head', 'options', 'trace']
http_method_names = ["get", "head", "options", "trace"]
permission_classes = (
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission,)
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission,
)
def get_queryset(self):
qs = super().get_queryset()
@ -89,10 +89,11 @@ class _TramitacaoAdministrativoViewSet:
class _AnexadoViewSet:
# TODO: Implementar regras de manutenção post, put, patch
# anexado deve possuir controle que impeça anexação cíclica
http_method_names = ['get', 'head', 'options', 'trace']
http_method_names = ["get", "head", "options", "trace"]
permission_classes = (
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission,)
_DocumentoAdministrativoViewSet.DocumentoAdministrativoPermission,
)
def get_queryset(self):
qs = super().get_queryset()

25
sapl/api/views_sessao.py

@ -1,26 +1,21 @@
from django.apps.registry import apps
from rest_framework.decorators import action
from rest_framework.response import Response
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \
customize, wrapper_queryset_response_for_drf_action
from sapl.api.serializers import ChoiceSerializer,\
SessaoPlenariaECidadaniaSerializer
from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao
from drfautoapi.drfautoapi import (
ApiViewSetConstrutor,
customize,
wrapper_queryset_response_for_drf_action,
)
from sapl.api.serializers import ChoiceSerializer, SessaoPlenariaECidadaniaSerializer
from sapl.sessao.models import ExpedienteSessao, SessaoPlenaria
from sapl.utils import choice_anos_com_sessaoplenaria
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('sessao')
]
)
ApiViewSetConstrutor.build_class([apps.get_app_config("sessao")])
@customize(SessaoPlenaria)
class _SessaoPlenariaViewSet:
@action(detail=False)
def years(self, request, *args, **kwargs):
years = choice_anos_com_sessaoplenaria()
@ -34,14 +29,14 @@ class _SessaoPlenariaViewSet:
@wrapper_queryset_response_for_drf_action(model=ExpedienteSessao)
def get_expedientes(self):
return self.get_queryset().filter(sessao_plenaria_id=self.kwargs['pk'])
return self.get_queryset().filter(sessao_plenaria_id=self.kwargs["pk"])
@action(detail=True)
def ecidadania(self, request, *args, **kwargs):
self.serializer_class = SessaoPlenariaECidadaniaSerializer
return self.retrieve(request, *args, **kwargs)
@action(detail=False, url_path='ecidadania')
@action(detail=False, url_path="ecidadania")
def ecidadania_list(self, request, *args, **kwargs):
self.serializer_class = SessaoPlenariaECidadaniaSerializer
return self.list(request, *args, **kwargs)

2
sapl/audiencia/__init__.py

@ -1 +1 @@
default_app_config = 'sapl.audiencia.apps.AppConfig'
default_app_config = "sapl.audiencia.apps.AppConfig"

6
sapl/audiencia/apps.py

@ -3,6 +3,6 @@ from django.utils.translation import ugettext_lazy as _
class AppConfig(apps.AppConfig):
name = 'sapl.audiencia'
label = 'audiencia'
verbose_name = _('Audiência Pública')
name = "sapl.audiencia"
label = "audiencia"
verbose_name = _("Audiência Pública")

233
sapl/audiencia/forms.py

@ -1,19 +1,26 @@
import logging
from datetime import datetime
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout
from django import forms
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import transaction
from django.utils.translation import ugettext_lazy as _
from crispy_forms.layout import Button, Column, Fieldset, HTML, Layout
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica, AnexoAudienciaPublica
from sapl.crispy_layout_mixin import form_actions, SaplFormHelper, SaplFormLayout, to_row
from sapl.audiencia.models import (
AnexoAudienciaPublica,
AudienciaPublica,
TipoAudienciaPublica,
)
from sapl.crispy_layout_mixin import (
SaplFormHelper,
SaplFormLayout,
form_actions,
to_row,
)
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.parlamentares.models import Parlamentar
from sapl.utils import timezone, FileFieldCheckMixin, validar_arquivo
from sapl.utils import FileFieldCheckMixin, timezone, validar_arquivo
class AudienciaForm(FileFieldCheckMixin, forms.ModelForm):
@ -22,57 +29,83 @@ class AudienciaForm(FileFieldCheckMixin, forms.ModelForm):
tipo = forms.ModelChoiceField(
required=True,
label=_('Tipo de Audiência Pública'),
queryset=TipoAudienciaPublica.objects.all().order_by('nome'))
label=_("Tipo de Audiência Pública"),
queryset=TipoAudienciaPublica.objects.all().order_by("nome"),
)
tipo_materia = forms.ModelChoiceField(
label=_('Tipo Matéria'),
label=_("Tipo Matéria"),
required=False,
queryset=TipoMateriaLegislativa.objects.all(),
empty_label=_('Selecione'))
empty_label=_("Selecione"),
)
numero_materia = forms.CharField(
label=_('Número Matéria'),
required=False)
numero_materia = forms.CharField(label=_("Número Matéria"), required=False)
ano_materia = forms.CharField(
label=_('Ano Matéria'),
required=False)
ano_materia = forms.CharField(label=_("Ano Matéria"), required=False)
materia = forms.ModelChoiceField(
required=False,
widget=forms.HiddenInput(),
queryset=MateriaLegislativa.objects.all())
queryset=MateriaLegislativa.objects.all(),
)
parlamentar_autor = forms.ModelChoiceField(
label=_("Parlamentar Autor"),
required=False,
queryset=Parlamentar.objects.all())
label=_("Parlamentar Autor"), required=False, queryset=Parlamentar.objects.all()
)
requerimento = forms.ModelChoiceField(
label=_("Requerimento"),
required=False,
queryset=MateriaLegislativa.objects.select_related("tipo").filter(tipo__descricao="Requerimento"))
queryset=MateriaLegislativa.objects.select_related("tipo").filter(
tipo__descricao="Requerimento"
),
)
class Meta:
model = AudienciaPublica
fields = ['tipo', 'numero', 'ano', 'nome',
'tema', 'data', 'hora_inicio', 'hora_fim',
'observacao', 'audiencia_cancelada', 'parlamentar_autor', 'requerimento', 'url_audio',
'url_video', 'upload_pauta', 'upload_ata',
'upload_anexo', 'tipo_materia', 'numero_materia',
'ano_materia', 'materia']
fields = [
"tipo",
"numero",
"ano",
"nome",
"tema",
"data",
"hora_inicio",
"hora_fim",
"observacao",
"audiencia_cancelada",
"parlamentar_autor",
"requerimento",
"url_audio",
"url_video",
"upload_pauta",
"upload_ata",
"upload_anexo",
"tipo_materia",
"numero_materia",
"ano_materia",
"materia",
]
def __init__(self, **kwargs):
super(AudienciaForm, self).__init__(**kwargs)
tipos = []
if not self.fields['tipo'].queryset:
tipos.append(TipoAudienciaPublica.objects.create(nome='Audiência Pública', tipo='A'))
tipos.append(TipoAudienciaPublica.objects.create(nome='Plebiscito', tipo='P'))
tipos.append(TipoAudienciaPublica.objects.create(nome='Referendo', tipo='R'))
tipos.append(TipoAudienciaPublica.objects.create(nome='Iniciativa Popular', tipo='I'))
if not self.fields["tipo"].queryset:
tipos.append(
TipoAudienciaPublica.objects.create(nome="Audiência Pública", tipo="A")
)
tipos.append(
TipoAudienciaPublica.objects.create(nome="Plebiscito", tipo="P")
)
tipos.append(
TipoAudienciaPublica.objects.create(nome="Referendo", tipo="R")
)
tipos.append(
TipoAudienciaPublica.objects.create(nome="Iniciativa Popular", tipo="I")
)
for t in tipos:
t.save()
@ -82,95 +115,125 @@ class AudienciaForm(FileFieldCheckMixin, forms.ModelForm):
if not self.is_valid():
return cleaned_data
materia = cleaned_data['numero_materia']
ano_materia = cleaned_data['ano_materia']
tipo_materia = cleaned_data['tipo_materia']
materia = cleaned_data["numero_materia"]
ano_materia = cleaned_data["ano_materia"]
tipo_materia = cleaned_data["tipo_materia"]
parlamentar_autor = cleaned_data["parlamentar_autor"]
requerimento = cleaned_data["requerimento"]
if cleaned_data["ano"] != cleaned_data["data"].year:
raise ValidationError(f"Ano da audiência ({cleaned_data['ano']}) difere "
f"do ano no campo data ({cleaned_data['data'].year})")
raise ValidationError(
f"Ano da audiência ({cleaned_data['ano']}) difere "
f"do ano no campo data ({cleaned_data['data'].year})"
)
#
# TODO: converter hora_inicio e hora_fim para TimeField
#
# valida hora inicio
try:
datetime.strptime(cleaned_data["hora_inicio"], '%H:%M').time()
datetime.strptime(cleaned_data["hora_inicio"], "%H:%M").time()
except ValueError:
raise ValidationError(f"Formato de horário de início inválido: {cleaned_data['hora_inicio']}")
raise ValidationError(
f"Formato de horário de início inválido: {cleaned_data['hora_inicio']}"
)
# valida hora fim
if cleaned_data["hora_fim"]:
try:
datetime.strptime(cleaned_data["hora_fim"], '%H:%M').time()
datetime.strptime(cleaned_data["hora_fim"], "%H:%M").time()
except ValueError:
raise ValidationError(f"Formato de horário de fim inválido: {cleaned_data['hora_fim']}")
raise ValidationError(
f"Formato de horário de fim inválido: {cleaned_data['hora_fim']}"
)
if materia and ano_materia and tipo_materia:
try:
self.logger.debug("Tentando obter MateriaLegislativa %s%s/%s." % (tipo_materia, materia, ano_materia))
self.logger.debug(
"Tentando obter MateriaLegislativa %s%s/%s."
% (tipo_materia, materia, ano_materia)
)
materia = MateriaLegislativa.objects.get(
numero=materia,
ano=ano_materia,
tipo=tipo_materia)
numero=materia, ano=ano_materia, tipo=tipo_materia
)
except ObjectDoesNotExist:
msg = _('A matéria %s%s/%s não existe no cadastro'
' de matérias legislativas.' % (tipo_materia, materia, ano_materia))
msg = _(
"A matéria %s%s/%s não existe no cadastro"
" de matérias legislativas." % (tipo_materia, materia, ano_materia)
)
self.logger.warning(
'A MateriaLegislativa %s%s/%s não existe no cadastro'
' de matérias legislativas.' % (tipo_materia, materia, ano_materia)
"A MateriaLegislativa %s%s/%s não existe no cadastro"
" de matérias legislativas." % (tipo_materia, materia, ano_materia)
)
raise ValidationError(msg)
else:
self.logger.info("MateriaLegislativa %s%s/%s obtida com sucesso." % (tipo_materia, materia, ano_materia))
cleaned_data['materia'] = materia
self.logger.info(
"MateriaLegislativa %s%s/%s obtida com sucesso."
% (tipo_materia, materia, ano_materia)
)
cleaned_data["materia"] = materia
else:
campos = [materia, tipo_materia, ano_materia]
if campos.count(None) + campos.count('') < len(campos):
msg = _('Preencha todos os campos relacionados à Matéria Legislativa')
if campos.count(None) + campos.count("") < len(campos):
msg = _("Preencha todos os campos relacionados à Matéria Legislativa")
self.logger.warning(
'Algum campo relacionado à MatériaLegislativa %s%s/%s \
não foi preenchido.' % (tipo_materia, materia, ano_materia)
"Algum campo relacionado à MatériaLegislativa %s%s/%s \
não foi preenchido."
% (tipo_materia, materia, ano_materia)
)
raise ValidationError(msg)
if not cleaned_data['numero']:
ultima_audiencia = AudienciaPublica.objects.all().order_by('ano', 'numero').last()
if not cleaned_data["numero"]:
ultima_audiencia = (
AudienciaPublica.objects.all().order_by("ano", "numero").last()
)
if ultima_audiencia:
cleaned_data['numero'] = ultima_audiencia.numero + 1
cleaned_data["numero"] = ultima_audiencia.numero + 1
else:
cleaned_data['numero'] = 1
cleaned_data["numero"] = 1
else:
if AudienciaPublica.objects.filter(numero=cleaned_data['numero'], ano=cleaned_data['ano']).exclude(pk=self.instance.pk).exists():
raise ValidationError(f"Já existe uma audiência pública com a numeração {str(cleaned_data['numero']).rjust(3, '0')}/{cleaned_data['ano']}.")
if (
AudienciaPublica.objects.filter(
numero=cleaned_data["numero"], ano=cleaned_data["ano"]
)
.exclude(pk=self.instance.pk)
.exists()
):
raise ValidationError(
f"Já existe uma audiência pública com a numeração {str(cleaned_data['numero']).rjust(3, '0')}/{cleaned_data['ano']}."
)
if self.cleaned_data['hora_inicio'] and self.cleaned_data['hora_fim']:
if self.cleaned_data['hora_fim'] < self.cleaned_data['hora_inicio']:
msg = _('A hora de fim ({}) não pode ser anterior a hora de início({})'
.format(self.cleaned_data['hora_fim'], self.cleaned_data['hora_inicio']))
self.logger.warning(
'Hora de fim anterior à hora de início.'
if self.cleaned_data["hora_inicio"] and self.cleaned_data["hora_fim"]:
if self.cleaned_data["hora_fim"] < self.cleaned_data["hora_inicio"]:
msg = _(
"A hora de fim ({}) não pode ser anterior a hora de início({})".format(
self.cleaned_data["hora_fim"], self.cleaned_data["hora_inicio"]
)
)
self.logger.warning("Hora de fim anterior à hora de início.")
raise ValidationError(msg)
# requerimento é optativo
if parlamentar_autor and requerimento:
if parlamentar_autor.autor.first() not in requerimento.autores.all():
raise ValidationError("Parlamentar Autor selecionado não faz"
raise ValidationError(
"Parlamentar Autor selecionado não faz"
" parte da autoria do Requerimento "
"selecionado.")
"selecionado."
)
elif parlamentar_autor:
raise ValidationError("Para informar um autor deve-se informar um requerimento.")
raise ValidationError(
"Para informar um autor deve-se informar um requerimento."
)
elif requerimento:
raise ValidationError("Para informar um requerimento deve-se informar um autor.")
raise ValidationError(
"Para informar um requerimento deve-se informar um autor."
)
upload_pauta = self.cleaned_data.get('upload_pauta', False)
upload_ata = self.cleaned_data.get('upload_ata', False)
upload_anexo = self.cleaned_data.get('upload_anexo', False)
upload_pauta = self.cleaned_data.get("upload_pauta", False)
upload_ata = self.cleaned_data.get("upload_ata", False)
upload_anexo = self.cleaned_data.get("upload_anexo", False)
if upload_pauta:
validar_arquivo(upload_pauta, "Pauta da Audiência Pública")
@ -185,26 +248,20 @@ class AudienciaForm(FileFieldCheckMixin, forms.ModelForm):
class AnexoAudienciaPublicaForm(forms.ModelForm):
class Meta:
model = AnexoAudienciaPublica
fields = ['arquivo',
'assunto']
fields = ["arquivo", "assunto"]
def __init__(self, *args, **kwargs):
row1 = to_row([("arquivo", 4)])
row1 = to_row(
[('arquivo', 4)])
row2 = to_row(
[('assunto', 12)])
row2 = to_row([("assunto", 12)])
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(_('Identificação Básica'),
row1, row2))
super(AnexoAudienciaPublicaForm, self).__init__(
*args, **kwargs)
Fieldset(_("Identificação Básica"), row1, row2)
)
super(AnexoAudienciaPublicaForm, self).__init__(*args, **kwargs)
def clean(self):
super(AnexoAudienciaPublicaForm, self).clean()
@ -212,7 +269,7 @@ class AnexoAudienciaPublicaForm(forms.ModelForm):
if not self.is_valid():
return self.cleaned_data
arquivo = self.cleaned_data.get('arquivo', False)
arquivo = self.cleaned_data.get("arquivo", False)
if arquivo:
validar_arquivo(arquivo, "Arquivo")

185
sapl/audiencia/models.py

@ -2,130 +2,152 @@ from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
from sapl.materia.models import MateriaLegislativa
from sapl.parlamentares.models import (CargoMesa, Parlamentar)
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, SaplGenericRelation,
restringe_tipos_de_arquivo_txt, texto_upload_path,
OverwriteStorage)
from sapl.materia.models import MateriaLegislativa
from sapl.parlamentares.models import CargoMesa, Parlamentar
from sapl.utils import (
RANGE_ANOS,
YES_NO_CHOICES,
OverwriteStorage,
SaplGenericRelation,
restringe_tipos_de_arquivo_txt,
texto_upload_path,
)
def get_audiencia_media_path(instance, subpath, filename):
return './sapl/audiencia/%s/%s/%s' % (instance.numero, subpath, filename)
return "./sapl/audiencia/%s/%s/%s" % (instance.numero, subpath, filename)
def pauta_upload_path(instance, filename):
return texto_upload_path(
instance, filename, subpath='pauta', pk_first=True)
return texto_upload_path(instance, filename, subpath="pauta", pk_first=True)
def ata_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath='ata', pk_first=True)
return texto_upload_path(instance, filename, subpath="ata", pk_first=True)
def anexo_upload_path(instance, filename):
return texto_upload_path(
instance, filename, subpath='anexo', pk_first=True)
return texto_upload_path(instance, filename, subpath="anexo", pk_first=True)
class TipoAudienciaPublica(models.Model):
TIPO_AUDIENCIA_CHOICES = Choices(('A', 'audiencia', _('Audiência Pública')),
('P', 'plebiscito', _('Plebiscito')),
('R', 'referendo', _('Referendo')),
('I', 'iniciativa', _('Iniciativa Popular')))
TIPO_AUDIENCIA_CHOICES = Choices(
("A", "audiencia", _("Audiência Pública")),
("P", "plebiscito", _("Plebiscito")),
("R", "referendo", _("Referendo")),
("I", "iniciativa", _("Iniciativa Popular")),
)
nome = models.CharField(
max_length=50, verbose_name=_('Nome do Tipo de Audiência Pública'), default='Audiência Pública')
max_length=50,
verbose_name=_("Nome do Tipo de Audiência Pública"),
default="Audiência Pública",
)
tipo = models.CharField(
max_length=1, verbose_name=_('Tipo de Audiência Pública'), choices=TIPO_AUDIENCIA_CHOICES, default='A')
max_length=1,
verbose_name=_("Tipo de Audiência Pública"),
choices=TIPO_AUDIENCIA_CHOICES,
default="A",
)
class Meta:
verbose_name = _('Tipo de Audiência Pública')
verbose_name_plural = _('Tipos de Audiência Pública')
ordering = ['nome']
verbose_name = _("Tipo de Audiência Pública")
verbose_name_plural = _("Tipos de Audiência Pública")
ordering = ["nome"]
def __str__(self):
return self.nome
class AudienciaPublica(models.Model):
materia = models.ForeignKey(
MateriaLegislativa,
on_delete=models.PROTECT,
null=True,
blank=True,
verbose_name=_('Matéria Legislativa'))
tipo = models.ForeignKey(TipoAudienciaPublica,
verbose_name=_("Matéria Legislativa"),
)
tipo = models.ForeignKey(
TipoAudienciaPublica,
on_delete=models.PROTECT,
null=True,
blank=True,
verbose_name=_('Tipo de Audiência Pública'))
numero = models.PositiveIntegerField(blank=True, verbose_name=_('Número'))
ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'),
choices=RANGE_ANOS)
nome = models.CharField(
max_length=100, verbose_name=_('Nome da Audiência Pública'))
tema = models.CharField(
max_length=100, verbose_name=_('Tema da Audiência Pública'))
data = models.DateField(verbose_name=_('Data'))
verbose_name=_("Tipo de Audiência Pública"),
)
numero = models.PositiveIntegerField(blank=True, verbose_name=_("Número"))
ano = models.PositiveSmallIntegerField(verbose_name=_("Ano"), choices=RANGE_ANOS)
nome = models.CharField(max_length=100, verbose_name=_("Nome da Audiência Pública"))
tema = models.CharField(max_length=100, verbose_name=_("Tema da Audiência Pública"))
data = models.DateField(verbose_name=_("Data"))
hora_inicio = models.CharField(
max_length=5, verbose_name=_('Horário Início(hh:mm)'))
max_length=5, verbose_name=_("Horário Início(hh:mm)")
)
hora_fim = models.CharField(
max_length=5, blank=True, verbose_name=_('Horário Fim(hh:mm)'))
max_length=5, blank=True, verbose_name=_("Horário Fim(hh:mm)")
)
observacao = models.TextField(
max_length=500, blank=True, verbose_name=_('Observação'))
max_length=500, blank=True, verbose_name=_("Observação")
)
audiencia_cancelada = models.BooleanField(
default=False,
choices=YES_NO_CHOICES,
verbose_name=_('Audiência Cancelada?'))
default=False, choices=YES_NO_CHOICES, verbose_name=_("Audiência Cancelada?")
)
parlamentar_autor = models.ForeignKey(
Parlamentar,
on_delete=models.PROTECT,
null=True,
blank=True,
verbose_name=_('Parlamentar Autor'))
verbose_name=_("Parlamentar Autor"),
)
requerimento = models.ForeignKey(
MateriaLegislativa,
null=True,
blank=True,
on_delete=models.PROTECT,
verbose_name=_('Requerimento da Audiência Pública'),
related_name=_('requerimento'))
verbose_name=_("Requerimento da Audiência Pública"),
related_name=_("requerimento"),
)
url_audio = models.URLField(
max_length=150, blank=True,
verbose_name=_('URL Arquivo Áudio (Formatos MP3 / AAC)'))
max_length=150,
blank=True,
verbose_name=_("URL Arquivo Áudio (Formatos MP3 / AAC)"),
)
url_video = models.URLField(
max_length=150, blank=True,
verbose_name=_('URL Arquivo Vídeo (Formatos MP4 / FLV / WebM)'))
max_length=150,
blank=True,
verbose_name=_("URL Arquivo Vídeo (Formatos MP4 / FLV / WebM)"),
)
upload_pauta = models.FileField(
max_length=300,
blank=True,
null=True,
upload_to=pauta_upload_path,
storage=OverwriteStorage(),
verbose_name=_('Pauta da Audiência Pública'),
validators=[restringe_tipos_de_arquivo_txt])
verbose_name=_("Pauta da Audiência Pública"),
validators=[restringe_tipos_de_arquivo_txt],
)
upload_ata = models.FileField(
max_length=300,
blank=True,
null=True,
upload_to=ata_upload_path,
verbose_name=_('Ata da Audiência Pública'),
verbose_name=_("Ata da Audiência Pública"),
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
validators=[restringe_tipos_de_arquivo_txt],
)
upload_anexo = models.FileField(
max_length=300,
blank=True,
null=True,
upload_to=anexo_upload_path,
storage=OverwriteStorage(),
verbose_name=_('Anexo da Audiência Pública'))
verbose_name=_("Anexo da Audiência Pública"),
)
class Meta:
verbose_name = _('Audiência Pública')
verbose_name_plural = _('Audiências Públicas')
ordering = ['ano', 'numero', 'nome', 'tipo']
verbose_name = _("Audiência Pública")
verbose_name_plural = _("Audiências Públicas")
ordering = ["ano", "numero", "nome", "tipo"]
def __str__(self):
return self.nome
@ -148,49 +170,52 @@ class AudienciaPublica(models.Model):
return result
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
if not self.pk and (self.upload_pauta or self.upload_ata or
self.upload_anexo):
def save(
self, force_insert=False, force_update=False, using=None, update_fields=None
):
if not self.pk and (self.upload_pauta or self.upload_ata or self.upload_anexo):
upload_pauta = self.upload_pauta
upload_ata = self.upload_ata
upload_anexo = self.upload_anexo
self.upload_pauta = None
self.upload_ata = None
self.upload_anexo = None
models.Model.save(self, force_insert=force_insert,
models.Model.save(
self,
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
update_fields=update_fields,
)
self.upload_pauta = upload_pauta
self.upload_ata = upload_ata
self.upload_anexo = upload_anexo
return models.Model.save(self, force_insert=force_insert,
return models.Model.save(
self,
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
update_fields=update_fields,
)
class AnexoAudienciaPublica(models.Model):
audiencia = models.ForeignKey(AudienciaPublica,
on_delete=models.PROTECT)
audiencia = models.ForeignKey(AudienciaPublica, on_delete=models.PROTECT)
arquivo = models.FileField(
max_length=300,
upload_to=texto_upload_path,
storage=OverwriteStorage(),
verbose_name=_('Arquivo'))
data = models.DateField(
auto_now=timezone.now)
assunto = models.TextField(
verbose_name=_('Assunto'))
verbose_name=_("Arquivo"),
)
data = models.DateField(auto_now=timezone.now)
assunto = models.TextField(verbose_name=_("Assunto"))
class Meta:
verbose_name = _('Anexo de Documento Acessório')
verbose_name_plural = _('Anexo de Documentos Acessórios')
ordering = ('id',)
verbose_name = _("Anexo de Documento Acessório")
verbose_name_plural = _("Anexo de Documentos Acessórios")
ordering = ("id",)
def __str__(self):
return self.assunto
@ -204,7 +229,9 @@ class AnexoAudienciaPublica(models.Model):
return result
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
def save(
self, force_insert=False, force_update=False, using=None, update_fields=None
):
if not self.pk and self.arquivo:
arquivo = self.arquivo
self.arquivo = None
@ -213,8 +240,14 @@ class AnexoAudienciaPublica(models.Model):
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
update_fields=update_fields,
)
self.arquivo = arquivo
return models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using,
update_fields=update_fields)
return models.Model.save(
self,
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields,
)

82
sapl/audiencia/tests/test_audiencia.py

@ -1,57 +1,62 @@
import pytest
import datetime
from model_bakery import baker
import pytest
from django.utils.translation import ugettext as _
from model_bakery import baker
from sapl.audiencia import forms
from sapl.audiencia.models import AnexoAudienciaPublica
from sapl.audiencia.models import TipoAudienciaPublica, AudienciaPublica
from sapl.audiencia.models import (
AnexoAudienciaPublica,
AudienciaPublica,
TipoAudienciaPublica,
)
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
@pytest.mark.django_db(transaction=False)
def test_tipo_audiencia_publica_model():
baker.make(TipoAudienciaPublica,
nome='Teste_Nome_Tipo_Audiencia_Publica',
tipo='A')
baker.make(TipoAudienciaPublica, nome="Teste_Nome_Tipo_Audiencia_Publica", tipo="A")
tipo_audiencia_publica = TipoAudienciaPublica.objects.first()
assert tipo_audiencia_publica.nome == 'Teste_Nome_Tipo_Audiencia_Publica'
assert tipo_audiencia_publica.tipo == 'A'
assert tipo_audiencia_publica.nome == "Teste_Nome_Tipo_Audiencia_Publica"
assert tipo_audiencia_publica.tipo == "A"
@pytest.mark.django_db(transaction=False)
def test_audiencia_publica_model():
baker.make(AudienciaPublica,
baker.make(
AudienciaPublica,
numero=1,
nome='Teste_Nome_Audiencia_Publica',
tema='Teste_Tema_Audiencia_Publica',
data='2016-03-21',
hora_inicio='16:03')
nome="Teste_Nome_Audiencia_Publica",
tema="Teste_Tema_Audiencia_Publica",
data="2016-03-21",
hora_inicio="16:03",
)
audiencia_publica = AudienciaPublica.objects.first()
data = '2016-03-21'
data = "2016-03-21"
teste_data = datetime.datetime.strptime(data, "%Y-%m-%d").date()
assert audiencia_publica.numero == 1
assert audiencia_publica.nome == 'Teste_Nome_Audiencia_Publica'
assert audiencia_publica.tema == 'Teste_Tema_Audiencia_Publica'
assert audiencia_publica.nome == "Teste_Nome_Audiencia_Publica"
assert audiencia_publica.tema == "Teste_Tema_Audiencia_Publica"
assert audiencia_publica.data == teste_data
assert audiencia_publica.hora_inicio == '16:03'
assert audiencia_publica.hora_inicio == "16:03"
@pytest.mark.django_db(transaction=False)
def test_anexo_audiencia_publica_model():
audiencia = baker.make(AudienciaPublica,
audiencia = baker.make(
AudienciaPublica,
numero=2,
nome='Nome_Audiencia_Publica',
tema='Tema_Audiencia_Publica',
data='2017-04-22',
hora_inicio='17:04')
nome="Nome_Audiencia_Publica",
tema="Tema_Audiencia_Publica",
data="2017-04-22",
hora_inicio="17:04",
)
baker.make(AnexoAudienciaPublica,
audiencia=audiencia)
baker.make(AnexoAudienciaPublica, audiencia=audiencia)
anexo_audiencia_publica = AnexoAudienciaPublica.objects.first()
assert anexo_audiencia_publica.audiencia == audiencia
@ -65,11 +70,11 @@ def test_valida_campos_obrigatorios_audiencia_form():
errors = form.errors
assert errors['nome'] == [_('Este campo é obrigatório.')]
assert errors['tema'] == [_('Este campo é obrigatório.')]
assert errors['tipo'] == [_('Este campo é obrigatório.')]
assert errors['data'] == [_('Este campo é obrigatório.')]
assert errors['hora_inicio'] == [_('Este campo é obrigatório.')]
assert errors["nome"] == [_("Este campo é obrigatório.")]
assert errors["tema"] == [_("Este campo é obrigatório.")]
assert errors["tipo"] == [_("Este campo é obrigatório.")]
assert errors["data"] == [_("Este campo é obrigatório.")]
assert errors["hora_inicio"] == [_("Este campo é obrigatório.")]
assert len(errors) == 6
@ -80,11 +85,14 @@ def test_audiencia_form_hora_invalida():
tipo = baker.make(TipoAudienciaPublica)
form = forms.AudienciaForm(data={'nome': 'Nome da Audiencia',
'tema': 'Tema da Audiencia',
'tipo': tipo,
'data': '2016-10-01',
'hora_inicio': '10:00',
'hora_fim': '9:00',
})
form = forms.AudienciaForm(
data={
"nome": "Nome da Audiencia",
"tema": "Tema da Audiencia",
"tipo": tipo,
"data": "2016-10-01",
"hora_inicio": "10:00",
"hora_fim": "9:00",
}
)
assert not form.is_valid()

8
sapl/audiencia/urls.py

@ -1,10 +1,14 @@
from django.conf.urls import include, url
from sapl.audiencia.views import (index, AudienciaCrud, AnexoAudienciaPublicaCrud)
from sapl.audiencia.views import AnexoAudienciaPublicaCrud, AudienciaCrud, index
from .apps import AppConfig
app_name = AppConfig.name
urlpatterns = [
url(r'^audiencia/', include(AudienciaCrud.get_urls() + AnexoAudienciaPublicaCrud.get_urls())),
url(
r"^audiencia/",
include(AudienciaCrud.get_urls() + AnexoAudienciaPublicaCrud.get_urls()),
),
]

71
sapl/audiencia/views.py

@ -1,13 +1,13 @@
import sapl
from django.http import HttpResponse
from django.urls import reverse
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import UpdateView
import sapl
from sapl.crud.base import RP_DETAIL, RP_LIST, Crud, MasterDetailCrud
from .forms import AudienciaForm, AnexoAudienciaPublicaForm
from .models import AudienciaPublica, AnexoAudienciaPublica
from .forms import AnexoAudienciaPublicaForm, AudienciaForm
from .models import AnexoAudienciaPublica, AudienciaPublica
def index(request):
@ -16,11 +16,14 @@ def index(request):
class AudienciaCrud(Crud):
model = AudienciaPublica
public = [RP_LIST, RP_DETAIL, ]
public = [
RP_LIST,
RP_DETAIL,
]
class BaseMixin(Crud.BaseMixin):
list_field_names = ['numero', 'nome', 'tipo', 'materia', 'data']
ordering = '-ano', '-numero', '-data', 'nome', 'tipo'
list_field_names = ["numero", "nome", "tipo", "materia", "data"]
ordering = "-ano", "-numero", "-data", "nome", "tipo"
class ListView(Crud.ListView):
paginate_by = 10
@ -28,20 +31,32 @@ class AudienciaCrud(Crud):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
audiencia_materia = {str(a.id): (a.materia, a.numero, a.ano) for a in context['object_list']}
for row in context['rows']:
audiencia_id = row[0][1].split('/')[-1]
tema = str(audiencia_materia[audiencia_id][1]).rjust(3, '0') + '/' + str(audiencia_materia[audiencia_id][2])
audiencia_materia = {
str(a.id): (a.materia, a.numero, a.ano) for a in context["object_list"]
}
for row in context["rows"]:
audiencia_id = row[0][1].split("/")[-1]
tema = (
str(audiencia_materia[audiencia_id][1]).rjust(3, "0")
+ "/"
+ str(audiencia_materia[audiencia_id][2])
)
row[0] = (tema, row[0][1])
coluna_materia = row[3] # Se mudar a ordem de listagem, mudar aqui.
if coluna_materia[0]:
materia = audiencia_materia[audiencia_id][0]
if materia is not None:
url_materia = reverse('sapl.materia:materialegislativa_detail', kwargs={'pk': materia.id})
url_materia = reverse(
"sapl.materia:materialegislativa_detail",
kwargs={"pk": materia.id},
)
else:
url_materia = None
row[3] = (coluna_materia[0], url_materia) # Se mudar a ordem de listagem, mudar aqui.
row[3] = (
coluna_materia[0],
url_materia,
) # Se mudar a ordem de listagem, mudar aqui.
return context
class CreateView(Crud.CreateView):
@ -56,17 +71,16 @@ class AudienciaCrud(Crud):
def get_initial(self):
initial = super(UpdateView, self).get_initial()
if self.object.materia:
initial['tipo_materia'] = self.object.materia.tipo.id
initial['numero_materia'] = self.object.materia.numero
initial['ano_materia'] = self.object.materia.ano
initial["tipo_materia"] = self.object.materia.tipo.id
initial["numero_materia"] = self.object.materia.numero
initial["ano_materia"] = self.object.materia.ano
return initial
class DeleteView(Crud.DeleteView):
pass
class DetailView(Crud.DetailView):
layout_key = 'AudienciaPublicaDetail'
layout_key = "AudienciaPublicaDetail"
@xframe_options_exempt
def get(self, request, *args, **kwargs):
@ -74,10 +88,9 @@ class AudienciaCrud(Crud):
class AudienciaPublicaMixin:
def has_permission(self):
app_config = sapl.base.models.AppConfig.objects.last()
if app_config and app_config.documentos_administrativos == 'O':
if app_config and app_config.documentos_administrativos == "O":
return True
return super().has_permission()
@ -85,12 +98,15 @@ class AudienciaPublicaMixin:
class AnexoAudienciaPublicaCrud(MasterDetailCrud):
model = AnexoAudienciaPublica
parent_field = 'audiencia'
help_topic = 'numeracao_docsacess'
public = [RP_LIST, RP_DETAIL, ]
parent_field = "audiencia"
help_topic = "numeracao_docsacess"
public = [
RP_LIST,
RP_DETAIL,
]
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['assunto']
list_field_names = ["assunto"]
class CreateView(MasterDetailCrud.CreateView):
form_class = AnexoAudienciaPublicaForm
@ -100,11 +116,10 @@ class AnexoAudienciaPublicaCrud(MasterDetailCrud):
form_class = AnexoAudienciaPublicaForm
class ListView(AudienciaPublicaMixin, 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', '-id')
kwargs = {self.crud.parent_field: self.kwargs["pk"]}
return qs.filter(**kwargs).order_by("-data", "-id")
class DetailView(AudienciaPublicaMixin, MasterDetailCrud.DetailView):
pass

2
sapl/base/__init__.py

@ -1 +1 @@
default_app_config = 'sapl.base.apps.AppConfig'
default_app_config = "sapl.base.apps.AppConfig"

4
sapl/base/admin.py

@ -7,8 +7,8 @@ from sapl.utils import register_all_models_in_admin
register_all_models_in_admin(__name__)
admin.site.site_title = 'Administração - SAPL'
admin.site.site_header = 'Administração - SAPL'
admin.site.site_title = "Administração - SAPL"
admin.site.site_header = "Administração - SAPL"
class AuditLogAdmin(admin.ModelAdmin):

6
sapl/base/apps.py

@ -3,9 +3,9 @@ from django.utils.translation import ugettext_lazy as _
class AppConfig(django.apps.AppConfig):
name = 'sapl.base'
label = 'base'
verbose_name = _('Dados Básicos')
name = "sapl.base"
label = "base"
verbose_name = _("Dados Básicos")
def ready(self):
from sapl.base import receivers

177
sapl/base/email_utils.py

@ -1,33 +1,32 @@
from datetime import datetime as dt
import logging
from datetime import datetime as dt
from django.core.mail import EmailMultiAlternatives, get_connection, send_mail
from django.urls import reverse
from django.template import Context, loader
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from sapl.base.models import CasaLegislativa
from sapl.materia.models import AcompanhamentoMateria
from sapl.protocoloadm.models import AcompanhamentoDocumento
from sapl.settings import EMAIL_SEND_USER
from sapl.utils import mail_service_configured
from django.utils.translation import ugettext_lazy as _
def load_email_templates(templates, context={}):
emails = []
for t in templates:
tpl = loader.get_template(t)
email = tpl.render(context)
if t.endswith(".html"):
email = email.replace('\\n', '').replace('\r', '')
email = email.replace("\\n", "").replace("\r", "")
emails.append(email)
return emails
def enviar_emails(sender, recipients, messages):
'''
"""
Recipients is a string list of email addresses
Messages is an array of dicts of the form:
@ -36,36 +35,41 @@ def enviar_emails(sender, recipients, messages):
'txt_message': 'text message',
'html_message': 'html message'
}
'''
"""
if len(messages) == 1:
# sends an email simultaneously to all recipients
send_mail(messages[0]['subject'],
messages[0]['txt_message'],
send_mail(
messages[0]["subject"],
messages[0]["txt_message"],
sender,
recipients,
html_message=messages[0]['html_message'],
fail_silently=False)
html_message=messages[0]["html_message"],
fail_silently=False,
)
elif len(recipients) > len(messages):
raise ValueError("Message list should have size 1 \
raise ValueError(
"Message list should have size 1 \
or equal recipient list size. \
recipients: %s, messages: %s" % (recipients, messages)
recipients: %s, messages: %s"
% (recipients, messages)
)
else:
# sends an email simultaneously to all reciepients
for (d, m) in zip(recipients, messages):
send_mail(m['subject'],
m['txt_message'],
for d, m in zip(recipients, messages):
send_mail(
m["subject"],
m["txt_message"],
sender,
[d],
html_message=m['html_message'],
fail_silently=False)
html_message=m["html_message"],
fail_silently=False,
)
def criar_email_confirmacao(base_url, casa_legislativa, doc_mat, tipo, hash_txt=''):
def criar_email_confirmacao(base_url, casa_legislativa, doc_mat, tipo, hash_txt=""):
if not casa_legislativa:
raise ValueError("Casa Legislativa é obrigatória")
@ -77,28 +81,34 @@ def criar_email_confirmacao(base_url, casa_legislativa, doc_mat, tipo, hash_txt=
raise ValueError(msg)
# FIXME i18n
casa_nome = ("{} de {} - {}".format(casa_legislativa.nome,
casa_legislativa.municipio,
casa_legislativa.uf))
casa_nome = "{} de {} - {}".format(
casa_legislativa.nome, casa_legislativa.municipio, casa_legislativa.uf
)
if tipo == "materia":
doc_mat_url = reverse('sapl.materia:materialegislativa_detail',
kwargs={'pk': doc_mat.id})
confirmacao_url = reverse('sapl.materia:acompanhar_confirmar',
kwargs={'pk': doc_mat.id})
doc_mat_url = reverse(
"sapl.materia:materialegislativa_detail", kwargs={"pk": doc_mat.id}
)
confirmacao_url = reverse(
"sapl.materia:acompanhar_confirmar", kwargs={"pk": doc_mat.id}
)
ementa = doc_mat.ementa
autores = [autoria.autor.nome for autoria in doc_mat.autoria_set.all()]
else:
doc_mat_url = reverse('sapl.protocoloadm:documentoadministrativo_detail',
kwargs={'pk': doc_mat.id})
confirmacao_url = reverse('sapl.protocoloadm:acompanhar_confirmar',
kwargs={'pk': doc_mat.id})
doc_mat_url = reverse(
"sapl.protocoloadm:documentoadministrativo_detail",
kwargs={"pk": doc_mat.id},
)
confirmacao_url = reverse(
"sapl.protocoloadm:acompanhar_confirmar", kwargs={"pk": doc_mat.id}
)
ementa = doc_mat.assunto
autores = ""
templates = load_email_templates(['email/acompanhar.txt',
'email/acompanhar.html'],
{"casa_legislativa": casa_nome,
templates = load_email_templates(
["email/acompanhar.txt", "email/acompanhar.html"],
{
"casa_legislativa": casa_nome,
"logotipo": casa_legislativa.logotipo,
"descricao_materia": ementa,
"autoria": autores,
@ -106,7 +116,9 @@ def criar_email_confirmacao(base_url, casa_legislativa, doc_mat, tipo, hash_txt=
"base_url": base_url,
"materia": str(doc_mat),
"materia_url": doc_mat_url,
"confirmacao_url": confirmacao_url, })
"confirmacao_url": confirmacao_url,
},
)
return templates
@ -117,7 +129,7 @@ def do_envia_email_confirmacao(base_url, casa, tipo, doc_mat, destinatario):
if not mail_service_configured():
logger = logging.getLogger(__name__)
logger.warning(_('Servidor de email não configurado.'))
logger.warning(_("Servidor de email não configurado."))
return
sender = EMAIL_SEND_USER
@ -130,25 +142,29 @@ def do_envia_email_confirmacao(base_url, casa, tipo, doc_mat, destinatario):
messages = []
recipients = []
email_texts = criar_email_confirmacao(base_url,
email_texts = criar_email_confirmacao(
base_url,
casa,
doc_mat,
tipo,
destinatario.hash,)
destinatario.hash,
)
recipients.append(destinatario.email)
messages.append({
'recipient': destinatario.email,
'subject': subject,
'txt_message': email_texts[0],
'html_message': email_texts[1]
})
messages.append(
{
"recipient": destinatario.email,
"subject": subject,
"txt_message": email_texts[0],
"html_message": email_texts[1],
}
)
enviar_emails(sender, recipients, messages)
def criar_email_tramitacao(base_url, casa_legislativa, tipo, doc_mat, status,
unidade_destino, hash_txt=''):
def criar_email_tramitacao(
base_url, casa_legislativa, tipo, doc_mat, status, unidade_destino, hash_txt=""
):
if not casa_legislativa:
raise ValueError("Casa Legislativa é obrigatória")
@ -160,34 +176,35 @@ def criar_email_tramitacao(base_url, casa_legislativa, tipo, doc_mat, status,
raise ValueError(msg)
# FIXME i18n
casa_nome = ("{} de {} - {}".format(casa_legislativa.nome,
casa_legislativa.municipio,
casa_legislativa.uf))
casa_nome = "{} de {} - {}".format(
casa_legislativa.nome, casa_legislativa.municipio, casa_legislativa.uf
)
if tipo == "materia":
doc_mat_url = reverse('sapl.materia:tramitacao_list',
kwargs={'pk': doc_mat.id})
url_excluir = reverse('sapl.materia:acompanhar_excluir',
kwargs={'pk': doc_mat.id})
doc_mat_url = reverse("sapl.materia:tramitacao_list", kwargs={"pk": doc_mat.id})
url_excluir = reverse(
"sapl.materia:acompanhar_excluir", kwargs={"pk": doc_mat.id}
)
ementa = doc_mat.ementa
autores = [autoria.autor.nome for autoria in doc_mat.autoria_set.all()]
tramitacao = doc_mat.tramitacao_set.order_by('-data_tramitacao', '-id').first()
tramitacao = doc_mat.tramitacao_set.order_by("-data_tramitacao", "-id").first()
else:
doc_mat_url = reverse('sapl.protocoloadm:tramitacaoadministrativo_list',
kwargs={'pk': doc_mat.id})
url_excluir = reverse('sapl.protocoloadm:acompanhar_excluir',
kwargs={'pk': doc_mat.id})
doc_mat_url = reverse(
"sapl.protocoloadm:tramitacaoadministrativo_list", kwargs={"pk": doc_mat.id}
)
url_excluir = reverse(
"sapl.protocoloadm:acompanhar_excluir", kwargs={"pk": doc_mat.id}
)
autores = ""
ementa = doc_mat.assunto
tramitacao = doc_mat.tramitacaoadministrativo_set.last()
templates = load_email_templates(['email/tramitacao.txt',
'email/tramitacao.html'],
{"casa_legislativa": casa_nome,
"data_registro": dt.strftime(
timezone.now(),
"%d/%m/%Y"),
templates = load_email_templates(
["email/tramitacao.txt", "email/tramitacao.html"],
{
"casa_legislativa": casa_nome,
"data_registro": dt.strftime(timezone.now(), "%d/%m/%Y"),
"cod_materia": doc_mat.id,
"logotipo": casa_legislativa.logotipo,
"descricao_materia": ementa,
@ -200,7 +217,9 @@ def criar_email_tramitacao(base_url, casa_legislativa, tipo, doc_mat, status,
"materia": str(doc_mat),
"base_url": base_url,
"materia_url": doc_mat_url,
"excluir_url": url_excluir})
"excluir_url": url_excluir,
},
)
return templates
@ -211,18 +230,20 @@ def do_envia_email_tramitacao(base_url, tipo, doc_mat, status, unidade_destino):
logger = logging.getLogger(__name__)
if not mail_service_configured():
logger.warning(_('Servidor de email não configurado.'))
logger.warning(_("Servidor de email não configurado."))
return
if tipo == "materia":
destinatarios = AcompanhamentoMateria.objects.filter(materia=doc_mat,
confirmado=True)
destinatarios = AcompanhamentoMateria.objects.filter(
materia=doc_mat, confirmado=True
)
else:
destinatarios = AcompanhamentoDocumento.objects.filter(documento=doc_mat,
confirmado=True)
destinatarios = AcompanhamentoDocumento.objects.filter(
documento=doc_mat, confirmado=True
)
if not destinatarios:
logger.debug(_('Não existem destinatários cadastrados para essa matéria.'))
logger.debug(_("Não existem destinatários cadastrados para essa matéria."))
return
casa = CasaLegislativa.objects.first()
@ -240,20 +261,23 @@ def do_envia_email_tramitacao(base_url, tipo, doc_mat, status, unidade_destino):
for destinatario in destinatarios:
try:
email_texts = criar_email_tramitacao(base_url,
email_texts = criar_email_tramitacao(
base_url,
casa,
tipo,
doc_mat,
status,
unidade_destino,
destinatario.hash)
destinatario.hash,
)
email = EmailMultiAlternatives(
subject,
email_texts[0],
sender,
[destinatario.email],
connection=connection)
connection=connection,
)
email.attach_alternative(email_texts[1], "text/html")
email.send()
@ -261,7 +285,6 @@ def do_envia_email_tramitacao(base_url, tipo, doc_mat, status, unidade_destino):
# a conexão será fechada
except Exception:
connection.close()
raise Exception(
'Erro ao enviar e-mail de acompanhamento de matéria.')
raise Exception("Erro ao enviar e-mail de acompanhamento de matéria.")
connection.close()

1251
sapl/base/forms.py

File diff suppressed because it is too large

10
sapl/base/management/commands/backfill_auditlog.py

@ -2,6 +2,7 @@ import json
import logging
from django.core.management.base import BaseCommand
from sapl.base.models import AuditLog
logger = logging.getLogger(__name__)
@ -16,8 +17,7 @@ class Command(BaseCommand):
update_list = []
for log in logs:
try:
obj = log.object[1:-1] \
if log.object.startswith('[') else log.object
obj = log.object[1:-1] if log.object.startswith("[") else log.object
data = json.loads(obj)
log.data = data
except Exception as e:
@ -27,12 +27,10 @@ class Command(BaseCommand):
else:
update_list.append(log)
if len(update_list) == 1000:
AuditLog.objects.bulk_update(update_list, ['data'])
AuditLog.objects.bulk_update(update_list, ["data"])
update_list = []
if update_list:
AuditLog.objects.bulk_update(update_list, ['data'])
AuditLog.objects.bulk_update(update_list, ["data"])
print(f"Logs backfilled: {len(logs) - error_counter}")
print(f"Logs with errors: {error_counter}")
print("Finished backfilling")

493
sapl/base/models.py

@ -9,40 +9,49 @@ from django.db.models.signals import post_migrate
from django.db.utils import DEFAULT_DB_ALIAS
from django.utils.translation import ugettext_lazy as _
from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES,
get_settings_auth_user_model, models_with_gr_for_model)
from sapl.utils import (
LISTA_DE_UFS,
YES_NO_CHOICES,
get_settings_auth_user_model,
models_with_gr_for_model,
)
DOC_ADM_OSTENSIVO = 'O'
DOC_ADM_RESTRITIVO = 'R'
DOC_ADM_OSTENSIVO = "O"
DOC_ADM_RESTRITIVO = "R"
TIPO_DOCUMENTO_ADMINISTRATIVO = ((DOC_ADM_OSTENSIVO, _('Ostensiva')),
(DOC_ADM_RESTRITIVO, _('Restritiva')))
TIPO_DOCUMENTO_ADMINISTRATIVO = (
(DOC_ADM_OSTENSIVO, _("Ostensiva")),
(DOC_ADM_RESTRITIVO, _("Restritiva")),
)
RELATORIO_ATOS_ACESSADOS = (('S', _('Sim')),
('N', _('Não')))
RELATORIO_ATOS_ACESSADOS = (("S", _("Sim")), ("N", _("Não")))
SEQUENCIA_NUMERACAO_PROTOCOLO = (('A', _('Sequencial por ano')),
('L', _('Sequencial por legislatura')),
('U', _('Sequencial único')))
SEQUENCIA_NUMERACAO_PROTOCOLO = (
("A", _("Sequencial por ano")),
("L", _("Sequencial por legislatura")),
("U", _("Sequencial único")),
)
SEQUENCIA_NUMERACAO_PROPOSICAO = (('A', _('Sequencial por ano para cada autor')),
('B', _('Sequencial por ano indepententemente do autor')))
SEQUENCIA_NUMERACAO_PROPOSICAO = (
("A", _("Sequencial por ano para cada autor")),
("B", _("Sequencial por ano indepententemente do autor")),
)
ESFERA_FEDERACAO_CHOICES = (('M', _('Municipal')),
('E', _('Estadual')),
('F', _('Federal')),
ESFERA_FEDERACAO_CHOICES = (
("M", _("Municipal")),
("E", _("Estadual")),
("F", _("Federal")),
)
ASSINATURA_ATA_CHOICES = (
('M', _('Mesa Diretora da Sessão')),
('P', _('Apenas o Presidente da Sessão')),
('T', _('Todos os Parlamentares Presentes na Sessão')),
("M", _("Mesa Diretora da Sessão")),
("P", _("Apenas o Presidente da Sessão")),
("T", _("Todos os Parlamentares Presentes na Sessão")),
)
ORDENACAO_PESQUISA_MATERIA = (
('S', _('Alfabética por Sigla')),
('R', _('Sequência Regimental')),
("S", _("Alfabética por Sigla")),
("R", _("Sequência Regimental")),
)
@ -50,50 +59,40 @@ class CasaLegislativa(models.Model):
# TODO ajustar todos os max_length !!!!
# cod_casa => id (pk)
codigo = models.CharField(max_length=100,
blank=True,
verbose_name=_('Codigo'))
nome = models.CharField(max_length=100, verbose_name=_('Nome'))
sigla = models.CharField(max_length=100, verbose_name=_('Sigla'))
endereco = models.CharField(max_length=100, verbose_name=_('Endereço'))
cep = models.CharField(max_length=100, blank=True, verbose_name=_('CEP'))
municipio = models.CharField(max_length=50, verbose_name=_('Município'))
uf = models.CharField(max_length=2,
choices=LISTA_DE_UFS,
verbose_name=_('UF'))
telefone = models.CharField(
max_length=100, blank=True, verbose_name=_('Telefone'))
fax = models.CharField(
max_length=100, blank=True, verbose_name=_('Fax'))
codigo = models.CharField(max_length=100, blank=True, verbose_name=_("Codigo"))
nome = models.CharField(max_length=100, verbose_name=_("Nome"))
sigla = models.CharField(max_length=100, verbose_name=_("Sigla"))
endereco = models.CharField(max_length=100, verbose_name=_("Endereço"))
cep = models.CharField(max_length=100, blank=True, verbose_name=_("CEP"))
municipio = models.CharField(max_length=50, verbose_name=_("Município"))
uf = models.CharField(max_length=2, choices=LISTA_DE_UFS, verbose_name=_("UF"))
telefone = models.CharField(max_length=100, blank=True, verbose_name=_("Telefone"))
fax = models.CharField(max_length=100, blank=True, verbose_name=_("Fax"))
logotipo = models.ImageField(
blank=True,
upload_to='sapl/public/casa/logotipo/',
verbose_name=_('Logotipo'))
blank=True, upload_to="sapl/public/casa/logotipo/", verbose_name=_("Logotipo")
)
endereco_web = models.URLField(
max_length=100, blank=True, verbose_name=_('HomePage'))
email = models.EmailField(
max_length=100, blank=True, verbose_name=_('E-mail'))
max_length=100, blank=True, verbose_name=_("HomePage")
)
email = models.EmailField(max_length=100, blank=True, verbose_name=_("E-mail"))
informacao_geral = models.TextField(
max_length=100,
blank=True,
verbose_name=_('Informação Geral'))
max_length=100, blank=True, verbose_name=_("Informação Geral")
)
class Meta:
verbose_name = _('Casa Legislativa')
verbose_name_plural = _('Casa Legislativa')
ordering = ('id',)
verbose_name = _("Casa Legislativa")
verbose_name_plural = _("Casa Legislativa")
ordering = ("id",)
def __str__(self):
return _('Casa Legislativa de %(municipio)s') % {
'municipio': self.municipio}
return _("Casa Legislativa de %(municipio)s") % {"municipio": self.municipio}
class AppConfig(models.Model):
POLITICA_PROTOCOLO_CHOICES = (
('O', _('Sempre Gerar Protocolo')),
('C', _('Perguntar se é pra gerar protocolo ao incorporar')),
('N', _('Nunca Protocolar ao incorporar uma proposição')),
("O", _("Sempre Gerar Protocolo")),
("C", _("Perguntar se é pra gerar protocolo ao incorporar")),
("N", _("Nunca Protocolar ao incorporar uma proposição")),
)
# MANTENHA A SEQUÊNCIA EQUIVALENTE COM /sapl/templates/base/layout.yaml
@ -105,8 +104,9 @@ class AppConfig(models.Model):
max_length=1,
blank=True,
default="",
verbose_name=_('Esfera Federação'),
choices=ESFERA_FEDERACAO_CHOICES)
verbose_name=_("Esfera Federação"),
choices=ESFERA_FEDERACAO_CHOICES,
)
# MÓDULO PARLAMENTARES
@ -120,29 +120,37 @@ class AppConfig(models.Model):
# Linha 1 -------------------------
documentos_administrativos = models.CharField(
max_length=1,
verbose_name=_('Visibilidade dos Documentos Administrativos'),
choices=TIPO_DOCUMENTO_ADMINISTRATIVO, default='O')
verbose_name=_("Visibilidade dos Documentos Administrativos"),
choices=TIPO_DOCUMENTO_ADMINISTRATIVO,
default="O",
)
tramitacao_documento = models.BooleanField(
verbose_name=_(
'Tramitar documentos anexados junto com os documentos principais?'),
choices=YES_NO_CHOICES, default=True)
"Tramitar documentos anexados junto com os documentos principais?"
),
choices=YES_NO_CHOICES,
default=True,
)
# Linha 2 -------------------------
protocolo_manual = models.BooleanField(
verbose_name=_('Permitir informe manual de data e hora de protocolo?'),
choices=YES_NO_CHOICES, default=False)
verbose_name=_("Permitir informe manual de data e hora de protocolo?"),
choices=YES_NO_CHOICES,
default=False,
)
sequencia_numeracao_protocolo = models.CharField(
max_length=1,
verbose_name=_('Sequência de numeração de protocolos'),
choices=SEQUENCIA_NUMERACAO_PROTOCOLO, default='A')
verbose_name=_("Sequência de numeração de protocolos"),
choices=SEQUENCIA_NUMERACAO_PROTOCOLO,
default="A",
)
inicio_numeracao_protocolo = models.PositiveIntegerField(
verbose_name=_('Início da numeração de protocolo'),
default=1
verbose_name=_("Início da numeração de protocolo"), default=1
)
# Linha 3 -------------------------
identificacao_de_documentos = models.CharField(
max_length=254,
verbose_name=_('Formato da identificação dos documentos'),
default='{sigla}{numero}/{ano}{-}{complemento} - {nome}',
verbose_name=_("Formato da identificação dos documentos"),
default="{sigla}{numero}/{ano}{-}{complemento} - {nome}",
help_text="""
Como mostrar a identificação dos documentos administrativos?
Você pode usar um conjunto de combinações que pretender.
@ -153,102 +161,124 @@ class AppConfig(models.Model):
Ainda pode ser usado {/}, {-}, {.} se você quiser que uma barra, traço, ou ponto
seja adicionado apenas se o próximo campo que será usado tenha algum conteúdo
(não use dois destes destes condicionais em sequência, somente o último será considerado).
"""
""",
)
# MÓDULO PROPOSIÇÕES
# Linha 1 ----------
sequencia_numeracao_proposicao = models.CharField(
max_length=1,
verbose_name=_('Sequência de numeração de proposições'),
choices=SEQUENCIA_NUMERACAO_PROPOSICAO, default='A')
verbose_name=_("Sequência de numeração de proposições"),
choices=SEQUENCIA_NUMERACAO_PROPOSICAO,
default="A",
)
receber_recibo_proposicao = models.BooleanField(
verbose_name=_('Protocolar proposição somente com recibo?'),
choices=YES_NO_CHOICES, default=True)
verbose_name=_("Protocolar proposição somente com recibo?"),
choices=YES_NO_CHOICES,
default=True,
)
proposicao_incorporacao_obrigatoria = models.CharField(
verbose_name=_('Regra de incorporação de proposições e protocolo'),
max_length=1, choices=POLITICA_PROTOCOLO_CHOICES, default='O')
verbose_name=_("Regra de incorporação de proposições e protocolo"),
max_length=1,
choices=POLITICA_PROTOCOLO_CHOICES,
default="O",
)
escolher_numero_materia_proposicao = models.BooleanField(
verbose_name=_(
'Indicar número da matéria a ser gerada na proposição?'),
choices=YES_NO_CHOICES, default=False)
verbose_name=_("Indicar número da matéria a ser gerada na proposição?"),
choices=YES_NO_CHOICES,
default=False,
)
# MÓDULO MATÉRIA LEGISLATIVA
# Linha 1 ------------------
tramitacao_origem_fixa = models.BooleanField(
verbose_name=_(
'Fixar origem de novas tramitações como sendo a tramitação de destino da última tramitação?'),
"Fixar origem de novas tramitações como sendo a tramitação de destino da última tramitação?"
),
choices=YES_NO_CHOICES,
default=True,
help_text=_('Ao utilizar a opção NÂO, você compreende que os controles '
'de origem e destino das tramitações são anulados, '
'podendo seu operador registrar quaisquer origem e '
'destino para as tramitações. Se você colocar Não, '
'fizer tramitações aleatórias e voltar para SIM, '
'o destino da tramitação mais recente será utilizado '
'para a origem de uma nova inserção!'))
help_text=_(
"Ao utilizar a opção NÂO, você compreende que os controles "
"de origem e destino das tramitações são anulados, "
"podendo seu operador registrar quaisquer origem e "
"destino para as tramitações. Se você colocar Não, "
"fizer tramitações aleatórias e voltar para SIM, "
"o destino da tramitação mais recente será utilizado "
"para a origem de uma nova inserção!"
),
)
tramitacao_materia = models.BooleanField(
verbose_name=_(
'Tramitar matérias anexadas junto com as matérias principais?'),
choices=YES_NO_CHOICES, default=True)
verbose_name=_("Tramitar matérias anexadas junto com as matérias principais?"),
choices=YES_NO_CHOICES,
default=True,
)
ordenacao_pesquisa_materia = models.CharField(
max_length=1,
verbose_name=_(
'Ordenação de Pesquisa da Matéria?'),
choices=ORDENACAO_PESQUISA_MATERIA, default='S')
verbose_name=_("Ordenação de Pesquisa da Matéria?"),
choices=ORDENACAO_PESQUISA_MATERIA,
default="S",
)
# MÓDULO NORMAS JURÍDICAS
# MÓDULO TEXTOS ARTICULADOS
# Linha 1 -----------------
texto_articulado_proposicao = models.BooleanField(
verbose_name=_('Usar Textos Articulados para Proposições'),
choices=YES_NO_CHOICES, default=False)
verbose_name=_("Usar Textos Articulados para Proposições"),
choices=YES_NO_CHOICES,
default=False,
)
texto_articulado_materia = models.BooleanField(
verbose_name=_('Usar Textos Articulados para Matérias'),
choices=YES_NO_CHOICES, default=False)
verbose_name=_("Usar Textos Articulados para Matérias"),
choices=YES_NO_CHOICES,
default=False,
)
texto_articulado_norma = models.BooleanField(
verbose_name=_('Usar Textos Articulados para Normas'),
choices=YES_NO_CHOICES, default=True)
verbose_name=_("Usar Textos Articulados para Normas"),
choices=YES_NO_CHOICES,
default=True,
)
# MÓDULO SESSÃO PLENÁRIA
assinatura_ata = models.CharField(
verbose_name=_('Quem deve assinar a ata'),
max_length=1, choices=ASSINATURA_ATA_CHOICES, default='T')
verbose_name=_("Quem deve assinar a ata"),
max_length=1,
choices=ASSINATURA_ATA_CHOICES,
default="T",
)
# MÓDULO PAINEL
cronometro_discurso = models.DurationField(
verbose_name=_('Cronômetro do Discurso'),
blank=True,
null=True)
verbose_name=_("Cronômetro do Discurso"), blank=True, null=True
)
cronometro_aparte = models.DurationField(
verbose_name=_('Cronômetro do Aparte'),
blank=True,
null=True)
verbose_name=_("Cronômetro do Aparte"), blank=True, null=True
)
cronometro_ordem = models.DurationField(
verbose_name=_('Cronômetro da Ordem'),
blank=True,
null=True)
verbose_name=_("Cronômetro da Ordem"), blank=True, null=True
)
cronometro_consideracoes = models.DurationField(
verbose_name=_('Cronômetro de Considerações Finais'),
blank=True,
null=True)
verbose_name=_("Cronômetro de Considerações Finais"), blank=True, null=True
)
mostrar_brasao_painel = models.BooleanField(
default=False,
verbose_name=_('Mostrar brasão da Casa no painel?'))
default=False, verbose_name=_("Mostrar brasão da Casa no painel?")
)
mostrar_voto = models.BooleanField(
verbose_name=_(
'Exibir voto do Parlamentar antes de encerrar a votação?'),
choices=YES_NO_CHOICES, default=False)
verbose_name=_("Exibir voto do Parlamentar antes de encerrar a votação?"),
choices=YES_NO_CHOICES,
default=False,
)
# MÓDULO ESTATÍSTICAS DE ACESSO
estatisticas_acesso_normas = models.CharField(
max_length=1,
verbose_name=_('Estatísticas de acesso a normas'),
choices=RELATORIO_ATOS_ACESSADOS, default='N')
verbose_name=_("Estatísticas de acesso a normas"),
choices=RELATORIO_ATOS_ACESSADOS,
default="N",
)
# MÓDULO SEGURANÇA
@ -260,37 +290,48 @@ class AppConfig(models.Model):
# choices=YES_NO_CHOICES, default=False)
google_recaptcha_site_key = models.CharField(
verbose_name=_('Chave pública gerada pelo Google Recaptcha'),
max_length=256, default='')
verbose_name=_("Chave pública gerada pelo Google Recaptcha"),
max_length=256,
default="",
)
google_recaptcha_secret_key = models.CharField(
verbose_name=_('Chave privada gerada pelo Google Recaptcha'),
max_length=256, default='')
verbose_name=_("Chave privada gerada pelo Google Recaptcha"),
max_length=256,
default="",
)
google_analytics_id_metrica = models.CharField(
verbose_name=_('ID da Métrica do Google Analytics'),
max_length=256, default='')
verbose_name=_("ID da Métrica do Google Analytics"), max_length=256, default=""
)
class Meta:
verbose_name = _('Configurações da Aplicação')
verbose_name_plural = _('Configurações da Aplicação')
verbose_name = _("Configurações da Aplicação")
verbose_name_plural = _("Configurações da Aplicação")
permissions = (
('menu_sistemas', _('Renderizar Menu Sistemas')),
('view_tabelas_auxiliares', _('Visualizar Tabelas Auxiliares')),
("menu_sistemas", _("Renderizar Menu Sistemas")),
("view_tabelas_auxiliares", _("Visualizar Tabelas Auxiliares")),
)
ordering = ('-id',)
ordering = ("-id",)
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
def save(
self, force_insert=False, force_update=False, using=None, update_fields=None
):
fields = self._meta.get_fields()
for f in fields:
if f.name != 'id' and not cache.get(f'sapl_{f.name}') is None:
cache.set(f'sapl_{f.name}', getattr(self, f.name), 600)
return models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
if f.name != "id" and not cache.get(f"sapl_{f.name}") is None:
cache.set(f"sapl_{f.name}", getattr(self, f.name), 600)
return models.Model.save(
self,
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields,
)
@classmethod
def attr(cls, attr):
value = cache.get(f'sapl_{attr}')
value = cache.get(f"sapl_{attr}")
if not value is None:
return value
@ -301,34 +342,33 @@ class AppConfig(models.Model):
config.save()
value = getattr(config, attr)
cache.set(f'sapl_{attr}', value, 600)
cache.set(f"sapl_{attr}", value, 600)
return value
def __str__(self):
return _('Configurações da Aplicação - %(id)s') % {
'id': self.id}
return _("Configurações da Aplicação - %(id)s") % {"id": self.id}
class TipoAutor(models.Model):
descricao = models.CharField(
max_length=50,
verbose_name=_('Descrição'),
help_text=_(
'Obs: Não crie tipos de autores semelhante aos tipos fixos. ')
verbose_name=_("Descrição"),
help_text=_("Obs: Não crie tipos de autores semelhante aos tipos fixos. "),
)
content_type = models.OneToOneField(
ContentType,
null=True,
default=None,
verbose_name=_('Modelagem no SAPL'),
on_delete=models.PROTECT)
verbose_name=_("Modelagem no SAPL"),
on_delete=models.PROTECT,
)
class Meta:
ordering = ['descricao']
verbose_name = _('Tipo de Autor')
verbose_name_plural = _('Tipos de Autor')
ordering = ["descricao"]
verbose_name = _("Tipo de Autor")
verbose_name_plural = _("Tipos de Autor")
def __str__(self):
return self.descricao
@ -337,40 +377,29 @@ class TipoAutor(models.Model):
class Autor(models.Model):
operadores = models.ManyToManyField(
get_settings_auth_user_model(),
through='OperadorAutor',
through_fields=('autor', 'user'),
through="OperadorAutor",
through_fields=("autor", "user"),
symmetrical=False,
related_name='autor_set',
verbose_name='Operadores')
related_name="autor_set",
verbose_name="Operadores",
)
tipo = models.ForeignKey(
TipoAutor,
verbose_name=_('Tipo do Autor'),
on_delete=models.PROTECT)
TipoAutor, verbose_name=_("Tipo do Autor"), on_delete=models.PROTECT
)
content_type = models.ForeignKey(
ContentType,
blank=True,
null=True,
default=None,
on_delete=models.PROTECT)
object_id = models.PositiveIntegerField(
blank=True,
null=True,
default=None)
autor_related = GenericForeignKey('content_type', 'object_id')
nome = models.CharField(
max_length=120,
blank=True,
verbose_name=_('Nome do Autor'))
cargo = models.CharField(
max_length=50,
blank=True)
ContentType, blank=True, null=True, default=None, on_delete=models.PROTECT
)
object_id = models.PositiveIntegerField(blank=True, null=True, default=None)
autor_related = GenericForeignKey("content_type", "object_id")
nome = models.CharField(max_length=120, blank=True, verbose_name=_("Nome do Autor"))
cargo = models.CharField(max_length=50, blank=True)
class Meta:
verbose_name = _('Autor')
verbose_name_plural = _('Autores')
unique_together = (('content_type', 'object_id'), )
ordering = ('nome',)
verbose_name = _("Autor")
verbose_name_plural = _("Autores")
unique_together = (("content_type", "object_id"),)
ordering = ("nome",)
def __str__(self):
if self.autor_related:
@ -378,79 +407,77 @@ class Autor(models.Model):
else:
if self.nome:
if self.cargo:
return '{} - {}'.format(self.nome, self.cargo)
return "{} - {}".format(self.nome, self.cargo)
else:
return str(self.nome)
return '?'
return "?"
class OperadorAutor(models.Model):
user = models.ForeignKey(
get_settings_auth_user_model(),
verbose_name=_('Operador do Autor'),
related_name='operadorautor_set',
on_delete=CASCADE)
verbose_name=_("Operador do Autor"),
related_name="operadorautor_set",
on_delete=CASCADE,
)
autor = models.ForeignKey(
Autor,
related_name='operadorautor_set',
verbose_name=_('Autor'),
on_delete=CASCADE)
related_name="operadorautor_set",
verbose_name=_("Autor"),
on_delete=CASCADE,
)
@property
def user_name(self):
return '%s - %s' % (
self.autor,
self.user)
return "%s - %s" % (self.autor, self.user)
class Meta:
verbose_name = _('Operador do Autor')
verbose_name_plural = _('Operadores do Autor')
verbose_name = _("Operador do Autor")
verbose_name_plural = _("Operadores do Autor")
unique_together = (
('user', 'autor', ),)
(
"user",
"autor",
),
)
def __str__(self):
return self.user_name
class AuditLog(models.Model):
operation = ('C', 'D', 'U')
operation = ("C", "D", "U")
MAX_DATA_LENGTH = 4096 # 4KB de texto
username = models.CharField(max_length=100,
verbose_name=_('username'),
blank=True,
db_index=True)
operation = models.CharField(max_length=1,
verbose_name=_('operation'),
db_index=True)
timestamp = models.DateTimeField(verbose_name=_('timestamp'),
db_index=True)
username = models.CharField(
max_length=100, verbose_name=_("username"), blank=True, db_index=True
)
operation = models.CharField(
max_length=1, verbose_name=_("operation"), db_index=True
)
timestamp = models.DateTimeField(verbose_name=_("timestamp"), db_index=True)
# DEPRECATED FIELD! TO BE REMOVED (EVENTUALLY)
object = models.CharField(max_length=MAX_DATA_LENGTH,
blank=True,
verbose_name=_('object'))
data = JSONField(null=True, verbose_name=_('data'))
object_id = models.PositiveIntegerField(verbose_name=_('object_id'),
db_index=True)
model_name = models.CharField(max_length=100,
verbose_name=_('model'),
db_index=True)
app_name = models.CharField(max_length=100,
verbose_name=_('app'),
db_index=True)
object = models.CharField(
max_length=MAX_DATA_LENGTH, blank=True, verbose_name=_("object")
)
data = JSONField(null=True, verbose_name=_("data"))
object_id = models.PositiveIntegerField(verbose_name=_("object_id"), db_index=True)
model_name = models.CharField(
max_length=100, verbose_name=_("model"), db_index=True
)
app_name = models.CharField(max_length=100, verbose_name=_("app"), db_index=True)
class Meta:
verbose_name = _('AuditLog')
verbose_name_plural = _('AuditLogs')
ordering = ('-id', '-timestamp')
verbose_name = _("AuditLog")
verbose_name_plural = _("AuditLogs")
ordering = ("-id", "-timestamp")
def __str__(self):
return "[%s] %s %s.%s %s" % (self.timestamp,
return "[%s] %s %s.%s %s" % (
self.timestamp,
self.operation,
self.app_name,
self.model_name,
@ -460,25 +487,23 @@ class AuditLog(models.Model):
class Metadata(models.Model):
content_type = models.ForeignKey(
ContentType,
ContentType, blank=True, null=True, default=None, on_delete=models.PROTECT
)
object_id = models.PositiveIntegerField(blank=True, null=True, default=None)
content_object = GenericForeignKey("content_type", "object_id")
metadata = JSONField(
verbose_name=_("Metadados"),
blank=True,
null=True,
default=None,
on_delete=models.PROTECT)
object_id = models.PositiveIntegerField(
blank=True,
null=True,
default=None)
content_object = GenericForeignKey('content_type', 'object_id')
metadata = JSONField(
verbose_name=_('Metadados'),
blank=True, null=True, default=None, encoder=DjangoJSONEncoder)
encoder=DjangoJSONEncoder,
)
class Meta:
verbose_name = _('Metadado')
verbose_name_plural = _('Metadados')
unique_together = (('content_type', 'object_id'), )
verbose_name = _("Metadado")
verbose_name_plural = _("Metadados")
unique_together = (("content_type", "object_id"),)
def __str__(self):
return f'Metadata de {self.content_object}'
return f"Metadata de {self.content_object}"

250
sapl/base/receivers.py

@ -1,37 +1,41 @@
from datetime import datetime
import inspect
import logging
from datetime import datetime
from PyPDF4.pdf import PdfFileReader
from asn1crypto import cms
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core import serializers
from django.core.files.uploadedfile import InMemoryUploadedFile, UploadedFile
from django.db.models.fields.files import FileField
from django.db.models.signals import post_delete, post_save, \
post_migrate, pre_save, pre_migrate
from django.db.models.signals import (
post_delete,
post_migrate,
post_save,
pre_migrate,
pre_save,
)
from django.db.utils import DEFAULT_DB_ALIAS
from django.dispatch import receiver
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from PyPDF4.pdf import PdfFileReader
from sapl.base.email_utils import do_envia_email_tramitacao
from sapl.base.models import AuditLog, TipoAutor, Autor, Metadata
from sapl.base.models import AuditLog, Autor, Metadata, TipoAutor
from sapl.decorators import receiver_multi_senders
from sapl.materia.models import Tramitacao
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import TramitacaoAdministrativo
from sapl.utils import get_base_url, models_with_gr_for_model
models_with_gr_for_autor = models_with_gr_for_model(Autor)
@receiver_multi_senders(post_save, senders=models_with_gr_for_autor)
def handle_update_autor_related(sender, **kwargs):
# for m in models_with_gr_for_autor:
instance = kwargs.get('instance')
instance = kwargs.get("instance")
autor = instance.autor.first()
if autor:
autor.nome = str(instance)
@ -43,7 +47,7 @@ def handle_update_autor_related(sender, **kwargs):
def handle_tramitacao_signal(sender, **kwargs):
logger = logging.getLogger(__name__)
tramitacao = kwargs.get('instance')
tramitacao = kwargs.get("instance")
if isinstance(tramitacao, Tramitacao):
tipo = "materia"
@ -54,9 +58,9 @@ def handle_tramitacao_signal(sender, **kwargs):
pilha_de_execucao = inspect.stack()
for i in pilha_de_execucao:
if i.function == 'migrate':
if i.function == "migrate":
return
request = i.frame.f_locals.get('request', None)
request = i.frame.f_locals.get("request", None)
if request:
break
@ -69,22 +73,25 @@ def handle_tramitacao_signal(sender, **kwargs):
tipo,
doc_mat,
tramitacao.status,
tramitacao.unidade_tramitacao_destino)
tramitacao.unidade_tramitacao_destino,
)
except Exception as e:
logger.error(f'user={request.user.username}. Tramitação criada, mas e-mail de acompanhamento '
'de matéria não enviado. Há problemas na configuração '
'do e-mail. ' + str(e))
logger.error(
f"user={request.user.username}. Tramitação criada, mas e-mail de acompanhamento "
"de matéria não enviado. Há problemas na configuração "
"do e-mail. " + str(e)
)
@receiver(post_delete)
def status_tramitacao_materia(sender, instance, **kwargs):
if sender == Tramitacao:
if instance.status.indicador == 'F':
if instance.status.indicador == "F":
materia = instance.materia
materia.em_tramitacao = True
materia.save()
elif sender == TramitacaoAdministrativo:
if instance.status.indicador == 'F':
if instance.status.indicador == "F":
documento = instance.documento
documento.tramitacao = True
documento.save()
@ -92,15 +99,17 @@ def status_tramitacao_materia(sender, instance, **kwargs):
def audit_log_function(sender, **kwargs):
try:
if not (sender._meta.app_config.name.startswith('sapl') or
sender._meta.label == settings.AUTH_USER_MODEL):
if not (
sender._meta.app_config.name.startswith("sapl")
or sender._meta.label == settings.AUTH_USER_MODEL
):
return
except:
# não é necessário usar logger, aqui é usada apenas para
# eliminar um o if complexo
return
instance = kwargs.get('instance')
instance = kwargs.get("instance")
if instance._meta.model == AuditLog:
return
@ -109,9 +118,9 @@ def audit_log_function(sender, **kwargs):
u = None
pilha_de_execucao = inspect.stack()
for i in pilha_de_execucao:
if i.function == 'migrate':
if i.function == "migrate":
return
r = i.frame.f_locals.get('request', None)
r = i.frame.f_locals.get("request", None)
try:
if r.user._meta.label == settings.AUTH_USER_MODEL:
u = r.user
@ -122,15 +131,16 @@ def audit_log_function(sender, **kwargs):
pass
try:
operation = kwargs.get('operation')
operation = kwargs.get("operation")
user = u
model_name = instance.__class__.__name__
app_name = instance._meta.app_label
object_id = instance.id
try:
import json
# [1:-1] below removes the surrounding square brackets
str_data = serializers.serialize('json', [instance])[1:-1]
str_data = serializers.serialize("json", [instance])[1:-1]
data = json.loads(str_data)
except:
# old version capped string at AuditLog.MAX_DATA_LENGTH
@ -140,58 +150,61 @@ def audit_log_function(sender, **kwargs):
if user:
username = user.username
else:
username = ''
username = ""
AuditLog.objects.create(username=username,
AuditLog.objects.create(
username=username,
operation=operation,
model_name=model_name,
app_name=app_name,
timestamp=timezone.now(),
object_id=object_id,
object='',
data=data)
object="",
data=data,
)
except Exception as e:
logger.error('Error saving auditing log object')
logger.error("Error saving auditing log object")
logger.error(e)
@receiver(post_delete)
def audit_log_post_delete(sender, **kwargs):
audit_log_function(sender, operation='D', **kwargs)
audit_log_function(sender, operation="D", **kwargs)
@receiver(post_save)
def audit_log_post_save(sender, **kwargs):
operation = 'C' if kwargs.get('created') else 'U'
operation = "C" if kwargs.get("created") else "U"
audit_log_function(sender, operation=operation, **kwargs)
def cria_models_tipo_autor(app_config=None, verbosity=2, interactive=True,
using=DEFAULT_DB_ALIAS, **kwargs):
print("\n\033[93m\033[1m{}\033[0m".format(
_('Atualizando registros TipoAutor do SAPL:')))
def cria_models_tipo_autor(
app_config=None, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs
):
print(
"\n\033[93m\033[1m{}\033[0m".format(
_("Atualizando registros TipoAutor do SAPL:")
)
)
for model in models_with_gr_for_autor:
content_type = ContentType.objects.get_for_model(model)
tipo_autor = TipoAutor.objects.filter(
content_type=content_type.id).exists()
tipo_autor = TipoAutor.objects.filter(content_type=content_type.id).exists()
if tipo_autor:
msg1 = "Carga de {} não efetuada.".format(
TipoAutor._meta.verbose_name)
msg1 = "Carga de {} não efetuada.".format(TipoAutor._meta.verbose_name)
msg2 = " Já Existe um {} {} relacionado...".format(
TipoAutor._meta.verbose_name,
model._meta.verbose_name)
TipoAutor._meta.verbose_name, model._meta.verbose_name
)
msg = " {}{}".format(msg1, msg2)
else:
novo_autor = TipoAutor()
novo_autor.content_type_id = content_type.id
novo_autor.descricao = model._meta.verbose_name
novo_autor.save()
msg1 = "Carga de {} efetuada.".format(
TipoAutor._meta.verbose_name)
msg1 = "Carga de {} efetuada.".format(TipoAutor._meta.verbose_name)
msg2 = " {} {} criado...".format(
TipoAutor._meta.verbose_name, content_type.model)
TipoAutor._meta.verbose_name, content_type.model
)
msg = " {}{}".format(msg1, msg2)
print(msg)
# Disconecta função para evitar a chamada repetidas vezes.
@ -202,62 +215,60 @@ post_migrate.connect(receiver=cria_models_tipo_autor)
def signed_files_extraction_function(sender, instance, **kwargs):
def run_signed_name_and_date_via_fields(fields):
signs = []
for key, field in fields.items():
if '/FT' not in field and field['/FT'] != '/Sig':
if "/FT" not in field and field["/FT"] != "/Sig":
continue
if '/V' not in field:
if "/V" not in field:
continue
content_sign = field['/V']['/Contents']
nome = 'Nome do assinante não localizado.'
oname = ''
content_sign = field["/V"]["/Contents"]
nome = "Nome do assinante não localizado."
oname = ""
try:
info = cms.ContentInfo.load(content_sign)
signed_data = info['content']
signed_data = info["content"]
oun_old = []
for cert in signed_data['certificates']:
subject = cert.native['tbs_certificate']['subject']
issuer = cert.native['tbs_certificate']['issuer']
oname = issuer.get('organization_name', '')
for cert in signed_data["certificates"]:
subject = cert.native["tbs_certificate"]["subject"]
issuer = cert.native["tbs_certificate"]["issuer"]
oname = issuer.get("organization_name", "")
if oname in ('Gov-Br', '1Doc'):
nome = subject['common_name'].split(':')[0]
if oname in ("Gov-Br", "1Doc"):
nome = subject["common_name"].split(":")[0]
continue
oun = subject['organizational_unit_name']
oun = subject["organizational_unit_name"]
if isinstance(oun, str):
continue
if len(oun) > len(oun_old):
oun_old = oun
nome = subject['common_name'].split(':')[0]
nome = subject["common_name"].split(":")[0]
if oun and isinstance(oun, list) and len(oun) == 4:
oname += ' - ' + oun[3]
oname += " - " + oun[3]
break
except:
if '/Name' in field['/V']:
nome = field['/V']['/Name']
if "/Name" in field["/V"]:
nome = field["/V"]["/Name"]
fd = None
try:
data = str(field['/V']['/M'])
data = str(field["/V"]["/M"])
if 'D:' not in data:
if "D:" not in data:
data = None
else:
if not data.endswith('Z'):
data = data.replace('Z', '+')
data = data.replace("'", '')
if not data.endswith("Z"):
data = data.replace("Z", "+")
data = data.replace("'", "")
fd = datetime.strptime(data[2:], '%Y%m%d%H%M%S%z')
fd = datetime.strptime(data[2:], "%Y%m%d%H%M%S%z")
except:
pass
@ -288,13 +299,17 @@ def signed_files_extraction_function(sender, instance, **kwargs):
pdf = PdfFileReader(file)
fields = pdf.getFields()
fields_br = list(
map(lambda x: x.get('/V', {}).get('/ByteRange', []), fields.values()))
map(lambda x: x.get("/V", {}).get("/ByteRange", []), fields.values())
)
except Exception as e:
try:
pdf = PdfFileReader(file, strict=False)
fields = pdf.getFields()
fields_br = list(
map(lambda x: x.get('/V', {}).get('/ByteRange', []), fields.values()))
map(
lambda x: x.get("/V", {}).get("/ByteRange", []), fields.values()
)
)
except Exception as ee:
fields = ee
@ -307,7 +322,6 @@ def signed_files_extraction_function(sender, instance, **kwargs):
return signs
for n in byterange:
start = pdfdata.find(b"[", n)
stop = pdfdata.find(b"]", start)
assert n != -1 and start != -1 and stop != -1
@ -324,33 +338,33 @@ def signed_files_extraction_function(sender, instance, **kwargs):
data2 = pdfdata[br[2] : br[2] + br[3]]
# signedData = data1 + data2
not_nome = nome = 'Nome do assinante não localizado.'
oname = ''
not_nome = nome = "Nome do assinante não localizado."
oname = ""
try:
info = cms.ContentInfo.load(bcontents)
signed_data = info['content']
signed_data = info["content"]
oun_old = []
for cert in signed_data['certificates']:
subject = cert.native['tbs_certificate']['subject']
issuer = cert.native['tbs_certificate']['issuer']
oname = issuer.get('organization_name', '')
for cert in signed_data["certificates"]:
subject = cert.native["tbs_certificate"]["subject"]
issuer = cert.native["tbs_certificate"]["issuer"]
oname = issuer.get("organization_name", "")
if oname in ('Gov-Br', '1Doc'):
nome = subject['common_name'].split(':')[0]
if oname in ("Gov-Br", "1Doc"):
nome = subject["common_name"].split(":")[0]
continue
oun = subject['organizational_unit_name']
oun = subject["organizational_unit_name"]
if isinstance(oun, str):
continue
if len(oun) > len(oun_old):
oun_old = oun
nome = subject['common_name'].split(':')[0]
nome = subject["common_name"].split(":")[0]
if oun and isinstance(oun, list) and len(oun) == 4:
oname += ' - ' + oun[3]
oname += " - " + oun[3]
break
except Exception as e:
@ -366,50 +380,51 @@ def signed_files_extraction_function(sender, instance, **kwargs):
return signs
def signed_name_and_date_extract(file):
try:
signs = run_signed_name_and_date_extract(file)
except:
return {}
signs = sorted(signs, key=lambda sign: (
sign[0], sign[1][1], sign[1][0]))
signs = sorted(signs, key=lambda sign: (sign[0], sign[1][1], sign[1][0]))
signs_dict = {}
for s in signs:
if s[0] not in signs_dict or 'ICP' in s[1][1] and 'ICP' not in signs_dict[s[0]][1]:
if (
s[0] not in signs_dict
or "ICP" in s[1][1]
and "ICP" not in signs_dict[s[0]][1]
):
signs_dict[s[0]] = s[1]
signs = sorted(signs_dict.items(), key=lambda sign: (
sign[0], sign[1][1], sign[1][0]))
signs = sorted(
signs_dict.items(), key=lambda sign: (sign[0], sign[1][1], sign[1][0])
)
sr = []
for s in signs:
tt = s[0].title().split(' ')
tt = s[0].title().split(" ")
for idx, t in enumerate(tt):
if t in ('Dos', 'De', 'Da', 'Do', 'Das', 'E'):
if t in ("Dos", "De", "Da", "Do", "Das", "E"):
tt[idx] = t.lower()
sr.append((' '.join(tt), s[1]))
sr.append((" ".join(tt), s[1]))
signs = sr
meta_signs = {
'autores': [],
'admin': []
}
meta_signs = {"autores": [], "admin": []}
for s in signs:
# cn = # settings.CERT_PRIVATE_KEY_NAME
# meta_signs['admin' if s[0] == cn else 'autores'].append(s)
meta_signs['autores'].append(s)
meta_signs["autores"].append(s)
return meta_signs
def filefield_from_model(m):
fields = m._meta.get_fields()
fields = tuple(map(lambda f: f.name, filter(
lambda x: isinstance(x, FileField), fields)))
fields = tuple(
map(lambda f: f.name, filter(lambda x: isinstance(x, FileField), fields))
)
return fields
FIELDFILE_NAME = filefield_from_model(instance)
@ -419,19 +434,17 @@ def signed_files_extraction_function(sender, instance, **kwargs):
try:
md = Metadata.objects.get(
content_type=ContentType.objects.get_for_model(
instance._meta.model),
object_id=instance.id,).metadata
content_type=ContentType.objects.get_for_model(instance._meta.model),
object_id=instance.id,
).metadata
except:
md = {}
for fn in FIELDFILE_NAME: # fn -> field_name
ff = getattr(instance, fn) # ff -> file_field
if md and 'signs' in md and \
fn in md['signs'] and\
md['signs'][fn]:
md['signs'][fn] = {}
if md and "signs" in md and fn in md["signs"] and md["signs"][fn]:
md["signs"][fn] = {}
if not ff:
continue
@ -448,35 +461,34 @@ def signed_files_extraction_function(sender, instance, **kwargs):
file.seek(0)
meta_signs = signed_name_and_date_extract(file)
if not meta_signs or not meta_signs['autores'] and not meta_signs['admin']:
if not meta_signs or not meta_signs["autores"] and not meta_signs["admin"]:
continue
if not md:
md = {'signs': {}}
md = {"signs": {}}
if 'signs' not in md:
md['signs'] = {}
if "signs" not in md:
md["signs"] = {}
md['signs'][fn] = meta_signs
md["signs"][fn] = meta_signs
except Exception as e:
# print(e)
pass
if md:
metadata = Metadata.objects.get_or_create(
content_type=ContentType.objects.get_for_model(
instance._meta.model),
object_id=instance.id,)
content_type=ContentType.objects.get_for_model(instance._meta.model),
object_id=instance.id,
)
metadata[0].metadata = md
metadata[0].save()
@receiver(pre_save, dispatch_uid='signed_files_extraction_pre_save_signal')
@receiver(pre_save, dispatch_uid="signed_files_extraction_pre_save_signal")
def signed_files_extraction_pre_save_signal(sender, instance, **kwargs):
signed_files_extraction_function(sender, instance, **kwargs)
@receiver(pre_migrate, dispatch_uid='disconnect_signals_pre_migrate')
@receiver(pre_migrate, dispatch_uid="disconnect_signals_pre_migrate")
def disconnect_signals_pre_migrate(*args, **kwargs):
pre_save.disconnect(dispatch_uid='signed_files_extraction_pre_save_signal')
pre_save.disconnect(dispatch_uid="signed_files_extraction_pre_save_signal")

136
sapl/base/search_indexes.py

@ -11,8 +11,11 @@ from haystack.fields import CharField
from haystack.indexes import SearchIndex
from haystack.utils import get_model_ct_tuple
from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC,
STATUS_TA_PUBLIC, Dispositivo)
from sapl.compilacao.models import (
STATUS_TA_IMMUTABLE_PUBLIC,
STATUS_TA_PUBLIC,
Dispositivo,
)
from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa
from sapl.norma.models import NormaJuridica
from sapl.sessao.models import SessaoPlenaria
@ -21,7 +24,6 @@ from sapl.utils import RemoveTag
class TextExtractField(CharField):
backend = None
logger = logging.getLogger(__name__)
@ -34,35 +36,36 @@ class TextExtractField(CharField):
def solr_extraction(self, arquivo):
if not self.backend:
self.backend = connections['default'].get_backend()
self.backend = connections["default"].get_backend()
try:
with open(arquivo.path, 'rb') as f:
with open(arquivo.path, "rb") as f:
content = self.backend.extract_file_contents(f)
data = ''
data = ""
if content:
# update from Solr 7.5 to 8.9
if content['contents']:
data += content['contents']
if content['file']:
data += content['file']
if content["contents"]:
data += content["contents"]
if content["file"]:
data += content["file"]
return data
except Exception as e:
print('erro processando arquivo: ' % arquivo.path)
print("erro processando arquivo: " % arquivo.path)
self.logger.error(arquivo.path)
self.logger.error('erro processando arquivo: ' % arquivo.path)
data = ''
self.logger.error("erro processando arquivo: " % arquivo.path)
data = ""
return data
def print_error(self, arquivo, error):
msg = 'Erro inesperado processando arquivo %s erro: %s' % (
arquivo.path, error)
msg = "Erro inesperado processando arquivo %s erro: %s" % (arquivo.path, error)
print(msg, error)
self.logger.error(msg, error)
def file_extractor(self, arquivo):
if not os.path.exists(arquivo.path) or \
not os.path.splitext(arquivo.path)[1][:1]:
return ''
if (
not os.path.exists(arquivo.path)
or not os.path.splitext(arquivo.path)[1][:1]
):
return ""
# Em ambiente de produção utiliza-se o SOLR
if SOLR_URL:
@ -71,33 +74,34 @@ class TextExtractField(CharField):
except Exception as err:
print(str(err))
self.print_error(arquivo, err)
return ''
return ""
def ta_extractor(self, value):
r = []
for ta in value.filter(privacidade__in=[
STATUS_TA_PUBLIC,
STATUS_TA_IMMUTABLE_PUBLIC]):
dispositivos = Dispositivo.objects.filter(
Q(ta=ta) | Q(ta_publicado=ta)
).order_by(
'ordem'
).annotate(
for ta in value.filter(
privacidade__in=[STATUS_TA_PUBLIC, STATUS_TA_IMMUTABLE_PUBLIC]
):
dispositivos = (
Dispositivo.objects.filter(Q(ta=ta) | Q(ta_publicado=ta))
.order_by("ordem")
.annotate(
rotulo_texto=Concat(
F('rotulo'), Value(' '), F('texto'),
F("rotulo"),
Value(" "),
F("texto"),
output_field=TextField(),
)
).values_list(
'rotulo_texto', flat=True)
)
.values_list("rotulo_texto", flat=True)
)
r += list(filter(lambda x: x.strip(), dispositivos))
return ' '.join(r)
return " ".join(r)
def string_extractor(self, value):
return value
def extract_data(self, obj):
data = ''
data = ""
for attr, func in self.model_attr:
if not hasattr(obj, attr) or not hasattr(self, func):
@ -106,32 +110,33 @@ class TextExtractField(CharField):
value = getattr(obj, attr)
if not value:
continue
data += getattr(self, func)(value) + ' '
data += getattr(self, func)(value) + " "
data = data.replace('\\n', ' ')
data = data.replace("\\n", " ")
return data
def prepare_template(self, obj):
app_label, model_name = get_model_ct_tuple(obj)
template_names = ['search/indexes/%s/%s_%s.txt' %
(app_label, model_name, self.instance_name)]
template_names = [
"search/indexes/%s/%s_%s.txt" % (app_label, model_name, self.instance_name)
]
t = loader.select_template(template_names)
return t.render({'object': obj,
'extracted': self.extract_data(obj)})
return t.render({"object": obj, "extracted": self.extract_data(obj)})
class DocumentoAcessorioIndex(SearchIndex, Indexable):
model = DocumentoAcessorio
text = TextExtractField(
document=True, use_template=True,
document=True,
use_template=True,
model_attr=(
('arquivo', 'file_extractor'),
('ementa', 'string_extractor'),
('indexacao', 'string_extractor'),
)
("arquivo", "file_extractor"),
("ementa", "string_extractor"),
("indexacao", "string_extractor"),
),
)
def __init__(self, **kwargs):
@ -145,44 +150,47 @@ class DocumentoAcessorioIndex(SearchIndex, Indexable):
return self.get_model().objects.all()
def get_updated_field(self):
return 'data_ultima_atualizacao'
return "data_ultima_atualizacao"
class NormaJuridicaIndex(DocumentoAcessorioIndex):
model = NormaJuridica
text = TextExtractField(
document=True, use_template=True,
document=True,
use_template=True,
model_attr=(
('texto_integral', 'file_extractor'),
('texto_articulado', 'ta_extractor'),
('ementa', 'string_extractor'),
('indexacao', 'string_extractor'),
('observacao', 'string_extractor'),
)
("texto_integral", "file_extractor"),
("texto_articulado", "ta_extractor"),
("ementa", "string_extractor"),
("indexacao", "string_extractor"),
("observacao", "string_extractor"),
),
)
class MateriaLegislativaIndex(DocumentoAcessorioIndex):
model = MateriaLegislativa
text = TextExtractField(
document=True, use_template=True,
document=True,
use_template=True,
model_attr=(
('texto_original', 'file_extractor'),
('texto_articulado', 'ta_extractor'),
('ementa', 'string_extractor'),
('indexacao', 'string_extractor'),
('observacao', 'string_extractor'),
)
("texto_original", "file_extractor"),
("texto_articulado", "ta_extractor"),
("ementa", "string_extractor"),
("indexacao", "string_extractor"),
("observacao", "string_extractor"),
),
)
class SessaoPlenariaIndex(DocumentoAcessorioIndex):
model = SessaoPlenaria
text = TextExtractField(
document=True, use_template=True,
document=True,
use_template=True,
model_attr=(
('upload_ata', 'file_extractor'),
('upload_anexo', 'file_extractor'),
('upload_pauta', 'file_extractor'),
)
("upload_ata", "file_extractor"),
("upload_anexo", "file_extractor"),
("upload_pauta", "file_extractor"),
),
)

5
sapl/base/templatetags/base_tags.py

@ -1,4 +1,3 @@
from django import template
register = template.Library()
@ -6,4 +5,6 @@ register = template.Library()
@register.filter
def tipoautor_contenttype_list(tipo):
return 'sapl.' + tipo.content_type.app_label + ':' + tipo.content_type.model + '_list'
return (
"sapl." + tipo.content_type.app_label + ":" + tipo.content_type.model + "_list"
)

112
sapl/base/templatetags/common_tags.py

@ -11,18 +11,18 @@ from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa, Proposic
from sapl.norma.models import NormaJuridica
from sapl.parlamentares.models import Filiacao
from sapl.sessao.models import SessaoPlenaria
from sapl.utils import filiacao_data, SEPARADOR_HASH_PROPOSICAO, is_report_allowed
from sapl.utils import SEPARADOR_HASH_PROPOSICAO, filiacao_data, is_report_allowed
register = template.Library()
def get_class(class_string):
if not hasattr(class_string, '__bases__'):
if not hasattr(class_string, "__bases__"):
class_string = str(class_string)
dot = class_string.rindex('.')
dot = class_string.rindex(".")
mod_name, class_name = class_string[:dot], class_string[dot + 1 :]
if class_name:
return getattr(__import__(mod_name, {}, {}, [str('')]), class_name)
return getattr(__import__(mod_name, {}, {}, [str("")]), class_name)
@register.simple_tag
@ -66,7 +66,13 @@ def model_verbose_name_plural(class_name):
@register.filter
def obfuscate_value(value, key):
if key in ["hash", "google_recaptcha_secret_key", "password", "google_recaptcha_site_key", "hash_code"]:
if key in [
"hash",
"google_recaptcha_secret_key",
"password",
"google_recaptcha_site_key",
"hash_code",
]:
return "***************"
return value
@ -98,7 +104,7 @@ def meta_model_value(instance, attr):
try:
return getattr(instance._meta, attr)
except:
return ''
return ""
@register.filter
@ -121,14 +127,15 @@ def sort_by_keys(value, key):
transformed = []
id_props = [x.id for x in value]
qs = Proposicao.objects.filter(pk__in=id_props)
key_descricao = {'1': 'data_envio',
'-1': '-data_envio',
'2': 'tipo',
'-2': '-tipo',
'3': 'descricao',
'-3': '-descricao',
'4': 'autor',
'-4': '-autor'
key_descricao = {
"1": "data_envio",
"-1": "-data_envio",
"2": "tipo",
"-2": "-tipo",
"3": "descricao",
"-3": "-descricao",
"4": "autor",
"-4": "-autor",
}
transformed = qs.order_by(key_descricao[key])
@ -176,7 +183,7 @@ def isinst(value, class_str):
@register.filter
@stringfilter
def strip_hash(value):
vet = value.split('/')
vet = value.split("/")
if len(vet) == 2:
return vet[0][1:]
else:
@ -193,7 +200,7 @@ def get_add_perm(value, arg):
except AttributeError:
return None
nome_model = view.__class__.model.__name__.lower()
can_add = '.add_' + nome_model
can_add = ".add_" + nome_model
return perm.__contains__(nome_app + can_add)
@ -208,7 +215,7 @@ def get_change_perm(value, arg):
except AttributeError:
return None
nome_model = view.__class__.model.__name__.lower()
can_change = '.change_' + nome_model
can_change = ".change_" + nome_model
return perm.__contains__(nome_app + can_change)
@ -223,7 +230,7 @@ def get_delete_perm(value, arg):
except AttributeError:
return None
nome_model = view.__class__.model.__name__.lower()
can_delete = '.delete_' + nome_model
can_delete = ".delete_" + nome_model
return perm.__contains__(nome_app + can_delete)
@ -232,8 +239,9 @@ def get_delete_perm(value, arg):
def ultima_filiacao(value):
parlamentar = value
ultima_filiacao = Filiacao.objects.filter(
parlamentar=parlamentar).order_by('-data').first()
ultima_filiacao = (
Filiacao.objects.filter(parlamentar=parlamentar).order_by("-data").first()
)
if ultima_filiacao:
return ultima_filiacao.partido
@ -249,28 +257,27 @@ def get_config_attr(attribute):
@register.filter
def str2intabs(value):
if not isinstance(value, str):
return ''
return ""
try:
v = int(value)
v = abs(v)
return v
except:
return ''
return ""
@register.filter
def has_iframe(request):
iframe = request.session.get('iframe', False)
if not iframe and 'iframe' in request.GET:
ival = request.GET['iframe']
iframe = request.session.get("iframe", False)
if not iframe and "iframe" in request.GET:
ival = request.GET["iframe"]
if ival and int(ival) == 1:
request.session['iframe'] = True
request.session["iframe"] = True
return True
elif 'iframe' in request.GET:
ival = request.GET['iframe']
elif "iframe" in request.GET:
ival = request.GET["iframe"]
if ival and int(ival) == 0:
del request.session['iframe']
del request.session["iframe"]
return False
return iframe
@ -278,7 +285,7 @@ def has_iframe(request):
@register.filter
def url(value):
if value.startswith('http://') or value.startswith('https://'):
if value.startswith("http://") or value.startswith("https://"):
return True
return False
@ -308,33 +315,37 @@ def youtube_url(value):
@register.filter
def facebook_url(value):
value = value.lower()
facebook_pattern = r"^((https?://)?((www|pt-br)\.)?facebook\.com(\/.+)?\/videos(\/.*)?)"
facebook_pattern = (
r"^((https?://)?((www|pt-br)\.)?facebook\.com(\/.+)?\/videos(\/.*)?)"
)
r = re.findall(facebook_pattern, value)
return True if r else False
@register.filter
def youtube_id(value):
from urllib.parse import urlparse, parse_qs
from urllib.parse import parse_qs, urlparse
u_pars = urlparse(value)
quer_v = parse_qs(u_pars.query).get('v')
quer_v = parse_qs(u_pars.query).get("v")
if quer_v:
return quer_v[0]
return ''
return ""
@register.filter
def file_extension(value):
import pathlib
return pathlib.Path(value).suffix.replace('.', '')
return pathlib.Path(value).suffix.replace(".", "")
@register.filter
def cronometro_to_seconds(value):
if not AppConfig.attr('cronometro_' + value):
if not AppConfig.attr("cronometro_" + value):
return 0
return AppConfig.attr('cronometro_' + value).seconds
return AppConfig.attr("cronometro_" + value).seconds
@register.filter
@ -345,27 +356,25 @@ def to_list_pk(object_list):
@register.filter
def search_get_model(object):
if type(object) == MateriaLegislativa:
return 'm'
return "m"
elif type(object) == DocumentoAcessorio:
return 'd'
return "d"
elif type(object) == NormaJuridica:
return 'n'
return "n"
elif type(object) == SessaoPlenaria:
return 's'
return "s"
return None
@register.filter
def urldetail_content_type(obj, value):
return '%s:%s_detail' % (
value._meta.app_config.name, obj.content_type.model)
return "%s:%s_detail" % (value._meta.app_config.name, obj.content_type.model)
@register.filter
def urldetail(obj):
return '%s:%s_detail' % (
obj._meta.app_config.name, obj._meta.model_name)
return "%s:%s_detail" % (obj._meta.app_config.name, obj._meta.model_name)
@register.filter
@ -373,7 +382,7 @@ def filiacao_data_filter(parlamentar, data_inicio):
try:
filiacao = filiacao_data(parlamentar, data_inicio)
except Exception:
filiacao = ''
filiacao = ""
return filiacao
@ -382,7 +391,7 @@ def filiacao_intervalo_filter(parlamentar, date_range):
try:
filiacao = filiacao_data(parlamentar, date_range[0], date_range[1])
except Exception:
filiacao = ''
filiacao = ""
return filiacao
@ -390,10 +399,11 @@ def filiacao_intervalo_filter(parlamentar, date_range):
def render_chunk_vendors(extension=None):
try:
tags = utils.get_as_tags(
'chunk-vendors', extension=extension, config='DEFAULT', attrs='')
return mark_safe('\n'.join(tags))
"chunk-vendors", extension=extension, config="DEFAULT", attrs=""
)
return mark_safe("\n".join(tags))
except:
return ''
return ""
@register.filter(is_safe=True)

128
sapl/base/templatetags/menus.py

@ -1,27 +1,26 @@
import logging
import yaml
from django import template
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
import yaml
register = template.Library()
logger = logging.getLogger(__name__)
@register.inclusion_tag('menus/menu.html', takes_context=True)
@register.inclusion_tag("menus/menu.html", takes_context=True)
def menu(context, path=None):
return nav_run(context, path)
@register.inclusion_tag('menus/subnav.html', takes_context=True)
@register.inclusion_tag("menus/subnav.html", takes_context=True)
def subnav(context, path=None):
return nav_run(context, path)
@register.inclusion_tag('menus/nav.html', takes_context=True)
@register.inclusion_tag("menus/nav.html", takes_context=True)
def navbar(context, path=None):
return nav_run(context, path)
@ -42,14 +41,14 @@ def nav_run(context, path=None):
será realizado o teste de permissão para renderizá-lo.
"""
menu = None
root_pk = context.get('root_pk', None)
root_pk = context.get("root_pk", None)
if not root_pk:
obj = context.get('object', None)
obj = context.get("object", None)
if obj:
root_pk = obj.pk
if root_pk or 'subnav_template_name' in context or path:
request = context['request']
if root_pk or "subnav_template_name" in context or path:
request = context["request"]
"""
As implementações das Views de Modelos que são dados auxiliares e
@ -60,14 +59,14 @@ def nav_run(context, path=None):
"""
rm = request.resolver_match
app_template = rm.app_name.rsplit('.', 1)[-1]
app_template = rm.app_name.rsplit(".", 1)[-1]
if path:
yaml_path = path
elif 'subnav_template_name' in context:
yaml_path = context['subnav_template_name']
elif "subnav_template_name" in context:
yaml_path = context["subnav_template_name"]
else:
yaml_path = '%s/%s' % (app_template, 'subnav.yaml')
yaml_path = "%s/%s" % (app_template, "subnav.yaml")
if not yaml_path:
return
@ -88,44 +87,47 @@ def nav_run(context, path=None):
menu = yaml.load(rendered, yaml.Loader)
resolve_urls_inplace(menu, root_pk, rm, context)
except Exception as e:
raise Exception(_("""Erro na conversão do yaml %s. App: %s.
raise Exception(
_(
"""Erro na conversão do yaml %s. App: %s.
Erro:
%s
""") % (
yaml_path, rm.app_name, str(e)))
"""
)
% (yaml_path, rm.app_name, str(e))
)
return {'menu': menu}
return {"menu": menu}
def resolve_urls_inplace(menu, pk, rm, context):
if isinstance(menu, list):
list_active = ''
list_active = ""
for item in menu:
menuactive = resolve_urls_inplace(item, pk, rm, context)
list_active = menuactive if menuactive else list_active
if not isinstance(item, list):
item['active'] = menuactive
item["active"] = menuactive
return list_active
else:
if 'url' in menu:
url_name = menu['url']
if 'check_permission' in menu and not context[
'request'].user.has_perm(menu['check_permission']):
menu['url'] = ''
menu['active'] = ''
if "url" in menu:
url_name = menu["url"]
if "check_permission" in menu and not context["request"].user.has_perm(
menu["check_permission"]
):
menu["url"] = ""
menu["active"] = ""
else:
if '/' in url_name:
if "/" in url_name:
pass
elif ':' in url_name:
elif ":" in url_name:
try:
menu['url'] = reverse('%s' % menu['url'])
menu["url"] = reverse("%s" % menu["url"])
except:
try:
menu['url'] = reverse('%s' % menu['url'],
kwargs={'pk': pk})
menu["url"] = reverse("%s" % menu["url"], kwargs={"pk": pk})
except:
# tem que ser root_pk pois quando está sendo
# renderizado um detail, update, delete
@ -149,18 +151,20 @@ def resolve_urls_inplace(menu, pk, rm, context):
2) Se existe no contexto um desses itens:
- context['root_pk'] pk do master
- context['object'] objeto do master
""".format(menu['title'], menu['url'])
""".format(
menu["title"], menu["url"]
)
logger.error(log)
raise Exception(log)
else:
try:
menu['url'] = reverse('%s:%s' % (
rm.app_name, menu['url']))
menu["url"] = reverse("%s:%s" % (rm.app_name, menu["url"]))
except:
try:
menu['url'] = reverse('%s:%s' % (
rm.app_name, menu['url']), kwargs={'pk': pk})
menu["url"] = reverse(
"%s:%s" % (rm.app_name, menu["url"]), kwargs={"pk": pk}
)
except:
log = """Erro na construção do Menu:
menu: {}
@ -169,13 +173,16 @@ def resolve_urls_inplace(menu, pk, rm, context):
2) Se existe no contexto um desses itens:
- context['root_pk'] pk do master
- context['object'] objeto do master
""".format(menu['title'], menu['url'])
""".format(
menu["title"], menu["url"]
)
logger.error(log)
raise Exception(log)
menu['active'] = 'active'\
if context['request'].path == menu['url'] else ''
if not menu['active']:
menu["active"] = (
"active" if context["request"].path == menu["url"] else ""
)
if not menu["active"]:
"""
Se não encontrada diretamente,
procura a url acionada dentro do crud, caso seja um.
@ -184,26 +191,29 @@ def resolve_urls_inplace(menu, pk, rm, context):
- visualização de detalhes, adição, edição, remoção.
"""
try:
if 'view' in context:
view = context['view']
if hasattr(view, 'crud'):
if "view" in context:
view = context["view"]
if hasattr(view, "crud"):
urls = view.crud.get_urls()
for u in urls:
if (u.name == url_name or
'urls_extras' in menu and
u.name in menu['urls_extras']):
menu['active'] = 'active'
if (
u.name == url_name
or "urls_extras" in menu
and u.name in menu["urls_extras"]
):
menu["active"] = "active"
break
except:
url_active = menu.get('url', '')
url_active = menu.get("url", "")
logger.warning(
f'Não foi possível definir se url {url_active} é a url ativa.')
elif 'check_permission' in menu and not context[
'request'].user.has_perm(menu['check_permission']):
menu['active'] = ''
del menu['children']
if 'children' in menu:
menu['active'] = resolve_urls_inplace(
menu['children'], pk, rm, context)
return menu['active']
f"Não foi possível definir se url {url_active} é a url ativa."
)
elif "check_permission" in menu and not context["request"].user.has_perm(
menu["check_permission"]
):
menu["active"] = ""
del menu["children"]
if "children" in menu:
menu["active"] = resolve_urls_inplace(menu["children"], pk, rm, context)
return menu["active"]

28
sapl/base/tests/test_base.py

@ -6,19 +6,21 @@ from sapl.base.models import CasaLegislativa
@pytest.mark.django_db(transaction=False)
def test_casa_legislativa_model():
baker.make(CasaLegislativa,
nome='Teste_Nome_Casa_Legislativa',
sigla='TSCL',
endereco='Teste_Endereço_Casa_Legislativa',
cep='12345678',
municipio='Teste_Municipio_Casa_Legislativa',
uf='DF')
baker.make(
CasaLegislativa,
nome="Teste_Nome_Casa_Legislativa",
sigla="TSCL",
endereco="Teste_Endereço_Casa_Legislativa",
cep="12345678",
municipio="Teste_Municipio_Casa_Legislativa",
uf="DF",
)
casa_legislativa = CasaLegislativa.objects.first()
assert casa_legislativa.nome == 'Teste_Nome_Casa_Legislativa'
assert casa_legislativa.sigla == 'TSCL'
assert casa_legislativa.endereco == 'Teste_Endereço_Casa_Legislativa'
assert casa_legislativa.cep == '12345678'
assert casa_legislativa.municipio == 'Teste_Municipio_Casa_Legislativa'
assert casa_legislativa.uf == 'DF'
assert casa_legislativa.nome == "Teste_Nome_Casa_Legislativa"
assert casa_legislativa.sigla == "TSCL"
assert casa_legislativa.endereco == "Teste_Endereço_Casa_Legislativa"
assert casa_legislativa.cep == "12345678"
assert casa_legislativa.municipio == "Teste_Municipio_Casa_Legislativa"
assert casa_legislativa.uf == "DF"

43
sapl/base/tests/test_form.py

@ -11,31 +11,34 @@ def test_valida_campos_obrigatorios_casa_legislativa_form():
errors = form.errors
assert errors['nome'] == [_('Este campo é obrigatório.')]
assert errors['sigla'] == [_('Este campo é obrigatório.')]
assert errors['endereco'] == [_('Este campo é obrigatório.')]
assert errors['cep'] == [_('Este campo é obrigatório.')]
assert errors['municipio'] == [_('Este campo é obrigatório.')]
assert errors['uf'] == [_('Este campo é obrigatório.')]
assert errors["nome"] == [_("Este campo é obrigatório.")]
assert errors["sigla"] == [_("Este campo é obrigatório.")]
assert errors["endereco"] == [_("Este campo é obrigatório.")]
assert errors["cep"] == [_("Este campo é obrigatório.")]
assert errors["municipio"] == [_("Este campo é obrigatório.")]
assert errors["uf"] == [_("Este campo é obrigatório.")]
assert len(errors) == 6
@pytest.mark.django_db(transaction=False)
def test_casa_legislativa_form_invalido():
form = CasaLegislativaForm(data={'codigo': 'codigo',
'nome': 'nome',
'sigla': 'sg',
'endereco': 'endereco',
'cep': '7000000',
'municipio': 'municipio',
'uf': 'uf',
'telefone': '33333333',
'fax': '33333333',
'logotipo': 'image',
'endereco_web': 'web',
'email': 'email',
'informacao_geral': 'informacao_geral'
})
form = CasaLegislativaForm(
data={
"codigo": "codigo",
"nome": "nome",
"sigla": "sg",
"endereco": "endereco",
"cep": "7000000",
"municipio": "municipio",
"uf": "uf",
"telefone": "33333333",
"fax": "33333333",
"logotipo": "image",
"endereco_web": "web",
"email": "email",
"informacao_geral": "informacao_geral",
}
)
assert not form.is_valid()

56
sapl/base/tests/test_login.py

@ -1,28 +1,28 @@
# -*- coding: utf-8 -*-
from django.contrib.auth import get_user_model
import pytest
from django.contrib.auth import get_user_model
pytestmark = pytest.mark.django_db
@pytest.fixture
def user():
return get_user_model().objects.create_user('jfirmino', password='123')
return get_user_model().objects.create_user("jfirmino", password="123")
def test_login_aparece_na_barra_para_usuario_nao_logado(client):
response = client.get('/')
assert '<a class="nav-link" href="/login/"><img src="/static/sapl/frontend/img/user.png"></a>' in str(
response.content)
response = client.get("/")
assert (
'<a class="nav-link" href="/login/"><img src="/static/sapl/frontend/img/user.png"></a>'
in str(response.content)
)
def test_username_do_usuario_logado_aparece_na_barra(client, user):
assert client.login(username='jfirmino', password='123')
response = client.get('/')
assert '<a class="nav-link" href="/login/">Login</a>' not in str(
response.content)
assert 'jfirmino' in str(response.content)
assert client.login(username="jfirmino", password="123")
response = client.get("/")
assert '<a class="nav-link" href="/login/">Login</a>' not in str(response.content)
assert "jfirmino" in str(response.content)
assert '<a href="/logout/">Sair</a>' in str(response.content)
@ -38,35 +38,41 @@ def test_username_do_usuario_logado_aparece_na_barra(client, user):
# assert '<a href="/logout/">Sair</a>' in str(response.content)
@pytest.mark.urls('sapl.base.tests.teststub_urls')
@pytest.mark.parametrize("link_login,destino", [
@pytest.mark.urls("sapl.base.tests.teststub_urls")
@pytest.mark.parametrize(
"link_login,destino",
[
# login redireciona para home
('/login/', '/'),
])
("/login/", "/"),
],
)
def test_login(app, user, link_login, destino):
pagina_login = app.get(link_login)
form = pagina_login.forms['login-form']
form['username'] = 'jfirmino'
form['password'] = '123'
form = pagina_login.forms["login-form"]
form["username"] = "jfirmino"
form["password"] = "123"
res = form.submit() # login
assert str(user.pk) == app.session['_auth_user_id']
assert str(user.pk) == app.session["_auth_user_id"]
assert res.url == destino
@pytest.mark.parametrize("link_logout,destino", [
@pytest.mark.parametrize(
"link_logout,destino",
[
# logout redireciona para a pagina de login
('/logout/', '/login/'),
])
("/logout/", "/login/"),
],
)
def test_logout(client, user, link_logout, destino):
# com um usuário logado ...
assert client.login(username='jfirmino', password='123')
assert str(user.pk) == client.session['_auth_user_id']
assert client.login(username="jfirmino", password="123")
assert str(user.pk) == client.session["_auth_user_id"]
# ... acionamos o link de logout
res = client.get(link_logout, follow=True)
destino_real = res.redirect_chain[-1][0]
assert '_auth_user_id' not in client.session
assert "_auth_user_id" not in client.session
assert destino_real == destino

393
sapl/base/tests/test_view_base.py

@ -1,67 +1,72 @@
import pytest
from model_bakery import baker
import datetime
import pytest
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from model_bakery import baker
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import Comissao, TipoComissao
from sapl.sessao.models import Bancada
from sapl.protocoloadm.models import (Protocolo, DocumentoAdministrativo,
TipoDocumentoAdministrativo, Anexado)
from sapl.materia.models import (TipoMateriaLegislativa, RegimeTramitacao,
MateriaLegislativa, Anexada)
from sapl.parlamentares.models import (Parlamentar, Partido, Filiacao,
Legislatura, Mandato)
from sapl.base.views import (protocolos_duplicados, protocolos_com_materias,
from sapl.base.views import (
anexados_ciclicos,
autores_duplicados,
bancada_comissao_autor_externo,
mandato_sem_data_inicio,
materias_protocolo_inexistente,
mandato_sem_data_inicio, parlamentares_duplicados,
parlamentares_mandatos_intersecao,
parlamentares_duplicados,
parlamentares_filiacoes_intersecao,
autores_duplicados,
bancada_comissao_autor_externo, anexados_ciclicos)
parlamentares_mandatos_intersecao,
protocolos_com_materias,
protocolos_duplicados,
)
from sapl.comissoes.models import Comissao, TipoComissao
from sapl.materia.models import (
Anexada,
MateriaLegislativa,
RegimeTramitacao,
TipoMateriaLegislativa,
)
from sapl.parlamentares.models import (
Filiacao,
Legislatura,
Mandato,
Parlamentar,
Partido,
)
from sapl.protocoloadm.models import (
Anexado,
DocumentoAdministrativo,
Protocolo,
TipoDocumentoAdministrativo,
)
from sapl.sessao.models import Bancada
@pytest.mark.django_db(transaction=False)
def test_lista_protocolos_com_materias():
baker.make(
Protocolo,
numero=15,
ano=2035
)
baker.make(
Protocolo,
numero=33,
ano=2035
)
baker.make(Protocolo, numero=15, ano=2035)
baker.make(Protocolo, numero=33, ano=2035)
tipo_materia = baker.make(
TipoMateriaLegislativa,
descricao="Tipo_Materia_Teste"
)
tipo_materia = baker.make(TipoMateriaLegislativa, descricao="Tipo_Materia_Teste")
regime_tramitacao = baker.make(
RegimeTramitacao,
descricao="Regime_Tramitacao_Teste"
RegimeTramitacao, descricao="Regime_Tramitacao_Teste"
)
baker.make(
MateriaLegislativa,
numero=16,
ano=2035,
data_apresentacao='2035-06-02',
data_apresentacao="2035-06-02",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia,
numero_protocolo=15
numero_protocolo=15,
)
baker.make(
MateriaLegislativa,
numero=17,
ano=2035,
data_apresentacao='2035-06-05',
data_apresentacao="2035-06-05",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia,
numero_protocolo=15
numero_protocolo=15,
)
lista_protocolos_com_materias = protocolos_com_materias()
@ -74,37 +79,29 @@ def test_lista_protocolos_com_materias():
@pytest.mark.django_db(transaction=False)
def test_lista_materias_protocolo_inexistente():
protocolo_a = baker.make(
Protocolo,
numero=15,
ano=2031
)
protocolo_a = baker.make(Protocolo, numero=15, ano=2031)
tipo_materia = baker.make(
TipoMateriaLegislativa,
descricao="Tipo_Materia_Teste"
)
tipo_materia = baker.make(TipoMateriaLegislativa, descricao="Tipo_Materia_Teste")
regime_tramitacao = baker.make(
RegimeTramitacao,
descricao="Regime_Tramitacao_Teste"
RegimeTramitacao, descricao="Regime_Tramitacao_Teste"
)
baker.make(
MateriaLegislativa,
numero=16,
ano=2031,
data_apresentacao='2031-06-02',
data_apresentacao="2031-06-02",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia,
numero_protocolo=15
numero_protocolo=15,
)
materia = baker.make(
MateriaLegislativa,
numero=17,
ano=2031,
data_apresentacao='2031-06-02',
data_apresentacao="2031-06-02",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia,
numero_protocolo=16
numero_protocolo=16,
)
lista_materias_protocolo_inexistente = materias_protocolo_inexistente()
@ -119,26 +116,22 @@ def test_lista_mandatos_sem_data_inicio():
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste",
nome_parlamentar="Nome_Parlamentar_Teste",
sexo='M'
sexo="M",
)
legislatura = baker.make(
Legislatura,
numero=1,
data_inicio='2015-05-02',
data_fim='2024-02-04',
data_eleicao='2015-02-05'
data_inicio="2015-05-02",
data_fim="2024-02-04",
data_eleicao="2015-02-05",
)
mandato_a = baker.make(
Mandato,
parlamentar=parlamentar,
legislatura=legislatura
)
mandato_a = baker.make(Mandato, parlamentar=parlamentar, legislatura=legislatura)
baker.make(
Mandato,
parlamentar=parlamentar,
legislatura=legislatura,
data_inicio_mandato='2015-05-27'
data_inicio_mandato="2015-05-27",
)
lista_mandatos_sem_data_inicio = mandato_sem_data_inicio()
@ -153,25 +146,23 @@ def test_lista_parlamentares_duplicados():
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste",
nome_parlamentar="Nome_Parlamentar_Teste",
sexo='M'
sexo="M",
)
baker.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste",
nome_parlamentar="Nome_Parlamentar_Teste",
sexo='M'
sexo="M",
)
baker.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste-1",
nome_parlamentar="Nome_Parlamentar_Teste-1",
sexo='M'
sexo="M",
)
lista_dict_parlamentares_duplicados = parlamentares_duplicados()
parlamentar_duplicado = list(
lista_dict_parlamentares_duplicados[0].values()
)
parlamentar_duplicado = list(lista_dict_parlamentares_duplicados[0].values())
parlamentar_duplicado.sort(key=str)
assert len(lista_dict_parlamentares_duplicados) == 1
@ -183,48 +174,48 @@ def test_lista_parlamentares_mandatos_intersecao():
legislatura = baker.make(
Legislatura,
numero=1,
data_inicio='2017-07-04',
data_fim='2170-05-01',
data_eleicao='2017-04-07'
data_inicio="2017-07-04",
data_fim="2170-05-01",
data_eleicao="2017-04-07",
)
parlamentar_a = baker.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste",
nome_parlamentar="Nome_Parlamentar_Teste",
sexo='M'
sexo="M",
)
parlamentar_b = baker.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste-1",
nome_parlamentar="Nome_Parlamentar_Teste-1",
sexo='M'
sexo="M",
)
mandato_a = baker.make(
Mandato,
parlamentar=parlamentar_a,
legislatura=legislatura,
data_inicio_mandato='2017-07-08',
data_fim_mandato='2018-01-07'
data_inicio_mandato="2017-07-08",
data_fim_mandato="2018-01-07",
)
mandato_b = baker.make(
Mandato,
parlamentar=parlamentar_a,
legislatura=legislatura,
data_inicio_mandato='2017-07-09'
data_inicio_mandato="2017-07-09",
)
baker.make(
Mandato,
parlamentar=parlamentar_b,
legislatura=legislatura,
data_inicio_mandato='2017-11-17',
data_fim_mandato='2018-08-02'
data_inicio_mandato="2017-11-17",
data_fim_mandato="2018-08-02",
)
baker.make(
Mandato,
parlamentar=parlamentar_b,
legislatura=legislatura,
data_inicio_mandato='2018-08-03'
data_inicio_mandato="2018-08-03",
)
lista_parlamentares = parlamentares_mandatos_intersecao()
@ -235,51 +226,42 @@ def test_lista_parlamentares_mandatos_intersecao():
@pytest.mark.django_db(transaction=False)
def test_lista_parlamentares_filiacoes_intersecao():
partido = baker.make(
Partido,
sigla="ST",
nome="Nome_Partido_Teste"
)
partido = baker.make(Partido, sigla="ST", nome="Nome_Partido_Teste")
parlamentar_a = baker.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste",
nome_parlamentar="Nome_Parlamentar_Teste",
sexo='M'
sexo="M",
)
parlamentar_b = baker.make(
Parlamentar,
nome_completo="Nome_Completo_Parlamentar_Teste-1",
nome_parlamentar="Nome_Parlamentar_Teste-1",
sexo='M'
sexo="M",
)
filiacao_a = baker.make(
Filiacao,
parlamentar=parlamentar_a,
partido=partido,
data='2018-02-02',
data_desfiliacao='2019-08-01'
data="2018-02-02",
data_desfiliacao="2019-08-01",
)
filiacao_b = baker.make(
Filiacao,
parlamentar=parlamentar_a,
partido=partido,
data='2018-02-23',
data_desfiliacao='2020-02-04'
)
baker.make(
Filiacao,
parlamentar=parlamentar_b,
partido=partido,
data='2018-02-07',
data_desfiliacao='2018-02-27'
data="2018-02-23",
data_desfiliacao="2020-02-04",
)
baker.make(
Filiacao,
parlamentar=parlamentar_b,
partido=partido,
data='2018-02-28'
data="2018-02-07",
data_desfiliacao="2018-02-27",
)
baker.make(Filiacao, parlamentar=parlamentar_b, partido=partido, data="2018-02-28")
lista_parlamentares = parlamentares_filiacoes_intersecao()
@ -289,120 +271,86 @@ def test_lista_parlamentares_filiacoes_intersecao():
@pytest.mark.django_db(transaction=False)
def test_lista_autores_duplicados():
tipo_autor = baker.make(
TipoAutor,
descricao="Tipo_Autor_Teste"
)
tipo_autor = baker.make(TipoAutor, descricao="Tipo_Autor_Teste")
baker.make(
Autor,
tipo=tipo_autor,
nome="Nome_Autor_Teste"
)
baker.make(
Autor,
tipo=tipo_autor,
nome="Nome_Autor_Teste"
)
baker.make(
Autor,
tipo=tipo_autor,
nome="Nome_Autor_Teste-1"
)
baker.make(Autor, tipo=tipo_autor, nome="Nome_Autor_Teste")
baker.make(Autor, tipo=tipo_autor, nome="Nome_Autor_Teste")
baker.make(Autor, tipo=tipo_autor, nome="Nome_Autor_Teste-1")
lista_autores_duplicados = autores_duplicados()
assert len(lista_autores_duplicados) == 1
assert lista_autores_duplicados[0]['count'] == 2
assert lista_autores_duplicados[0]['nome'] == "Nome_Autor_Teste"
assert lista_autores_duplicados[0]["count"] == 2
assert lista_autores_duplicados[0]["nome"] == "Nome_Autor_Teste"
@pytest.mark.django_db(transaction=False)
def test_lista_bancada_comissao_autor_externo():
tipo_autor = baker.make(
TipoAutor,
descricao="Tipo_Autor_Teste"
)
tipo_autor_externo = baker.make(
TipoAutor,
descricao="Externo"
)
tipo_autor = baker.make(TipoAutor, descricao="Tipo_Autor_Teste")
tipo_autor_externo = baker.make(TipoAutor, descricao="Externo")
legislatura = baker.make(
Legislatura,
numero=1,
data_inicio='2012-01-03',
data_fim='2013-01-02',
data_eleicao='2011-10-04'
data_inicio="2012-01-03",
data_fim="2013-01-02",
data_eleicao="2011-10-04",
)
bancada_a = baker.make(
Bancada,
legislatura=legislatura,
nome="Bancada_Teste",
data_criacao='2012-01-08',
)
bancada_a.autor.create(
nome="Nome_Autor_Teste",
tipo=tipo_autor
data_criacao="2012-01-08",
)
bancada_a.autor.create(nome="Nome_Autor_Teste", tipo=tipo_autor)
bancada_b = baker.make(
Bancada,
legislatura=legislatura,
nome="Bancada_Teste-1",
data_criacao='2012-02-02'
data_criacao="2012-02-02",
)
autor_bancada_b = bancada_b.autor.create(
nome="Nome_Autor_Externo_Teste",
tipo=tipo_autor_externo
nome="Nome_Autor_Externo_Teste", tipo=tipo_autor_externo
)
tipo_comissao = baker.make(
TipoComissao,
nome="Tipo_Comissao_Teste",
natureza='T',
sigla="TCT"
TipoComissao, nome="Tipo_Comissao_Teste", natureza="T", sigla="TCT"
)
comissao_a = baker.make(
Comissao,
nome="Comissao_Teste",
sigla="CT",
data_criacao='2012-03-08',
)
comissao_a.autor.create(
nome="Nome_Autor_Teste",
tipo=tipo_autor
data_criacao="2012-03-08",
)
comissao_a.autor.create(nome="Nome_Autor_Teste", tipo=tipo_autor)
comissao_b = baker.make(
Comissao,
nome="Comissao_Teste-1",
sigla="CT1",
data_criacao='2012-04-01',
data_criacao="2012-04-01",
)
autor_comissao_b = comissao_b.autor.create(
nome="Nome_Autor_Externo_Teste",
tipo=tipo_autor_externo
nome="Nome_Autor_Externo_Teste", tipo=tipo_autor_externo
)
lista_bancada_comissao = bancada_comissao_autor_externo()
assert len(lista_bancada_comissao) == 2
assert lista_bancada_comissao[0][0:2] == (autor_bancada_b, bancada_b)
assert lista_bancada_comissao[0][2:4] == ('Bancada', 'sistema/bancada')
assert lista_bancada_comissao[0][2:4] == ("Bancada", "sistema/bancada")
assert lista_bancada_comissao[1][0:2] == (autor_comissao_b, comissao_b)
assert lista_bancada_comissao[1][2:4] == ('Comissão', 'comissao')
assert lista_bancada_comissao[1][2:4] == ("Comissão", "comissao")
@pytest.mark.django_db(transaction=False)
def test_lista_anexados_ciclicas():
## DocumentoAdministrativo
tipo_documento = baker.make(
TipoDocumentoAdministrativo,
sigla="TT",
descricao="Tipo_Teste"
TipoDocumentoAdministrativo, sigla="TT", descricao="Tipo_Teste"
)
documento_a = baker.make(
@ -410,98 +358,92 @@ def test_lista_anexados_ciclicas():
tipo=tipo_documento,
numero=26,
ano=2019,
data='2019-05-15',
data="2019-05-15",
)
documento_b = baker.make(
DocumentoAdministrativo,
tipo=tipo_documento,
numero=27,
ano=2019,
data='2019-05-16',
data="2019-05-16",
)
documento_c = baker.make(
DocumentoAdministrativo,
tipo=tipo_documento,
numero=28,
ano=2019,
data='2019-05-17',
data="2019-05-17",
)
documento_a1 = baker.make(
DocumentoAdministrativo,
tipo=tipo_documento,
numero=29,
ano=2019,
data='2019-05-18',
data="2019-05-18",
)
documento_b1 = baker.make(
DocumentoAdministrativo,
tipo=tipo_documento,
numero=30,
ano=2019,
data='2019-05-19',
data="2019-05-19",
)
documento_c1 = baker.make(
DocumentoAdministrativo,
tipo=tipo_documento,
numero=31,
ano=2019,
data='2019-05-20',
data="2019-05-20",
)
baker.make(
Anexado,
documento_principal=documento_a,
documento_anexado=documento_b,
data_anexacao='2019-05-21'
data_anexacao="2019-05-21",
)
baker.make(
Anexado,
documento_principal=documento_a,
documento_anexado=documento_c,
data_anexacao='2019-05-22'
data_anexacao="2019-05-22",
)
baker.make(
Anexado,
documento_principal=documento_b,
documento_anexado=documento_c,
data_anexacao='2019-05-23'
data_anexacao="2019-05-23",
)
baker.make(
Anexado,
documento_principal=documento_a1,
documento_anexado=documento_b1,
data_anexacao='2019-05-24'
data_anexacao="2019-05-24",
)
baker.make(
Anexado,
documento_principal=documento_a1,
documento_anexado=documento_c1,
data_anexacao='2019-05-25'
data_anexacao="2019-05-25",
)
baker.make(
Anexado,
documento_principal=documento_b1,
documento_anexado=documento_c1,
data_anexacao='2019-05-26'
data_anexacao="2019-05-26",
)
baker.make(
Anexado,
documento_principal=documento_c1,
documento_anexado=documento_b1,
data_anexacao='2019-05-27'
data_anexacao="2019-05-27",
)
lista_documento_ciclicos = anexados_ciclicos(False)
## Matéria
tipo_materia = baker.make(
TipoMateriaLegislativa,
descricao="Tipo_Teste"
)
regime_tramitacao = baker.make(
RegimeTramitacao,
descricao="Regime_Teste"
)
tipo_materia = baker.make(TipoMateriaLegislativa, descricao="Tipo_Teste")
regime_tramitacao = baker.make(RegimeTramitacao, descricao="Regime_Teste")
materia_a = baker.make(
MateriaLegislativa,
@ -509,7 +451,7 @@ def test_lista_anexados_ciclicas():
ano=2018,
data_apresentacao="2018-01-04",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
tipo=tipo_materia,
)
materia_b = baker.make(
MateriaLegislativa,
@ -517,7 +459,7 @@ def test_lista_anexados_ciclicas():
ano=2019,
data_apresentacao="2019-05-04",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
tipo=tipo_materia,
)
materia_c = baker.make(
MateriaLegislativa,
@ -525,7 +467,7 @@ def test_lista_anexados_ciclicas():
ano=2019,
data_apresentacao="2019-05-05",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
tipo=tipo_materia,
)
materia_a1 = baker.make(
MateriaLegislativa,
@ -533,7 +475,7 @@ def test_lista_anexados_ciclicas():
ano=2018,
data_apresentacao="2019-05-06",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
tipo=tipo_materia,
)
materia_b1 = baker.make(
MateriaLegislativa,
@ -541,7 +483,7 @@ def test_lista_anexados_ciclicas():
ano=2019,
data_apresentacao="2019-05-07",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
tipo=tipo_materia,
)
materia_c1 = baker.make(
MateriaLegislativa,
@ -549,90 +491,111 @@ def test_lista_anexados_ciclicas():
ano=2019,
data_apresentacao="2019-05-08",
regime_tramitacao=regime_tramitacao,
tipo=tipo_materia
tipo=tipo_materia,
)
baker.make(
Anexada,
materia_principal=materia_a,
materia_anexada=materia_b,
data_anexacao='2019-05-11'
data_anexacao="2019-05-11",
)
baker.make(
Anexada,
materia_principal=materia_a,
materia_anexada=materia_c,
data_anexacao='2019-05-12'
data_anexacao="2019-05-12",
)
baker.make(
Anexada,
materia_principal=materia_b,
materia_anexada=materia_c,
data_anexacao='2019-05-13'
data_anexacao="2019-05-13",
)
baker.make(
Anexada,
materia_principal=materia_a1,
materia_anexada=materia_b1,
data_anexacao='2019-05-11'
data_anexacao="2019-05-11",
)
baker.make(
Anexada,
materia_principal=materia_a1,
materia_anexada=materia_c1,
data_anexacao='2019-05-12'
data_anexacao="2019-05-12",
)
baker.make(
Anexada,
materia_principal=materia_b1,
materia_anexada=materia_c1,
data_anexacao='2019-05-13'
data_anexacao="2019-05-13",
)
baker.make(
Anexada,
materia_principal=materia_c1,
materia_anexada=materia_b1,
data_anexacao='2019-05-14'
data_anexacao="2019-05-14",
)
lista_materias_ciclicas = anexados_ciclicos(True)
assert len(lista_materias_ciclicas) == 2
assert lista_materias_ciclicas[0] == (datetime.date(2019,5,13), materia_b1, materia_c1)
assert lista_materias_ciclicas[1] == (datetime.date(2019,5,14), materia_c1, materia_b1)
assert lista_materias_ciclicas[0] == (
datetime.date(2019, 5, 13),
materia_b1,
materia_c1,
)
assert lista_materias_ciclicas[1] == (
datetime.date(2019, 5, 14),
materia_c1,
materia_b1,
)
assert len(lista_documento_ciclicos) == 2
assert lista_documento_ciclicos[0] == (datetime.date(2019,5,26), documento_b1, documento_c1)
assert lista_documento_ciclicos[1] == (datetime.date(2019,5,27), documento_c1, documento_b1)
assert lista_documento_ciclicos[0] == (
datetime.date(2019, 5, 26),
documento_b1,
documento_c1,
)
assert lista_documento_ciclicos[1] == (
datetime.date(2019, 5, 27),
documento_c1,
documento_b1,
)
@pytest.mark.django_db(transaction=False)
def test_incluir_casa_legislativa_errors(admin_client):
response = admin_client.post(reverse('sapl.base:casalegislativa_create'),
{'salvar': 'salvar'},
follow=True)
assert (response.context_data['form'].errors['nome'] ==
[_('Este campo é obrigatório.')])
assert (response.context_data['form'].errors['sigla'] ==
[_('Este campo é obrigatório.')])
assert (response.context_data['form'].errors['endereco'] ==
[_('Este campo é obrigatório.')])
assert (response.context_data['form'].errors['cep'] ==
[_('Este campo é obrigatório.')])
assert (response.context_data['form'].errors['municipio'] ==
[_('Este campo é obrigatório.')])
assert (response.context_data['form'].errors['uf'] ==
[_('Este campo é obrigatório.')])
response = admin_client.post(
reverse("sapl.base:casalegislativa_create"), {"salvar": "salvar"}, follow=True
)
assert response.context_data["form"].errors["nome"] == [
_("Este campo é obrigatório.")
]
assert response.context_data["form"].errors["sigla"] == [
_("Este campo é obrigatório.")
]
assert response.context_data["form"].errors["endereco"] == [
_("Este campo é obrigatório.")
]
assert response.context_data["form"].errors["cep"] == [
_("Este campo é obrigatório.")
]
assert response.context_data["form"].errors["municipio"] == [
_("Este campo é obrigatório.")
]
assert response.context_data["form"].errors["uf"] == [
_("Este campo é obrigatório.")
]
@pytest.mark.django_db(transaction=False)
def test_incluir_tipo_autor_errors(admin_client):
response = admin_client.post(
reverse("sapl.base:tipoautor_create"), {"salvar": "salvar"}, follow=True
)
response = admin_client.post(reverse('sapl.base:tipoautor_create'),
{'salvar': 'salvar'},
follow=True)
assert (response.context_data['form'].errors['descricao'] ==
[_('Este campo é obrigatório.')])
assert response.context_data["form"].errors["descricao"] == [
_("Este campo é obrigatório.")
]

4
sapl/base/tests/teststub_urls.py

@ -3,7 +3,5 @@ from django.views.generic.base import TemplateView
from sapl.urls import urlpatterns as original_patterns
ptrn = [url(r'^zzzz$',
TemplateView.as_view(
template_name='index.html'), name='zzzz')]
ptrn = [url(r"^zzzz$", TemplateView.as_view(template_name="index.html"), name="zzzz")]
urlpatterns = original_patterns + ptrn

281
sapl/base/urls.py

@ -5,133 +5,216 @@ from django.contrib.auth import views
from django.contrib.auth.decorators import permission_required
from django.views.generic.base import RedirectView, TemplateView
from sapl.base.views import (AutorCrud, ConfirmarEmailView, TipoAutorCrud, get_estatistica,
RecuperarSenhaEmailView, RecuperarSenhaFinalizadoView,
RecuperarSenhaConfirmaView, RecuperarSenhaCompletoView, IndexView, UserCrud)
from sapl.settings import MEDIA_URL, LOGOUT_REDIRECT_URL
from sapl.base.views import (
AutorCrud,
ConfirmarEmailView,
IndexView,
RecuperarSenhaCompletoView,
RecuperarSenhaConfirmaView,
RecuperarSenhaEmailView,
RecuperarSenhaFinalizadoView,
TipoAutorCrud,
UserCrud,
get_estatistica,
)
from sapl.settings import LOGOUT_REDIRECT_URL, MEDIA_URL
from .apps import AppConfig
from .views import (LoginSapl, AlterarSenha, AppConfigCrud, CasaLegislativaCrud,
HelpTopicView, LogotipoView, PesquisarAuditLogView,
SaplSearchView,
from .views import (
AlterarSenha,
AppConfigCrud,
CasaLegislativaCrud,
HelpTopicView,
ListarAnexadasCiclicasView,
ListarAnexadosCiclicosView,
ListarAutoresDuplicadosView,
ListarBancadaComissaoAutorExternoView,
ListarFiliacoesSemDataFiliacaoView,
ListarInconsistenciasView,
ListarProtocolosDuplicadosView, ListarProtocolosComMateriasView, ListarMatProtocoloInexistenteView,
ListarParlamentaresDuplicadosView, ListarFiliacoesSemDataFiliacaoView,
ListarMandatoSemDataInicioView, ListarParlMandatosIntersecaoView, ListarParlFiliacoesIntersecaoView,
ListarAutoresDuplicadosView, ListarBancadaComissaoAutorExternoView, ListarLegislaturaInfindavelView,
ListarAnexadasCiclicasView, ListarAnexadosCiclicosView, pesquisa_textual)
ListarLegislaturaInfindavelView,
ListarMandatoSemDataInicioView,
ListarMatProtocoloInexistenteView,
ListarParlamentaresDuplicadosView,
ListarParlFiliacoesIntersecaoView,
ListarParlMandatosIntersecaoView,
ListarProtocolosComMateriasView,
ListarProtocolosDuplicadosView,
LoginSapl,
LogotipoView,
PesquisarAuditLogView,
SaplSearchView,
pesquisa_textual,
)
app_name = AppConfig.name
admin_user = [
url(r'^sistema/usuario/', include(UserCrud.get_urls())),
url(r"^sistema/usuario/", include(UserCrud.get_urls())),
]
alterar_senha = [
url(r'^sistema/alterar-senha/$',
AlterarSenha.as_view(),
name='alterar_senha'),
url(r"^sistema/alterar-senha/$", AlterarSenha.as_view(), name="alterar_senha"),
]
recuperar_senha = [
url(r'^recuperar-senha/email/$', RecuperarSenhaEmailView.as_view(),
name='recuperar_senha_email'),
url(r'^recuperar-senha/finalizado/$',
RecuperarSenhaFinalizadoView.as_view(), name='recuperar_senha_finalizado'),
url(r'^recuperar-senha/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$', RecuperarSenhaConfirmaView.as_view(),
name='recuperar_senha_confirma'),
url(r'^recuperar-senha/completo/$',
RecuperarSenhaCompletoView.as_view(), name='recuperar_senha_completo'),
url(
r"^recuperar-senha/email/$",
RecuperarSenhaEmailView.as_view(),
name="recuperar_senha_email",
),
url(
r"^recuperar-senha/finalizado/$",
RecuperarSenhaFinalizadoView.as_view(),
name="recuperar_senha_finalizado",
),
url(
r"^recuperar-senha/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$",
RecuperarSenhaConfirmaView.as_view(),
name="recuperar_senha_confirma",
),
url(
r"^recuperar-senha/completo/$",
RecuperarSenhaCompletoView.as_view(),
name="recuperar_senha_completo",
),
]
urlpatterns = [
url(r'^$', IndexView.as_view(template_name='index.html'), name='sapl_index'),
url(r'^sistema/autor/tipo/', include(TipoAutorCrud.get_urls())),
url(r'^sistema/autor/', include(AutorCrud.get_urls())),
url(r'^sistema/ajuda/(?P<topic>\w+)$',
HelpTopicView.as_view(), name='help_topic'),
url(r'^sistema/ajuda/$', TemplateView.as_view(template_name='ajuda.html'),
name='help'),
url(r'^sistema/casa-legislativa/', include(CasaLegislativaCrud.get_urls()),
name="casa_legislativa"),
url(r'^sistema/app-config/', include(AppConfigCrud.get_urls())),
url(r'^email/validate/(?P<uidb64>[0-9A-Za-z_\-]+)/'
'(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})$',
ConfirmarEmailView.as_view(), name='confirmar_email'),
url(r'^sistema/inconsistencias/$',
urlpatterns = (
[
url(r"^$", IndexView.as_view(template_name="index.html"), name="sapl_index"),
url(r"^sistema/autor/tipo/", include(TipoAutorCrud.get_urls())),
url(r"^sistema/autor/", include(AutorCrud.get_urls())),
url(
r"^sistema/ajuda/(?P<topic>\w+)$",
HelpTopicView.as_view(),
name="help_topic",
),
url(
r"^sistema/ajuda/$",
TemplateView.as_view(template_name="ajuda.html"),
name="help",
),
url(
r"^sistema/casa-legislativa/",
include(CasaLegislativaCrud.get_urls()),
name="casa_legislativa",
),
url(r"^sistema/app-config/", include(AppConfigCrud.get_urls())),
url(
r"^email/validate/(?P<uidb64>[0-9A-Za-z_\-]+)/"
"(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})$",
ConfirmarEmailView.as_view(),
name="confirmar_email",
),
url(
r"^sistema/inconsistencias/$",
ListarInconsistenciasView.as_view(),
name='lista_inconsistencias'),
url(r'^sistema/inconsistencias/protocolos_duplicados$',
name="lista_inconsistencias",
),
url(
r"^sistema/inconsistencias/protocolos_duplicados$",
ListarProtocolosDuplicadosView.as_view(),
name='lista_protocolos_duplicados'),
url(r'^sistema/inconsistencias/protocolos_com_materias$',
name="lista_protocolos_duplicados",
),
url(
r"^sistema/inconsistencias/protocolos_com_materias$",
ListarProtocolosComMateriasView.as_view(),
name='lista_protocolos_com_materias'),
url(r'^sistema/inconsistencias/materias_protocolo_inexistente$',
name="lista_protocolos_com_materias",
),
url(
r"^sistema/inconsistencias/materias_protocolo_inexistente$",
ListarMatProtocoloInexistenteView.as_view(),
name='lista_materias_protocolo_inexistente'),
url(r'^sistema/inconsistencias/filiacoes_sem_data_filiacao$',
name="lista_materias_protocolo_inexistente",
),
url(
r"^sistema/inconsistencias/filiacoes_sem_data_filiacao$",
ListarFiliacoesSemDataFiliacaoView.as_view(),
name='lista_filiacoes_sem_data_filiacao'),
url(r'^sistema/inconsistencias/mandato_sem_data_inicio',
name="lista_filiacoes_sem_data_filiacao",
),
url(
r"^sistema/inconsistencias/mandato_sem_data_inicio",
ListarMandatoSemDataInicioView.as_view(),
name='lista_mandato_sem_data_inicio'),
url(r'^sistema/inconsistencias/parlamentares_duplicados$',
name="lista_mandato_sem_data_inicio",
),
url(
r"^sistema/inconsistencias/parlamentares_duplicados$",
ListarParlamentaresDuplicadosView.as_view(),
name='lista_parlamentares_duplicados'),
url(r'^sistema/inconsistencias/parlamentares_mandatos_intersecao$',
name="lista_parlamentares_duplicados",
),
url(
r"^sistema/inconsistencias/parlamentares_mandatos_intersecao$",
ListarParlMandatosIntersecaoView.as_view(),
name='lista_parlamentares_mandatos_intersecao'),
url(r'^sistema/inconsistencias/parlamentares_filiacoes_intersecao$',
name="lista_parlamentares_mandatos_intersecao",
),
url(
r"^sistema/inconsistencias/parlamentares_filiacoes_intersecao$",
ListarParlFiliacoesIntersecaoView.as_view(),
name='lista_parlamentares_filiacoes_intersecao'),
url(r'^sistema/inconsistencias/autores_duplicados$',
name="lista_parlamentares_filiacoes_intersecao",
),
url(
r"^sistema/inconsistencias/autores_duplicados$",
ListarAutoresDuplicadosView.as_view(),
name='lista_autores_duplicados'),
url(r'^sistema/inconsistencias/bancada_comissao_autor_externo$',
name="lista_autores_duplicados",
),
url(
r"^sistema/inconsistencias/bancada_comissao_autor_externo$",
ListarBancadaComissaoAutorExternoView.as_view(),
name='lista_bancada_comissao_autor_externo'),
url(r'^sistema/inconsistencias/legislatura_infindavel$',
name="lista_bancada_comissao_autor_externo",
),
url(
r"^sistema/inconsistencias/legislatura_infindavel$",
ListarLegislaturaInfindavelView.as_view(),
name='lista_legislatura_infindavel'),
url(r'sistema/inconsistencias/anexadas_ciclicas$',
name="lista_legislatura_infindavel",
),
url(
r"sistema/inconsistencias/anexadas_ciclicas$",
ListarAnexadasCiclicasView.as_view(),
name='lista_anexadas_ciclicas'),
url(r'sistema/inconsistencias/anexados_ciclicos$',
name="lista_anexadas_ciclicas",
),
url(
r"sistema/inconsistencias/anexados_ciclicos$",
ListarAnexadosCiclicosView.as_view(),
name='lista_anexados_ciclicos'),
url(r'^sistema/pesquisa-textual',
pesquisa_textual,
name='pesquisa_textual'),
url(r'^sistema/estatisticas', get_estatistica),
name="lista_anexados_ciclicos",
),
url(r"^sistema/pesquisa-textual", pesquisa_textual, name="pesquisa_textual"),
url(r"^sistema/estatisticas", get_estatistica),
# todos os sublinks de sistema devem vir acima deste
url(r'^sistema/$', permission_required('base.view_tabelas_auxiliares')
(TemplateView.as_view(template_name='sistema.html')),
name='sistema'),
url(r'^login/$', LoginSapl.as_view(), name='login'),
url(r'^logout/$', views.LogoutView.as_view(),
{'next_page': LOGOUT_REDIRECT_URL}, name='logout'),
url(r'^sistema/search/', SaplSearchView(), name='haystack_search'),
url(r'^sistema/auditlog/$', PesquisarAuditLogView.as_view(), name='pesquisar_auditlog'),
url(
r"^sistema/$",
permission_required("base.view_tabelas_auxiliares")(
TemplateView.as_view(template_name="sistema.html")
),
name="sistema",
),
url(r"^login/$", LoginSapl.as_view(), name="login"),
url(
r"^logout/$",
views.LogoutView.as_view(),
{"next_page": LOGOUT_REDIRECT_URL},
name="logout",
),
url(r"^sistema/search/", SaplSearchView(), name="haystack_search"),
url(
r"^sistema/auditlog/$",
PesquisarAuditLogView.as_view(),
name="pesquisar_auditlog",
),
# Folhas XSLT e extras referenciadas por documentos migrados do sapl 2.5
url(r'^(sapl/)?XSLT/HTML/(?P<path>.*)$', RedirectView.as_view(
url=os.path.join(MEDIA_URL, 'sapl/public/XSLT/HTML/%(path)s'),
permanent=False)),
url(
r"^(sapl/)?XSLT/HTML/(?P<path>.*)$",
RedirectView.as_view(
url=os.path.join(MEDIA_URL, "sapl/public/XSLT/HTML/%(path)s"),
permanent=False,
),
),
# url do logotipo usada em documentos migrados do sapl 2.5
url(r'^(sapl/)?sapl_documentos/props_sapl/logo_casa',
LogotipoView.as_view(), name='logotipo'),
] + recuperar_senha + alterar_senha + admin_user
url(
r"^(sapl/)?sapl_documentos/props_sapl/logo_casa",
LogotipoView.as_view(),
name="logotipo",
),
]
+ recuperar_senha
+ alterar_senha
+ admin_user
)

1226
sapl/base/views.py

File diff suppressed because it is too large

2
sapl/comissoes/__init__.py

@ -1 +1 @@
default_app_config = 'sapl.comissoes.apps.AppConfig'
default_app_config = "sapl.comissoes.apps.AppConfig"

6
sapl/comissoes/apps.py

@ -3,6 +3,6 @@ from django.utils.translation import ugettext_lazy as _
class AppConfig(apps.AppConfig):
name = 'sapl.comissoes'
label = 'comissoes'
verbose_name = _('Comissões')
name = "sapl.comissoes"
label = "comissoes"
verbose_name = _("Comissões")

455
sapl/comissoes/forms.py

@ -1,34 +1,35 @@
import django_filters
import logging
import django_filters
from crispy_forms.layout import Fieldset, Layout
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import Q
from django.forms import ModelForm
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from sapl.base.models import Autor, TipoAutor
from sapl.comissoes.models import (Comissao, Composicao,
DocumentoAcessorio, Participacao,
Periodo, Reuniao)
from sapl.crispy_layout_mixin import (form_actions, SaplFormHelper,
to_row)
from sapl.comissoes.models import (
Comissao,
Composicao,
DocumentoAcessorio,
Participacao,
Periodo,
Reuniao,
)
from sapl.crispy_layout_mixin import SaplFormHelper, form_actions, to_row
from sapl.materia.models import MateriaEmTramitacao, PautaReuniao
from sapl.parlamentares.models import (Legislatura, Mandato,
Parlamentar)
from sapl.utils import (FileFieldCheckMixin,
FilterOverridesMetaMixin, validar_arquivo)
from sapl.parlamentares.models import Legislatura, Mandato, Parlamentar
from sapl.utils import FileFieldCheckMixin, FilterOverridesMetaMixin, validar_arquivo
class ComposicaoForm(forms.ModelForm):
comissao = forms.CharField(
required=False, label='Comissao', widget=forms.HiddenInput())
required=False, label="Comissao", widget=forms.HiddenInput()
)
logger = logging.getLogger(__name__)
class Meta:
@ -37,48 +38,60 @@ class ComposicaoForm(forms.ModelForm):
def __init__(self, user=None, **kwargs):
super(ComposicaoForm, self).__init__(**kwargs)
self.fields['comissao'].widget.attrs['disabled'] = 'disabled'
self.fields["comissao"].widget.attrs["disabled"] = "disabled"
def clean(self):
data = super().clean()
data['comissao'] = self.initial['comissao']
comissao_pk = self.initial['comissao'].id
data["comissao"] = self.initial["comissao"]
comissao_pk = self.initial["comissao"].id
if not self.is_valid():
return data
periodo = data['periodo']
periodo = data["periodo"]
if periodo.data_fim:
intersecao_periodo = Composicao.objects.filter(
Q(periodo__data_inicio__lte=periodo.data_fim, periodo__data_fim__gte=periodo.data_fim) |
Q(periodo__data_inicio__gte=periodo.data_inicio, periodo__data_fim__lte=periodo.data_inicio),
comissao_id=comissao_pk)
Q(
periodo__data_inicio__lte=periodo.data_fim,
periodo__data_fim__gte=periodo.data_fim,
)
| Q(
periodo__data_inicio__gte=periodo.data_inicio,
periodo__data_fim__lte=periodo.data_inicio,
),
comissao_id=comissao_pk,
)
else:
intersecao_periodo = Composicao.objects.filter(
Q(periodo__data_inicio__gte=periodo.data_inicio, periodo__data_fim__lte=periodo.data_inicio),
comissao_id=comissao_pk)
Q(
periodo__data_inicio__gte=periodo.data_inicio,
periodo__data_fim__lte=periodo.data_inicio,
),
comissao_id=comissao_pk,
)
if intersecao_periodo:
if periodo.data_fim:
self.logger.warning(
'O período informado ({} a {}) choca com períodos já cadastrados para esta comissão'.format(
"O período informado ({} a {}) choca com períodos já cadastrados para esta comissão".format(
periodo.data_inicio, periodo.data_fim
)
)
else:
self.logger.warning(
'O período informado ({} - ) choca com períodos já cadastrados para esta comissão'.format(
"O período informado ({} - ) choca com períodos já cadastrados para esta comissão".format(
periodo.data_inicio
)
)
raise ValidationError('O período informado choca com períodos já cadastrados para esta comissão')
raise ValidationError(
"O período informado choca com períodos já cadastrados para esta comissão"
)
return data
class PeriodoForm(forms.ModelForm):
logger = logging.getLogger(__name__)
class Meta:
@ -91,54 +104,57 @@ class PeriodoForm(forms.ModelForm):
if not self.is_valid():
return cleaned_data
data_inicio = cleaned_data['data_inicio']
data_fim = cleaned_data['data_fim']
data_inicio = cleaned_data["data_inicio"]
data_fim = cleaned_data["data_fim"]
if data_fim and data_fim < data_inicio:
self.logger.warning(
'A Data Final ({}) é menor que '
'a Data Inicial({}).'.format(data_fim, data_inicio)
"A Data Final ({}) é menor que "
"a Data Inicial({}).".format(data_fim, data_inicio)
)
raise ValidationError(
"A Data Final não pode ser menor que " "a Data Inicial"
)
raise ValidationError('A Data Final não pode ser menor que '
'a Data Inicial')
# Evita NoneType exception se não preenchida a data_fim
if not data_fim:
data_fim = data_inicio
legislatura = Legislatura.objects.filter(data_inicio__lte=data_inicio,
legislatura = Legislatura.objects.filter(
data_inicio__lte=data_inicio,
data_fim__gte=data_fim,
)
if not legislatura:
self.logger.warning(
'O período informado ({} a {})'
'não está contido em uma única '
'legislatura existente'.format(data_inicio, data_fim)
"O período informado ({} a {})"
"não está contido em uma única "
"legislatura existente".format(data_inicio, data_fim)
)
raise ValidationError(
"O período informado "
"deve estar contido em uma única "
"legislatura existente"
)
raise ValidationError('O período informado '
'deve estar contido em uma única '
'legislatura existente')
return cleaned_data
class ParticipacaoCreateForm(forms.ModelForm):
logger = logging.getLogger(__name__)
parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput())
class Meta:
model = Participacao
fields = '__all__'
exclude = ['composicao']
fields = "__all__"
exclude = ["composicao"]
def __init__(self, user=None, **kwargs):
super(ParticipacaoCreateForm, self).__init__(**kwargs)
if self.instance:
comissao = kwargs['initial']
comissao_pk = int(comissao['parent_pk'])
comissao = kwargs["initial"]
comissao_pk = int(comissao["parent_pk"])
composicao = Composicao.objects.get(id=comissao_pk)
participantes = composicao.participacao_set.all()
id_part = [p.parlamentar.id for p in participantes]
@ -147,22 +163,25 @@ class ParticipacaoCreateForm(forms.ModelForm):
qs = self.create_participacao()
parlamentares = Mandato.objects.filter(qs,
parlamentar__ativo=True
).prefetch_related('parlamentar').\
values_list('parlamentar',
flat=True
).distinct()
qs = Parlamentar.objects.filter(id__in=parlamentares).distinct().\
exclude(id__in=id_part)
parlamentares = (
Mandato.objects.filter(qs, parlamentar__ativo=True)
.prefetch_related("parlamentar")
.values_list("parlamentar", flat=True)
.distinct()
)
qs = (
Parlamentar.objects.filter(id__in=parlamentares)
.distinct()
.exclude(id__in=id_part)
)
eligible = self.verifica()
result = list(set(qs) & set(eligible))
if result == eligible:
self.fields['parlamentar'].queryset = qs
self.fields["parlamentar"].queryset = qs
else:
ids = [e.id for e in eligible]
qs = Parlamentar.objects.filter(id__in=ids)
self.fields['parlamentar'].queryset = qs
self.fields["parlamentar"].queryset = qs
def clean(self):
cleaned_data = super(ParticipacaoCreateForm, self).clean()
@ -170,51 +189,62 @@ class ParticipacaoCreateForm(forms.ModelForm):
if not self.is_valid():
return cleaned_data
data_designacao = cleaned_data['data_designacao']
data_desligamento = cleaned_data['data_desligamento']
data_designacao = cleaned_data["data_designacao"]
data_desligamento = cleaned_data["data_desligamento"]
if data_desligamento and \
data_designacao > data_desligamento:
if data_desligamento and data_designacao > data_desligamento:
self.logger.warning(
'Data de designação ({}) superior '
'à data de desligamento ({})'.format(data_designacao, data_desligamento)
"Data de designação ({}) superior "
"à data de desligamento ({})".format(data_designacao, data_desligamento)
)
raise ValidationError(
_("Data de designação não pode ser superior " "à data de desligamento")
)
raise ValidationError(_('Data de designação não pode ser superior '
'à data de desligamento'))
composicao = Composicao.objects.get(id=self.initial['parent_pk'])
composicao = Composicao.objects.get(id=self.initial["parent_pk"])
cargos_unicos = [
c.cargo.nome for c in composicao.participacao_set.filter(cargo__unico=True)]
c.cargo.nome for c in composicao.participacao_set.filter(cargo__unico=True)
]
if cleaned_data['cargo'].nome in cargos_unicos:
msg = _('Este cargo é único para esta Comissão.')
if cleaned_data["cargo"].nome in cargos_unicos:
msg = _("Este cargo é único para esta Comissão.")
self.logger.warning(
'Este cargo ({}) é único para esta Comissão.'.format(
cleaned_data['cargo'].nome
"Este cargo ({}) é único para esta Comissão.".format(
cleaned_data["cargo"].nome
)
)
raise ValidationError(msg)
return cleaned_data
def create_participacao(self):
composicao = Composicao.objects.get(id=self.initial['parent_pk'])
composicao = Composicao.objects.get(id=self.initial["parent_pk"])
data_inicio_comissao = composicao.periodo.data_inicio
data_fim_comissao = composicao.periodo.data_fim if composicao.periodo.data_fim else timezone.now()
q1 = Q(data_fim_mandato__isnull=False,
data_fim_mandato__gte=data_inicio_comissao)
q2 = Q(data_inicio_mandato__gte=data_inicio_comissao) \
& Q(data_inicio_mandato__lte=data_fim_comissao)
q3 = Q(data_fim_mandato__isnull=True,
data_inicio_mandato__lte=data_inicio_comissao)
data_fim_comissao = (
composicao.periodo.data_fim
if composicao.periodo.data_fim
else timezone.now()
)
q1 = Q(
data_fim_mandato__isnull=False, data_fim_mandato__gte=data_inicio_comissao
)
q2 = Q(data_inicio_mandato__gte=data_inicio_comissao) & Q(
data_inicio_mandato__lte=data_fim_comissao
)
q3 = Q(
data_fim_mandato__isnull=True, data_inicio_mandato__lte=data_inicio_comissao
)
qs = q1 | q2 | q3
return qs
def verifica(self):
composicao = Composicao.objects.get(id=self.initial['parent_pk'])
composicao = Composicao.objects.get(id=self.initial["parent_pk"])
participantes = composicao.participacao_set.all()
participantes_id = [p.parlamentar.id for p in participantes]
parlamentares = Parlamentar.objects.all().exclude(
id__in=participantes_id).order_by('nome_completo')
parlamentares = (
Parlamentar.objects.all()
.exclude(id__in=participantes_id)
.order_by("nome_completo")
)
parlamentares = [p for p in parlamentares if p.ativo]
lista = []
@ -226,9 +256,13 @@ class ParticipacaoCreateForm(forms.ModelForm):
data_fim = m.data_fim_mandato
comp_data_inicio = composicao.periodo.data_inicio
comp_data_fim = composicao.periodo.data_fim
if (data_fim and data_fim >= comp_data_inicio)\
or (data_inicio >= comp_data_inicio and data_inicio <= comp_data_fim)\
or (data_fim is None and data_inicio <= comp_data_inicio):
if (
(data_fim and data_fim >= comp_data_inicio)
or (
data_inicio >= comp_data_inicio and data_inicio <= comp_data_fim
)
or (data_fim is None and data_inicio <= comp_data_inicio)
):
lista.append(p)
lista = list(set(lista))
@ -237,25 +271,32 @@ class ParticipacaoCreateForm(forms.ModelForm):
class ParticipacaoEditForm(forms.ModelForm):
logger = logging.getLogger(__name__)
parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput())
nome_parlamentar = forms.CharField(required=False, label='Parlamentar')
nome_parlamentar = forms.CharField(required=False, label="Parlamentar")
class Meta:
model = Participacao
fields = ['nome_parlamentar', 'parlamentar', 'cargo', 'titular',
'data_designacao', 'data_desligamento',
'motivo_desligamento', 'observacao']
fields = [
"nome_parlamentar",
"parlamentar",
"cargo",
"titular",
"data_designacao",
"data_desligamento",
"motivo_desligamento",
"observacao",
]
widgets = {
'parlamentar': forms.HiddenInput(),
"parlamentar": forms.HiddenInput(),
}
def __init__(self, user=None, **kwargs):
super(ParticipacaoEditForm, self).__init__(**kwargs)
self.initial['nome_parlamentar'] = Parlamentar.objects.get(
id=self.initial['parlamentar']).nome_parlamentar
self.fields['nome_parlamentar'].widget.attrs['disabled'] = 'disabled'
self.initial["nome_parlamentar"] = Parlamentar.objects.get(
id=self.initial["parlamentar"]
).nome_parlamentar
self.fields["nome_parlamentar"].widget.attrs["disabled"] = "disabled"
def clean(self):
cleaned_data = super(ParticipacaoEditForm, self).clean()
@ -263,29 +304,33 @@ class ParticipacaoEditForm(forms.ModelForm):
if not self.is_valid():
return cleaned_data
data_designacao = cleaned_data['data_designacao']
data_desligamento = cleaned_data['data_desligamento']
data_designacao = cleaned_data["data_designacao"]
data_desligamento = cleaned_data["data_desligamento"]
if data_desligamento and \
data_designacao > data_desligamento:
if data_desligamento and data_designacao > data_desligamento:
self.logger.warning(
'Data de designação ({}) superior '
'à data de desligamento ({})'.format(data_designacao, data_desligamento)
"Data de designação ({}) superior "
"à data de desligamento ({})".format(data_designacao, data_desligamento)
)
raise ValidationError(
_("Data de designação não pode ser superior " "à data de desligamento")
)
raise ValidationError(_('Data de designação não pode ser superior '
'à data de desligamento'))
composicao_id = self.instance.composicao_id
composicao = Composicao.objects.get(id=composicao_id)
cargos_unicos = [c.cargo.nome for c in
composicao.participacao_set.filter(cargo__unico=True).exclude(id=self.instance.pk)]
cargos_unicos = [
c.cargo.nome
for c in composicao.participacao_set.filter(cargo__unico=True).exclude(
id=self.instance.pk
)
]
if cleaned_data['cargo'].nome in cargos_unicos:
msg = _('Este cargo é único para esta Comissão.')
if cleaned_data["cargo"].nome in cargos_unicos:
msg = _("Este cargo é único para esta Comissão.")
self.logger.warning(
'Este cargo ({}) é único para esta Comissão (id={}).'.format(
cleaned_data['cargo'].nome, composicao_id
"Este cargo ({}) é único para esta Comissão (id={}).".format(
cleaned_data["cargo"].nome, composicao_id
)
)
raise ValidationError(msg)
@ -294,23 +339,28 @@ class ParticipacaoEditForm(forms.ModelForm):
class ComissaoForm(forms.ModelForm):
logger = logging.getLogger(__name__)
class Meta:
model = Comissao
fields = '__all__'
fields = "__all__"
def __init__(self, user=None, **kwargs):
super(ComissaoForm, self).__init__(**kwargs)
inst = self.instance
if inst.pk:
if inst.tipo.natureza == 'P':
self.fields['apelido_temp'].widget.attrs['disabled'] = 'disabled'
self.fields['data_instalacao_temp'].widget.attrs['disabled'] = 'disabled'
self.fields['data_final_prevista_temp'].widget.attrs['disabled'] = 'disabled'
self.fields['data_prorrogada_temp'].widget.attrs['disabled'] = 'disabled'
self.fields['data_fim_comissao'].widget.attrs['disabled'] = 'disabled'
if inst.tipo.natureza == "P":
self.fields["apelido_temp"].widget.attrs["disabled"] = "disabled"
self.fields["data_instalacao_temp"].widget.attrs[
"disabled"
] = "disabled"
self.fields["data_final_prevista_temp"].widget.attrs[
"disabled"
] = "disabled"
self.fields["data_prorrogada_temp"].widget.attrs[
"disabled"
] = "disabled"
self.fields["data_fim_comissao"].widget.attrs["disabled"] = "disabled"
def clean(self):
super(ComissaoForm, self).clean()
@ -318,71 +368,92 @@ class ComissaoForm(forms.ModelForm):
if not self.is_valid():
return self.cleaned_data
if len(self.cleaned_data['nome']) > 100:
msg = _('Nome da Comissão informado ({}) tem mais de 50 caracteres.'.format(
self.cleaned_data['nome']))
self.logger.warning(
'Nome da Comissão deve ter no máximo 50 caracteres.'
if len(self.cleaned_data["nome"]) > 100:
msg = _(
"Nome da Comissão informado ({}) tem mais de 50 caracteres.".format(
self.cleaned_data["nome"]
)
)
self.logger.warning("Nome da Comissão deve ter no máximo 50 caracteres.")
raise ValidationError(msg)
if (self.cleaned_data['data_extincao'] and
self.cleaned_data['data_extincao'] <
self.cleaned_data['data_criacao']):
msg = _('Data de extinção não pode ser menor que a de criação')
if (
self.cleaned_data["data_extincao"]
and self.cleaned_data["data_extincao"] < self.cleaned_data["data_criacao"]
):
msg = _("Data de extinção não pode ser menor que a de criação")
self.logger.warning(
'Data de extinção ({}) não pode ser menor que a de criação ({}).'.format(
self.cleaned_data['data_extincao'], self.cleaned_data['data_criacao']
"Data de extinção ({}) não pode ser menor que a de criação ({}).".format(
self.cleaned_data["data_extincao"],
self.cleaned_data["data_criacao"],
)
)
raise ValidationError(msg)
if (self.cleaned_data['data_final_prevista_temp'] and
self.cleaned_data['data_final_prevista_temp'] <
self.cleaned_data['data_criacao']):
msg = _('Data Prevista para Término não pode ser menor que a de criação')
if (
self.cleaned_data["data_final_prevista_temp"]
and self.cleaned_data["data_final_prevista_temp"]
< self.cleaned_data["data_criacao"]
):
msg = _("Data Prevista para Término não pode ser menor que a de criação")
self.logger.warning(
'Data Prevista para Término ({}) não pode ser menor que a de criação ({}).'.format(
self.cleaned_data['data_final_prevista_temp'], self.cleaned_data['data_criacao']
"Data Prevista para Término ({}) não pode ser menor que a de criação ({}).".format(
self.cleaned_data["data_final_prevista_temp"],
self.cleaned_data["data_criacao"],
)
)
raise ValidationError(msg)
if (self.cleaned_data['data_prorrogada_temp'] and
self.cleaned_data['data_prorrogada_temp'] <
self.cleaned_data['data_criacao']):
msg = _('Data Novo Prazo não pode ser menor que a de criação')
if (
self.cleaned_data["data_prorrogada_temp"]
and self.cleaned_data["data_prorrogada_temp"]
< self.cleaned_data["data_criacao"]
):
msg = _("Data Novo Prazo não pode ser menor que a de criação")
self.logger.warning(
'Data Novo Prazo ({}) não pode ser menor que a de criação ({}).'.format(
self.cleaned_data['data_prorrogada_temp'], self.cleaned_data['data_criacao']
"Data Novo Prazo ({}) não pode ser menor que a de criação ({}).".format(
self.cleaned_data["data_prorrogada_temp"],
self.cleaned_data["data_criacao"],
)
)
raise ValidationError(msg)
if (self.cleaned_data['data_instalacao_temp'] and
self.cleaned_data['data_instalacao_temp'] <
self.cleaned_data['data_criacao']):
msg = _('Data de Instalação não pode ser menor que a de criação')
if (
self.cleaned_data["data_instalacao_temp"]
and self.cleaned_data["data_instalacao_temp"]
< self.cleaned_data["data_criacao"]
):
msg = _("Data de Instalação não pode ser menor que a de criação")
self.logger.warning(
'Data de Instalação ({}) não pode ser menor que a de criação ({}).'.format(
self.cleaned_data['data_instalacao_temp'], self.cleaned_data['data_criacao']
"Data de Instalação ({}) não pode ser menor que a de criação ({}).".format(
self.cleaned_data["data_instalacao_temp"],
self.cleaned_data["data_criacao"],
)
)
raise ValidationError(msg)
if (self.cleaned_data['data_final_prevista_temp'] and self.cleaned_data['data_instalacao_temp'] and
self.cleaned_data['data_final_prevista_temp'] <
self.cleaned_data['data_instalacao_temp']):
if (
self.cleaned_data["data_final_prevista_temp"]
and self.cleaned_data["data_instalacao_temp"]
and self.cleaned_data["data_final_prevista_temp"]
< self.cleaned_data["data_instalacao_temp"]
):
msg = _(
'Data Prevista para Término não pode ser menor que a de Instalação.')
"Data Prevista para Término não pode ser menor que a de Instalação."
)
self.logger.warning(
'Data Prevista para Término ({}) não pode ser menor que a de Instalação ({}).'.format(
self.cleaned_data['data_final_prevista_temp'], self.cleaned_data['data_instalacao_temp']
"Data Prevista para Término ({}) não pode ser menor que a de Instalação ({}).".format(
self.cleaned_data["data_final_prevista_temp"],
self.cleaned_data["data_instalacao_temp"],
)
)
raise ValidationError(msg)
if (self.cleaned_data['data_prorrogada_temp'] and self.cleaned_data['data_instalacao_temp'] and
self.cleaned_data['data_prorrogada_temp'] <
self.cleaned_data['data_instalacao_temp']):
msg = _('Data Novo Prazo não pode ser menor que a de Instalação.')
if (
self.cleaned_data["data_prorrogada_temp"]
and self.cleaned_data["data_instalacao_temp"]
and self.cleaned_data["data_prorrogada_temp"]
< self.cleaned_data["data_instalacao_temp"]
):
msg = _("Data Novo Prazo não pode ser menor que a de Instalação.")
self.logger.warning(
'Data Novo Prazo ({}) não pode ser menor que a de Instalação ({}).'.format(
self.cleaned_data['data_prorrogada_temp'], self.cleaned_data['data_instalacao_temp']
"Data Novo Prazo ({}) não pode ser menor que a de Instalação ({}).".format(
self.cleaned_data["data_prorrogada_temp"],
self.cleaned_data["data_instalacao_temp"],
)
)
raise ValidationError(msg)
@ -396,12 +467,9 @@ class ComissaoForm(forms.ModelForm):
content_type = ContentType.objects.get_for_model(Comissao)
object_id = comissao.pk
tipo = TipoAutor.objects.get(content_type=content_type)
nome = comissao.sigla + ' - ' + comissao.nome
nome = comissao.sigla + " - " + comissao.nome
Autor.objects.create(
content_type=content_type,
object_id=object_id,
tipo=tipo,
nome=nome
content_type=content_type, object_id=object_id, tipo=tipo, nome=nome
)
return comissao
else:
@ -410,14 +478,14 @@ class ComissaoForm(forms.ModelForm):
class ReuniaoForm(ModelForm):
logger = logging.getLogger(__name__)
comissao = forms.ModelChoiceField(queryset=Comissao.objects.all(),
widget=forms.HiddenInput())
comissao = forms.ModelChoiceField(
queryset=Comissao.objects.all(), widget=forms.HiddenInput()
)
class Meta:
model = Reuniao
exclude = ['cod_andamento_reuniao']
exclude = ["cod_andamento_reuniao"]
def clean(self):
super(ReuniaoForm, self).clean()
@ -425,21 +493,21 @@ class ReuniaoForm(ModelForm):
if not self.is_valid():
return self.cleaned_data
if self.cleaned_data['hora_fim']:
if (self.cleaned_data['hora_fim'] <
self.cleaned_data['hora_inicio']):
if self.cleaned_data["hora_fim"]:
if self.cleaned_data["hora_fim"] < self.cleaned_data["hora_inicio"]:
msg = _(
'A hora de término da reunião não pode ser menor que a de início')
"A hora de término da reunião não pode ser menor que a de início"
)
self.logger.warning(
"A hora de término da reunião ({}) não pode ser menor que a de início ({}).".format(
self.cleaned_data['hora_fim'], self.cleaned_data['hora_inicio']
self.cleaned_data["hora_fim"], self.cleaned_data["hora_inicio"]
)
)
raise ValidationError(msg)
upload_pauta = self.cleaned_data.get('upload_pauta', False)
upload_ata = self.cleaned_data.get('upload_ata', False)
upload_anexo = self.cleaned_data.get('upload_anexo', False)
upload_pauta = self.cleaned_data.get("upload_pauta", False)
upload_ata = self.cleaned_data.get("upload_ata", False)
upload_anexo = self.cleaned_data.get("upload_anexo", False)
if upload_pauta:
validar_arquivo(upload_pauta, "Pauta da Reunião")
@ -454,59 +522,62 @@ class ReuniaoForm(ModelForm):
class PautaReuniaoFilterSet(django_filters.FilterSet):
class Meta(FilterOverridesMetaMixin):
model = MateriaEmTramitacao
fields = ['materia__tipo', 'materia__ano', 'materia__numero', 'materia__data_apresentacao']
fields = [
"materia__tipo",
"materia__ano",
"materia__numero",
"materia__data_apresentacao",
]
def __init__(self, *args, **kwargs):
super(PautaReuniaoFilterSet, self).__init__(*args, **kwargs)
self.filters['materia__tipo'].label = "Tipo da Matéria"
self.filters['materia__ano'].label = "Ano da Matéria"
self.filters['materia__numero'].label = "Número da Matéria"
self.filters['materia__data_apresentacao'].label = "Data (Inicial - Final)"
self.filters["materia__tipo"].label = "Tipo da Matéria"
self.filters["materia__ano"].label = "Ano da Matéria"
self.filters["materia__numero"].label = "Número da Matéria"
self.filters["materia__data_apresentacao"].label = "Data (Inicial - Final)"
row1 = to_row([('materia__tipo', 4), ('materia__ano', 4), ('materia__numero', 4)])
row2 = to_row([('materia__data_apresentacao', 12)])
row1 = to_row(
[("materia__tipo", 4), ("materia__ano", 4), ("materia__numero", 4)]
)
row2 = to_row([("materia__data_apresentacao", 12)])
self.form.helper = SaplFormHelper()
self.form.helper.form_method = "GET"
self.form.helper.layout = Layout(
Fieldset(
_("Pesquisa de Matérias"), row1, row2,
form_actions(label="Pesquisar")
_("Pesquisa de Matérias"), row1, row2, form_actions(label="Pesquisar")
)
)
class PautaReuniaoForm(forms.ModelForm):
class Meta:
model = PautaReuniao
exclude = ['reuniao']
exclude = ["reuniao"]
class DocumentoAcessorioCreateForm(FileFieldCheckMixin, forms.ModelForm):
parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput())
class Meta:
model = DocumentoAcessorio
exclude = ['reuniao']
exclude = ["reuniao"]
def __init__(self, user=None, **kwargs):
super(DocumentoAcessorioCreateForm, self).__init__(**kwargs)
if self.instance:
reuniao = Reuniao.objects.get(id=self.initial['parent_pk'])
reuniao = Reuniao.objects.get(id=self.initial["parent_pk"])
comissao = reuniao.comissao
comissao_pk = comissao.id
documentos = reuniao.documentoacessorio_set.all()
return self.create_documentoacessorio()
def create_documentoacessorio(self):
reuniao = Reuniao.objects.get(id=self.initial['parent_pk'])
reuniao = Reuniao.objects.get(id=self.initial["parent_pk"])
def clean(self):
super(DocumentoAcessorioCreateForm, self).clean()
@ -514,7 +585,7 @@ class DocumentoAcessorioCreateForm(FileFieldCheckMixin, forms.ModelForm):
if not self.is_valid():
return self.cleaned_data
arquivo = self.cleaned_data.get('arquivo')
arquivo = self.cleaned_data.get("arquivo")
if arquivo:
validar_arquivo(arquivo, "Texto Integral")
@ -529,13 +600,11 @@ class DocumentoAcessorioCreateForm(FileFieldCheckMixin, forms.ModelForm):
class DocumentoAcessorioEditForm(FileFieldCheckMixin, forms.ModelForm):
parent_pk = forms.CharField(required=False) # widget=forms.HiddenInput())
class Meta:
model = DocumentoAcessorio
fields = ['nome', 'data', 'autor', 'ementa',
'indexacao', 'arquivo']
fields = ["nome", "data", "autor", "ementa", "indexacao", "arquivo"]
def __init__(self, user=None, **kwargs):
super(DocumentoAcessorioEditForm, self).__init__(**kwargs)
@ -546,7 +615,7 @@ class DocumentoAcessorioEditForm(FileFieldCheckMixin, forms.ModelForm):
if not self.is_valid():
return self.cleaned_data
arquivo = self.cleaned_data.get('arquivo')
arquivo = self.cleaned_data.get("arquivo")
if arquivo:
validar_arquivo(arquivo, "Texto Integral")

389
sapl/comissoes/models.py

@ -4,201 +4,212 @@ from model_utils import Choices
from sapl.base.models import Autor
from sapl.parlamentares.models import Parlamentar
from sapl.utils import (YES_NO_CHOICES, SaplGenericRelation,
restringe_tipos_de_arquivo_txt, texto_upload_path,
OverwriteStorage)
from sapl.utils import (
YES_NO_CHOICES,
OverwriteStorage,
SaplGenericRelation,
restringe_tipos_de_arquivo_txt,
texto_upload_path,
)
class TipoComissao(models.Model):
NATUREZA_CHOICES = Choices(('T', 'temporaria', _('Temporária')),
('P', 'permanente', _('Permanente')))
nome = models.CharField(max_length=50, verbose_name=_('Nome'))
NATUREZA_CHOICES = Choices(
("T", "temporaria", _("Temporária")), ("P", "permanente", _("Permanente"))
)
nome = models.CharField(max_length=50, verbose_name=_("Nome"))
natureza = models.CharField(
max_length=1, verbose_name=_('Natureza'), choices=NATUREZA_CHOICES)
sigla = models.CharField(max_length=10, verbose_name=_('Sigla'))
max_length=1, verbose_name=_("Natureza"), choices=NATUREZA_CHOICES
)
sigla = models.CharField(max_length=10, verbose_name=_("Sigla"))
dispositivo_regimental = models.CharField(
max_length=50,
blank=True,
verbose_name=_('Dispositivo Regimental'))
max_length=50, blank=True, verbose_name=_("Dispositivo Regimental")
)
class Meta:
verbose_name = _('Tipo de Comissão')
verbose_name_plural = _('Tipos de Comissão')
ordering = ('id',)
verbose_name = _("Tipo de Comissão")
verbose_name_plural = _("Tipos de Comissão")
ordering = ("id",)
def __str__(self):
return self.nome
class Comissao(models.Model):
tipo = models.ForeignKey(TipoComissao,
on_delete=models.PROTECT,
verbose_name=_('Tipo'))
nome = models.CharField(max_length=100, verbose_name=_('Nome'))
sigla = models.CharField(max_length=10, verbose_name=_('Sigla'))
data_criacao = models.DateField(verbose_name=_('Data de Criação'))
tipo = models.ForeignKey(
TipoComissao, on_delete=models.PROTECT, verbose_name=_("Tipo")
)
nome = models.CharField(max_length=100, verbose_name=_("Nome"))
sigla = models.CharField(max_length=10, verbose_name=_("Sigla"))
data_criacao = models.DateField(verbose_name=_("Data de Criação"))
data_extincao = models.DateField(
blank=True, null=True, verbose_name=_('Data de Extinção'))
blank=True, null=True, verbose_name=_("Data de Extinção")
)
apelido_temp = models.CharField(
max_length=100, blank=True, verbose_name=_('Apelido'))
max_length=100, blank=True, verbose_name=_("Apelido")
)
data_instalacao_temp = models.DateField(
blank=True, null=True, verbose_name=_('Data Instalação'))
blank=True, null=True, verbose_name=_("Data Instalação")
)
data_final_prevista_temp = models.DateField(
blank=True, null=True, verbose_name=_('Data Prevista Término'))
blank=True, null=True, verbose_name=_("Data Prevista Término")
)
data_prorrogada_temp = models.DateField(
blank=True, null=True, verbose_name=_('Novo Prazo'))
blank=True, null=True, verbose_name=_("Novo Prazo")
)
data_fim_comissao = models.DateField(
blank=True, null=True, verbose_name=_('Data Término'))
blank=True, null=True, verbose_name=_("Data Término")
)
secretario = models.CharField(
max_length=30, blank=True, verbose_name=_('Secretário'))
max_length=30, blank=True, verbose_name=_("Secretário")
)
telefone_reuniao = models.CharField(
max_length=15, blank=True,
verbose_name=_('Tel. Sala Reunião'))
max_length=15, blank=True, verbose_name=_("Tel. Sala Reunião")
)
endereco_secretaria = models.CharField(
max_length=100, blank=True,
verbose_name=_('Endereço Secretaria'))
max_length=100, blank=True, verbose_name=_("Endereço Secretaria")
)
telefone_secretaria = models.CharField(
max_length=15, blank=True,
verbose_name=_('Tel. Secretaria'))
max_length=15, blank=True, verbose_name=_("Tel. Secretaria")
)
fax_secretaria = models.CharField(
max_length=15, blank=True, verbose_name=_('Fax Secretaria'))
max_length=15, blank=True, verbose_name=_("Fax Secretaria")
)
agenda_reuniao = models.CharField(
max_length=100, blank=True,
verbose_name=_('Data/Hora Reunião'))
max_length=100, blank=True, verbose_name=_("Data/Hora Reunião")
)
local_reuniao = models.CharField(
max_length=100, blank=True, verbose_name=_('Local Reunião'))
finalidade = models.TextField(
blank=True, verbose_name=_('Finalidade'))
email = models.EmailField(max_length=100,
blank=True,
verbose_name=_('E-mail'))
max_length=100, blank=True, verbose_name=_("Local Reunião")
)
finalidade = models.TextField(blank=True, verbose_name=_("Finalidade"))
email = models.EmailField(max_length=100, blank=True, verbose_name=_("E-mail"))
unidade_deliberativa = models.BooleanField(
choices=YES_NO_CHOICES,
verbose_name=_('Unidade Deliberativa'),
default=False)
choices=YES_NO_CHOICES, verbose_name=_("Unidade Deliberativa"), default=False
)
ativa = models.BooleanField(
default=False,
choices=YES_NO_CHOICES,
verbose_name=_('Comissão Ativa?'))
autor = SaplGenericRelation(Autor,
related_query_name='comissao_set',
fields_search=(
('nome', '__icontains'),
('sigla', '__icontains')
))
default=False, choices=YES_NO_CHOICES, verbose_name=_("Comissão Ativa?")
)
autor = SaplGenericRelation(
Autor,
related_query_name="comissao_set",
fields_search=(("nome", "__icontains"), ("sigla", "__icontains")),
)
class Meta:
verbose_name = _('Comissão')
verbose_name_plural = _('Comissões')
ordering = ['nome']
verbose_name = _("Comissão")
verbose_name_plural = _("Comissões")
ordering = ["nome"]
def __str__(self):
return self.sigla + ' - ' + self.nome
return self.sigla + " - " + self.nome
class Periodo(models.Model): # PeriodoCompComissao
data_inicio = models.DateField(verbose_name=_('Data Início'))
data_fim = models.DateField(
blank=True, null=True, verbose_name=_('Data Fim'))
data_inicio = models.DateField(verbose_name=_("Data Início"))
data_fim = models.DateField(blank=True, null=True, verbose_name=_("Data Fim"))
class Meta:
verbose_name = _('Período de composição de Comissão')
verbose_name_plural = _('Períodos de composição de Comissão')
ordering = ['-data_inicio', '-data_fim']
verbose_name = _("Período de composição de Comissão")
verbose_name_plural = _("Períodos de composição de Comissão")
ordering = ["-data_inicio", "-data_fim"]
def __str__(self):
if self.data_inicio and self.data_fim:
return '%s - %s' % (self.data_inicio.strftime("%d/%m/%Y"),
self.data_fim.strftime("%d/%m/%Y"))
return "%s - %s" % (
self.data_inicio.strftime("%d/%m/%Y"),
self.data_fim.strftime("%d/%m/%Y"),
)
elif self.data_inicio and not self.data_fim:
return '%s - ' % self.data_inicio.strftime("%d/%m/%Y")
return "%s - " % self.data_inicio.strftime("%d/%m/%Y")
else:
return '-'
return "-"
class CargoComissao(models.Model):
id_ordenacao = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Posição na Ordenação'),
blank=True,
null=True,
verbose_name=_("Posição na Ordenação"),
)
nome = models.CharField(max_length=50, verbose_name=_('Nome do Cargo'))
nome = models.CharField(max_length=50, verbose_name=_("Nome do Cargo"))
unico = models.BooleanField(
choices=YES_NO_CHOICES, verbose_name=_('Cargo Único'), default=True
choices=YES_NO_CHOICES, verbose_name=_("Cargo Único"), default=True
)
class Meta:
verbose_name = _('Cargo de Comissão')
verbose_name_plural = _('Cargos de Comissão')
ordering = ['id_ordenacao']
verbose_name = _("Cargo de Comissão")
verbose_name_plural = _("Cargos de Comissão")
ordering = ["id_ordenacao"]
def __str__(self):
return self.nome
class Composicao(models.Model): # IGNORE
comissao = models.ForeignKey(Comissao,
on_delete=models.CASCADE,
verbose_name=_('Comissão'))
periodo = models.ForeignKey(Periodo,
on_delete=models.PROTECT,
verbose_name=_('Período'))
comissao = models.ForeignKey(
Comissao, on_delete=models.CASCADE, verbose_name=_("Comissão")
)
periodo = models.ForeignKey(
Periodo, on_delete=models.PROTECT, verbose_name=_("Período")
)
class Meta:
verbose_name = _('Composição de Comissão')
verbose_name_plural = _('Composições de Comissão')
ordering = ['periodo']
verbose_name = _("Composição de Comissão")
verbose_name_plural = _("Composições de Comissão")
ordering = ["periodo"]
def __str__(self):
return '%s: %s' % (self.comissao.sigla, self.periodo)
return "%s: %s" % (self.comissao.sigla, self.periodo)
class Participacao(models.Model): # ComposicaoComissao
composicao = models.ForeignKey(Composicao,
related_name='participacao_set',
composicao = models.ForeignKey(
Composicao,
related_name="participacao_set",
on_delete=models.CASCADE,
verbose_name=_('Composição'))
parlamentar = models.ForeignKey(Parlamentar,
on_delete=models.PROTECT,
verbose_name='Parlamentar')
cargo = models.ForeignKey(CargoComissao,
on_delete=models.PROTECT,
verbose_name='Cargo')
verbose_name=_("Composição"),
)
parlamentar = models.ForeignKey(
Parlamentar, on_delete=models.PROTECT, verbose_name="Parlamentar"
)
cargo = models.ForeignKey(
CargoComissao, on_delete=models.PROTECT, verbose_name="Cargo"
)
titular = models.BooleanField(
verbose_name=_('Titular'),
default=False,
choices=YES_NO_CHOICES)
data_designacao = models.DateField(verbose_name=_('Data Designação'))
data_desligamento = models.DateField(blank=True,
null=True,
verbose_name=_('Data Desligamento'))
verbose_name=_("Titular"), default=False, choices=YES_NO_CHOICES
)
data_designacao = models.DateField(verbose_name=_("Data Designação"))
data_desligamento = models.DateField(
blank=True, null=True, verbose_name=_("Data Desligamento")
)
motivo_desligamento = models.TextField(
blank=True, verbose_name=_('Motivo Desligamento'))
observacao = models.TextField(
blank=True, verbose_name=_('Observação'))
blank=True, verbose_name=_("Motivo Desligamento")
)
observacao = models.TextField(blank=True, verbose_name=_("Observação"))
class Meta:
verbose_name = _('Participação em Comissão')
verbose_name_plural = _('Participações em Comissão')
ordering = ['-titular', 'cargo__id_ordenacao']
verbose_name = _("Participação em Comissão")
verbose_name_plural = _("Participações em Comissão")
ordering = ["-titular", "cargo__id_ordenacao"]
def __str__(self):
return '%s : %s' % (self.cargo, self.parlamentar)
return "%s : %s" % (self.cargo, self.parlamentar)
def get_comissao_media_path(instance, subpath, filename):
return './sapl/comissao/%s/%s/%s' % (instance.numero, subpath, filename)
return "./sapl/comissao/%s/%s/%s" % (instance.numero, subpath, filename)
def pauta_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath='pauta', pk_first=True)
return texto_upload_path(instance, filename, subpath="pauta", pk_first=True)
def ata_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath='ata', pk_first=True)
return texto_upload_path(instance, filename, subpath="ata", pk_first=True)
def anexo_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath='anexo', pk_first=True)
return texto_upload_path(instance, filename, subpath="anexo", pk_first=True)
class Reuniao(models.Model):
@ -206,59 +217,68 @@ class Reuniao(models.Model):
Periodo,
null=True,
on_delete=models.PROTECT,
verbose_name=_('Periodo da Composicão da Comissão'))
verbose_name=_("Periodo da Composicão da Comissão"),
)
comissao = models.ForeignKey(
Comissao,
on_delete=models.CASCADE,
verbose_name=_('Comissão'))
numero = models.PositiveIntegerField(verbose_name=_('Número'))
nome = models.CharField(
max_length=150, verbose_name=_('Nome da Reunião'))
Comissao, on_delete=models.CASCADE, verbose_name=_("Comissão")
)
numero = models.PositiveIntegerField(verbose_name=_("Número"))
nome = models.CharField(max_length=150, verbose_name=_("Nome da Reunião"))
tema = models.CharField(
max_length=150, blank=True, verbose_name=_('Tema da Reunião'))
data = models.DateField(verbose_name=_('Data'))
max_length=150, blank=True, verbose_name=_("Tema da Reunião")
)
data = models.DateField(verbose_name=_("Data"))
hora_inicio = models.TimeField(
null=True,
verbose_name=_('Horário de Início (hh:mm)'))
null=True, verbose_name=_("Horário de Início (hh:mm)")
)
hora_fim = models.TimeField(
blank=True,
null=True,
verbose_name=_('Horário de Término (hh:mm)'))
blank=True, null=True, verbose_name=_("Horário de Término (hh:mm)")
)
local_reuniao = models.CharField(
max_length=100, blank=True, verbose_name=_('Local da Reunião'))
observacao = models.TextField(
blank=True, verbose_name=_('Observação'))
max_length=100, blank=True, verbose_name=_("Local da Reunião")
)
observacao = models.TextField(blank=True, verbose_name=_("Observação"))
url_audio = models.URLField(
max_length=150, blank=True,
verbose_name=_('URL do Arquivo de Áudio (Formatos MP3 / AAC)'))
max_length=150,
blank=True,
verbose_name=_("URL do Arquivo de Áudio (Formatos MP3 / AAC)"),
)
url_video = models.URLField(
max_length=150, blank=True,
verbose_name=_('URL do Arquivo de Vídeo (Formatos MP4 / FLV / WebM)'))
max_length=150,
blank=True,
verbose_name=_("URL do Arquivo de Vídeo (Formatos MP4 / FLV / WebM)"),
)
upload_pauta = models.FileField(
max_length=300,
blank=True, null=True,
blank=True,
null=True,
upload_to=pauta_upload_path,
verbose_name=_('Pauta da Reunião'),
verbose_name=_("Pauta da Reunião"),
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
validators=[restringe_tipos_de_arquivo_txt],
)
upload_ata = models.FileField(
max_length=300,
blank=True, null=True,
blank=True,
null=True,
upload_to=ata_upload_path,
verbose_name=_('Ata da Reunião'),
verbose_name=_("Ata da Reunião"),
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
validators=[restringe_tipos_de_arquivo_txt],
)
upload_anexo = models.FileField(
max_length=300,
blank=True, null=True,
blank=True,
null=True,
upload_to=anexo_upload_path,
storage=OverwriteStorage(),
verbose_name=_('Anexo da Reunião'))
verbose_name=_("Anexo da Reunião"),
)
class Meta:
verbose_name = _('Reunião de Comissão')
verbose_name_plural = _('Reuniões de Comissão')
ordering = ('-data', '-nome')
verbose_name = _("Reunião de Comissão")
verbose_name_plural = _("Reuniões de Comissão")
ordering = ("-data", "-nome")
def __str__(self):
return self.nome
@ -281,67 +301,68 @@ class Reuniao(models.Model):
return result
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
if not self.pk and (self.upload_pauta or self.upload_ata or
self.upload_anexo):
def save(
self, force_insert=False, force_update=False, using=None, update_fields=None
):
if not self.pk and (self.upload_pauta or self.upload_ata or self.upload_anexo):
upload_pauta = self.upload_pauta
upload_ata = self.upload_ata
upload_anexo = self.upload_anexo
self.upload_pauta = None
self.upload_ata = None
self.upload_anexo = None
models.Model.save(self, force_insert=force_insert,
models.Model.save(
self,
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
update_fields=update_fields,
)
self.upload_pauta = upload_pauta
self.upload_ata = upload_ata
self.upload_anexo = upload_anexo
return models.Model.save(self, force_insert=force_insert,
return models.Model.save(
self,
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
update_fields=update_fields,
)
class DocumentoAcessorio(models.Model):
reuniao = models.ForeignKey(Reuniao,
related_name='documentoacessorio_set',
on_delete=models.PROTECT)
nome = models.CharField(max_length=50, verbose_name=_('Nome'))
data = models.DateField(blank=True, null=True,
default=None, verbose_name=_('Data'))
autor = models.CharField(
max_length=200, verbose_name=_('Autor'))
ementa = models.TextField(blank=True, verbose_name=_('Ementa'))
reuniao = models.ForeignKey(
Reuniao, related_name="documentoacessorio_set", on_delete=models.PROTECT
)
nome = models.CharField(max_length=50, verbose_name=_("Nome"))
data = models.DateField(blank=True, null=True, default=None, verbose_name=_("Data"))
autor = models.CharField(max_length=200, verbose_name=_("Autor"))
ementa = models.TextField(blank=True, verbose_name=_("Ementa"))
indexacao = models.TextField(blank=True)
arquivo = models.FileField(
max_length=300,
blank=True,
null=True,
upload_to=anexo_upload_path,
verbose_name=_('Texto Integral'),
verbose_name=_("Texto Integral"),
storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt])
validators=[restringe_tipos_de_arquivo_txt],
)
data_ultima_atualizacao = models.DateTimeField(
blank=True, null=True,
auto_now=True,
verbose_name=_('Data'))
blank=True, null=True, auto_now=True, verbose_name=_("Data")
)
class Meta:
verbose_name = _('Documento Acessório')
verbose_name_plural = _('Documentos Acessórios')
ordering = ('data', 'id')
verbose_name = _("Documento Acessório")
verbose_name_plural = _("Documentos Acessórios")
ordering = ("data", "id")
def __str__(self):
return _('%(nome)s por %(autor)s') % {
'nome': self.nome,
'autor': self.autor}
return _("%(nome)s por %(autor)s") % {"nome": self.nome, "autor": self.autor}
def delete(self, using=None, keep_parents=False):
arquivo = self.arquivo
@ -352,19 +373,25 @@ class DocumentoAcessorio(models.Model):
return result
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
def save(
self, force_insert=False, force_update=False, using=None, update_fields=None
):
if not self.pk and self.arquivo:
arquivo = self.arquivo
self.arquivo = None
models.Model.save(self, force_insert=force_insert,
models.Model.save(
self,
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
update_fields=update_fields,
)
self.arquivo = arquivo
return models.Model.save(self, force_insert=force_insert,
return models.Model.save(
self,
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields)
update_fields=update_fields,
)

160
sapl/comissoes/tests/test_comissoes.py

@ -3,61 +3,54 @@ from django.urls import reverse
from django.utils.translation import ugettext as _
from model_bakery import baker
from sapl.comissoes.models import Comissao, Composicao, Periodo
from sapl.comissoes.models import TipoComissao, Reuniao
from sapl.parlamentares.models import Filiacao, Parlamentar, Partido
from sapl.comissoes import forms
from sapl.comissoes.models import Comissao, Composicao, Periodo, Reuniao, TipoComissao
from sapl.parlamentares.models import Filiacao, Parlamentar, Partido
def make_composicao(comissao):
periodo = baker.make(Periodo,
data_inicio='2016-01-01',
data_fim='2016-12-31')
baker.make(Composicao,
periodo=periodo,
comissao=comissao)
periodo = baker.make(Periodo, data_inicio="2016-01-01", data_fim="2016-12-31")
baker.make(Composicao, periodo=periodo, comissao=comissao)
return Composicao.objects.first()
def make_comissao():
tipo = baker.make(TipoComissao)
baker.make(Comissao,
baker.make(
Comissao,
tipo=tipo,
nome='Comissão Teste',
sigla='CT',
data_criacao='2016-03-22')
nome="Comissão Teste",
sigla="CT",
data_criacao="2016-03-22",
)
return Comissao.objects.first()
def make_filiacao():
partido = baker.make(Partido,
nome='Partido Meu',
sigla='PM')
parlamentar = baker.make(Parlamentar,
nome_parlamentar='Eduardo',
nome_completo='Eduardo',
sexo='M',
ativo=True)
baker.make(Filiacao,
data='2016-03-22',
parlamentar=parlamentar,
partido=partido)
partido = baker.make(Partido, nome="Partido Meu", sigla="PM")
parlamentar = baker.make(
Parlamentar,
nome_parlamentar="Eduardo",
nome_completo="Eduardo",
sexo="M",
ativo=True,
)
baker.make(Filiacao, data="2016-03-22", parlamentar=parlamentar, partido=partido)
return Filiacao.objects.first()
@pytest.mark.django_db(transaction=False)
def test_tipo_comissao_model():
baker.make(TipoComissao,
nome='Teste_Nome_Tipo_Comissao',
natureza='T',
sigla='TSTC')
baker.make(
TipoComissao, nome="Teste_Nome_Tipo_Comissao", natureza="T", sigla="TSTC"
)
tipo_comissao = TipoComissao.objects.first()
assert tipo_comissao.nome == 'Teste_Nome_Tipo_Comissao'
assert tipo_comissao.natureza == 'T'
assert tipo_comissao.sigla == 'TSTC'
assert tipo_comissao.nome == "Teste_Nome_Tipo_Comissao"
assert tipo_comissao.natureza == "T"
assert tipo_comissao.sigla == "TSTC"
@pytest.mark.django_db(transaction=False)
@ -65,67 +58,72 @@ def test_incluir_parlamentar_errors(admin_client):
comissao = make_comissao()
composicao = make_composicao(comissao)
response = admin_client.post(reverse('sapl.comissoes:participacao_create',
kwargs={'pk': composicao.pk}),
{'salvar': 'salvar'},
follow=True)
response = admin_client.post(
reverse("sapl.comissoes:participacao_create", kwargs={"pk": composicao.pk}),
{"salvar": "salvar"},
follow=True,
)
assert (response.context_data['form'].errors['parlamentar'] ==
['Este campo é obrigatório.'])
assert (response.context_data['form'].errors['cargo'] ==
['Este campo é obrigatório.'])
assert (response.context_data['form'].errors['data_designacao'] ==
['Este campo é obrigatório.'])
assert response.context_data["form"].errors["parlamentar"] == [
"Este campo é obrigatório."
]
assert response.context_data["form"].errors["cargo"] == [
"Este campo é obrigatório."
]
assert response.context_data["form"].errors["data_designacao"] == [
"Este campo é obrigatório."
]
@pytest.mark.django_db(transaction=False)
def test_incluir_comissao_submit(admin_client):
tipo = baker.make(TipoComissao,
sigla='T',
nome='Teste')
response = admin_client.post(reverse('sapl.comissoes:comissao_create'),
{'tipo': tipo.pk,
'nome': 'Comissão Teste',
'sigla': 'CT',
'ativa': True,
'data_criacao': '2016-03-22',
'unidade_deliberativa': True,
'salvar': 'salvar'},
follow=True)
tipo = baker.make(TipoComissao, sigla="T", nome="Teste")
response = admin_client.post(
reverse("sapl.comissoes:comissao_create"),
{
"tipo": tipo.pk,
"nome": "Comissão Teste",
"sigla": "CT",
"ativa": True,
"data_criacao": "2016-03-22",
"unidade_deliberativa": True,
"salvar": "salvar",
},
follow=True,
)
assert response.status_code == 200
comissao = Comissao.objects.first()
assert comissao.nome == 'Comissão Teste'
assert comissao.nome == "Comissão Teste"
assert comissao.tipo == tipo
@pytest.mark.django_db(transaction=False)
def test_incluir_comissao_errors(admin_client):
response = admin_client.post(
reverse("sapl.comissoes:comissao_create"), {"salvar": "salvar"}, follow=True
)
response = admin_client.post(reverse('sapl.comissoes:comissao_create'),
{'salvar': 'salvar'},
follow=True)
assert (response.context_data['form'].errors['tipo'] ==
['Este campo é obrigatório.'])
assert (response.context_data['form'].errors['nome'] ==
['Este campo é obrigatório.'])
assert (response.context_data['form'].errors['sigla'] ==
['Este campo é obrigatório.'])
assert (response.context_data['form'].errors['data_criacao'] ==
['Este campo é obrigatório.'])
assert response.context_data["form"].errors["tipo"] == ["Este campo é obrigatório."]
assert response.context_data["form"].errors["nome"] == ["Este campo é obrigatório."]
assert response.context_data["form"].errors["sigla"] == [
"Este campo é obrigatório."
]
assert response.context_data["form"].errors["data_criacao"] == [
"Este campo é obrigatório."
]
@pytest.mark.django_db(transaction=False)
def test_periodo_invalidas():
form = forms.PeriodoForm(data={'data_inicio': '10/11/2017',
'data_fim': '09/11/2017'
})
form = forms.PeriodoForm(
data={"data_inicio": "10/11/2017", "data_fim": "09/11/2017"}
)
assert not form.is_valid()
assert form.errors['__all__'] == [_('A Data Final não pode ser menor que '
'a Data Inicial')]
assert form.errors["__all__"] == [
_("A Data Final não pode ser menor que " "a Data Inicial")
]
@pytest.mark.django_db(transaction=False)
@ -136,7 +134,7 @@ def test_valida_campos_obrigatorios_periodo_form():
errors = form.errors
assert errors['data_inicio'] == [_('Este campo é obrigatório.')]
assert errors["data_inicio"] == [_("Este campo é obrigatório.")]
assert len(errors) == 1
@ -149,11 +147,11 @@ def test_valida_campos_obrigatorios_reuniao_form():
errors = form.errors
assert errors['comissao'] == [_('Este campo é obrigatório.')]
assert errors['periodo'] == [_('Este campo é obrigatório.')]
assert errors['numero'] == [_('Este campo é obrigatório.')]
assert errors['nome'] == [_('Este campo é obrigatório.')]
assert errors['data'] == [_('Este campo é obrigatório.')]
assert errors['hora_inicio'] == [_('Este campo é obrigatório.')]
assert errors["comissao"] == [_("Este campo é obrigatório.")]
assert errors["periodo"] == [_("Este campo é obrigatório.")]
assert errors["numero"] == [_("Este campo é obrigatório.")]
assert errors["nome"] == [_("Este campo é obrigatório.")]
assert errors["data"] == [_("Este campo é obrigatório.")]
assert errors["hora_inicio"] == [_("Este campo é obrigatório.")]
assert len(errors) == 6

69
sapl/comissoes/urls.py

@ -1,30 +1,55 @@
from django.conf.urls import include, url
from sapl.comissoes.views import (AdicionaPautaView, CargoComissaoCrud, ComissaoCrud,
ComposicaoCrud, DocumentoAcessorioCrud,
MateriasTramitacaoListView, ParticipacaoCrud,
get_participacoes_comissao, PeriodoComposicaoCrud,
RemovePautaView, ReuniaoCrud, TipoComissaoCrud)
from sapl.comissoes.views import (
AdicionaPautaView,
CargoComissaoCrud,
ComissaoCrud,
ComposicaoCrud,
DocumentoAcessorioCrud,
MateriasTramitacaoListView,
ParticipacaoCrud,
PeriodoComposicaoCrud,
RemovePautaView,
ReuniaoCrud,
TipoComissaoCrud,
get_participacoes_comissao,
)
from .apps import AppConfig
app_name = AppConfig.name
urlpatterns = [
url(r'^comissao/', include(ComissaoCrud.get_urls() +
ComposicaoCrud.get_urls() +
ReuniaoCrud.get_urls() +
ParticipacaoCrud.get_urls() +
DocumentoAcessorioCrud.get_urls())),
url(r'^comissao/(?P<pk>\d+)/materias-em-tramitacao$',
MateriasTramitacaoListView.as_view(), name='materias_em_tramitacao'),
url(r'^comissao/(?P<pk>\d+)/pauta/add', AdicionaPautaView.as_view(), name='pauta_add'),
url(r'^comissao/(?P<pk>\d+)/pauta/remove', RemovePautaView.as_view(), name='pauta_remove'),
url(r'^sistema/comissao/cargo/', include(CargoComissaoCrud.get_urls())),
url(r'^sistema/comissao/periodo-composicao/',
include(PeriodoComposicaoCrud.get_urls())),
url(r'^sistema/comissao/tipo/', include(TipoComissaoCrud.get_urls())),
url(r'^sistema/comissao/recupera-participacoes', get_participacoes_comissao),
url(
r"^comissao/",
include(
ComissaoCrud.get_urls()
+ ComposicaoCrud.get_urls()
+ ReuniaoCrud.get_urls()
+ ParticipacaoCrud.get_urls()
+ DocumentoAcessorioCrud.get_urls()
),
),
url(
r"^comissao/(?P<pk>\d+)/materias-em-tramitacao$",
MateriasTramitacaoListView.as_view(),
name="materias_em_tramitacao",
),
url(
r"^comissao/(?P<pk>\d+)/pauta/add",
AdicionaPautaView.as_view(),
name="pauta_add",
),
url(
r"^comissao/(?P<pk>\d+)/pauta/remove",
RemovePautaView.as_view(),
name="pauta_remove",
),
url(r"^sistema/comissao/cargo/", include(CargoComissaoCrud.get_urls())),
url(
r"^sistema/comissao/periodo-composicao/",
include(PeriodoComposicaoCrud.get_urls()),
),
url(r"^sistema/comissao/tipo/", include(TipoComissaoCrud.get_urls())),
url(r"^sistema/comissao/recupera-participacoes", get_participacoes_comissao),
]

328
sapl/comissoes/views.py

@ -2,59 +2,82 @@ import logging
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.urls import reverse
from django.db.models import F
from django.http.response import HttpResponseRedirect, JsonResponse
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import CreateView, DeleteView, FormView, ListView
from django.views.generic.base import RedirectView
from django.views.generic.detail import DetailView
from django.views.generic.edit import FormMixin, UpdateView
from django.utils.translation import ugettext_lazy as _
from django_filters.views import FilterView
from sapl.base.models import AppConfig as AppsAppConfig
from sapl.comissoes.apps import AppConfig
from sapl.comissoes.forms import (ComissaoForm, ComposicaoForm,
from sapl.comissoes.forms import (
ComissaoForm,
ComposicaoForm,
DocumentoAcessorioCreateForm,
DocumentoAcessorioEditForm,
ParticipacaoCreateForm,
ParticipacaoEditForm,
PautaReuniaoFilterSet, PautaReuniaoForm,
PeriodoForm, ReuniaoForm)
from sapl.crud.base import (Crud, CrudAux, MasterDetailCrud,
PermissionRequiredForAppCrudMixin, RP_DETAIL,
RP_LIST)
from sapl.materia.models import (MateriaEmTramitacao, MateriaLegislativa,
PautaReuniao, Tramitacao)
PautaReuniaoFilterSet,
PautaReuniaoForm,
PeriodoForm,
ReuniaoForm,
)
from sapl.crud.base import (
RP_DETAIL,
RP_LIST,
Crud,
CrudAux,
MasterDetailCrud,
PermissionRequiredForAppCrudMixin,
)
from sapl.materia.models import (
MateriaEmTramitacao,
MateriaLegislativa,
PautaReuniao,
Tramitacao,
)
from sapl.utils import show_results_filter_set
from .models import (CargoComissao, Comissao, Composicao, DocumentoAcessorio,
Participacao, Periodo, Reuniao, TipoComissao)
from .models import (
CargoComissao,
Comissao,
Composicao,
DocumentoAcessorio,
Participacao,
Periodo,
Reuniao,
TipoComissao,
)
def pegar_url_composicao(pk):
participacao = Participacao.objects.get(id=pk)
comp_pk = participacao.composicao.pk
url = reverse('sapl.comissoes:composicao_detail', kwargs={'pk': comp_pk})
url = reverse("sapl.comissoes:composicao_detail", kwargs={"pk": comp_pk})
return url
def pegar_url_reuniao(pk):
documentoacessorio = DocumentoAcessorio.objects.get(id=pk)
r_pk = documentoacessorio.reuniao.pk
url = reverse('sapl.comissoes:reuniao_detail', kwargs={'pk': r_pk})
url = reverse("sapl.comissoes:reuniao_detail", kwargs={"pk": r_pk})
return url
CargoComissaoCrud = CrudAux.build(
CargoComissao, 'cargo_comissao',
list_field_names=['nome', 'id_ordenacao', 'unico']
CargoComissao, "cargo_comissao", list_field_names=["nome", "id_ordenacao", "unico"]
)
TipoComissaoCrud = CrudAux.build(
TipoComissao, 'tipo_comissao', list_field_names=[
'sigla', 'nome', 'natureza', 'dispositivo_regimental'])
TipoComissao,
"tipo_comissao",
list_field_names=["sigla", "nome", "natureza", "dispositivo_regimental"],
)
class PeriodoComposicaoCrud(CrudAux):
@ -71,48 +94,55 @@ class PeriodoComposicaoCrud(CrudAux):
class ParticipacaoCrud(MasterDetailCrud):
model = Participacao
parent_field = 'composicao__comissao'
public = [RP_DETAIL, ]
parent_field = "composicao__comissao"
public = [
RP_DETAIL,
]
ListView = None
link_return_to_parent_field = True
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['composicao', 'parlamentar', 'cargo']
list_field_names = ["composicao", "parlamentar", "cargo"]
class CreateView(MasterDetailCrud.CreateView):
form_class = ParticipacaoCreateForm
def get_initial(self):
initial = super().get_initial()
initial['parent_pk'] = self.kwargs['pk']
initial["parent_pk"] = self.kwargs["pk"]
return initial
class UpdateView(MasterDetailCrud.UpdateView):
layout_key = 'ParticipacaoEdit'
layout_key = "ParticipacaoEdit"
form_class = ParticipacaoEditForm
class DeleteView(MasterDetailCrud.DeleteView):
def get_success_url(self):
composicao_comissao_pk = self.object.composicao.comissao.pk
composicao_pk = self.object.composicao.pk
return '{}?pk={}'.format(reverse('sapl.comissoes:composicao_list',
args=[composicao_comissao_pk]),
composicao_pk)
return "{}?pk={}".format(
reverse(
"sapl.comissoes:composicao_list", args=[composicao_comissao_pk]
),
composicao_pk,
)
class ComposicaoCrud(MasterDetailCrud):
model = Composicao
parent_field = 'comissao'
model_set = 'participacao_set'
public = [RP_LIST, RP_DETAIL, ]
parent_field = "comissao"
model_set = "participacao_set"
public = [
RP_LIST,
RP_DETAIL,
]
class CreateView(MasterDetailCrud.CreateView):
form_class = ComposicaoForm
def get_initial(self):
comissao = Comissao.objects.get(id=self.kwargs['pk'])
return {'comissao': comissao}
comissao = Comissao.objects.get(id=self.kwargs["pk"])
return {"comissao": comissao}
class ListView(MasterDetailCrud.ListView):
logger = logging.getLogger(__name__)
@ -120,13 +150,19 @@ class ComposicaoCrud(MasterDetailCrud):
paginate_by = None
def take_composicao_pk(self):
username = self.request.user.username
try:
self.logger.debug('user=' + username + '. Tentando obter pk da composição.')
return int(self.request.GET['pk'])
self.logger.debug(
"user=" + username + ". Tentando obter pk da composição."
)
return int(self.request.GET["pk"])
except Exception as e:
self.logger.error('user=' + username + '. Erro ao obter pk da composição. Retornado 0. ' + str(e))
self.logger.error(
"user="
+ username
+ ". Erro ao obter pk da composição. Retornado 0. "
+ str(e)
)
return 0
def get_context_data(self, **kwargs):
@ -137,17 +173,17 @@ class ComposicaoCrud(MasterDetailCrud):
if composicao_pk == 0:
# Composicao eh ordenada por Periodo, que por sua vez esta em
# ordem descrescente de data de inicio (issue #1920)
ultima_composicao = context['composicao_list'].first()
ultima_composicao = context["composicao_list"].first()
if ultima_composicao:
context['composicao_pk'] = ultima_composicao.pk
context["composicao_pk"] = ultima_composicao.pk
else:
context['composicao_pk'] = 0
context["composicao_pk"] = 0
else:
context['composicao_pk'] = composicao_pk
context["composicao_pk"] = composicao_pk
context['participacao_set'] = Participacao.objects.filter(
composicao__pk=context['composicao_pk']
).order_by('-titular', 'cargo__id_ordenacao', 'id')
context["participacao_set"] = Participacao.objects.filter(
composicao__pk=context["composicao_pk"]
).order_by("-titular", "cargo__id_ordenacao", "id")
return context
class DeleteView(MasterDetailCrud.DeleteView):
@ -155,18 +191,31 @@ class ComposicaoCrud(MasterDetailCrud):
composicao = self.get_object()
composicao.delete()
return HttpResponseRedirect(
reverse('sapl.comissoes:composicao_list', kwargs={'pk': composicao.comissao.pk}))
reverse(
"sapl.comissoes:composicao_list",
kwargs={"pk": composicao.comissao.pk},
)
)
class ComissaoCrud(Crud):
model = Comissao
help_topic = 'modulo_comissoes'
public = [RP_LIST, RP_DETAIL, ]
help_topic = "modulo_comissoes"
public = [
RP_LIST,
RP_DETAIL,
]
class BaseMixin(Crud.BaseMixin):
list_field_names = ['nome', 'sigla', 'tipo',
'data_criacao', 'data_extincao', 'ativa']
ordering = '-ativa', 'sigla'
list_field_names = [
"nome",
"sigla",
"tipo",
"data_criacao",
"data_extincao",
"ativa",
]
ordering = "-ativa", "sigla"
class CreateView(Crud.CreateView):
form_class = ComissaoForm
@ -185,7 +234,7 @@ class ComissaoCrud(Crud):
def lista_materias_comissao(comissao_pk):
materias = MateriaEmTramitacao.objects.filter(
tramitacao__unidade_tramitacao_destino__comissao=comissao_pk
).order_by('materia__tipo', '-materia__ano', '-materia__numero')
).order_by("materia__tipo", "-materia__ano", "-materia__numero")
return materias
@ -195,23 +244,25 @@ class MateriasTramitacaoListView(ListView):
paginate_by = 10
def get_queryset(self):
return list(lista_materias_comissao(self.kwargs['pk']))
return list(lista_materias_comissao(self.kwargs["pk"]))
def get_context_data(self, **kwargs):
context = super(
MateriasTramitacaoListView, self).get_context_data(**kwargs)
context['object'] = Comissao.objects.get(id=self.kwargs['pk'])
context['qtde'] = len(self.object_list)
context = super(MateriasTramitacaoListView, self).get_context_data(**kwargs)
context["object"] = Comissao.objects.get(id=self.kwargs["pk"])
context["qtde"] = len(self.object_list)
return context
class ReuniaoCrud(MasterDetailCrud):
model = Reuniao
parent_field = 'comissao'
public = [RP_LIST, RP_DETAIL, ]
parent_field = "comissao"
public = [
RP_LIST,
RP_DETAIL,
]
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['data', 'nome', 'tema', 'upload_ata']
list_field_names = ["data", "nome", "tema", "upload_ata"]
class DetailView(MasterDetailCrud.DetailView):
template_name = "comissoes/reuniao_detail.html"
@ -220,22 +271,24 @@ class ReuniaoCrud(MasterDetailCrud):
context = super().get_context_data(**kwargs)
docs = []
documentos = DocumentoAcessorio.objects.filter(reuniao=self.kwargs['pk']).order_by('nome')
documentos = DocumentoAcessorio.objects.filter(
reuniao=self.kwargs["pk"]
).order_by("nome")
docs.extend(documentos)
context['docs'] = docs
context['num_docs'] = len(docs)
context["docs"] = docs
context["num_docs"] = len(docs)
mats = []
materias_pauta = PautaReuniao.objects.filter(reuniao=self.kwargs['pk'])
materias_pauta = PautaReuniao.objects.filter(reuniao=self.kwargs["pk"])
materias_pk = [materia_pauta.materia.pk for materia_pauta in materias_pauta]
context['mats'] = MateriaLegislativa.objects.filter(
context["mats"] = MateriaLegislativa.objects.filter(
pk__in=materias_pk
).order_by('tipo', '-ano', 'numero')
context['num_mats'] = len(context['mats'])
).order_by("tipo", "-ano", "numero")
context["num_mats"] = len(context["mats"])
context['reuniao_pk'] = self.kwargs['pk']
context["reuniao_pk"] = self.kwargs["pk"]
return context
@ -244,13 +297,19 @@ class ReuniaoCrud(MasterDetailCrud):
paginate_by = 10
def take_reuniao_pk(self):
username = self.request.user.username
try:
self.logger.debug('user=' + username + '. Tentando obter pk da reunião.')
return int(self.request.GET['pk'])
self.logger.debug(
"user=" + username + ". Tentando obter pk da reunião."
)
return int(self.request.GET["pk"])
except Exception as e:
self.logger.error('user=' + username + '. Erro ao obter pk da reunião. Retornado 0. ' + str(e))
self.logger.error(
"user="
+ username
+ ". Erro ao obter pk da reunião. Retornado 0. "
+ str(e)
)
return 0
def get_context_data(self, **kwargs):
@ -259,124 +318,127 @@ class ReuniaoCrud(MasterDetailCrud):
reuniao_pk = self.take_reuniao_pk()
if reuniao_pk == 0:
ultima_reuniao = list(context['reuniao_list'])
ultima_reuniao = list(context["reuniao_list"])
if len(ultima_reuniao) > 0:
ultimo = ultima_reuniao[-1]
context['reuniao_pk'] = ultimo.pk
context["reuniao_pk"] = ultimo.pk
else:
context['reuniao_pk'] = 0
context["reuniao_pk"] = 0
else:
context['reuniao_pk'] = reuniao_pk
context["reuniao_pk"] = reuniao_pk
context['documentoacessorio_set'] = DocumentoAcessorio.objects.filter(
reuniao__pk=context['reuniao_pk']
).order_by('id')
context["documentoacessorio_set"] = DocumentoAcessorio.objects.filter(
reuniao__pk=context["reuniao_pk"]
).order_by("id")
return context
class UpdateView(MasterDetailCrud.UpdateView):
form_class = ReuniaoForm
def get_initial(self):
return {'comissao': self.object.comissao}
return {"comissao": self.object.comissao}
class CreateView(MasterDetailCrud.CreateView):
form_class = ReuniaoForm
def get_initial(self):
comissao = Comissao.objects.get(id=self.kwargs['pk'])
comissao = Comissao.objects.get(id=self.kwargs["pk"])
return {'comissao': comissao}
return {"comissao": comissao}
class RemovePautaView(PermissionRequiredMixin, CreateView):
model = PautaReuniao
form_class = PautaReuniaoForm
template_name = 'comissoes/pauta.html'
permission_required = ('comissoes.add_reuniao', )
template_name = "comissoes/pauta.html"
permission_required = ("comissoes.add_reuniao",)
def get_context_data(self, **kwargs):
context = super(
RemovePautaView, self
).get_context_data(**kwargs)
context = super(RemovePautaView, self).get_context_data(**kwargs)
# Remove = 0; Adiciona = 1
context['opcao'] = 0
context["opcao"] = 0
context['object'] = Reuniao.objects.get(pk=self.kwargs['pk'])
context['root_pk'] = context['object'].comissao.pk
context["object"] = Reuniao.objects.get(pk=self.kwargs["pk"])
context["root_pk"] = context["object"].comissao.pk
materias_pauta = PautaReuniao.objects.filter(reuniao=context['object'])
materias_pauta = PautaReuniao.objects.filter(reuniao=context["object"])
materias_pk = [materia_pauta.materia.pk for materia_pauta in materias_pauta]
context['materias'] = MateriaLegislativa.objects.filter(
context["materias"] = MateriaLegislativa.objects.filter(
pk__in=materias_pk
).order_by('tipo', '-ano', 'numero')
context['numero_materias'] = len(context['materias'])
).order_by("tipo", "-ano", "numero")
context["numero_materias"] = len(context["materias"])
return context
def post(self, request, *args, **kwargs):
success_url = reverse('sapl.comissoes:reuniao_detail', kwargs={'pk':kwargs['pk']})
marcadas = request.POST.getlist('materia_id')
success_url = reverse(
"sapl.comissoes:reuniao_detail", kwargs={"pk": kwargs["pk"]}
)
marcadas = request.POST.getlist("materia_id")
if not marcadas:
msg=_('Nenhuma matéria foi selecionada.')
msg = _("Nenhuma matéria foi selecionada.")
messages.add_message(request, messages.WARNING, msg)
return HttpResponseRedirect(success_url)
reuniao = Reuniao.objects.get(pk=kwargs['pk'])
reuniao = Reuniao.objects.get(pk=kwargs["pk"])
for materia in MateriaLegislativa.objects.filter(id__in=marcadas):
PautaReuniao.objects.filter(reuniao=reuniao, materia=materia).delete()
msg=_('Matéria(s) removida(s) com sucesso!')
msg = _("Matéria(s) removida(s) com sucesso!")
messages.add_message(request, messages.SUCCESS, msg)
return HttpResponseRedirect(success_url)
class AdicionaPautaView(PermissionRequiredMixin, FilterView):
filterset_class = PautaReuniaoFilterSet
template_name = 'comissoes/pauta.html'
permission_required = ('comissoes.add_reuniao', )
template_name = "comissoes/pauta.html"
permission_required = ("comissoes.add_reuniao",)
def get_context_data(self, **kwargs):
context = super(
AdicionaPautaView, self
).get_context_data(**kwargs)
context = super(AdicionaPautaView, self).get_context_data(**kwargs)
# Adiciona = 1; Remove = 0
context['opcao'] = 1
context["opcao"] = 1
context['object'] = Reuniao.objects.get(pk=self.kwargs['pk'])
context['root_pk'] = context['object'].comissao.pk
context["object"] = Reuniao.objects.get(pk=self.kwargs["pk"])
context["root_pk"] = context["object"].comissao.pk
qr = self.request.GET.copy()
materias_pauta = PautaReuniao.objects.filter(reuniao=context['object'])
materias_pauta = PautaReuniao.objects.filter(reuniao=context["object"])
nao_listar = [mp.materia.pk for mp in materias_pauta]
if not len(qr):
context['object_list'] = []
context["object_list"] = []
else:
context['object_list'] = context['object_list'].filter(
tramitacao__unidade_tramitacao_destino__comissao=context['root_pk']
).exclude(materia__pk__in=nao_listar).order_by(
"materia__tipo", "-materia__ano", "materia__numero"
context["object_list"] = (
context["object_list"]
.filter(
tramitacao__unidade_tramitacao_destino__comissao=context["root_pk"]
)
.exclude(materia__pk__in=nao_listar)
.order_by("materia__tipo", "-materia__ano", "materia__numero")
)
context['numero_resultados'] = len(context['object_list'])
context['show_results'] = show_results_filter_set(qr)
context["numero_resultados"] = len(context["object_list"])
context["show_results"] = show_results_filter_set(qr)
return context
def post(self, request, *args, **kwargs):
success_url = reverse('sapl.comissoes:reuniao_detail', kwargs={'pk':kwargs['pk']})
marcadas = request.POST.getlist('materia_id')
success_url = reverse(
"sapl.comissoes:reuniao_detail", kwargs={"pk": kwargs["pk"]}
)
marcadas = request.POST.getlist("materia_id")
if not marcadas:
msg = _('Nenhuma máteria foi selecionada.')
msg = _("Nenhuma máteria foi selecionada.")
messages.add_message(request, messages.WARNING, msg)
return HttpResponseRedirect(success_url)
reuniao = Reuniao.objects.get(pk=kwargs['pk'])
reuniao = Reuniao.objects.get(pk=kwargs["pk"])
pautas = []
for materia in MateriaLegislativa.objects.filter(id__in=marcadas):
pauta = PautaReuniao()
@ -385,50 +447,54 @@ class AdicionaPautaView(PermissionRequiredMixin, FilterView):
pautas.append(pauta)
PautaReuniao.objects.bulk_create(pautas)
msg = _('Matéria(s) adicionada(s) com sucesso!')
msg = _("Matéria(s) adicionada(s) com sucesso!")
messages.add_message(request, messages.SUCCESS, msg)
return HttpResponseRedirect(success_url)
class DocumentoAcessorioCrud(MasterDetailCrud):
model = DocumentoAcessorio
parent_field = 'reuniao__comissao'
public = [RP_DETAIL, ]
parent_field = "reuniao__comissao"
public = [
RP_DETAIL,
]
ListView = None
link_return_to_parent_field = True
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['nome', 'tipo', 'data', 'autor', 'arquivo']
list_field_names = ["nome", "tipo", "data", "autor", "arquivo"]
class CreateView(MasterDetailCrud.CreateView):
form_class = DocumentoAcessorioCreateForm
def get_initial(self):
initial = super().get_initial()
initial['parent_pk'] = self.kwargs['pk']
initial["parent_pk"] = self.kwargs["pk"]
return initial
class UpdateView(MasterDetailCrud.UpdateView):
layout_key = 'DocumentoAcessorioEdit'
layout_key = "DocumentoAcessorioEdit"
form_class = DocumentoAcessorioEditForm
class DeleteView(MasterDetailCrud.DeleteView):
def delete(self, *args, **kwargs):
obj = self.get_object()
obj.delete()
return HttpResponseRedirect(
reverse('sapl.comissoes:reuniao_detail',
kwargs={'pk': obj.reuniao.pk}))
reverse("sapl.comissoes:reuniao_detail", kwargs={"pk": obj.reuniao.pk})
)
def get_participacoes_comissao(request):
parlamentares = []
composicao_id = request.GET.get('composicao_id')
composicao_id = request.GET.get("composicao_id")
if composicao_id:
parlamentares = [{'nome': p.parlamentar.nome_parlamentar, 'id': p.parlamentar.id} for p in
Participacao.objects.filter(composicao_id=composicao_id).order_by(
'parlamentar__nome_parlamentar')]
parlamentares = [
{"nome": p.parlamentar.nome_parlamentar, "id": p.parlamentar.id}
for p in Participacao.objects.filter(composicao_id=composicao_id).order_by(
"parlamentar__nome_parlamentar"
)
]
return JsonResponse(parlamentares, safe=False)

2
sapl/compilacao/__init__.py

@ -1 +1 @@
default_app_config = 'sapl.compilacao.apps.AppConfig'
default_app_config = "sapl.compilacao.apps.AppConfig"

8
sapl/compilacao/admin.py

@ -1,4 +1,5 @@
from django.contrib import admin
from sapl.compilacao.models import TipoDispositivo
from sapl.utils import register_all_models_in_admin
@ -8,5 +9,8 @@ admin.site.unregister(TipoDispositivo)
@admin.register(TipoDispositivo)
class TipoDispositivoAdmin(admin.ModelAdmin):
readonly_fields = ("rotulo_prefixo_texto", "rotulo_sufixo_texto",)
list_display = [f.name for f in TipoDispositivo._meta.fields if f.name != 'id']
readonly_fields = (
"rotulo_prefixo_texto",
"rotulo_sufixo_texto",
)
list_display = [f.name for f in TipoDispositivo._meta.fields if f.name != "id"]

55
sapl/compilacao/apps.py

@ -1,4 +1,3 @@
from django import apps
from django.conf import settings
from django.db import connection, models
@ -7,36 +6,39 @@ from django.utils.translation import ugettext_lazy as _
class AppConfig(apps.AppConfig):
name = 'sapl.compilacao'
label = 'compilacao'
verbose_name = _('Compilação')
name = "sapl.compilacao"
label = "compilacao"
verbose_name = _("Compilação")
@staticmethod
def import_pattern():
from django.contrib.contenttypes.models import ContentType
from unipath import Path
from sapl.compilacao.models import TipoTextoArticulado
from sapl.compilacao.utils import get_integrations_view_names
from django.contrib.contenttypes.models import ContentType
from unipath import Path
compilacao_app = Path(__file__).ancestor(1)
# print(compilacao_app)
with open(compilacao_app + '/compilacao_data_tables.sql', 'r') as f:
with open(compilacao_app + "/compilacao_data_tables.sql", "r") as f:
lines = f.readlines()
lines = [line.rstrip('\n') for line in lines]
lines = [line.rstrip("\n") for line in lines]
with connection.cursor() as cursor:
for line in lines:
line = line.strip()
if not line or line.startswith('#'):
if not line or line.startswith("#"):
continue
try:
cursor.execute(line)
except IntegrityError as e:
if not settings.DEBUG:
print("{} {} {}".format(_('Ocorreu erro na importação:'), line, str(e)))
print(
"{} {} {}".format(
_("Ocorreu erro na importação:"), line, str(e)
)
)
except Exception as ee:
print(ee)
@ -46,12 +48,12 @@ class AppConfig(apps.AppConfig):
verbose_name = verbose_name.upper().split()
if len(verbose_name) == 1:
verbose_name = verbose_name[0]
sigla = ''
sigla = ""
for letra in verbose_name:
if letra in 'BCDFGHJKLMNPQRSTVWXYZ':
if letra in "BCDFGHJKLMNPQRSTVWXYZ":
sigla += letra
else:
sigla = ''.join([palavra[0] for palavra in verbose_name])
sigla = "".join([palavra[0] for palavra in verbose_name])
return sigla[:3]
for view in integrations_view_names:
@ -60,28 +62,31 @@ class AppConfig(apps.AppConfig):
tipo.sigla = cria_sigla(
view.model._meta.verbose_name
if view.model._meta.verbose_name
else view.model._meta.model_name)
else view.model._meta.model_name
)
tipo.descricao = view.model._meta.verbose_name
tipo.content_type = ContentType.objects.get_by_natural_key(
view.model._meta.app_label, view.model._meta.model_name)
view.model._meta.app_label, view.model._meta.model_name
)
tipo.save()
except IntegrityError as e:
if not settings.DEBUG:
print("{} {}".format(_('Ocorreu erro na criação tipo de ta:'), str(e)))
print(
"{} {}".format(_("Ocorreu erro na criação tipo de ta:"), str(e))
)
def init_compilacao_base(app_config, verbosity=2, interactive=True,
using=DEFAULT_DB_ALIAS, **kwargs):
def init_compilacao_base(
app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs
):
if app_config != AppConfig and not isinstance(app_config, AppConfig):
return
from sapl.compilacao.models import TipoDispositivo
if not TipoDispositivo.objects.exists():
print('')
print("\033[93m\033[1m{}\033[0m".format(_('Iniciando Textos Articulados...')))
if not TipoDispositivo.objects.exists():
print("")
print("\033[93m\033[1m{}\033[0m".format(_("Iniciando Textos Articulados...")))
AppConfig.import_pattern()
models.signals.post_migrate.connect(
receiver=init_compilacao_base)
models.signals.post_migrate.connect(receiver=init_compilacao_base)

1781
sapl/compilacao/forms.py

File diff suppressed because it is too large

1459
sapl/compilacao/models.py

File diff suppressed because it is too large

179
sapl/compilacao/templatetags/compilacao_filters.py

@ -1,4 +1,3 @@
from django import template
from django.core.signing import Signer
from django.db.models import Q
@ -11,7 +10,6 @@ register = template.Library()
class DispositivoTreeNode(template.Node):
def __init__(self, template_nodes, dispositivo_list_var):
self.template_nodes = template_nodes
self.dispositivo_list_var = dispositivo_list_var
@ -20,103 +18,108 @@ class DispositivoTreeNode(template.Node):
bits_alts = []
bits_filhos = []
context.push()
for child in node['alts']:
for child in node["alts"]:
bits_alts.append(self._render_node(context, child))
for child in node['filhos']:
for child in node["filhos"]:
bits_filhos.append(self._render_node(context, child))
context['node'] = node
context['alts'] = mark_safe(''.join(bits_alts))
context['filhos'] = mark_safe(''.join(bits_filhos))
context["node"] = node
context["alts"] = mark_safe("".join(bits_alts))
context["filhos"] = mark_safe("".join(bits_filhos))
rendered = self.template_nodes.render(context)
context.pop()
return rendered
def render(self, context):
dispositivo_list_var = self.dispositivo_list_var.resolve(context)
bits = [self._render_node(context, node)
for node in dispositivo_list_var]
return ''.join(bits)
bits = [self._render_node(context, node) for node in dispositivo_list_var]
return "".join(bits)
@register.tag
def dispositivotree(parser, token):
bits = token.contents.split()
if len(bits) != 2:
raise template.TemplateSyntaxError(
_('%s tag requires a queryset') % bits[0])
raise template.TemplateSyntaxError(_("%s tag requires a queryset") % bits[0])
dispositivo_list_var = template.Variable(bits[1])
template_nodes = parser.parse(('enddispositivotree',))
template_nodes = parser.parse(("enddispositivotree",))
parser.delete_first_token()
return DispositivoTreeNode(template_nodes, dispositivo_list_var)
# --------------------------------------------------------------
@register.filter
def get_bloco_atualizador(pk_atualizador):
return Dispositivo.objects.order_by('ordem_bloco_atualizador').filter(
Q(dispositivo_pai_id=pk_atualizador) |
Q(dispositivo_atualizador_id=pk_atualizador)).select_related()
return (
Dispositivo.objects.order_by("ordem_bloco_atualizador")
.filter(
Q(dispositivo_pai_id=pk_atualizador)
| Q(dispositivo_atualizador_id=pk_atualizador)
)
.select_related()
)
@register.simple_tag
def dispositivo_desativado(dispositivo, inicio_vigencia, fim_vigencia):
if dispositivo.dispositivo_de_revogacao:
return 'revogado'
return "revogado"
if inicio_vigencia and fim_vigencia:
if dispositivo.fim_vigencia is None:
return ''
return ""
elif dispositivo.fim_vigencia >= fim_vigencia:
return ''
return 'desativado'
return ""
return "desativado"
else:
if dispositivo.fim_vigencia is not None:
return 'desativado'
return ''
return "desativado"
return ""
@register.simple_tag
def nota_automatica(dispositivo, ta_pub_list):
if dispositivo.ta_publicado and dispositivo.dispositivo_atualizador is not None and dispositivo.dispositivo_atualizador.dispositivo_pai is not None:
if (
dispositivo.ta_publicado
and dispositivo.dispositivo_atualizador is not None
and dispositivo.dispositivo_atualizador.dispositivo_pai is not None
):
d = dispositivo.dispositivo_atualizador.dispositivo_pai
if d.auto_inserido:
d = d.dispositivo_pai
ta_publicado = ta_pub_list[dispositivo.ta_publicado_id] if\
ta_pub_list else dispositivo.ta_publicado
ta_publicado = (
ta_pub_list[dispositivo.ta_publicado_id]
if ta_pub_list
else dispositivo.ta_publicado
)
if dispositivo.dispositivo_de_revogacao:
return _('Revogado pelo %s - %s.') % (
d, ta_publicado)
return _("Revogado pelo %s - %s.") % (d, ta_publicado)
elif not dispositivo.dispositivo_substituido_id:
return _('Inclusão feita pelo %s - %s.') % (
d, ta_publicado)
return _("Inclusão feita pelo %s - %s.") % (d, ta_publicado)
else:
if dispositivo.tipo_dispositivo.dispositivo_de_articulacao:
return _('Alteração de rótulo feita pelo %s - %s.') % (
d, ta_publicado)
return _("Alteração de rótulo feita pelo %s - %s.") % (d, ta_publicado)
else:
return _('Alteração feita pelo %s - %s.') % (
d, ta_publicado)
return _("Alteração feita pelo %s - %s.") % (d, ta_publicado)
return ''
return ""
@register.simple_tag
def set_nivel_old(view, value):
view.flag_nivel_old = value
return ''
return ""
@register.simple_tag
def close_div(value_max, value_min, varr):
return mark_safe('</div>' * (int(value_max) - int(value_min) + 1 + varr))
return mark_safe("</div>" * (int(value_max) - int(value_min) + 1 + varr))
@register.filter
@ -124,7 +127,8 @@ def get_sign_vigencia(value):
string = "%s,%s,%s" % (
value.ta_publicado_id if value.ta_publicado_id else 0,
value.inicio_vigencia,
value.fim_vigencia)
value.fim_vigencia,
)
signer = Signer()
return signer.sign(str(string))
@ -142,8 +146,7 @@ def isinst(value, class_str):
@register.filter
def render_actions_head(view, d_atual):
if view.__class__.__name__ != 'DispositivoSimpleEditView':
if view.__class__.__name__ != "DispositivoSimpleEditView":
return False
# Menu
@ -160,45 +163,51 @@ def render_actions_head(view, d_atual):
@register.filter
def short_string(str, length):
if len(str) > length:
return str[:length] + '...'
return str[:length] + "..."
else:
return str
@register.filter
def nomenclatura(d):
result = ''
if d.rotulo != '':
if d.tipo_dispositivo.rotulo_prefixo_texto != '':
result = ""
if d.rotulo != "":
if d.tipo_dispositivo.rotulo_prefixo_texto != "":
result = d.rotulo
else:
result = '(' + d.tipo_dispositivo.nome + ' ' + \
d.rotulo + ')'
result = "(" + d.tipo_dispositivo.nome + " " + d.rotulo + ")"
else:
r = d.rotulo_padrao()
if r:
r += ' '
result = '(' + d.tipo_dispositivo.nome + r + ')'
r += " "
result = "(" + d.tipo_dispositivo.nome + r + ")"
return result
def update_dispositivos_parents(dpts_parents, ta_id):
dpts = Dispositivo.objects.order_by('ordem').filter(
ta_id=ta_id).values_list(
'pk', 'dispositivo_pai_id', 'rotulo', 'tipo_dispositivo__nome',
'tipo_dispositivo__rotulo_prefixo_texto')
dpts = (
Dispositivo.objects.order_by("ordem")
.filter(ta_id=ta_id)
.values_list(
"pk",
"dispositivo_pai_id",
"rotulo",
"tipo_dispositivo__nome",
"tipo_dispositivo__rotulo_prefixo_texto",
)
)
for d in dpts:
dpts_parents[str(d[0])] = {
'd': d, 'p': [], 'h': None}
dpts_parents[str(d[0])] = {"d": d, "p": [], "h": None}
def parents(k):
pai = dpts_parents[str(k)]['d'][1]
p = dpts_parents[str(k)]['p']
pai = dpts_parents[str(k)]["d"][1]
p = dpts_parents[str(k)]["p"]
if not p:
if pai:
parent_k = [pai, ] + parents(pai)
parent_k = [
pai,
] + parents(pai)
else:
parent_k = []
else:
@ -207,12 +216,12 @@ def update_dispositivos_parents(dpts_parents, ta_id):
return parent_k
for k in dpts_parents:
dpts_parents[str(k)]['p'] = parents(k)
dpts_parents[str(k)]["p"] = parents(k)
@register.simple_tag
def heranca(request, d, ignore_ultimo=0, ignore_primeiro=0):
ta_dpts_parents = request.session.get('herancas')
ta_dpts_parents = request.session.get("herancas")
if not ta_dpts_parents:
ta_dpts_parents = {}
@ -225,7 +234,7 @@ def heranca(request, d, ignore_ultimo=0, ignore_primeiro=0):
ta_dpts_parents[ta_id] = dpts_parents
update_dispositivos_parents(dpts_parents, ta_id)
herancas_fila = request.session.get('herancas_fila')
herancas_fila = request.session.get("herancas_fila")
if not herancas_fila:
herancas_fila = []
@ -234,22 +243,21 @@ def heranca(request, d, ignore_ultimo=0, ignore_primeiro=0):
ta_remove = herancas_fila.pop(0)
del ta_dpts_parents[str(ta_remove)]
request.session['herancas_fila'] = herancas_fila
request.session['herancas'] = ta_dpts_parents
request.session["herancas_fila"] = herancas_fila
request.session["herancas"] = ta_dpts_parents
h = ta_dpts_parents[ta_id][d_pk]['h']
h = ta_dpts_parents[ta_id][d_pk]["h"]
if h:
return h
dpts_parents = ta_dpts_parents[ta_id]
parents = dpts_parents[d_pk]['p']
result = ''
parents = dpts_parents[d_pk]["p"]
result = ""
if parents:
pk_last = parents[-1]
for pk in parents:
if ignore_ultimo and pk == pk_last:
break
@ -257,23 +265,21 @@ def heranca(request, d, ignore_ultimo=0, ignore_primeiro=0):
ignore_primeiro = 0
continue
p = dpts_parents[str(pk)]['d']
p = dpts_parents[str(pk)]["d"]
if p[4] != '':
result = p[2] + ' ' + result
if p[4] != "":
result = p[2] + " " + result
else:
result = '(' + p[3] + ' ' + \
p[2] + ')' + ' ' + result
result = "(" + p[3] + " " + p[2] + ")" + " " + result
dpts_parents[d_pk]['h'] = result
dpts_parents[d_pk]["h"] = result
return result
@register.simple_tag
def nomenclatura_heranca(d, ignore_ultimo=0, ignore_primeiro=0):
result = ''
result = ""
while d is not None:
if ignore_ultimo and d.dispositivo_pai is None:
break
if ignore_primeiro:
@ -281,22 +287,27 @@ def nomenclatura_heranca(d, ignore_ultimo=0, ignore_primeiro=0):
d = d.dispositivo_pai
continue
if d.rotulo != '':
if d.tipo_dispositivo.rotulo_prefixo_texto != '':
result = d.rotulo + ' ' + result
if d.rotulo != "":
if d.tipo_dispositivo.rotulo_prefixo_texto != "":
result = d.rotulo + " " + result
else:
result = '(' + d.tipo_dispositivo.nome + ' ' + \
d.rotulo + ')' + ' ' + result
result = (
"(" + d.tipo_dispositivo.nome + " " + d.rotulo + ")" + " " + result
)
else:
result = '(' + d.tipo_dispositivo.nome + \
d.rotulo_padrao() + ')' + ' ' + result
result = (
"(" + d.tipo_dispositivo.nome + d.rotulo_padrao() + ")" + " " + result
)
d = d.dispositivo_pai
return result
@register.filter
def list(obj):
return [obj, ]
return [
obj,
]
@register.filter

78
sapl/compilacao/tests/test_compilacao.py

@ -1,104 +1,90 @@
import pytest
from model_bakery import baker
from sapl.compilacao.models import PerfilEstruturalTextoArticulado
from sapl.compilacao.models import TipoTextoArticulado
from sapl.compilacao.models import TextoArticulado, TipoNota
from sapl.compilacao.models import TipoVide, TipoDispositivo
from sapl.compilacao.models import TipoDispositivoRelationship
from sapl.compilacao.models import (
PerfilEstruturalTextoArticulado,
TextoArticulado,
TipoDispositivo,
TipoDispositivoRelationship,
TipoNota,
TipoTextoArticulado,
TipoVide,
)
@pytest.mark.django_db(transaction=False)
def test_perfil_estrutural_texto_articulado_model():
perfil_estrutural_texto_articulado = baker.make(
PerfilEstruturalTextoArticulado,
nome='Teste_Nome_Perfil',
sigla='TSPETA')
PerfilEstruturalTextoArticulado, nome="Teste_Nome_Perfil", sigla="TSPETA"
)
assert perfil_estrutural_texto_articulado.nome == 'Teste_Nome_Perfil'
assert perfil_estrutural_texto_articulado.sigla == 'TSPETA'
assert perfil_estrutural_texto_articulado.nome == "Teste_Nome_Perfil"
assert perfil_estrutural_texto_articulado.sigla == "TSPETA"
@pytest.mark.django_db(transaction=False)
def test_tipo_texto_articulado_model():
tipo_texto_articulado = baker.make(
TipoTextoArticulado,
sigla='TTP',
descricao='T_Desc_Tipo_Texto_Articulado'
TipoTextoArticulado, sigla="TTP", descricao="T_Desc_Tipo_Texto_Articulado"
)
assert tipo_texto_articulado.sigla == 'TTP'
assert tipo_texto_articulado.descricao == 'T_Desc_Tipo_Texto_Articulado'
assert tipo_texto_articulado.sigla == "TTP"
assert tipo_texto_articulado.descricao == "T_Desc_Tipo_Texto_Articulado"
@pytest.mark.django_db(transaction=False)
def test_texto_articulado_model():
texto_articulado = baker.make(
TextoArticulado,
ementa='Teste_Ementa_Texto_Articulado',
numero='12345678',
ementa="Teste_Ementa_Texto_Articulado",
numero="12345678",
ano=2016,
)
assert texto_articulado.ementa == 'Teste_Ementa_Texto_Articulado'
assert texto_articulado.numero == '12345678'
assert texto_articulado.ementa == "Teste_Ementa_Texto_Articulado"
assert texto_articulado.numero == "12345678"
assert texto_articulado.ano == 2016
@pytest.mark.django_db(transaction=False)
def test_tipo_nota_model():
tipo_nota = baker.make(
TipoNota,
sigla='TTN',
nome='Teste_Nome_Tipo_Nota'
)
tipo_nota = baker.make(TipoNota, sigla="TTN", nome="Teste_Nome_Tipo_Nota")
assert tipo_nota.sigla == 'TTN'
assert tipo_nota.nome == 'Teste_Nome_Tipo_Nota'
assert tipo_nota.sigla == "TTN"
assert tipo_nota.nome == "Teste_Nome_Tipo_Nota"
@pytest.mark.django_db(transaction=False)
def test_tipo_vide_model():
tipo_vide = baker.make(
TipoVide,
sigla='TTV',
nome='Teste_Nome_Tipo_Vide'
)
tipo_vide = baker.make(TipoVide, sigla="TTV", nome="Teste_Nome_Tipo_Vide")
assert tipo_vide.sigla == 'TTV'
assert tipo_vide.nome == 'Teste_Nome_Tipo_Vide'
assert tipo_vide.sigla == "TTV"
assert tipo_vide.nome == "Teste_Nome_Tipo_Vide"
@pytest.mark.django_db(transaction=False)
def test_tipo_dispositivo_model():
tipo_dispositivo = baker.make(
TipoDispositivo,
nome='Teste_Nome_Tipo_Dispositivo',
rotulo_ordinal=0
TipoDispositivo, nome="Teste_Nome_Tipo_Dispositivo", rotulo_ordinal=0
)
assert tipo_dispositivo.nome == 'Teste_Nome_Tipo_Dispositivo'
assert tipo_dispositivo.nome == "Teste_Nome_Tipo_Dispositivo"
assert tipo_dispositivo.rotulo_ordinal == 0
@pytest.mark.django_db(transaction=False)
def test_tipo_dispositivo_relationship_model():
tipo_dispositivo_pai = baker.make(
TipoDispositivo,
nome='Tipo_Dispositivo_Pai',
rotulo_ordinal=0
TipoDispositivo, nome="Tipo_Dispositivo_Pai", rotulo_ordinal=0
)
t_dispositivo_filho = baker.make(
TipoDispositivo,
nome='Tipo_Dispositivo_Filho',
rotulo_ordinal=0
TipoDispositivo, nome="Tipo_Dispositivo_Filho", rotulo_ordinal=0
)
p_e_texto_articulado = baker.make(
PerfilEstruturalTextoArticulado,
nome='Teste_Nome_Perfil',
sigla='TSPETA')
PerfilEstruturalTextoArticulado, nome="Teste_Nome_Perfil", sigla="TSPETA"
)
tipo_dispositivo_relationship = baker.make(
TipoDispositivoRelationship,

43
sapl/compilacao/tests/test_tipo_texto_articulado_form.py

@ -14,10 +14,10 @@ def test_valida_campos_obrigatorios_tipo_texto_articulado_form():
errors = form.errors
assert errors['sigla'] == [_('Este campo é obrigatório.')]
assert errors['descricao'] == [_('Este campo é obrigatório.')]
assert errors['participacao_social'] == [_('Este campo é obrigatório.')]
assert errors['publicacao_func'] == [_('Este campo é obrigatório.')]
assert errors["sigla"] == [_("Este campo é obrigatório.")]
assert errors["descricao"] == [_("Este campo é obrigatório.")]
assert errors["participacao_social"] == [_("Este campo é obrigatório.")]
assert errors["publicacao_func"] == [_("Este campo é obrigatório.")]
assert len(errors) == 4
@ -29,12 +29,12 @@ def test_valida_campos_obrigatorios_nota_form():
errors = form.errors
assert errors['texto'] == [_('Este campo é obrigatório')]
assert errors['publicidade'] == [_('Este campo é obrigatório.')]
assert errors['tipo'] == [_('Este campo é obrigatório.')]
assert errors['publicacao'] == [_('Este campo é obrigatório')]
assert errors['efetividade'] == [_('Este campo é obrigatório')]
assert errors['dispositivo'] == [_('Este campo é obrigatório.')]
assert errors["texto"] == [_("Este campo é obrigatório")]
assert errors["publicidade"] == [_("Este campo é obrigatório.")]
assert errors["tipo"] == [_("Este campo é obrigatório.")]
assert errors["publicacao"] == [_("Este campo é obrigatório")]
assert errors["efetividade"] == [_("Este campo é obrigatório")]
assert errors["dispositivo"] == [_("Este campo é obrigatório.")]
assert len(errors) == 6
@ -43,15 +43,18 @@ def test_valida_campos_obrigatorios_nota_form():
def test_nota_form_invalido():
tipo = baker.make(TipoNota)
form = forms.NotaForm(data={'titulo': 'titulo',
'texto': 'teste',
'url_externa': 'www.test.com',
'publicidade': 'publicidade',
'tipo': str(tipo.pk),
'publicacao': '10/05/2017',
'efetividade': '10/05/2017',
'dispositivo': 'dispositivo',
'pk': 'pk'
})
form = forms.NotaForm(
data={
"titulo": "titulo",
"texto": "teste",
"url_externa": "www.test.com",
"publicidade": "publicidade",
"tipo": str(tipo.pk),
"publicacao": "10/05/2017",
"efetividade": "10/05/2017",
"dispositivo": "dispositivo",
"pk": "pk",
}
)
assert not form.is_valid()

237
sapl/compilacao/urls.py

@ -1,124 +1,153 @@
from django.conf.urls import include, url
from sapl.compilacao import views
from sapl.compilacao.views import (TipoDispositivoCrud, TipoNotaCrud,
TipoPublicacaoCrud, TipoVideCrud,
from sapl.compilacao.views import (
TipoDispositivoCrud,
TipoNotaCrud,
TipoPublicacaoCrud,
TipoTextoArticuladoCrud,
TipoVideCrud,
VeiculoPublicacaoCrud,
TipoTextoArticuladoCrud)
)
from .apps import AppConfig
app_name = AppConfig.name
urlpatterns_compilacao = [
url(r'^$', views.TaListView.as_view(), name='ta_list'),
url(r'^create$', views.TaCreateView.as_view(), name='ta_create'),
url(r'^(?P<pk>[0-9]+)$', views.TaDetailView.as_view(), name='ta_detail'),
url(r'^(?P<pk>[0-9]+)/edit$',
views.TaUpdateView.as_view(), name='ta_edit'),
url(r'^(?P<pk>[0-9]+)/delete$',
views.TaDeleteView.as_view(), name='ta_delete'),
url(r'^(?P<ta_id>[0-9]+)/text$',
views.TextView.as_view(), name='ta_text'),
url(r'^(?P<ta_id>[0-9]+)/text/vigencia/(?P<sign>.*:[A-Za-z0-9_-]+)/$',
views.TextView.as_view(), name='ta_vigencia'),
url(r'^(?P<ta_id>[0-9]+)/text/edit',
views.TextEditView.as_view(), name='ta_text_edit'),
url(r'^(?P<ta_id>[0-9]+)/text/notifications',
views.TextNotificacoesView.as_view(), name='ta_text_notificacoes'),
url(r'^(?P<ta_id>[0-9]+)/text/(?P<dispositivo_id>[0-9]+)/$',
views.DispositivoView.as_view(), name='dispositivo'),
url(r'^(?P<ta_id>[0-9]+)/text/(?P<dispositivo_id>[0-9]+)/refresh',
url(r"^$", views.TaListView.as_view(), name="ta_list"),
url(r"^create$", views.TaCreateView.as_view(), name="ta_create"),
url(r"^(?P<pk>[0-9]+)$", views.TaDetailView.as_view(), name="ta_detail"),
url(r"^(?P<pk>[0-9]+)/edit$", views.TaUpdateView.as_view(), name="ta_edit"),
url(r"^(?P<pk>[0-9]+)/delete$", views.TaDeleteView.as_view(), name="ta_delete"),
url(r"^(?P<ta_id>[0-9]+)/text$", views.TextView.as_view(), name="ta_text"),
url(
r"^(?P<ta_id>[0-9]+)/text/vigencia/(?P<sign>.*:[A-Za-z0-9_-]+)/$",
views.TextView.as_view(),
name="ta_vigencia",
),
url(
r"^(?P<ta_id>[0-9]+)/text/edit",
views.TextEditView.as_view(),
name="ta_text_edit",
),
url(
r"^(?P<ta_id>[0-9]+)/text/notifications",
views.TextNotificacoesView.as_view(),
name="ta_text_notificacoes",
),
url(
r"^(?P<ta_id>[0-9]+)/text/(?P<dispositivo_id>[0-9]+)/$",
views.DispositivoView.as_view(),
name="dispositivo",
),
url(
r"^(?P<ta_id>[0-9]+)/text/(?P<dispositivo_id>[0-9]+)/refresh",
views.DispositivoDinamicEditView.as_view(),
name='dispositivo_refresh'),
url(r'^(?P<ta_id>[0-9]+)/text/(?P<pk>[0-9]+)/edit$',
views.DispositivoEdicaoBasicaView.as_view(), name='dispositivo_edit'),
url(r'^(?P<ta_id>[0-9]+)/text/(?P<pk>[0-9]+)/edit/vigencia',
name="dispositivo_refresh",
),
url(
r"^(?P<ta_id>[0-9]+)/text/(?P<pk>[0-9]+)/edit$",
views.DispositivoEdicaoBasicaView.as_view(),
name="dispositivo_edit",
),
url(
r"^(?P<ta_id>[0-9]+)/text/(?P<pk>[0-9]+)/edit/vigencia",
views.DispositivoEdicaoVigenciaView.as_view(),
name='dispositivo_edit_vigencia'),
url(r'^(?P<ta_id>[0-9]+)/text/(?P<pk>[0-9]+)/edit/alteracao',
name="dispositivo_edit_vigencia",
),
url(
r"^(?P<ta_id>[0-9]+)/text/(?P<pk>[0-9]+)/edit/alteracao",
views.DispositivoEdicaoAlteracaoView.as_view(),
name='dispositivo_edit_alteracao'),
url(r'^(?P<ta_id>[0-9]+)/text/(?P<pk>[0-9]+)/edit/definidor_vigencia',
name="dispositivo_edit_alteracao",
),
url(
r"^(?P<ta_id>[0-9]+)/text/(?P<pk>[0-9]+)/edit/definidor_vigencia",
views.DispositivoDefinidorVigenciaView.as_view(),
name='dispositivo_edit_definidor_vigencia'),
url(r'^(?P<ta_id>[0-9]+)/text/'
'(?P<dispositivo_id>[0-9]+)/nota/create$',
views.NotasCreateView.as_view(), name='nota_create'),
url(r'^(?P<ta_id>[0-9]+)/text/'
'(?P<dispositivo_id>[0-9]+)/nota/(?P<pk>[0-9]+)/edit$',
views.NotasEditView.as_view(), name='nota_edit'),
url(r'^(?P<ta_id>[0-9]+)/text/'
'(?P<dispositivo_id>[0-9]+)/nota/(?P<pk>[0-9]+)/delete$',
views.NotasDeleteView.as_view(), name='nota_delete'),
url(r'^(?P<ta_id>[0-9]+)/text/'
'(?P<dispositivo_id>[0-9]+)/vide/create$',
views.VideCreateView.as_view(), name='vide_create'),
url(r'^(?P<ta_id>[0-9]+)/text/'
'(?P<dispositivo_id>[0-9]+)/vide/(?P<pk>[0-9]+)/edit$',
views.VideEditView.as_view(), name='vide_edit'),
url(r'^(?P<ta_id>[0-9]+)/text/'
'(?P<dispositivo_id>[0-9]+)/vide/(?P<pk>[0-9]+)/delete$',
views.VideDeleteView.as_view(), name='vide_delete'),
url(r'^search_fragment_form$',
name="dispositivo_edit_definidor_vigencia",
),
url(
r"^(?P<ta_id>[0-9]+)/text/" "(?P<dispositivo_id>[0-9]+)/nota/create$",
views.NotasCreateView.as_view(),
name="nota_create",
),
url(
r"^(?P<ta_id>[0-9]+)/text/"
"(?P<dispositivo_id>[0-9]+)/nota/(?P<pk>[0-9]+)/edit$",
views.NotasEditView.as_view(),
name="nota_edit",
),
url(
r"^(?P<ta_id>[0-9]+)/text/"
"(?P<dispositivo_id>[0-9]+)/nota/(?P<pk>[0-9]+)/delete$",
views.NotasDeleteView.as_view(),
name="nota_delete",
),
url(
r"^(?P<ta_id>[0-9]+)/text/" "(?P<dispositivo_id>[0-9]+)/vide/create$",
views.VideCreateView.as_view(),
name="vide_create",
),
url(
r"^(?P<ta_id>[0-9]+)/text/"
"(?P<dispositivo_id>[0-9]+)/vide/(?P<pk>[0-9]+)/edit$",
views.VideEditView.as_view(),
name="vide_edit",
),
url(
r"^(?P<ta_id>[0-9]+)/text/"
"(?P<dispositivo_id>[0-9]+)/vide/(?P<pk>[0-9]+)/delete$",
views.VideDeleteView.as_view(),
name="vide_delete",
),
url(
r"^search_fragment_form$",
views.DispositivoSearchFragmentFormView.as_view(),
name='dispositivo_fragment_form'),
url(r'^search_form$',
name="dispositivo_fragment_form",
),
url(
r"^search_form$",
views.DispositivoSearchModalView.as_view(),
name='dispositivo_search_form'),
url(r'^(?P<ta_id>[0-9]+)/publicacao$',
views.PublicacaoListView.as_view(), name='ta_pub_list'),
url(r'^(?P<ta_id>[0-9]+)/publicacao/create$',
views.PublicacaoCreateView.as_view(), name='ta_pub_create'),
url(r'^(?P<ta_id>[0-9]+)/publicacao/(?P<pk>[0-9]+)$',
views.PublicacaoDetailView.as_view(), name='ta_pub_detail'),
url(r'^(?P<ta_id>[0-9]+)/publicacao/(?P<pk>[0-9]+)/edit$',
views.PublicacaoUpdateView.as_view(), name='ta_pub_edit'),
url(r'^(?P<ta_id>[0-9]+)/publicacao/(?P<pk>[0-9]+)/delete$',
views.PublicacaoDeleteView.as_view(), name='ta_pub_delete'),
name="dispositivo_search_form",
),
url(
r"^(?P<ta_id>[0-9]+)/publicacao$",
views.PublicacaoListView.as_view(),
name="ta_pub_list",
),
url(
r"^(?P<ta_id>[0-9]+)/publicacao/create$",
views.PublicacaoCreateView.as_view(),
name="ta_pub_create",
),
url(
r"^(?P<ta_id>[0-9]+)/publicacao/(?P<pk>[0-9]+)$",
views.PublicacaoDetailView.as_view(),
name="ta_pub_detail",
),
url(
r"^(?P<ta_id>[0-9]+)/publicacao/(?P<pk>[0-9]+)/edit$",
views.PublicacaoUpdateView.as_view(),
name="ta_pub_edit",
),
url(
r"^(?P<ta_id>[0-9]+)/publicacao/(?P<pk>[0-9]+)/delete$",
views.PublicacaoDeleteView.as_view(),
name="ta_pub_delete",
),
]
urlpatterns = [
url(r'^ta/', include(urlpatterns_compilacao)),
url(r'^sistema/ta/config/tipo-nota/',
include(TipoNotaCrud.get_urls())),
url(r'^sistema/ta/config/tipo-vide/',
include(TipoVideCrud.get_urls())),
url(r'^sistema/ta/config/tipo-publicacao/',
include(TipoPublicacaoCrud.get_urls())),
url(r'^sistema/ta/config/veiculo-publicacao/',
include(VeiculoPublicacaoCrud.get_urls())),
url(r'^sistema/ta/config/tipo/',
include(TipoTextoArticuladoCrud.get_urls())),
url(r'^sistema/ta/config/tipodispositivo/',
include(TipoDispositivoCrud.get_urls())),
url(r"^ta/", include(urlpatterns_compilacao)),
url(r"^sistema/ta/config/tipo-nota/", include(TipoNotaCrud.get_urls())),
url(r"^sistema/ta/config/tipo-vide/", include(TipoVideCrud.get_urls())),
url(r"^sistema/ta/config/tipo-publicacao/", include(TipoPublicacaoCrud.get_urls())),
url(
r"^sistema/ta/config/veiculo-publicacao/",
include(VeiculoPublicacaoCrud.get_urls()),
),
url(r"^sistema/ta/config/tipo/", include(TipoTextoArticuladoCrud.get_urls())),
url(
r"^sistema/ta/config/tipodispositivo/", include(TipoDispositivoCrud.get_urls())
),
]

57
sapl/compilacao/utils.py

@ -1,29 +1,31 @@
import sys
DISPOSITIVO_SELECT_RELATED = (
'tipo_dispositivo',
'ta_publicado',
'ta',
'dispositivo_atualizador',
'dispositivo_atualizador__dispositivo_pai',
'dispositivo_atualizador__dispositivo_pai__ta',
'dispositivo_atualizador__dispositivo_pai__ta__tipo_ta',
'dispositivo_pai',
'dispositivo_pai__tipo_dispositivo',
'ta_publicado',
'ta',)
"tipo_dispositivo",
"ta_publicado",
"ta",
"dispositivo_atualizador",
"dispositivo_atualizador__dispositivo_pai",
"dispositivo_atualizador__dispositivo_pai__ta",
"dispositivo_atualizador__dispositivo_pai__ta__tipo_ta",
"dispositivo_pai",
"dispositivo_pai__tipo_dispositivo",
"ta_publicado",
"ta",
)
DISPOSITIVO_SELECT_RELATED_EDIT = (
'ta_publicado',
'ta',
'dispositivo_atualizador',
'dispositivo_atualizador__dispositivo_pai',
'dispositivo_atualizador__dispositivo_pai__ta',
'dispositivo_atualizador__dispositivo_pai__ta__tipo_ta',
'dispositivo_pai',
'dispositivo_pai__tipo_dispositivo',
'ta_publicado',
'ta',)
"ta_publicado",
"ta",
"dispositivo_atualizador",
"dispositivo_atualizador__dispositivo_pai",
"dispositivo_atualizador__dispositivo_pai__ta",
"dispositivo_atualizador__dispositivo_pai__ta__tipo_ta",
"dispositivo_pai",
"dispositivo_pai__tipo_dispositivo",
"ta_publicado",
"ta",
)
def int_to_roman(int_value):
@ -32,8 +34,7 @@ def int_to_roman(int_value):
if not 0 < int_value < 4000:
raise ValueError("Argument must be between 1 and 3999")
ints = (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1)
nums = ('M', 'CM', 'D', 'CD', 'C', 'XC',
'L', 'XL', 'X', 'IX', 'V', 'IV', 'I')
nums = ("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I")
result = ""
for i in range(len(ints)):
count = int(int_value / ints[i])
@ -43,9 +44,9 @@ def int_to_roman(int_value):
def int_to_letter(int_value):
result = ''
result = ""
if not int_value:
return '0'
return "0"
int_value -= 1
while int_value >= 26:
rest = int_value % 26
@ -59,10 +60,10 @@ def get_integrations_view_names():
result = []
modules = sys.modules
for key, value in modules.items():
if key.endswith('.views'):
if key.endswith(".views"):
for v in value.__dict__.values():
if hasattr(v, '__bases__'):
if hasattr(v, "__bases__"):
for base in v.__bases__:
if 'IntegracaoTaView' in str(base):
if "IntegracaoTaView" in str(base):
result.append(v)
return result

3087
sapl/compilacao/views.py

File diff suppressed because it is too large

39
sapl/context_processors.py

@ -2,13 +2,15 @@ import logging
from django.utils.translation import ugettext_lazy as _
from sapl.utils import google_recaptcha_configured as \
google_recaptcha_configured_utils, sapn_is_enabled, cached_call, get_base_url
from sapl.utils import cached_call, get_base_url
from sapl.utils import google_recaptcha_configured as google_recaptcha_configured_utils
from sapl.utils import mail_service_configured as mail_service_configured_utils
from sapl.utils import sapn_is_enabled
def parliament_info(request):
from sapl.base.views import get_casalegislativa
casa = get_casalegislativa()
if casa:
return casa.__dict__
@ -19,32 +21,37 @@ def parliament_info(request):
def mail_service_configured(request):
if not mail_service_configured_utils(request):
logger = logging.getLogger(__name__)
logger.warning(_('Servidor de email não configurado.'))
return {'mail_service_configured': False}
return {'mail_service_configured': True}
logger.warning(_("Servidor de email não configurado."))
return {"mail_service_configured": False}
return {"mail_service_configured": True}
def google_recaptcha_configured(request):
if not google_recaptcha_configured_utils():
logger = logging.getLogger(__name__)
logger.warning(_('Google Recaptcha não configurado.'))
return {'google_recaptcha_configured': False}
return {'google_recaptcha_configured': True}
logger.warning(_("Google Recaptcha não configurado."))
return {"google_recaptcha_configured": False}
return {"google_recaptcha_configured": True}
@cached_call("site-title", timeout=60 * 2)
def enable_sapn(request):
verbose_name = _('Sistema de Apoio ao Processo Legislativo') \
if not sapn_is_enabled() \
else _('Sistema de Apoio à Publicação de Leis e Normas')
verbose_name = (
_("Sistema de Apoio ao Processo Legislativo")
if not sapn_is_enabled()
else _("Sistema de Apoio à Publicação de Leis e Normas")
)
from sapl.base.models import CasaLegislativa
casa_legislativa = CasaLegislativa.objects.first()
nome_casa = casa_legislativa.nome if casa_legislativa and casa_legislativa.nome else ''
nome_casa = (
casa_legislativa.nome if casa_legislativa and casa_legislativa.nome else ""
)
return {
'sapl_as_sapn': sapn_is_enabled(),
'nome_sistema': verbose_name,
'nome_casa': nome_casa,
'base_url': get_base_url(request),
"sapl_as_sapn": sapn_is_enabled(),
"nome_sistema": verbose_name,
"nome_casa": nome_casa,
"base_url": get_base_url(request),
}

273
sapl/crispy_layout_mixin.py

@ -1,5 +1,6 @@
from math import ceil
import yaml
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Div, Fieldset, Layout, Submit
@ -9,7 +10,6 @@ from django.urls import reverse, reverse_lazy
from django.utils import formats
from django.utils.encoding import force_text
from django.utils.translation import ugettext as _
import yaml
def heads_and_tails(list_of_lists):
@ -19,11 +19,11 @@ def heads_and_tails(list_of_lists):
def to_column(name_span):
fieldname, span = name_span
return Div(fieldname, css_class='col-md-%d' % span)
return Div(fieldname, css_class="col-md-%d" % span)
def to_row(names_spans):
return Div(*map(to_column, names_spans), css_class='row')
return Div(*map(to_column, names_spans), css_class="row")
def to_fieldsets(fields):
@ -36,21 +36,28 @@ def to_fieldsets(fields):
yield field
def form_actions(more=[Div(css_class='clearfix')],
label=_('Salvar'), name='salvar',
css_class='float-right', disabled=True):
if disabled and force_text(label) != 'Pesquisar':
doubleclick = 'this.form.submit();this.disabled=true;'
def form_actions(
more=[Div(css_class="clearfix")],
label=_("Salvar"),
name="salvar",
css_class="float-right",
disabled=True,
):
if disabled and force_text(label) != "Pesquisar":
doubleclick = "this.form.submit();this.disabled=true;"
else:
doubleclick = 'return true;'
doubleclick = "return true;"
return FormActions(
*more,
Submit(name, label, css_class=css_class,
Submit(
name,
label,
css_class=css_class,
# para impedir resubmissão do form
onclick=doubleclick),
css_class='form-group row justify-content-between'
onclick=doubleclick,
),
css_class="form-group row justify-content-between"
)
@ -84,16 +91,22 @@ class SaplFormHelper(FormHelper):
class SaplFormLayout(Layout):
def __init__(self, *fields, cancel_label=_('Cancelar'),
save_label=_('Salvar'), actions=None):
def __init__(
self, *fields, cancel_label=_("Cancelar"), save_label=_("Salvar"), actions=None
):
buttons = actions
if not buttons:
buttons = form_actions(label=save_label, more=[
HTML('<a href="{{ view.cancel_url }}"'
' class="btn btn-dark">%s</a>' % cancel_label)
if cancel_label else None])
buttons = form_actions(
label=save_label,
more=[
HTML(
'<a href="{{ view.cancel_url }}"'
' class="btn btn-dark">%s</a>' % cancel_label
)
if cancel_label
else None
],
)
_fields = list(to_fieldsets(fields))
if buttons:
@ -102,7 +115,7 @@ class SaplFormLayout(Layout):
def get_field_display(obj, fieldname):
field = ''
field = ""
try:
field = obj._meta.get_field(fieldname)
except Exception as e:
@ -114,14 +127,13 @@ def get_field_display(obj, fieldname):
try:
verbose_name = value.model._meta.verbose_name
except AttributeError:
verbose_name = ''
verbose_name = ""
else:
verbose_name = str(field.verbose_name)\
if hasattr(field, 'verbose_name') else ''
verbose_name = str(field.verbose_name) if hasattr(field, "verbose_name") else ""
if hasattr(field, 'choices') and field.choices:
value = getattr(obj, 'get_%s_display' % fieldname)()
if hasattr(field, "choices") and field.choices:
value = getattr(obj, "get_%s_display" % fieldname)()
else:
value = getattr(obj, fieldname)
@ -129,89 +141,94 @@ def get_field_display(obj, fieldname):
str_type_from_field = str(type(field))
if value is None:
display = ''
elif '.date' in str_type_from_value:
display = ""
elif ".date" in str_type_from_value:
display = formats.date_format(value, "SHORT_DATE_FORMAT")
elif 'bool' in str_type_from_value:
display = _('Sim') if value else _('Não')
elif 'ImageFieldFile' in str(type(value)):
elif "bool" in str_type_from_value:
display = _("Sim") if value else _("Não")
elif "ImageFieldFile" in str(type(value)):
if value:
display = '<img src="{}" />'.format(value.url)
else:
display = ''
elif 'FieldFile' in str_type_from_value:
display = ""
elif "FieldFile" in str_type_from_value:
if value:
display = '<a href="{}">{}</a>'.format(
value.url,
value.name.split('/')[-1:][0])
value.url, value.name.split("/")[-1:][0]
)
else:
display = ''
elif 'ManyRelatedManager' in str_type_from_value\
or 'RelatedManager' in str_type_from_value\
or 'GenericRelatedObjectManager' in str_type_from_value:
display = '<ul>'
display = ""
elif (
"ManyRelatedManager" in str_type_from_value
or "RelatedManager" in str_type_from_value
or "GenericRelatedObjectManager" in str_type_from_value
):
display = "<ul>"
for v in value.all():
display += '<li>%s</li>' % str(v)
display += '</ul>'
display += "<li>%s</li>" % str(v)
display += "</ul>"
if not verbose_name:
if hasattr(field, 'related_model'):
verbose_name = str(
field.related_model._meta.verbose_name_plural)
elif hasattr(field, 'model'):
if hasattr(field, "related_model"):
verbose_name = str(field.related_model._meta.verbose_name_plural)
elif hasattr(field, "model"):
verbose_name = str(field.model._meta.verbose_name_plural)
elif 'GenericForeignKey' in str_type_from_field:
elif "GenericForeignKey" in str_type_from_field:
display = '<a href="{}">{}</a>'.format(
reverse(
'%s:%s_detail' % (
value._meta.app_config.name, obj.content_type.model),
args=(value.id,)),
value)
elif 'TextField' in str_type_from_field:
display = value.replace('\n', '<br/>')
"%s:%s_detail" % (value._meta.app_config.name, obj.content_type.model),
args=(value.id,),
),
value,
)
elif "TextField" in str_type_from_field:
display = value.replace("\n", "<br/>")
display = '<div class="dont-break-out">{}</div>'.format(display)
else:
display = str(value)
return verbose_name, display or '&nbsp;'
return verbose_name, display or "&nbsp;"
class CrispyLayoutFormMixin:
@property
def layout_key(self):
if hasattr(super(CrispyLayoutFormMixin, self), 'layout_key'):
if hasattr(super(CrispyLayoutFormMixin, self), "layout_key"):
return super(CrispyLayoutFormMixin, self).layout_key
else:
return self.model.__name__
@property
def layout_key_set(self):
if hasattr(super(CrispyLayoutFormMixin, self), 'layout_key_set'):
if hasattr(super(CrispyLayoutFormMixin, self), "layout_key_set"):
return super(CrispyLayoutFormMixin, self).layout_key_set
else:
obj = self.crud if hasattr(self, 'crud') else self
return getattr(obj.model,
obj.model_set).field.model.__name__
obj = self.crud if hasattr(self, "crud") else self
return getattr(obj.model, obj.model_set).field.model.__name__
def get_layout(self, yaml_layout=None):
if not yaml_layout:
yaml_layout = '%s/layouts.yaml' % self.model._meta.app_config.label
yaml_layout = "%s/layouts.yaml" % self.model._meta.app_config.label
return read_layout_from_yaml(yaml_layout, self.layout_key)
def get_layout_set(self):
obj = self.crud if hasattr(self, 'crud') else self
yaml_layout = '%s/layouts.yaml' % getattr(
obj.model, obj.model_set).field.model._meta.app_config.label
obj = self.crud if hasattr(self, "crud") else self
yaml_layout = (
"%s/layouts.yaml"
% getattr(obj.model, obj.model_set).field.model._meta.app_config.label
)
return read_layout_from_yaml(yaml_layout, self.layout_key_set)
@property
def fields(self):
if hasattr(self, 'form_class') and self.form_class:
if hasattr(self, "form_class") and self.form_class:
return None
else:
'''Returns all fields in the layout'''
return [fieldname for legend_rows in self.get_layout()
"""Returns all fields in the layout"""
return [
fieldname
for legend_rows in self.get_layout()
for row in legend_rows[1:]
for fieldname, span in row]
for fieldname, span in row
]
def get_form(self, form_class=None):
try:
@ -230,24 +247,24 @@ class CrispyLayoutFormMixin:
@property
def list_field_names(self):
'''The list of field names to display on table
"""The list of field names to display on table
This base implementation returns the field names
in the first fieldset of the layout.
'''
obj = self.crud if hasattr(self, 'crud') else self
if hasattr(obj, 'list_field_names') and obj.list_field_names:
"""
obj = self.crud if hasattr(self, "crud") else self
if hasattr(obj, "list_field_names") and obj.list_field_names:
return obj.list_field_names
rows = self.get_layout()[0][1:]
return [fieldname for row in rows for fieldname, __ in row]
@property
def list_field_names_set(self):
'''The list of field names to display on table
"""The list of field names to display on table
This base implementation returns the field names
in the first fieldset of the layout.
'''
"""
rows = self.get_layout_set()[0][1:]
return [fieldname for row in rows for fieldname, __ in row]
@ -255,65 +272,65 @@ class CrispyLayoutFormMixin:
obj = self.get_object()
func = None
if '|' in fieldname:
fieldname, func = tuple(fieldname.split('|'))
if "|" in fieldname:
fieldname, func = tuple(fieldname.split("|"))
try:
verbose_name, field_display = get_field_display(obj, fieldname)
except:
verbose_name, field_display = '', ''
verbose_name, field_display = "", ""
if func:
verbose_name, field_display = getattr(self, func)(obj, fieldname)
hook_fieldname = 'hook_%s' % fieldname
hook_fieldname = "hook_%s" % fieldname
if hasattr(self, hook_fieldname):
try:
verbose_name, field_display = getattr(
self, hook_fieldname)(obj, verbose_name=verbose_name, field_display=field_display)
verbose_name, field_display = getattr(self, hook_fieldname)(
obj, verbose_name=verbose_name, field_display=field_display
)
except:
verbose_name, field_display = getattr(
self, hook_fieldname)(obj)
verbose_name, field_display = getattr(self, hook_fieldname)(obj)
elif not func:
verbose_name, field_display = get_field_display(obj, fieldname)
return {
'id': fieldname,
'span': span,
'verbose_name': verbose_name,
'text': field_display,
"id": fieldname,
"span": span,
"verbose_name": verbose_name,
"text": field_display,
}
def fk_urlify_for_detail(self, obj, fieldname):
field = obj._meta.get_field(fieldname)
value = getattr(obj, fieldname)
display = '<a href="{}">{}</a>'.format(
reverse(
'%s:%s_detail' % (
value._meta.app_config.name, value._meta.model_name),
args=(value.id,)),
value)
"%s:%s_detail" % (value._meta.app_config.name, value._meta.model_name),
args=(value.id,),
),
value,
)
return field.verbose_name, display
def fk_urlify_for_list(self, obj, field):
value = getattr(obj, field)
return reverse(
'%s:%s_detail' % (
value._meta.app_config.name,
value._meta.model_name),
kwargs={'pk': value.id}),
return (
reverse(
"%s:%s_detail" % (value._meta.app_config.name, value._meta.model_name),
kwargs={"pk": value.id},
),
)
def m2m_urlize_for_detail(self, obj, fieldname):
manager, fieldname = tuple(fieldname.split('__'))
manager, fieldname = tuple(fieldname.split("__"))
manager = getattr(obj, manager)
verbose_name = manager.model._meta.verbose_name
display = ''
display = ""
for item in manager.all():
obj_m2m = getattr(item, fieldname)
@ -324,44 +341,50 @@ class CrispyLayoutFormMixin:
display += '<li><a href="{}">{}</a></li>'.format(
reverse(
'%s:%s_detail' % (
obj_m2m._meta.app_config.name, obj_m2m._meta.model_name),
args=(obj_m2m.id,)),
obj_m2m)
"%s:%s_detail"
% (obj_m2m._meta.app_config.name, obj_m2m._meta.model_name),
args=(obj_m2m.id,),
),
obj_m2m,
)
display += ''
display += ""
if display:
display = '<ul>%s</ul>' % display
display = "<ul>%s</ul>" % display
else:
verbose_name = ''
verbose_name = ""
return verbose_name, display
def widget__signs(self, obj, fieldname):
from sapl.base.models import Metadata
try:
md = Metadata.objects.get(
content_type=ContentType.objects.get_for_model(
obj._meta.model),
object_id=obj.id,)
autores = md.metadata['signs'][fieldname]['autores']
t = template.loader.get_template('base/widget__signs.html')
rendered = str(t.render(context={'signs': autores}))
content_type=ContentType.objects.get_for_model(obj._meta.model),
object_id=obj.id,
)
autores = md.metadata["signs"][fieldname]["autores"]
t = template.loader.get_template("base/widget__signs.html")
rendered = str(t.render(context={"signs": autores}))
except Exception as e:
return '', ''
return "", ""
return 'Assinaturas Eletrônicas', rendered
return "Assinaturas Eletrônicas", rendered
@property
def layout_display(self):
return [
{'legend': legend,
'rows': [[self.get_column(fieldname, span)
for fieldname, span in row]
for row in rows]
} for legend, rows in heads_and_tails(self.get_layout())]
{
"legend": legend,
"rows": [
[self.get_column(fieldname, span) for fieldname, span in row]
for row in rows
],
}
for legend, rows in heads_and_tails(self.get_layout())
]
def read_yaml_from_file(yaml_layout):
@ -381,7 +404,7 @@ def read_layout_from_yaml(yaml_layout, key):
base = yaml[key]
def line_to_namespans(line):
split = [cell.split(':') for cell in line.split()]
split = [cell.split(":") for cell in line.split()]
namespans = [[s[0], int(s[1]) if len(s) > 1 else 0] for s in split]
remaining = 12 - sum(s for n, s in namespans)
nondefined = [ns for ns in namespans if not ns[1]]
@ -392,5 +415,7 @@ def read_layout_from_yaml(yaml_layout, key):
remaining = remaining - span
return list(map(tuple, namespans))
return [[legend] + [line_to_namespans(l) for l in lines]
for legend, lines in base.items()]
return [
[legend] + [line_to_namespans(l) for l in lines]
for legend, lines in base.items()
]

1079
sapl/crud/base.py

File diff suppressed because it is too large

68
sapl/crud/tests/settings.py

@ -3,68 +3,72 @@ from os.path import dirname, join
BASE_DIR = dirname(dirname(dirname(__file__)))
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": ":memory:",
},
}
INSTALLED_APPS = (
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.messages',
'django.contrib.sessions',
'crud.tests.stub_app',
'crispy_forms',
"django.contrib.contenttypes",
"django.contrib.auth",
"django.contrib.messages",
"django.contrib.sessions",
"crud.tests.stub_app",
"crispy_forms",
)
ROOT_URLCONF = 'crud.tests.stub_app.urls'
ROOT_URLCONF = "crud.tests.stub_app.urls"
USE_TZ = True
SECRET_KEY = 'zzz...'
SECRET_KEY = "zzz..."
TEMPLATES = [{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [join(BASE_DIR, 'crud/tests/stub_app/templates'),
join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
join(BASE_DIR, "crud/tests/stub_app/templates"),
join(BASE_DIR, "templates"),
],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"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.contrib.messages.context_processors.messages',
"django.contrib.messages.context_processors.messages",
],
},
}]
}
]
STATIC_URL = '/static/'
STATIC_URL = "/static/"
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
"django.contrib.sessions.middleware.SessionMiddleware",
# 'django.middleware.locale.LocaleMiddleware',
# 'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
"django.contrib.auth.middleware.AuthenticationMiddleware",
# 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
"django.contrib.messages.middleware.MessageMiddleware",
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 'django.middleware.security.SecurityMiddleware',
)
SILENCED_SYSTEM_CHECKS = [
'1_7.W001', # Unset MIDDLEWARE_CLASSES warning
"1_7.W001", # Unset MIDDLEWARE_CLASSES warning
]
TIME_ZONE = 'America/Sao_Paulo'
TIME_ZONE = "America/Sao_Paulo"
USE_I18N = True
USE_L10N = False
USE_TZ = True
# DATE_FORMAT = 'N j, Y'
DATE_FORMAT = 'd/m/Y'
SHORT_DATE_FORMAT = 'd/m/Y'
DATE_INPUT_FORMATS = ('%d/%m/%Y', '%m-%d-%Y', '%Y-%m-%d')
DATE_FORMAT = "d/m/Y"
SHORT_DATE_FORMAT = "d/m/Y"
DATE_INPUT_FORMATS = ("%d/%m/%Y", "%m-%d-%Y", "%Y-%m-%d")

6
sapl/crud/tests/stub_app/models.py

@ -11,13 +11,13 @@ class Continent(models.Model):
class Country(models.Model):
name = models.CharField(max_length=50)
continent = models.ForeignKey(Continent)
is_cold = models.BooleanField(choices=[(True, 'Yes'), (False, 'No')])
is_cold = models.BooleanField(choices=[(True, "Yes"), (False, "No")])
population = models.PositiveIntegerField(blank=True, null=True)
description = models.TextField(blank=True)
class Meta:
verbose_name = 'Country'
verbose_name_plural = 'Countries'
verbose_name = "Country"
verbose_name_plural = "Countries"
def __str__(self):
return self.name

5
sapl/crud/tests/stub_app/urls.py

@ -3,6 +3,7 @@ from django.conf.urls import include, url
from .views import CityCrud, CountryCrud
urlpatterns = [
url(r'^country/', include(
CountryCrud.get_urls() + CityCrud.get_urls(), 'stub_app')),
url(
r"^country/", include(CountryCrud.get_urls() + CityCrud.get_urls(), "stub_app")
),
]

4
sapl/crud/tests/stub_app/views.py

@ -6,7 +6,7 @@ from .models import City, Country
class CountryCrud(Crud):
model = Country
help_topic = 'help_topic',
help_topic = ("help_topic",)
class ListView(CrudListView):
paginate_by = 10
@ -14,4 +14,4 @@ class CountryCrud(Crud):
class CityCrud(MasterDetailCrud):
model = City
help_topic = 'help_topic',
help_topic = ("help_topic",)

2
sapl/crud/tests/test_base.py

@ -1,7 +1,7 @@
from django.core.urlresolvers import reverse
from model_bakery import baker
get_field_display, make_pagination)
from sapl.crud.base import get_field_display, make_pagination
from sapl.crud.tests.stub_app.models import Continent, Country
from sapl.crud.tests.stub_app.views import CountryCrud

17
sapl/crud/tests/test_masterdetail.py

@ -2,13 +2,16 @@ import pytest
from django.urls import reverse
@pytest.mark.parametrize('path_name', [
'/country/1/city stub_app:city_list',
'/country/1/city/create stub_app:city_create',
'/country/city/1 stub_app:city_detail',
'/country/city/1/edit stub_app:city_update',
'/country/city/1/delete stub_app:city_delete',
])
@pytest.mark.parametrize(
"path_name",
[
"/country/1/city stub_app:city_list",
"/country/1/city/create stub_app:city_create",
"/country/city/1 stub_app:city_detail",
"/country/city/1/edit stub_app:city_update",
"/country/city/1/delete stub_app:city_delete",
],
)
def test_reverse(path_name):
path, name = path_name.split()
assert path == reverse(name, args=(1,))

2
sapl/crud/urls.py

@ -1,5 +1,5 @@
from django.conf.urls import include, url
urlpatterns = [
url(r'', include('stub_app.urls')),
url(r"", include("stub_app.urls")),
]

23
sapl/decorators.py

@ -18,26 +18,21 @@ def vigencia_atual(decorated_method):
def display_atual(self):
string_displayed = decorated_method(self)
if hasattr(self, 'data_inicio') and hasattr(self, 'data_fim'):
if hasattr(self, "data_inicio") and hasattr(self, "data_fim"):
today = timezone.now().today().date()
e_atual = self.data_inicio <= today <= self.data_fim
string_displayed = "{} {}".format(
string_displayed, "(Atual)" if e_atual else "")
string_displayed, "(Atual)" if e_atual else ""
)
else:
instancia_sem_atributo = "{} [{}, {}].".format(
'Instância não possui os atributos',
'data_inicio',
'data_fim')
"Instância não possui os atributos", "data_inicio", "data_fim"
)
mensagem_decorator = "Decorator @{} foi desabilitado.".format(
vigencia_atual.__name__()
)
print(_('{} {}'.format(
_(instancia_sem_atributo),
_(mensagem_decorator)
)
)
)
print(_("{} {}".format(_(instancia_sem_atributo), _(mensagem_decorator))))
return string_displayed
@ -59,13 +54,13 @@ def receiver_multi_senders(signal, **kwargs):
"""
def _decorator(func):
senders = kwargs.get('senders', [])
senders = kwargs.get("senders", [])
if isinstance(signal, (list, tuple)):
if not senders:
for s in signal:
s.connect(func, **kwargs)
else:
senders = kwargs.pop('senders')
senders = kwargs.pop("senders")
for sender in senders:
for s in signal:
s.connect(func, sender=sender, **kwargs)
@ -74,7 +69,7 @@ def receiver_multi_senders(signal, **kwargs):
if not senders:
signal.connect(func, **kwargs)
else:
senders = kwargs.pop('senders')
senders = kwargs.pop("senders")
for sender in senders:
signal.connect(func, sender=sender, **kwargs)

33
sapl/endpoint_restriction_middleware.py

@ -1,24 +1,25 @@
from django.http import HttpResponseForbidden
import logging
from django.http import HttpResponseForbidden
# lista de IPs permitidos (localhost, redes locais, etc)
# https://en.wikipedia.org/wiki/Reserved_IP_addresses
ALLOWED_IPS = [
'127.0.0.1',
'::1',
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'fc00::/7',
'::1',
'fe80::/10',
'192.0.2.0/24',
'2001:db8::/32',
'224.0.0.0/4',
'ff00::/8'
"127.0.0.1",
"::1",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"fc00::/7",
"::1",
"fe80::/10",
"192.0.2.0/24",
"2001:db8::/32",
"224.0.0.0/4",
"ff00::/8",
]
RESTRICTED_ENDPOINTS = ['/metrics']
RESTRICTED_ENDPOINTS = ["/metrics"]
class EndpointRestrictionMiddleware:
@ -29,10 +30,10 @@ class EndpointRestrictionMiddleware:
def __call__(self, request):
# IP do cliente
client_ip = request.META.get('REMOTE_ADDR')
client_ip = request.META.get("REMOTE_ADDR")
# bloqueia acesso a endpoints restritos para IPs nao permitidos
if request.path in RESTRICTED_ENDPOINTS and client_ip not in ALLOWED_IPS:
return HttpResponseForbidden('Acesso proibido')
return HttpResponseForbidden("Acesso proibido")
return self.get_response(request)

4
sapl/hashers.py

@ -6,7 +6,7 @@ from django.utils.encoding import force_bytes
def to_base64(source):
return base64.b64encode(source).decode('utf-8')
return base64.b64encode(source).decode("utf-8")
class ZopeSHA1PasswordHasher(PBKDF2PasswordHasher):
@ -40,7 +40,7 @@ def get_salt_from_zope_sha1(data):
return to_base64(salt)
ZOPE_SHA1_PREFIX = '{SSHA}'
ZOPE_SHA1_PREFIX = "{SSHA}"
def zope_encoded_password_to_django(encoded):

332
sapl/lexml/OAIServer.py

@ -10,9 +10,8 @@ from django.utils import timezone
from lxml import etree
from lxml.builder import ElementMaker
from sapl.base.models import AppConfig, CasaLegislativa
from sapl.lexml.models import LexmlPublicador, LexmlProvedor
from sapl.lexml.models import LexmlProvedor, LexmlPublicador
from sapl.norma.models import NormaJuridica
from sapl.utils import LISTA_DE_UFS
@ -25,13 +24,17 @@ class OAILEXML:
def __init__(self, prefix):
self.prefix = prefix
self.ns = {'oai_lexml': 'http://www.lexml.gov.br/oai_lexml', }
self.schemas = {'oai_lexml': 'http://projeto.lexml.gov.br/esquemas/oai_lexml.xsd'}
self.ns = {
"oai_lexml": "http://www.lexml.gov.br/oai_lexml",
}
self.schemas = {
"oai_lexml": "http://projeto.lexml.gov.br/esquemas/oai_lexml.xsd"
}
def __call__(self, element, metadata):
data = metadata.record
if data.get('metadata'):
value = etree.XML(data['metadata'])
if data.get("metadata"):
value = etree.XML(data["metadata"])
element.append(value)
@ -41,48 +44,58 @@ class OAIServer:
Underlying code is based on pyoai's oaipmh.server'
"""
XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance'
ns = {'lexml': 'http://www.lexml.gov.br/oai_lexml'}
schema = {'oai_lexml': 'http://projeto.lexml.gov.br/esquemas/oai_lexml.xsd'}
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
ns = {"lexml": "http://www.lexml.gov.br/oai_lexml"}
schema = {"oai_lexml": "http://projeto.lexml.gov.br/esquemas/oai_lexml.xsd"}
def __init__(self, config={}):
self.config = config
def identify(self):
result = oaipmh.common.Identify(
repositoryName=self.config['titulo'],
baseURL=self.config['base_url'],
protocolVersion='2.0',
adminEmails=self.config['email'],
repositoryName=self.config["titulo"],
baseURL=self.config["base_url"],
protocolVersion="2.0",
adminEmails=self.config["email"],
earliestDatestamp=datetime(2001, 1, 1, 10, 00),
deletedRecord='transient',
granularity='YYYY-MM-DDThh:mm:ssZ',
compression=['identity'],
toolkit_description=False)
if self.config.get('descricao'):
result.add_description(self.config['descricao'])
deletedRecord="transient",
granularity="YYYY-MM-DDThh:mm:ssZ",
compression=["identity"],
toolkit_description=False,
)
if self.config.get("descricao"):
result.add_description(self.config["descricao"])
return result
def create_header_and_metadata(self, record):
header = self.create_header(record)
metadata = oaipmh.common.Metadata(None, record['metadata'])
metadata = oaipmh.common.Metadata(None, record["metadata"])
metadata.record = record
return header, metadata
def list_query(self, from_=None, until=None, offset=0, batch_size=10, identifier=None):
def list_query(
self, from_=None, until=None, offset=0, batch_size=10, identifier=None
):
if identifier:
identifier = int(identifier.split('/')[-1]) # Get internal id
identifier = int(identifier.split("/")[-1]) # Get internal id
else:
identifier = ''
identifier = ""
until = datetime.now() if not until or until > datetime.now() else until
return self.oai_query(offset=offset, batch_size=batch_size, from_=from_, until=until,
identifier=identifier)
return self.oai_query(
offset=offset,
batch_size=batch_size,
from_=from_,
until=until,
identifier=identifier,
)
def check_metadata_prefix(self, metadata_prefix):
if not metadata_prefix in self.config['metadata_prefixes']:
if not metadata_prefix in self.config["metadata_prefixes"]:
raise oaipmh.error.CannotDisseminateFormatError
def listRecords(self, metadataPrefix, from_=None, until=None, cursor=0, batch_size=10):
def listRecords(
self, metadataPrefix, from_=None, until=None, cursor=0, batch_size=10
):
self.check_metadata_prefix(metadataPrefix)
for record in self.list_query(from_, until, cursor, batch_size):
header, metadata = self.create_header_and_metadata(record)
@ -92,11 +105,15 @@ class OAIServer:
return "oai:{}".format(internal_id)
def create_header(self, record):
oai_id = self.get_oai_id(record['record']['id'])
timestamp = record['record']['when_modified'] if record['record']['when_modified'] else datetime.now()
oai_id = self.get_oai_id(record["record"]["id"])
timestamp = (
record["record"]["when_modified"]
if record["record"]["when_modified"]
else datetime.now()
)
timestamp = timestamp.replace(tzinfo=None)
sets = []
deleted = record['record']['deleted']
deleted = record["record"]["deleted"]
return oaipmh.common.Header(None, oai_id, timestamp, sets, deleted)
def get_esfera_federacao(self):
@ -104,129 +121,179 @@ class OAIServer:
return appconfig.esfera_federacao
def recupera_norma(self, offset, batch_size, from_, until, identifier, esfera):
kwargs = {'data__isnull': False,
'esfera_federacao__isnull': False,
'timestamp__isnull': False,
'timestamp__lte': until}
kwargs = {
"data__isnull": False,
"esfera_federacao__isnull": False,
"timestamp__isnull": False,
"timestamp__lte": until,
}
if from_:
kwargs['timestamp__gte'] = from_
kwargs["timestamp__gte"] = from_
if identifier:
kwargs['numero'] = identifier
kwargs["numero"] = identifier
if esfera:
kwargs['esfera_federacao'] = esfera
return NormaJuridica.objects.select_related('tipo').filter(**kwargs)[offset:offset + batch_size]
kwargs["esfera_federacao"] = esfera
return NormaJuridica.objects.select_related("tipo").filter(**kwargs)[
offset : offset + batch_size
]
def monta_id(self, norma):
if norma:
num = len(casa.endereco_web.split('.'))
dominio = '.'.join(casa.endereco_web.split('.')[1:num])
prefixo_oai = '{}.{}:sapl/'.format(casa.sigla.lower(), dominio)
num = len(casa.endereco_web.split("."))
dominio = ".".join(casa.endereco_web.split(".")[1:num])
prefixo_oai = "{}.{}:sapl/".format(casa.sigla.lower(), dominio)
numero_interno = norma.numero
tipo_norma = norma.tipo.equivalente_lexml
ano_norma = norma.ano
identificador = '{}{};{};{}'.format(prefixo_oai, tipo_norma, ano_norma, numero_interno)
identificador = "{}{};{};{}".format(
prefixo_oai, tipo_norma, ano_norma, numero_interno
)
return identificador
else:
return None
@staticmethod
def remove_acentos(linha):
res = unicodedata.normalize('NFKD', linha).encode('ASCII', 'ignore')
res = unicodedata.normalize("NFKD", linha).encode("ASCII", "ignore")
res = res.decode("UTF-8")
remove_list = ["\'", "\"", "-"]
remove_list = ["'", '"', "-"]
for i in remove_list:
res = res.replace(i, "")
return res
def monta_urn(self, norma, esfera):
if norma:
urn = 'urn:lex:br;'
esferas = {'M': 'municipal', 'E': 'estadual'}
urn = "urn:lex:br;"
esferas = {"M": "municipal", "E": "estadual"}
municipio = self.remove_acentos(casa.municipio.lower())
uf_map = dict(LISTA_DE_UFS)
uf_desc = uf_map.get(casa.uf.upper(), '').lower()
uf_desc = uf_map.get(casa.uf.upper(), "").lower()
uf_desc = self.remove_acentos(uf_desc)
for x in [' ', '.de.', '.da.', '.das.', '.do.', '.dos.']:
municipio = municipio.replace(x, '.')
uf_desc = uf_desc.replace(x, '.')
if esfera == 'M':
urn += '{};{}:'.format(uf_desc, municipio)
if norma.tipo.equivalente_lexml == 'regimento.interno' or norma.tipo.equivalente_lexml == 'resolucao':
urn += 'camara.'
urn += esferas[esfera] + ':'
elif esfera == 'E':
urn += '{}:{}:'.format(uf_desc, esferas[esfera])
for x in [" ", ".de.", ".da.", ".das.", ".do.", ".dos."]:
municipio = municipio.replace(x, ".")
uf_desc = uf_desc.replace(x, ".")
if esfera == "M":
urn += "{};{}:".format(uf_desc, municipio)
if (
norma.tipo.equivalente_lexml == "regimento.interno"
or norma.tipo.equivalente_lexml == "resolucao"
):
urn += "camara."
urn += esferas[esfera] + ":"
elif esfera == "E":
urn += "{}:{}:".format(uf_desc, esferas[esfera])
else:
urn += ':'
urn += ":"
if norma.tipo.equivalente_lexml:
urn += '{}:{};'.format(norma.tipo.equivalente_lexml, norma.data.isoformat())
urn += "{}:{};".format(
norma.tipo.equivalente_lexml, norma.data.isoformat()
)
else:
urn += '{};'.format(norma.data.isoformat())
if norma.tipo.equivalente_lexml == 'lei.organica' or norma.tipo.equivalente_lexml == 'constituicao':
urn += "{};".format(norma.data.isoformat())
if (
norma.tipo.equivalente_lexml == "lei.organica"
or norma.tipo.equivalente_lexml == "constituicao"
):
urn += str(norma.ano)
else:
urn += str(norma.numero)
if norma.data_vigencia and norma.data_publicacao:
urn += '@{};publicacao;{}'.format(norma.data_vigencia.isoformat(), norma.data_publicacao.isoformat())
urn += "@{};publicacao;{}".format(
norma.data_vigencia.isoformat(), norma.data_publicacao.isoformat()
)
elif norma.data_publicacao:
urn += '@inicio.vigencia;publicacao;{}'.format(norma.data_publicacao.isoformat())
urn += "@inicio.vigencia;publicacao;{}".format(
norma.data_publicacao.isoformat()
)
return urn
else:
return None
def data_por_extenso(self, data):
data = data.strftime('%d-%m-%Y')
if data != '':
meses = {1: 'Janeiro', 2: 'Fevereiro', 3: 'Março', 4: 'Abril', 5: 'Maio', 6: 'Junho', 7: 'Julho',
8: 'Agosto', 9: 'Setembro', 10: 'Outubro', 11: 'Novembro', 12: 'Dezembro'}
return '{} de {} de {}'.format(data[0:2], meses[int(data[3:5])], data[6:])
data = data.strftime("%d-%m-%Y")
if data != "":
meses = {
1: "Janeiro",
2: "Fevereiro",
3: "Março",
4: "Abril",
5: "Maio",
6: "Junho",
7: "Julho",
8: "Agosto",
9: "Setembro",
10: "Outubro",
11: "Novembro",
12: "Dezembro",
}
return "{} de {} de {}".format(data[0:2], meses[int(data[3:5])], data[6:])
else:
return ''
return ""
def monta_xml(self, urn, norma):
BASE_URL_SAPL = self.config['base_url']
BASE_URL_SAPL = BASE_URL_SAPL[:BASE_URL_SAPL.find('/', 8)]
BASE_URL_SAPL = self.config["base_url"]
BASE_URL_SAPL = BASE_URL_SAPL[: BASE_URL_SAPL.find("/", 8)]
publicador = LexmlPublicador.objects.first()
if norma and publicador:
LEXML = ElementMaker(namespace=self.ns['lexml'], nsmap=self.ns)
LEXML = ElementMaker(namespace=self.ns["lexml"], nsmap=self.ns)
oai_lexml = LEXML.LexML()
oai_lexml.attrib['{{{pre}}}schemaLocation'.format(pre=self.XSI_NS)] = '{} {}'.format(
'http://www.lexml.gov.br/oai_lexml', 'http://projeto.lexml.gov.br/esquemas/oai_lexml.xsd')
oai_lexml.attrib[
"{{{pre}}}schemaLocation".format(pre=self.XSI_NS)
] = "{} {}".format(
"http://www.lexml.gov.br/oai_lexml",
"http://projeto.lexml.gov.br/esquemas/oai_lexml.xsd",
)
texto_integral = norma.texto_integral
mime_types = {'doc': 'application/msword',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'odt': 'application/vnd.oasis.opendocument.text',
'pdf': 'application/pdf',
'rtf': 'application/rtf'}
mime_types = {
"doc": "application/msword",
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"odt": "application/vnd.oasis.opendocument.text",
"pdf": "application/pdf",
"rtf": "application/rtf",
}
if texto_integral:
url_conteudo = BASE_URL_SAPL + texto_integral.url
extensao = texto_integral.url.split('.')[-1]
formato = mime_types.get(extensao, 'application/octet-stream')
extensao = texto_integral.url.split(".")[-1]
formato = mime_types.get(extensao, "application/octet-stream")
else:
formato = 'text/html'
url_conteudo = BASE_URL_SAPL + reverse('sapl.norma:normajuridica_detail',
kwargs={'pk': norma.pk})
formato = "text/html"
url_conteudo = BASE_URL_SAPL + reverse(
"sapl.norma:normajuridica_detail", kwargs={"pk": norma.pk}
)
element_maker = ElementMaker()
id_publicador = str(publicador.id_publicador)
item_conteudo = element_maker.Item(url_conteudo, formato=formato, idPublicador=id_publicador,
tipo='conteudo')
item_conteudo = element_maker.Item(
url_conteudo,
formato=formato,
idPublicador=id_publicador,
tipo="conteudo",
)
oai_lexml.append(item_conteudo)
url = BASE_URL_SAPL + reverse('sapl.norma:normajuridica_detail', kwargs={'pk': norma.pk})
item_metadado = element_maker.Item(url, formato='text/html', idPublicador=id_publicador, tipo='metadado')
url = BASE_URL_SAPL + reverse(
"sapl.norma:normajuridica_detail", kwargs={"pk": norma.pk}
)
item_metadado = element_maker.Item(
url, formato="text/html", idPublicador=id_publicador, tipo="metadado"
)
oai_lexml.append(item_metadado)
documento_individual = element_maker.DocumentoIndividual(urn)
oai_lexml.append(documento_individual)
if norma.tipo.equivalente_lexml == 'lei.organica':
epigrafe = '{} de {} - {}, de {}'.format(norma.tipo.descricao, casa.municipio,
casa.uf, norma.ano)
elif norma.tipo.equivalente_lexml == 'constituicao':
epigrafe = '{} do Estado de {}, de {}'.format(norma.tipo.descricao, casa.municipio,
norma.ano)
if norma.tipo.equivalente_lexml == "lei.organica":
epigrafe = "{} de {} - {}, de {}".format(
norma.tipo.descricao, casa.municipio, casa.uf, norma.ano
)
elif norma.tipo.equivalente_lexml == "constituicao":
epigrafe = "{} do Estado de {}, de {}".format(
norma.tipo.descricao, casa.municipio, norma.ano
)
else:
epigrafe = '{}{}, de {}'.format(norma.tipo.descricao, norma.numero,
self.data_por_extenso(norma.data))
epigrafe = "{}{}, de {}".format(
norma.tipo.descricao,
norma.numero,
self.data_por_extenso(norma.data),
)
oai_lexml.append(element_maker.Epigrafe(epigrafe))
oai_lexml.append(element_maker.Ementa(norma.ementa))
indexacao = norma.indexacao
@ -236,48 +303,50 @@ class OAIServer:
else:
return None
def oai_query(self, offset=0, batch_size=10, from_=None, until=None, identifier=None):
def oai_query(
self, offset=0, batch_size=10, from_=None, until=None, identifier=None
):
if from_:
from_ = timezone.make_aware(from_) # convert from naive to timezone aware datetime
from_ = timezone.make_aware(
from_
) # convert from naive to timezone aware datetime
esfera = self.get_esfera_federacao()
offset = 0 if offset < 0 else offset
batch_size = 10 if batch_size < 0 else batch_size
until = timezone.make_aware(until) \
if until and timezone.make_aware(until) < timezone.now() \
until = (
timezone.make_aware(until)
if until and timezone.make_aware(until) < timezone.now()
else timezone.now()
)
normas = self.recupera_norma(offset,
batch_size,
from_,
until,
identifier,
esfera)
normas = self.recupera_norma(
offset, batch_size, from_, until, identifier, esfera
)
for norma in normas:
resultado = {}
identificador = self.monta_id(norma)
urn = self.monta_urn(norma, esfera)
xml_lexml = self.monta_xml(urn, norma)
resultado['tx_metadado_xml'] = xml_lexml
resultado['cd_status'] = 'N'
resultado['id'] = identificador
resultado['when_modified'] = norma.timestamp
resultado['deleted'] = 0
yield {'record': resultado,
'metadata': resultado['tx_metadado_xml']}
resultado["tx_metadado_xml"] = xml_lexml
resultado["cd_status"] = "N"
resultado["id"] = identificador
resultado["when_modified"] = norma.timestamp
resultado["deleted"] = 0
yield {"record": resultado, "metadata": resultado["tx_metadado_xml"]}
def OAIServerFactory(config={}):
"""
Create a new OAI batching OAI Server given a config and a database
"""
for prefix in config['metadata_prefixes']:
for prefix in config["metadata_prefixes"]:
metadata_registry = oaipmh.metadata.MetadataRegistry()
metadata_registry.registerWriter(prefix, OAILEXML(prefix))
return oaipmh.server.BatchingServer(
OAIServer(config),
metadata_registry=metadata_registry,
resumption_batch_size=config['batch_size']
resumption_batch_size=config["batch_size"],
)
@ -293,30 +362,32 @@ def casa_legislativa():
def get_xml_provedor():
"""antigo get_descricao_casa()"""
descricao = ''
descricao = ""
provedor = LexmlProvedor.objects.first()
if provedor:
descricao = provedor.xml
if descricao:
descricao = descricao.encode('utf-8')
descricao = descricao.encode("utf-8")
return descricao
def get_config(url, batch_size):
config = {'content_type': None,
'delay': 0,
'base_asset_path': None,
'metadata_prefixes': ['oai_lexml'],
'titulo': casa_legislativa().nome, # Inicializa variável global casa
'email': [casa.email], # lista de e-mails, antigo `def get_email()`
'base_url': url[:url.find('/', 8)] + reverse('sapl.lexml:lexml_endpoint')[:-4], # remove '/oai' suffix
'descricao': get_xml_provedor(),
'batch_size': batch_size
config = {
"content_type": None,
"delay": 0,
"base_asset_path": None,
"metadata_prefixes": ["oai_lexml"],
"titulo": casa_legislativa().nome, # Inicializa variável global casa
"email": [casa.email], # lista de e-mails, antigo `def get_email()`
"base_url": url[: url.find("/", 8)]
+ reverse("sapl.lexml:lexml_endpoint")[:-4], # remove '/oai' suffix
"descricao": get_xml_provedor(),
"batch_size": batch_size,
}
return config
if __name__ == '__main__':
if __name__ == "__main__":
"""
Para executar localmente (estando no diretório raiz):
@ -325,7 +396,6 @@ if __name__ == '__main__':
Executar comando
%run sapl/lexml/OAIServer.py
"""
oai_server = OAIServerFactory(get_config('http://127.0.0.1:8000/', 10))
r = oai_server.handleRequest({'verb': 'ListRecords',
'metadataPrefix': 'oai_lexml'})
print(r.decode('UTF-8'))
oai_server = OAIServerFactory(get_config("http://127.0.0.1:8000/", 10))
r = oai_server.handleRequest({"verb": "ListRecords", "metadataPrefix": "oai_lexml"})
print(r.decode("UTF-8"))

2
sapl/lexml/__init__.py

@ -1 +1 @@
default_app_config = 'sapl.lexml.apps.AppConfig'
default_app_config = "sapl.lexml.apps.AppConfig"

6
sapl/lexml/apps.py

@ -3,6 +3,6 @@ from django.utils.translation import ugettext_lazy as _
class AppConfig(apps.AppConfig):
name = 'sapl.lexml'
label = 'lexml'
verbose_name = _('LexML')
name = "sapl.lexml"
label = "lexml"
verbose_name = _("LexML")

24
sapl/lexml/forms.py

@ -1,13 +1,14 @@
import os
import re
import xml.dom.minidom as dom
from io import StringIO
from django.core.exceptions import ValidationError
from django.forms import ModelForm
from sapl.settings import PROJECT_DIR
from django.utils.translation import ugettext_lazy as _
from io import StringIO
from lxml import etree
import os
import re
import xml.dom.minidom as dom
from sapl.settings import PROJECT_DIR
from .models import LexmlProvedor
@ -21,7 +22,7 @@ class LexmlProvedorForm(ModelForm):
"id_responsavel",
"nome_responsavel",
"email_responsavel",
"xml"
"xml",
]
def clean(self):
@ -44,14 +45,17 @@ def validar_xml(xml):
try:
dom.parse(xml)
except Exception as e:
raise ValidationError(_(F"XML mal formatado. Error: {e}"))
raise ValidationError(_(f"XML mal formatado. Error: {e}"))
def validar_schema(xml):
xml_schema = open(os.path.join(PROJECT_DIR, 'sapl/templates/lexml/schema.xsd'), 'rb').read()
xml_schema = open(
os.path.join(PROJECT_DIR, "sapl/templates/lexml/schema.xsd"), "rb"
).read()
schema_root = etree.XML(xml_schema)
schema = etree.XMLSchema(schema_root)
parser = etree.XMLParser(schema=schema)
try:
root = etree.fromstring(xml.encode(), parser)
except Exception as e:
raise ValidationError(_(F"XML mal formatado. Error: {e}"))
raise ValidationError(_(f"XML mal formatado. Error: {e}"))

60
sapl/lexml/models.py

@ -3,64 +3,58 @@ from django.utils.translation import ugettext_lazy as _
class LexmlProvedor(models.Model): # LexmlRegistroProvedor
id_provedor = models.PositiveIntegerField(verbose_name=_('Id do provedor'))
nome = models.CharField(max_length=255, verbose_name=_('Nome do provedor'))
id_provedor = models.PositiveIntegerField(verbose_name=_("Id do provedor"))
nome = models.CharField(max_length=255, verbose_name=_("Nome do provedor"))
sigla = models.CharField(max_length=15)
email_responsavel = models.EmailField(
max_length=50,
blank=True,
verbose_name=_('E-mail do responsável'))
max_length=50, blank=True, verbose_name=_("E-mail do responsável")
)
nome_responsavel = models.CharField(
max_length=255,
blank=True,
verbose_name=_('Nome do responsável'))
max_length=255, blank=True, verbose_name=_("Nome do responsável")
)
tipo = models.CharField(max_length=50)
id_responsavel = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Id do responsável'))
blank=True, null=True, verbose_name=_("Id do responsável")
)
xml = models.TextField(
blank=True,
verbose_name=_('XML fornecido pela equipe do LexML:'))
blank=True, verbose_name=_("XML fornecido pela equipe do LexML:")
)
@property
def pretty_xml(self):
import html
safe_xml = html.escape(self.xml)
return safe_xml.replace('\\n', '<br/>').replace(' ', '&nbsp;')
return safe_xml.replace("\\n", "<br/>").replace(" ", "&nbsp;")
class Meta:
verbose_name = _('Provedor Lexml')
verbose_name_plural = _('Provedores Lexml')
ordering = ('id',)
verbose_name = _("Provedor Lexml")
verbose_name_plural = _("Provedores Lexml")
ordering = ("id",)
def __str__(self):
return self.nome
class LexmlPublicador(models.Model):
id_publicador = models.PositiveIntegerField(
verbose_name=_('Id do publicador'))
nome = models.CharField(
max_length=255, verbose_name=_('Nome do publicador'))
id_publicador = models.PositiveIntegerField(verbose_name=_("Id do publicador"))
nome = models.CharField(max_length=255, verbose_name=_("Nome do publicador"))
email_responsavel = models.EmailField(
max_length=50,
blank=True,
verbose_name=_('E-mail do responsável'))
max_length=50, blank=True, verbose_name=_("E-mail do responsável")
)
sigla = models.CharField(
max_length=255,
blank=True,
verbose_name=_('Sigla do Publicador'))
max_length=255, blank=True, verbose_name=_("Sigla do Publicador")
)
nome_responsavel = models.CharField(
max_length=255,
blank=True,
verbose_name=_('Nome do responsável'))
max_length=255, blank=True, verbose_name=_("Nome do responsável")
)
tipo = models.CharField(max_length=50)
id_responsavel = models.PositiveIntegerField(
verbose_name=_('Id do responsável'))
id_responsavel = models.PositiveIntegerField(verbose_name=_("Id do responsável"))
class Meta:
verbose_name = _('Publicador Lexml')
verbose_name_plural = _('Publicadores Lexml')
ordering = ('id',)
verbose_name = _("Publicador Lexml")
verbose_name_plural = _("Publicadores Lexml")
ordering = ("id",)
def __str__(self):
return self.nome

22
sapl/lexml/urls.py

@ -1,17 +1,23 @@
from django.conf.urls import include, url
from sapl.lexml.views import LexmlProvedorCrud, LexmlPublicadorCrud, lexml_request, request_search
from sapl.lexml.views import (
LexmlProvedorCrud,
LexmlPublicadorCrud,
lexml_request,
request_search,
)
from .apps import AppConfig
app_name = AppConfig.name
urlpatterns = [
url(r'^sistema/lexml/provedor/',
include(LexmlProvedorCrud.get_urls())),
url(r'^sistema/lexml/publicador/',
include(LexmlPublicadorCrud.get_urls())),
url(r'^sistema/lexml/request_search/(?P<keyword>[\w\-]+)/', request_search, name='lexml_search'),
url(r'^sistema/lexml/oai', lexml_request, name='lexml_endpoint'),
url(r"^sistema/lexml/provedor/", include(LexmlProvedorCrud.get_urls())),
url(r"^sistema/lexml/publicador/", include(LexmlPublicadorCrud.get_urls())),
url(
r"^sistema/lexml/request_search/(?P<keyword>[\w\-]+)/",
request_search,
name="lexml_search",
),
url(r"^sistema/lexml/oai", lexml_request, name="lexml_endpoint"),
]

21
sapl/lexml/views.py

@ -1,20 +1,19 @@
from django.http import HttpResponse
from django.shortcuts import render
from sapl.crud.base import CrudAux, Crud
from sapl.crud.base import Crud, CrudAux
from sapl.lexml.OAIServer import OAIServerFactory, get_config
from sapl.rules import RP_DETAIL, RP_LIST
from .models import LexmlProvedor, LexmlPublicador
from .forms import LexmlProvedorForm
from .models import LexmlProvedor, LexmlPublicador
LexmlPublicadorCrud = CrudAux.build(LexmlPublicador, 'lexml_publicador')
LexmlPublicadorCrud = CrudAux.build(LexmlPublicador, "lexml_publicador")
class LexmlProvedorCrud(Crud):
model = LexmlProvedor
help_topic = 'lexml_provedor'
help_topic = "lexml_provedor"
public = [RP_LIST, RP_DETAIL]
class CreateView(Crud.CreateView):
@ -24,19 +23,19 @@ class LexmlProvedorCrud(Crud):
form_class = LexmlProvedorForm
class DetailView(Crud.DetailView):
layout_key = 'LexmlProvedorDetail'
layout_key = "LexmlProvedorDetail"
def lexml_request(request):
request_dict = request.GET.copy()
if request_dict.get('batch_size'):
del request_dict['batch_size']
if request_dict.get("batch_size"):
del request_dict["batch_size"]
config = get_config(request.get_raw_uri(), int(request.GET.get('batch_size', '10')))
config = get_config(request.get_raw_uri(), int(request.GET.get("batch_size", "10")))
oai_server = OAIServerFactory(config)
r = oai_server.handleRequest(request_dict)
response = r.decode('UTF-8')
return HttpResponse(response, content_type='text/xml')
response = r.decode("UTF-8")
return HttpResponse(response, content_type="text/xml")
def request_search(request, keyword):

2
sapl/materia/__init__.py

@ -1 +1 @@
default_app_config = 'sapl.materia.apps.AppConfig'
default_app_config = "sapl.materia.apps.AppConfig"

6
sapl/materia/admin.py

@ -4,8 +4,7 @@ from django.contrib import admin
from sapl.base.models import TipoAutor
from sapl.comissoes.models import TipoComissao
from sapl.materia.models import Proposicao
from sapl.parlamentares.models import (SituacaoMilitar, TipoAfastamento,
TipoDependente)
from sapl.parlamentares.models import SituacaoMilitar, TipoAfastamento, TipoDependente
from sapl.utils import register_all_models_in_admin
register_all_models_in_admin(__name__)
@ -13,7 +12,6 @@ register_all_models_in_admin(__name__)
if not settings.DEBUG:
class RestricaoAdmin(admin.ModelAdmin):
def has_add_permission(self, request, obj=None):
return False
@ -29,7 +27,7 @@ if not settings.DEBUG:
TipoComissao,
TipoAfastamento,
SituacaoMilitar,
TipoDependente
TipoDependente,
)
for model in models:

6
sapl/materia/apps.py

@ -3,6 +3,6 @@ from django.utils.translation import ugettext_lazy as _
class AppConfig(apps.AppConfig):
name = 'sapl.materia'
label = 'materia'
verbose_name = _('Matéria')
name = "sapl.materia"
label = "materia"
verbose_name = _("Matéria")

2959
sapl/materia/forms.py

File diff suppressed because it is too large

1183
sapl/materia/models.py

File diff suppressed because it is too large

69
sapl/materia/tests/test_email_templates.py

@ -5,21 +5,22 @@ from sapl.base.email_utils import enviar_emails, load_email_templates
def test_email_template_loading():
expected = "<html><body>Hello Django</body></html>"
emails = load_email_templates(['email/test_tramitacao.html'],
context={"name": "Django"})
emails = load_email_templates(
["email/test_tramitacao.html"], context={"name": "Django"}
)
# strip \n and \r to compare with expected
actual = emails[0].replace('\n', '').replace('\r', '')
actual = emails[0].replace("\n", "").replace("\r", "")
assert actual == expected
def test_html_email_body_with_materia():
templates = load_email_templates(['email/tramitacao.txt',
'email/tramitacao.html'],
{"image": 'img/logo.png',
"casa_legislativa":
"Assembléia Parlamentar",
templates = load_email_templates(
["email/tramitacao.txt", "email/tramitacao.html"],
{
"image": "img/logo.png",
"casa_legislativa": "Assembléia Parlamentar",
"data_registro": "25/02/2016",
"cod_materia": "1",
"descricao_materia": "Ementa de teste",
@ -30,37 +31,45 @@ def test_html_email_body_with_materia():
"hash_txt": "abc01f",
"materia_id": "794",
"base_url": "http://localhost:8000",
"materia_url":
"/materia/764/acompanhar-materia",
"excluir_url":
"/materia/764/acompanhar-excluir"})
"materia_url": "/materia/764/acompanhar-materia",
"excluir_url": "/materia/764/acompanhar-excluir",
},
)
assert len(templates) == 2
def test_enviar_email_distintos():
NUM_MESSAGES = 10
messages = [{'recipient': 'user-' + str(i) + '@test.com',
'subject': 'subject: ' + str(i),
'txt_message': 'txt: ' + str(i),
'html_message': '<html></html>',
} for i in range(NUM_MESSAGES)]
recipients = [m['recipient'] for m in messages]
enviar_emails('test@sapl.com', recipients, messages)
messages = [
{
"recipient": "user-" + str(i) + "@test.com",
"subject": "subject: " + str(i),
"txt_message": "txt: " + str(i),
"html_message": "<html></html>",
}
for i in range(NUM_MESSAGES)
]
recipients = [m["recipient"] for m in messages]
enviar_emails("test@sapl.com", recipients, messages)
assert len(mail.outbox) == NUM_MESSAGES
def test_enviar_same_email():
NUM_MESSAGES = 10
messages = [{'recipient': 'user-' + str(i) + '@test.com',
'subject': 'subject: ' + str(i),
'txt_message': 'txt: ' + str(i),
'html_message': '<html></html>',
} for i in range(NUM_MESSAGES)]
recipients = [m['recipient'] for m in messages]
enviar_emails('test@sapl.com', recipients, [messages[0]])
messages = [
{
"recipient": "user-" + str(i) + "@test.com",
"subject": "subject: " + str(i),
"txt_message": "txt: " + str(i),
"html_message": "<html></html>",
}
for i in range(NUM_MESSAGES)
]
recipients = [m["recipient"] for m in messages]
enviar_emails("test@sapl.com", recipients, [messages[0]])
assert len(mail.outbox) == 1

953
sapl/materia/tests/test_materia.py

File diff suppressed because it is too large

151
sapl/materia/tests/test_materia_form.py

@ -15,9 +15,9 @@ def test_valida_campos_obrigatorios_ficha_pesquisa_form():
errors = form.errors
assert errors['tipo_materia'] == [_('Este campo é obrigatório.')]
assert errors['data_inicial'] == [_('Este campo é obrigatório.')]
assert errors['data_final'] == [_('Este campo é obrigatório.')]
assert errors["tipo_materia"] == [_("Este campo é obrigatório.")]
assert errors["data_inicial"] == [_("Este campo é obrigatório.")]
assert errors["data_final"] == [_("Este campo é obrigatório.")]
assert len(errors) == 3
@ -26,23 +26,30 @@ def test_valida_campos_obrigatorios_ficha_pesquisa_form():
def test_ficha_pesquisa_form_datas_invalidas():
tipo = baker.make(TipoMateriaLegislativa)
form = forms.FichaPesquisaForm(data={'tipo_materia': str(tipo.pk),
'data_inicial': '10/11/2017',
'data_final': '09/11/2017'
})
form = forms.FichaPesquisaForm(
data={
"tipo_materia": str(tipo.pk),
"data_inicial": "10/11/2017",
"data_final": "09/11/2017",
}
)
assert not form.is_valid()
assert form.errors['__all__'] == [_('A Data Final não pode ser menor que '
'a Data Inicial')]
assert form.errors["__all__"] == [
_("A Data Final não pode ser menor que " "a Data Inicial")
]
@pytest.mark.django_db(transaction=False)
def test_ficha_pesquisa_form_invalido():
tipo = baker.make(TipoMateriaLegislativa)
form = forms.FichaPesquisaForm(data={'tipo_materia': str(tipo.pk),
'data_inicial': '10/11/2017',
'data_final': '09/11/2017'
})
form = forms.FichaPesquisaForm(
data={
"tipo_materia": str(tipo.pk),
"data_inicial": "10/11/2017",
"data_final": "09/11/2017",
}
)
assert not form.is_valid()
@ -55,7 +62,7 @@ def test_valida_campos_obrigatorios_ficha_seleciona_form():
errors = form.errors
assert errors['materia'] == [_('Este campo é obrigatório.')]
assert errors["materia"] == [_("Este campo é obrigatório.")]
assert len(errors) == 1
@ -64,7 +71,7 @@ def test_valida_campos_obrigatorios_ficha_seleciona_form():
def test_ficha_seleciona_form_valido():
materia = baker.make(MateriaLegislativa)
form = forms.FichaSelecionaForm(data={'materia': str(materia.pk)})
form = forms.FichaSelecionaForm(data={"materia": str(materia.pk)})
assert form.is_valid()
@ -76,13 +83,13 @@ def test_valida_campos_obrigatorios_materialegislativa_form():
assert not form.is_valid()
errors = form.errors
assert errors['tipo'] == [_('Este campo é obrigatório.')]
assert errors['ano'] == [_('Este campo é obrigatório.')]
assert errors['data_apresentacao'] == [_('Este campo é obrigatório.')]
assert errors['numero'] == [_('Este campo é obrigatório.')]
assert errors['ementa'] == [_('Este campo é obrigatório.')]
assert errors['regime_tramitacao'] == [_('Este campo é obrigatório.')]
assert errors['em_tramitacao'] == [_('Este campo é obrigatório.')]
assert errors["tipo"] == [_("Este campo é obrigatório.")]
assert errors["ano"] == [_("Este campo é obrigatório.")]
assert errors["data_apresentacao"] == [_("Este campo é obrigatório.")]
assert errors["numero"] == [_("Este campo é obrigatório.")]
assert errors["ementa"] == [_("Este campo é obrigatório.")]
assert errors["regime_tramitacao"] == [_("Este campo é obrigatório.")]
assert errors["em_tramitacao"] == [_("Este campo é obrigatório.")]
assert len(errors) == 7
@ -93,7 +100,8 @@ def test_valida_campos_obrigatorios_unidade_tramitacao_form():
assert not form.is_valid()
errors = form.errors
assert errors['__all__'] == [_('Somente um campo deve ser preenchido!')]
assert errors["__all__"] == [_("Somente um campo deve ser preenchido!")]
@pytest.mark.django_db(transaction=False)
def test_valida_campos_obrigatorios_orgao_form():
@ -102,9 +110,9 @@ def test_valida_campos_obrigatorios_orgao_form():
assert not form.is_valid()
errors = form.errors
assert errors['nome'] == [_('Este campo é obrigatório.')]
assert errors['sigla'] == [_('Este campo é obrigatório.')]
assert errors['unidade_deliberativa'] == [_('Este campo é obrigatório.')]
assert errors["nome"] == [_("Este campo é obrigatório.")]
assert errors["sigla"] == [_("Este campo é obrigatório.")]
assert errors["unidade_deliberativa"] == [_("Este campo é obrigatório.")]
assert len(errors) == 3
@ -116,11 +124,12 @@ def test_valida_campos_obrigatorios_materia_assunto_form():
errors = form.errors
assert errors['assunto'] == [_('Este campo é obrigatório.')]
assert errors['materia'] == [_('Este campo é obrigatório.')]
assert errors["assunto"] == [_("Este campo é obrigatório.")]
assert errors["materia"] == [_("Este campo é obrigatório.")]
assert len(errors) == 2
@pytest.mark.django_db(transaction=False)
def test_valida_campos_obrigatorios_autoria_form():
form = forms.AutoriaForm(data={}, instance=None)
@ -131,9 +140,9 @@ def test_valida_campos_obrigatorios_autoria_form():
assert len(errors) == 3
assert errors['tipo_autor'] == [_('Este campo é obrigatório.')]
assert errors['autor'] == [_('Este campo é obrigatório.')]
assert errors['primeiro_autor'] == [_('Este campo é obrigatório.')]
assert errors["tipo_autor"] == [_("Este campo é obrigatório.")]
assert errors["autor"] == [_("Este campo é obrigatório.")]
assert errors["primeiro_autor"] == [_("Este campo é obrigatório.")]
@pytest.mark.django_db(transaction=False)
@ -146,10 +155,12 @@ def test_valida_campos_obrigatorios_autoria_multicreate_form():
assert len(errors) == 4
assert errors['__all__'] == [_('Ao menos um autor deve ser selecionado para inclusão')]
assert errors['tipo_autor'] == [_('Este campo é obrigatório.')]
assert errors['autor'] == [_('Este campo é obrigatório.')]
assert errors['primeiro_autor'] == [_('Este campo é obrigatório.')]
assert errors["__all__"] == [
_("Ao menos um autor deve ser selecionado para inclusão")
]
assert errors["tipo_autor"] == [_("Este campo é obrigatório.")]
assert errors["autor"] == [_("Este campo é obrigatório.")]
assert errors["primeiro_autor"] == [_("Este campo é obrigatório.")]
@pytest.mark.django_db(transaction=False)
@ -159,12 +170,13 @@ def test_valida_campos_obrigatorios_tipo_proposicao_form():
assert not form.is_valid()
errors = form.errors
assert errors['tipo_conteudo_related'] == [_('Este campo é obrigatório.')]
assert errors['descricao'] == [_('Este campo é obrigatório.')]
assert errors['content_type'] == [_('Este campo é obrigatório.')]
assert errors["tipo_conteudo_related"] == [_("Este campo é obrigatório.")]
assert errors["descricao"] == [_("Este campo é obrigatório.")]
assert errors["content_type"] == [_("Este campo é obrigatório.")]
assert len(errors) == 3
@pytest.mark.django_db(transaction=False)
def test_valida_campos_obrigatorios_devolver_proposicao_form():
form = forms.DevolverProposicaoForm(data={})
@ -172,27 +184,30 @@ def test_valida_campos_obrigatorios_devolver_proposicao_form():
assert not form.is_valid()
errors = form.errors
assert errors['__all__'] == [_('Adicione uma Justificativa para devolução.')]
assert errors["__all__"] == [_("Adicione uma Justificativa para devolução.")]
assert len(errors) == 1
@pytest.mark.django_db(transaction=False)
def test_valida_campos_obrigatorios_relatoria_form():
tipo_comissao = baker.make(TipoComissao)
comissao = baker.make(Comissao,
comissao = baker.make(
Comissao,
tipo=tipo_comissao,
nome='Comissao Teste',
sigla='T',
data_criacao='2016-03-21')
form = forms.RelatoriaForm(initial={'comissao':comissao}, data={})
nome="Comissao Teste",
sigla="T",
data_criacao="2016-03-21",
)
form = forms.RelatoriaForm(initial={"comissao": comissao}, data={})
assert not form.is_valid()
errors = form.errors
assert errors['parlamentar'] == [_('Este campo é obrigatório.')]
assert errors['data_designacao_relator'] == [_('Este campo é obrigatório.')]
assert errors['composicao'] == [_('Este campo é obrigatório.')]
assert errors["parlamentar"] == [_("Este campo é obrigatório.")]
assert errors["data_designacao_relator"] == [_("Este campo é obrigatório.")]
assert errors["composicao"] == [_("Este campo é obrigatório.")]
assert len(errors) == 3
@ -205,11 +220,11 @@ def test_valida_campos_obrigatorios_tramitacao_form():
errors = form.errors
assert errors['unidade_tramitacao_local'] == [_('Este campo é obrigatório.')]
assert errors['status'] == [_('Este campo é obrigatório.')]
assert errors['data_tramitacao'] == [_('Este campo é obrigatório.')]
assert errors['unidade_tramitacao_destino'] == [_('Este campo é obrigatório.')]
assert errors['urgente'] == [_('Este campo é obrigatório.')]
assert errors["unidade_tramitacao_local"] == [_("Este campo é obrigatório.")]
assert errors["status"] == [_("Este campo é obrigatório.")]
assert errors["data_tramitacao"] == [_("Este campo é obrigatório.")]
assert errors["unidade_tramitacao_destino"] == [_("Este campo é obrigatório.")]
assert errors["urgente"] == [_("Este campo é obrigatório.")]
assert len(errors) == 5
@ -222,11 +237,11 @@ def test_valida_campos_obrigatorios_tramitacao_update_form():
errors = form.errors
assert errors['unidade_tramitacao_local'] == [_('Este campo é obrigatório.')]
assert errors['status'] == [_('Este campo é obrigatório.')]
assert errors['data_tramitacao'] == [_('Este campo é obrigatório.')]
assert errors['unidade_tramitacao_destino'] == [_('Este campo é obrigatório.')]
assert errors['urgente'] == [_('Este campo é obrigatório.')]
assert errors["unidade_tramitacao_local"] == [_("Este campo é obrigatório.")]
assert errors["status"] == [_("Este campo é obrigatório.")]
assert errors["data_tramitacao"] == [_("Este campo é obrigatório.")]
assert errors["unidade_tramitacao_destino"] == [_("Este campo é obrigatório.")]
assert errors["urgente"] == [_("Este campo é obrigatório.")]
assert len(errors) == 5
@ -238,9 +253,9 @@ def test_valida_campos_obrigatorios_legislacao_citada_form():
assert not form.is_valid()
errors = form.errors
assert errors['tipo'] == [_('Este campo é obrigatório.')]
assert errors['ano'] == [_('Este campo é obrigatório.')]
assert errors['numero'] == [_('Este campo é obrigatório.')]
assert errors["tipo"] == [_("Este campo é obrigatório.")]
assert errors["ano"] == [_("Este campo é obrigatório.")]
assert errors["numero"] == [_("Este campo é obrigatório.")]
assert len(errors) == 3
@ -253,10 +268,10 @@ def test_valida_campos_obrigatorios_numeracao_form():
errors = form.errors
assert errors['tipo_materia'] == [_('Este campo é obrigatório.')]
assert errors['ano_materia'] == [_('Este campo é obrigatório.')]
assert errors['numero_materia'] == [_('Este campo é obrigatório.')]
assert errors['data_materia'] == [_('Este campo é obrigatório.')]
assert errors["tipo_materia"] == [_("Este campo é obrigatório.")]
assert errors["ano_materia"] == [_("Este campo é obrigatório.")]
assert errors["numero_materia"] == [_("Este campo é obrigatório.")]
assert errors["data_materia"] == [_("Este campo é obrigatório.")]
assert len(errors) == 4
@ -269,9 +284,9 @@ def test_valida_campos_obrigatorios_anexada_form():
errors = form.errors
assert errors['tipo'] == [_('Este campo é obrigatório.')]
assert errors['ano'] == [_('Este campo é obrigatório.')]
assert errors['numero'] == [_('Este campo é obrigatório.')]
assert errors['data_anexacao'] == [_('Este campo é obrigatório.')]
assert errors["tipo"] == [_("Este campo é obrigatório.")]
assert errors["ano"] == [_("Este campo é obrigatório.")]
assert errors["numero"] == [_("Este campo é obrigatório.")]
assert errors["data_anexacao"] == [_("Este campo é obrigatório.")]
assert len(errors) == 4

23
sapl/materia/tests/test_materia_urls.py

@ -2,18 +2,15 @@ import pytest
from django.urls import reverse
@pytest.mark.parametrize("test_input,kwargs,expected", [
('sapl.materia:relatoria_update',
{'pk': '11'},
'/materia/relatoria/11/edit'),
('sapl.materia:tramitacao_update',
{'pk': '8'},
'/materia/tramitacao/8/edit'),
('sapl.materia:proposicao_create', {}, '/proposicao/create'),
('sapl.materia:proposicao_update',
{'pk': '3'},
'/proposicao/3/edit'),
('sapl.materia:proposicao_list', {}, '/proposicao/'),
])
@pytest.mark.parametrize(
"test_input,kwargs,expected",
[
("sapl.materia:relatoria_update", {"pk": "11"}, "/materia/relatoria/11/edit"),
("sapl.materia:tramitacao_update", {"pk": "8"}, "/materia/tramitacao/8/edit"),
("sapl.materia:proposicao_create", {}, "/proposicao/create"),
("sapl.materia:proposicao_update", {"pk": "3"}, "/proposicao/3/edit"),
("sapl.materia:proposicao_list", {}, "/proposicao/"),
],
)
def test_reverse(test_input, kwargs, expected):
assert reverse(test_input, kwargs=kwargs) == expected

387
sapl/materia/urls.py

@ -1,194 +1,285 @@
from django.conf.urls import include, url
from sapl.materia.views import (AcompanhamentoConfirmarView,
from sapl.materia.views import (
AcompanhamentoConfirmarView,
AcompanhamentoExcluirView,
AcompanhamentoMateriaView, AnexadaCrud,
AssuntoMateriaCrud, AutoriaCrud,
AutoriaMultiCreateView, ConfirmarProposicao,
CriarProtocoloMateriaView, DespachoInicialCrud,
AcompanhamentoMateriaView,
AnexadaCrud,
AssuntoMateriaCrud,
AutoriaCrud,
AutoriaMultiCreateView,
ConfirmarProposicao,
CriarProtocoloMateriaView,
DespachoInicialCrud,
DespachoInicialMultiCreateView,
DocumentoAcessorioCrud,
DocumentoAcessorioEmLoteView,
EtiquetaPesquisaView,
ExcluirTramitacaoEmLoteView,
FichaPesquisaView,
FichaSelecionaView,
HistoricoProposicaoView,
ImpressosView,
LegislacaoCitadaCrud,
MateriaAnexadaEmLoteView,
EtiquetaPesquisaView, FichaPesquisaView,
FichaSelecionaView, ImpressosView,
LegislacaoCitadaCrud, MateriaAssuntoCrud,
MateriaAssuntoCrud,
MateriaLegislativaCrud,
MateriaLegislativaPesquisaView, MateriaTaView,
NumeracaoCrud, OrgaoCrud, OrigemCrud,
PrimeiraTramitacaoEmLoteView, ProposicaoCrud,
ProposicaoDevolvida, ProposicaoPendente,
ProposicaoRecebida, ProposicaoTaView,
ReceberProposicao, ReciboProposicaoView,
RegimeTramitacaoCrud, RelatoriaCrud,
StatusTramitacaoCrud, TipoDocumentoCrud,
TipoFimRelatoriaCrud, TipoMateriaCrud,
TipoProposicaoCrud, TramitacaoCrud,
TramitacaoEmLoteView, UnidadeTramitacaoCrud,
proposicao_texto, recuperar_materia,
ExcluirTramitacaoEmLoteView,
RetornarProposicao,
MateriaLegislativaPesquisaView,
MateriaPesquisaSimplesView,
DespachoInicialMultiCreateView,
get_zip_docacessorios, get_pdf_docacessorios,
MateriaTaView,
NumeracaoCrud,
OrgaoCrud,
OrigemCrud,
PesquisarStatusTramitacaoView,
PrimeiraTramitacaoEmLoteView,
ProposicaoCrud,
ProposicaoDevolvida,
ProposicaoPendente,
ProposicaoRecebida,
ProposicaoTaView,
ReceberProposicao,
ReciboProposicaoView,
RegimeTramitacaoCrud,
RelatoriaCrud,
RetornarProposicao,
StatusTramitacaoCrud,
TipoDocumentoCrud,
TipoFimRelatoriaCrud,
TipoMateriaCrud,
TipoProposicaoCrud,
TramitacaoCrud,
TramitacaoEmLoteView,
UnidadeTramitacaoCrud,
configEtiquetaMateriaLegislativaCrud,
PesquisarStatusTramitacaoView, HistoricoProposicaoView)
from sapl.norma.views import NormaPesquisaSimplesView
from sapl.protocoloadm.views import (
FichaPesquisaAdmView, FichaSelecionaAdmView
get_pdf_docacessorios,
get_zip_docacessorios,
proposicao_texto,
recuperar_materia,
)
from sapl.norma.views import NormaPesquisaSimplesView
from sapl.protocoloadm.views import FichaPesquisaAdmView, FichaSelecionaAdmView
from .apps import AppConfig
app_name = AppConfig.name
urlpatterns_impressos = [
url(r'^materia/impressos/$',
ImpressosView.as_view(),
name='impressos'),
url(r'^materia/impressos/etiqueta-pesquisa/$',
url(r"^materia/impressos/$", ImpressosView.as_view(), name="impressos"),
url(
r"^materia/impressos/etiqueta-pesquisa/$",
EtiquetaPesquisaView.as_view(),
name='impressos_etiqueta'),
url(r'^materia/impressos/ficha-pesquisa/$',
name="impressos_etiqueta",
),
url(
r"^materia/impressos/ficha-pesquisa/$",
FichaPesquisaView.as_view(),
name='impressos_ficha_pesquisa'),
url(r'^materia/impressos/ficha-seleciona/$',
name="impressos_ficha_pesquisa",
),
url(
r"^materia/impressos/ficha-seleciona/$",
FichaSelecionaView.as_view(),
name='impressos_ficha_seleciona'),
url(r'^materia/impressos/norma-pesquisa/$',
name="impressos_ficha_seleciona",
),
url(
r"^materia/impressos/norma-pesquisa/$",
NormaPesquisaSimplesView.as_view(),
name='impressos_norma_pesquisa'),
url(r'^materia/impressos/materia-pesquisa/$',
name="impressos_norma_pesquisa",
),
url(
r"^materia/impressos/materia-pesquisa/$",
MateriaPesquisaSimplesView.as_view(),
name='impressos_materia_pesquisa'),
url(r'^materia/impressos/ficha-pesquisa-adm/$',
name="impressos_materia_pesquisa",
),
url(
r"^materia/impressos/ficha-pesquisa-adm/$",
FichaPesquisaAdmView.as_view(),
name='impressos_ficha_pesquisa_adm'),
url(r'^materia/impressos/ficha-seleciona-adm/$',
name="impressos_ficha_pesquisa_adm",
),
url(
r"^materia/impressos/ficha-seleciona-adm/$",
FichaSelecionaAdmView.as_view(),
name='impressos_ficha_seleciona_adm'),
name="impressos_ficha_seleciona_adm",
),
]
urlpatterns_materia = [
# Esta customização substitui a url do crud desque que ela permaneça antes
# da inclusão das urls de DespachoInicialCrud
url(r'^materia/(?P<pk>\d+)/despachoinicial/create',
url(
r"^materia/(?P<pk>\d+)/despachoinicial/create",
DespachoInicialMultiCreateView.as_view(),
name='despacho-inicial-multi'),
url(r'^materia/', include(MateriaLegislativaCrud.get_urls() +
AnexadaCrud.get_urls() +
AutoriaCrud.get_urls() +
DespachoInicialCrud.get_urls() +
MateriaAssuntoCrud.get_urls() +
NumeracaoCrud.get_urls() +
LegislacaoCitadaCrud.get_urls() +
TramitacaoCrud.get_urls() +
RelatoriaCrud.get_urls() +
DocumentoAcessorioCrud.get_urls())),
url(r'^materia/(?P<pk>[0-9]+)/create_simplificado$',
name="despacho-inicial-multi",
),
url(
r"^materia/",
include(
MateriaLegislativaCrud.get_urls()
+ AnexadaCrud.get_urls()
+ AutoriaCrud.get_urls()
+ DespachoInicialCrud.get_urls()
+ MateriaAssuntoCrud.get_urls()
+ NumeracaoCrud.get_urls()
+ LegislacaoCitadaCrud.get_urls()
+ 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, name='recuperar_materia'),
url(r'^materia/(?P<pk>[0-9]+)/ta$',
MateriaTaView.as_view(), name='materia_ta'),
url(r'^materia/pesquisar-materia$',
MateriaLegislativaPesquisaView.as_view(), name='pesquisar_materia'),
url(r'^materia/(?P<pk>\d+)/acompanhar-materia/$',
AcompanhamentoMateriaView.as_view(), name='acompanhar_materia'),
url(r'^materia/(?P<pk>\d+)/acompanhar-confirmar$',
name="materia_create_simplificado",
),
url(r"^materia/recuperar-materia", recuperar_materia, name="recuperar_materia"),
url(r"^materia/(?P<pk>[0-9]+)/ta$", MateriaTaView.as_view(), name="materia_ta"),
url(
r"^materia/pesquisar-materia$",
MateriaLegislativaPesquisaView.as_view(),
name="pesquisar_materia",
),
url(
r"^materia/(?P<pk>\d+)/acompanhar-materia/$",
AcompanhamentoMateriaView.as_view(),
name="acompanhar_materia",
),
url(
r"^materia/(?P<pk>\d+)/acompanhar-confirmar$",
AcompanhamentoConfirmarView.as_view(),
name='acompanhar_confirmar'),
url(r'^materia/(?P<pk>\d+)/acompanhar-excluir$',
name="acompanhar_confirmar",
),
url(
r"^materia/(?P<pk>\d+)/acompanhar-excluir$",
AcompanhamentoExcluirView.as_view(),
name='acompanhar_excluir'),
url(r'^materia/(?P<pk>\d+)/autoria/multicreate',
name="acompanhar_excluir",
),
url(
r"^materia/(?P<pk>\d+)/autoria/multicreate",
AutoriaMultiCreateView.as_view(),
name='autoria_multicreate'),
url(r'^materia/acessorio-em-lote', DocumentoAcessorioEmLoteView.as_view(),
name='acessorio_em_lote'),
url(r'^materia/(?P<pk>\d+)/anexada-em-lote', MateriaAnexadaEmLoteView.as_view(),
name='anexada_em_lote'),
url(r'^materia/primeira-tramitacao-em-lote',
name="autoria_multicreate",
),
url(
r"^materia/acessorio-em-lote",
DocumentoAcessorioEmLoteView.as_view(),
name="acessorio_em_lote",
),
url(
r"^materia/(?P<pk>\d+)/anexada-em-lote",
MateriaAnexadaEmLoteView.as_view(),
name="anexada_em_lote",
),
url(
r"^materia/primeira-tramitacao-em-lote",
PrimeiraTramitacaoEmLoteView.as_view(),
name='primeira_tramitacao_em_lote'),
url(r'^materia/tramitacao-em-lote', TramitacaoEmLoteView.as_view(),
name='tramitacao_em_lote'),
url(r'^materia/excluir-tramitacao-em-lote', ExcluirTramitacaoEmLoteView.as_view(),
name='excluir_tramitacao_em_lote'),
url(r'^materia/docacessorio/zip/(?P<pk>\d+)$', get_zip_docacessorios,
name='compress_docacessorios'),
url(r'^materia/docacessorio/pdf/(?P<pk>\d+)$', get_pdf_docacessorios,
name='merge_docacessorios')
name="primeira_tramitacao_em_lote",
),
url(
r"^materia/tramitacao-em-lote",
TramitacaoEmLoteView.as_view(),
name="tramitacao_em_lote",
),
url(
r"^materia/excluir-tramitacao-em-lote",
ExcluirTramitacaoEmLoteView.as_view(),
name="excluir_tramitacao_em_lote",
),
url(
r"^materia/docacessorio/zip/(?P<pk>\d+)$",
get_zip_docacessorios,
name="compress_docacessorios",
),
url(
r"^materia/docacessorio/pdf/(?P<pk>\d+)$",
get_pdf_docacessorios,
name="merge_docacessorios",
),
]
urlpatterns_proposicao = [
url(r'^proposicao/', include(ProposicaoCrud.get_urls())),
url(r'^proposicao/recibo/(?P<pk>\d+)', ReciboProposicaoView.as_view(),
name='recibo-proposicao'),
url(r'^proposicao/receber/', ReceberProposicao.as_view(),
name='receber-proposicao'),
url(r'^proposicao/pendente/', ProposicaoPendente.as_view(),
name='proposicao-pendente'),
url(r'^proposicao/recebida/', ProposicaoRecebida.as_view(),
name='proposicao-recebida'),
url(r'^proposicao/devolvida/', ProposicaoDevolvida.as_view(),
name='proposicao-devolvida'),
url(r'^proposicao/confirmar/P(?P<hash>[0-9A-Fa-f]+)/'
r'(?P<pk>\d+)', ConfirmarProposicao.as_view(),
name='proposicao-confirmar'),
url(r'^sistema/proposicao/tipo/',
include(TipoProposicaoCrud.get_urls())),
url(r'^proposicao/(?P<pk>[0-9]+)/ta$',
ProposicaoTaView.as_view(), name='proposicao_ta'),
url(r'^proposicao/texto/(?P<pk>\d+)$', proposicao_texto,
name='proposicao_texto'),
url(r'^proposicao/(?P<pk>\d+)/retornar', RetornarProposicao.as_view(),
name='retornar-proposicao'),
url(r'^proposicao/historico', HistoricoProposicaoView.as_view(),
name='historico-proposicao'),
url(r"^proposicao/", include(ProposicaoCrud.get_urls())),
url(
r"^proposicao/recibo/(?P<pk>\d+)",
ReciboProposicaoView.as_view(),
name="recibo-proposicao",
),
url(
r"^proposicao/receber/", ReceberProposicao.as_view(), name="receber-proposicao"
),
url(
r"^proposicao/pendente/",
ProposicaoPendente.as_view(),
name="proposicao-pendente",
),
url(
r"^proposicao/recebida/",
ProposicaoRecebida.as_view(),
name="proposicao-recebida",
),
url(
r"^proposicao/devolvida/",
ProposicaoDevolvida.as_view(),
name="proposicao-devolvida",
),
url(
r"^proposicao/confirmar/P(?P<hash>[0-9A-Fa-f]+)/" r"(?P<pk>\d+)",
ConfirmarProposicao.as_view(),
name="proposicao-confirmar",
),
url(r"^sistema/proposicao/tipo/", include(TipoProposicaoCrud.get_urls())),
url(
r"^proposicao/(?P<pk>[0-9]+)/ta$",
ProposicaoTaView.as_view(),
name="proposicao_ta",
),
url(r"^proposicao/texto/(?P<pk>\d+)$", proposicao_texto, name="proposicao_texto"),
url(
r"^proposicao/(?P<pk>\d+)/retornar",
RetornarProposicao.as_view(),
name="retornar-proposicao",
),
url(
r"^proposicao/historico",
HistoricoProposicaoView.as_view(),
name="historico-proposicao",
),
]
urlpatterns_sistema = [
url(r'^sistema/assunto-materia/',
include(AssuntoMateriaCrud.get_urls())),
url(r'^sistema/proposicao/tipo/',
include(TipoProposicaoCrud.get_urls())),
url(r'^sistema/materia/tipo/', include(TipoMateriaCrud.get_urls())),
url(r'^sistema/materia/regime-tramitacao/',
include(RegimeTramitacaoCrud.get_urls())),
url(r'^sistema/materia/tipo-documento/',
include(TipoDocumentoCrud.get_urls())),
url(r'^sistema/materia/tipo-fim-relatoria/',
include(TipoFimRelatoriaCrud.get_urls())),
url(r'^sistema/materia/unidade-tramitacao/',
include(UnidadeTramitacaoCrud.get_urls())),
url(r'^sistema/materia/origem/', include(OrigemCrud.get_urls())),
url(r'^sistema/materia/status-tramitacao/', include(
StatusTramitacaoCrud.get_urls()
)),
url(r"^sistema/assunto-materia/", include(AssuntoMateriaCrud.get_urls())),
url(r"^sistema/proposicao/tipo/", include(TipoProposicaoCrud.get_urls())),
url(r"^sistema/materia/tipo/", include(TipoMateriaCrud.get_urls())),
url(
r"^sistema/materia/regime-tramitacao/", include(RegimeTramitacaoCrud.get_urls())
),
url(r"^sistema/materia/tipo-documento/", include(TipoDocumentoCrud.get_urls())),
url(
r"^sistema/materia/tipo-fim-relatoria/",
include(TipoFimRelatoriaCrud.get_urls()),
),
url(
r"^sistema/materia/unidade-tramitacao/",
include(UnidadeTramitacaoCrud.get_urls()),
),
url(r"^sistema/materia/origem/", include(OrigemCrud.get_urls())),
url(
r"^sistema/materia/status-tramitacao/", include(StatusTramitacaoCrud.get_urls())
),
url(
r'^sistema/materia/pesquisar-status-tramitacao/',
r"^sistema/materia/pesquisar-status-tramitacao/",
PesquisarStatusTramitacaoView.as_view(),
name="pesquisar_statustramitacao"
name="pesquisar_statustramitacao",
),
url(r"^sistema/materia/orgao/", include(OrgaoCrud.get_urls())),
url(
r"^sistema/materia/config-etiqueta-materia-legislativas/",
configEtiquetaMateriaLegislativaCrud,
name="configEtiquetaMateriaLegislativaCrud",
),
url(r'^sistema/materia/orgao/', include(OrgaoCrud.get_urls())),
url(r'^sistema/materia/config-etiqueta-materia-legislativas/',configEtiquetaMateriaLegislativaCrud, name="configEtiquetaMateriaLegislativaCrud"),
]
urlpatterns = urlpatterns_impressos + urlpatterns_materia + \
urlpatterns_proposicao + urlpatterns_sistema
urlpatterns = (
urlpatterns_impressos
+ urlpatterns_materia
+ urlpatterns_proposicao
+ urlpatterns_sistema
)

2683
sapl/materia/views.py

File diff suppressed because it is too large

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

Loading…
Cancel
Save