Browse Source

Formata código com isort e black

upgrade-sapl
Edward Ribeiro 2 months ago
parent
commit
989f640243
  1. 9
      conftest.py
  2. 31
      docker/startup_scripts/create_admin.py
  3. 13
      docker/startup_scripts/genkey.py
  4. 7
      docker/startup_scripts/gunicorn.conf.py
  5. 176
      docker/startup_scripts/solr_cli.py
  6. 208
      drfautoapi/drfautoapi.py
  7. 6
      sapl/api/apps.py
  8. 8
      sapl/api/deprecated.py
  9. 136
      sapl/api/forms.py
  10. 127
      sapl/api/pagination.py
  11. 51
      sapl/api/permissions.py
  12. 221
      sapl/api/serializers.py
  13. 44
      sapl/api/urls.py
  14. 40
      sapl/api/views.py
  15. 9
      sapl/api/views_audiencia.py
  16. 70
      sapl/api/views_base.py
  17. 19
      sapl/api/views_comissoes.py
  18. 12
      sapl/api/views_compilacao.py
  19. 55
      sapl/api/views_materia.py
  20. 12
      sapl/api/views_norma.py
  21. 11
      sapl/api/views_painel.py
  22. 63
      sapl/api/views_parlamentares.py
  23. 40
      sapl/api/views_protocoloadm.py
  24. 23
      sapl/api/views_sessao.py
  25. 6
      sapl/audiencia/apps.py
  26. 228
      sapl/audiencia/forms.py
  27. 194
      sapl/audiencia/models.py
  28. 83
      sapl/audiencia/tests/test_audiencia.py
  29. 11
      sapl/audiencia/urls.py
  30. 75
      sapl/audiencia/views.py
  31. 4
      sapl/base/admin.py
  32. 7
      sapl/base/apps.py
  33. 257
      sapl/base/email_utils.py
  34. 1238
      sapl/base/forms.py
  35. 10
      sapl/base/management/commands/backfill_auditlog.py
  36. 499
      sapl/base/models.py
  37. 269
      sapl/base/receivers.py
  38. 135
      sapl/base/search_indexes.py
  39. 5
      sapl/base/templatetags/base_tags.py
  40. 122
      sapl/base/templatetags/common_tags.py
  41. 128
      sapl/base/templatetags/menus.py
  42. 28
      sapl/base/tests/test_base.py
  43. 43
      sapl/base/tests/test_form.py
  44. 60
      sapl/base/tests/test_login.py
  45. 1116
      sapl/base/tests/test_view_base.py
  46. 4
      sapl/base/tests/teststub_urls.py
  47. 304
      sapl/base/urls.py
  48. 1254
      sapl/base/views.py
  49. 6
      sapl/comissoes/apps.py
  50. 458
      sapl/comissoes/forms.py
  51. 404
      sapl/comissoes/models.py
  52. 165
      sapl/comissoes/tests/test_comissoes.py
  53. 60
      sapl/comissoes/urls.py
  54. 319
      sapl/comissoes/views.py
  55. 8
      sapl/compilacao/admin.py
  56. 55
      sapl/compilacao/apps.py
  57. 1801
      sapl/compilacao/forms.py
  58. 1470
      sapl/compilacao/models.py
  59. 179
      sapl/compilacao/templatetags/compilacao_filters.py
  60. 73
      sapl/compilacao/tests/test_compilacao.py
  61. 43
      sapl/compilacao/tests/test_tipo_texto_articulado_form.py
  62. 218
      sapl/compilacao/urls.py
  63. 57
      sapl/compilacao/utils.py
  64. 3142
      sapl/compilacao/views.py
  65. 40
      sapl/context_processors.py
  66. 287
      sapl/crispy_layout_mixin.py
  67. 1104
      sapl/crud/base.py
  68. 74
      sapl/crud/tests/settings.py
  69. 6
      sapl/crud/tests/stub_app/models.py
  70. 3
      sapl/crud/tests/stub_app/urls.py
  71. 4
      sapl/crud/tests/stub_app/views.py
  72. 304
      sapl/crud/tests/test_base.py
  73. 17
      sapl/crud/tests/test_masterdetail.py
  74. 2
      sapl/crud/urls.py
  75. 23
      sapl/decorators.py
  76. 33
      sapl/endpoint_restriction_middleware.py
  77. 6
      sapl/hashers.py
  78. 358
      sapl/lexml/OAIServer.py
  79. 6
      sapl/lexml/apps.py
  80. 24
      sapl/lexml/forms.py
  81. 60
      sapl/lexml/models.py
  82. 18
      sapl/lexml/urls.py
  83. 21
      sapl/lexml/views.py
  84. 3
      sapl/materia/admin.py
  85. 6
      sapl/materia/apps.py
  86. 3063
      sapl/materia/forms.py
  87. 1235
      sapl/materia/models.py
  88. 89
      sapl/materia/tests/test_email_templates.py
  89. 961
      sapl/materia/tests/test_materia.py
  90. 155
      sapl/materia/tests/test_materia_form.py
  91. 23
      sapl/materia/tests/test_materia_urls.py
  92. 338
      sapl/materia/urls.py
  93. 2683
      sapl/materia/views.py
  94. 12
      sapl/middleware.py
  95. 6
      sapl/norma/apps.py
  96. 595
      sapl/norma/forms.py
  97. 522
      sapl/norma/models.py
  98. 181
      sapl/norma/tests/test_norma.py
  99. 54
      sapl/norma/urls.py
  100. 488
      sapl/norma/views.py

9
conftest.py

@ -3,18 +3,17 @@ from django_webtest import DjangoTestApp, WebTestMixin
class OurTestApp(DjangoTestApp): class OurTestApp(DjangoTestApp):
def __init__(self, *args, **kwargs): 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) super(OurTestApp, self).__init__(*args, **kwargs)
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
kwargs.setdefault('user', self.default_user) kwargs.setdefault("user", self.default_user)
kwargs.setdefault('auto_follow', True) kwargs.setdefault("auto_follow", True)
return super(OurTestApp, self).get(*args, **kwargs) return super(OurTestApp, self).get(*args, **kwargs)
@pytest.fixture(scope='function') @pytest.fixture(scope="function")
def app(request, admin_user): def app(request, admin_user):
"""WebTest's TestApp. """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): def get_enviroment_admin_password(username):
password = os.environ.get('ADMIN_PASSWORD') password = os.environ.get("ADMIN_PASSWORD")
if not password: if not password:
print( print(
"[SUPERUSER] Environment variable $ADMIN_PASSWORD" "[SUPERUSER] Environment variable $ADMIN_PASSWORD"
" for user %s was not set. Leaving..." % username) " for user %s was not set. Leaving..." % username
sys.exit('MISSING_ADMIN_PASSWORD') )
sys.exit("MISSING_ADMIN_PASSWORD")
return password return password
def create_user_interlegis(): def create_user_interlegis():
from django.contrib.auth.models import User 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...") 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: if not created:
print("[SUPERUSER INTERLEGIS] User interlegis already exists." print(
" Updating password.") "[SUPERUSER INTERLEGIS] User interlegis already exists."
" Updating password."
)
user.is_superuser = True user.is_superuser = True
user.is_staff = True user.is_staff = True
user.set_password(password) user.set_password(password)
@ -37,19 +40,21 @@ def create_superuser():
from django.contrib.auth.models import User from django.contrib.auth.models import User
username = "admin" username = "admin"
email = os.environ.get('ADMIN_EMAIL', '') email = os.environ.get("ADMIN_EMAIL", "")
if User.objects.filter(username=username).exists(): if User.objects.filter(username=username).exists():
print("[SUPERUSER] User %s already exists." print(
" Exiting without change." % username) "[SUPERUSER] User %s already exists." " Exiting without change." % username
sys.exit('ADMIN_USER_EXISTS') )
sys.exit("ADMIN_USER_EXISTS")
else: else:
password = get_enviroment_admin_password(username) password = get_enviroment_admin_password(username)
print("[SUPERUSER] Creating superuser...") print("[SUPERUSER] Creating superuser...")
u = User.objects.create_superuser( u = User.objects.create_superuser(
username=username, password=password, email=email) username=username, password=password, email=email
)
u.save() u.save()
print("[SUPERUSER] Done.") print("[SUPERUSER] Done.")
@ -57,7 +62,7 @@ def create_superuser():
sys.exit(0) sys.exit(0)
if __name__ == '__main__': if __name__ == "__main__":
django.setup() django.setup()
create_user_interlegis() # must come before create_superuser create_user_interlegis() # must come before create_superuser
create_superuser() create_superuser()

13
docker/startup_scripts/genkey.py

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

7
docker/startup_scripts/gunicorn.conf.py

@ -35,9 +35,9 @@ wsgi_app = WSGI_APP
# Logs # Logs
loglevel = "debug" loglevel = "debug"
errorlog = "-" # send to stderr (so you see it in docker logs or terminal) errorlog = "-" # send to stderr (so you see it in docker logs or terminal)
accesslog = "-" # send to stdout accesslog = "-" # send to stdout
capture_output = True # capture print/tracebacks from app capture_output = True # capture print/tracebacks from app
# accesslog = "/var/log/sapl/access.log" # accesslog = "/var/log/sapl/access.log"
# errorlog = "/var/log/sapl/error.log" # errorlog = "/var/log/sapl/error.log"
@ -68,6 +68,7 @@ def on_starting(server):
def post_fork(server, worker): def post_fork(server, worker):
try: try:
from django import db from django import db
db.connections.close_all() db.connections.close_all()
except Exception: except Exception:
# Django not initialized yet or not available # Django not initialized yet or not available

176
docker/startup_scripts/solr_cli.py

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

208
drfautoapi/drfautoapi.py

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

6
sapl/api/apps.py

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

8
sapl/api/deprecated.py

@ -7,13 +7,11 @@ from sapl.api.serializers import SessaoPlenariaECidadaniaSerializer
from sapl.sessao.models import SessaoPlenaria from sapl.sessao.models import SessaoPlenaria
class SessaoPlenariaViewSet(ListModelMixin, class SessaoPlenariaViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
RetrieveModelMixin,
GenericViewSet):
""" """
Deprecated - Será eliminado na versão 3.2 Deprecated - Será eliminado na versão 3.2
* TODO: * TODO:
* eliminar endpoint, transferido para SaplApiViewSetConstrutor * eliminar endpoint, transferido para SaplApiViewSetConstrutor
* /api/sessao-planaria -> /api/sessao/sessaoplenaria/ecidadania * /api/sessao-planaria -> /api/sessao/sessaoplenaria/ecidadania
* /api/sessao-planaria/{pk} -> /api/sessao/sessaoplenaria/{pk}/ecidadania * /api/sessao-planaria/{pk} -> /api/sessao/sessaoplenaria/{pk}/ecidadania
@ -24,4 +22,4 @@ class SessaoPlenariaViewSet(ListModelMixin,
serializer_class = SessaoPlenariaECidadaniaSerializer serializer_class = SessaoPlenariaECidadaniaSerializer
queryset = SessaoPlenaria.objects.all() queryset = SessaoPlenaria.objects.all()
filter_backends = (DjangoFilterBackend,) filter_backends = (DjangoFilterBackend,)
filter_fields = ('data_inicio', 'data_fim', 'interativa') filter_fields = ("data_inicio", "data_fim", "interativa")

136
sapl/api/forms.py

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

127
sapl/api/pagination.py

@ -5,71 +5,73 @@ from rest_framework.response import Response
class StandardPagination(pagination.PageNumberPagination): class StandardPagination(pagination.PageNumberPagination):
page_size = 10 page_size = 10
page_size_query_param = 'page_size' page_size_query_param = "page_size"
max_page_size = 100 max_page_size = 100
def paginate_queryset(self, queryset, request, view=None): 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 None
return super().paginate_queryset(queryset, request, view=view) return super().paginate_queryset(queryset, request, view=view)
def get_paginated_response_schema(self, schema): def get_paginated_response_schema(self, schema):
r = { r = {
'type': 'object', "type": "object",
'properties': { "properties": {
'pagination': { "pagination": {
'type': 'object', "type": "object",
'properties': { "properties": {
'links': { "links": {
'type': 'object', "type": "object",
'properties': { "properties": {
'next': { "next": {
'type': 'string', "type": "string",
'nullable': True, "nullable": True,
'format': 'uri', "format": "uri",
'example': 'http://api.example.org/accounts/?{page_query_param}=4'.format( "example": "http://api.example.org/accounts/?{page_query_param}=4".format(
page_query_param=self.page_query_param) page_query_param=self.page_query_param
),
}, },
'previous': { "previous": {
'type': 'string', "type": "string",
'nullable': True, "nullable": True,
'format': 'uri', "format": "uri",
'example': 'http://api.example.org/accounts/?{page_query_param}=2'.format( "example": "http://api.example.org/accounts/?{page_query_param}=2".format(
page_query_param=self.page_query_param) page_query_param=self.page_query_param
),
}, },
} },
}, },
'previous_page': { "previous_page": {
'type': 'integer', "type": "integer",
'example': 123, "example": 123,
}, },
'next_page': { "next_page": {
'type': 'integer', "type": "integer",
'example': 123, "example": 123,
}, },
'start_index': { "start_index": {
'type': 'integer', "type": "integer",
'example': 123, "example": 123,
}, },
'end_index': { "end_index": {
'type': 'integer', "type": "integer",
'example': 123, "example": 123,
}, },
'total_entries': { "total_entries": {
'type': 'integer', "type": "integer",
'example': 123, "example": 123,
}, },
'total_pages': { "total_pages": {
'type': 'integer', "type": "integer",
'example': 123, "example": 123,
}, },
'page': { "page": {
'type': 'integer', "type": "integer",
'example': 123, "example": 123,
}, },
} },
}, },
'results': schema, "results": schema,
}, },
} }
return r return r
@ -85,20 +87,21 @@ class StandardPagination(pagination.PageNumberPagination):
except EmptyPage: except EmptyPage:
next_page_number = None next_page_number = None
return Response({ return Response(
'pagination': { {
'links': { "pagination": {
'next': self.get_next_link(), "links": {
'previous': self.get_previous_link(), "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,
}, },
'previous_page': previous_page_number, "results": data,
'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 rest_framework.permissions import DjangoModelPermissions
from sapl.rules.map_rules import rules_patterns_public from sapl.rules.map_rules import rules_patterns_public
class SaplModelPermissions(DjangoModelPermissions): class SaplModelPermissions(DjangoModelPermissions):
perms_map = { perms_map = {
'GET': ['%(app_label)s.list_%(model_name)s', "GET": [
'%(app_label)s.detail_%(model_name)s'], "%(app_label)s.list_%(model_name)s",
'OPTIONS': ['%(app_label)s.list_%(model_name)s', "%(app_label)s.detail_%(model_name)s",
'%(app_label)s.detail_%(model_name)s'], ],
'HEAD': ['%(app_label)s.list_%(model_name)s', "OPTIONS": [
'%(app_label)s.detail_%(model_name)s'], "%(app_label)s.list_%(model_name)s",
'POST': ['%(app_label)s.add_%(model_name)s'], "%(app_label)s.detail_%(model_name)s",
'PUT': ['%(app_label)s.change_%(model_name)s'], ],
'PATCH': ['%(app_label)s.change_%(model_name)s'], "HEAD": [
'DELETE': ['%(app_label)s.delete_%(model_name)s'], "%(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): def has_permission(self, request, view):
if getattr(view, '_ignore_model_permissions', False): if getattr(view, "_ignore_model_permissions", False):
return True return True
if hasattr(view, 'get_queryset'): if hasattr(view, "get_queryset"):
queryset = view.get_queryset() queryset = view.get_queryset()
else: else:
queryset = getattr(view, 'queryset', None) queryset = getattr(view, "queryset", None)
assert queryset is not None, ( assert queryset is not None, (
'Cannot apply DjangoModelPermissions on a view that ' "Cannot apply DjangoModelPermissions on a view that "
'does not set `.queryset` or have a `.get_queryset()` method.' "does not set `.queryset` or have a `.get_queryset()` method."
) )
perms = self.get_required_permissions(request.method, queryset.model) perms = self.get_required_permissions(request.method, queryset.model)
key = '{}:{}'.format( key = "{}:{}".format(
queryset.model._meta.app_label, queryset.model._meta.app_label, queryset.model._meta.model_name
queryset.model._meta.model_name) )
if key in rules_patterns_public: if key in rules_patterns_public:
perms = set(perms) perms = set(perms)
@ -47,7 +52,7 @@ class SaplModelPermissions(DjangoModelPermissions):
return True return True
return ( return (
request.user and request.user
(request.user.is_authenticated or not self.authenticated_users_only) and and (request.user.is_authenticated or not self.authenticated_users_only)
request.user.has_perms(perms) and request.user.has_perms(perms)
) )

221
sapl/api/serializers.py

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

44
sapl/api/urls.py

@ -1,16 +1,14 @@
from django.urls import include, path, re_path from django.urls import include, path, re_path
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, \ from drf_spectacular.views import (SpectacularAPIView, SpectacularRedocView,
SpectacularRedocView SpectacularSwaggerView)
from rest_framework.authtoken.views import obtain_auth_token from rest_framework.authtoken.views import obtain_auth_token
from sapl.api.deprecated import SessaoPlenariaViewSet from sapl.api.deprecated import SessaoPlenariaViewSet
from sapl.api.views import AppVersionView, recria_token,\ from sapl.api.views import (AppVersionView, SaplApiViewSetConstrutor,
SaplApiViewSetConstrutor recria_token)
from .apps import AppConfig from .apps import AppConfig
app_name = AppConfig.name app_name = AppConfig.name
router = SaplApiViewSetConstrutor.router() router = SaplApiViewSetConstrutor.router()
@ -19,26 +17,30 @@ router = SaplApiViewSetConstrutor.router()
# verificar se ainda permanece necessidade desses endpoint's # verificar se ainda permanece necessidade desses endpoint's
# /api/sessao-planaria -> /api/sessao/sessaoplenaria/ecidadania # /api/sessao-planaria -> /api/sessao/sessaoplenaria/ecidadania
# /api/sessao-planaria/{pk} -> /api/sessao/sessaoplenaria/{pk}/ecidadania # /api/sessao-planaria/{pk} -> /api/sessao/sessaoplenaria/{pk}/ecidadania
router.register(r'sessao-plenaria', SessaoPlenariaViewSet, router.register(
basename='sessao_plenaria_old') r"sessao-plenaria", SessaoPlenariaViewSet, basename="sessao_plenaria_old"
)
urlpatterns_router = router.urls urlpatterns_router = router.urls
urlpatterns_api_doc = [ urlpatterns_api_doc = [
re_path('^schema/swagger-ui/', re_path(
SpectacularSwaggerView.as_view(url_name='sapl.api:schema_api'), "^schema/swagger-ui/",
name='swagger_ui_schema_api'), SpectacularSwaggerView.as_view(url_name="sapl.api:schema_api"),
re_path('^schema/redoc/', name="swagger_ui_schema_api",
SpectacularRedocView.as_view(url_name='sapl.api:schema_api'), ),
name='redoc_schema_api'), re_path(
re_path('^schema/', SpectacularAPIView.as_view(), name='schema_api'), "^schema/redoc/",
SpectacularRedocView.as_view(url_name="sapl.api:schema_api"),
name="redoc_schema_api",
),
re_path("^schema/", SpectacularAPIView.as_view(), name="schema_api"),
] ]
urlpatterns = [ urlpatterns = [
path('api/', include(urlpatterns_api_doc)), path("api/", include(urlpatterns_api_doc)),
path('api/', include(urlpatterns_router)), path("api/", include(urlpatterns_router)),
re_path(r"^api/version", AppVersionView.as_view()),
re_path(r'^api/version', AppVersionView.as_view()), path("api/auth/token", obtain_auth_token),
path('api/auth/token', obtain_auth_token), re_path(r"^api/recriar-token/(?P<pk>\d*)$", recria_token, name="recria_token"),
re_path(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 django.conf import settings
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from rest_framework.decorators import api_view, permission_classes 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.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
@ -12,7 +12,7 @@ from drfautoapi.drfautoapi import ApiViewSetConstrutor
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@api_view(['POST']) @api_view(["POST"])
@permission_classes([IsAdminUser]) @permission_classes([IsAdminUser])
def recria_token(request, pk): def recria_token(request, pk):
Token.objects.filter(user_id=pk).delete() Token.objects.filter(user_id=pk).delete()
@ -26,28 +26,30 @@ class AppVersionView(APIView):
def get(self, request): def get(self, request):
content = { content = {
'name': 'SAPL', "name": "SAPL",
'description': 'Sistema de Apoio ao Processo Legislativo', "description": "Sistema de Apoio ao Processo Legislativo",
'version': settings.SAPL_VERSION, "version": settings.SAPL_VERSION,
'user': request.user.username, "user": request.user.username,
'is_authenticated': request.user.is_authenticated, "is_authenticated": request.user.is_authenticated,
} }
return Response(content) return Response(content)
SaplApiViewSetConstrutor = ApiViewSetConstrutor SaplApiViewSetConstrutor = ApiViewSetConstrutor
SaplApiViewSetConstrutor.import_modules([ SaplApiViewSetConstrutor.import_modules(
'sapl.api.views_audiencia', [
'sapl.api.views_base', "sapl.api.views_audiencia",
'sapl.api.views_comissoes', "sapl.api.views_base",
'sapl.api.views_compilacao', "sapl.api.views_comissoes",
'sapl.api.views_materia', "sapl.api.views_compilacao",
'sapl.api.views_norma', "sapl.api.views_materia",
'sapl.api.views_painel', "sapl.api.views_norma",
'sapl.api.views_parlamentares', "sapl.api.views_painel",
'sapl.api.views_protocoloadm', "sapl.api.views_parlamentares",
'sapl.api.views_sessao', "sapl.api.views_protocoloadm",
]) "sapl.api.views_sessao",
]
)
""" """

9
sapl/api/views_audiencia.py

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

19
sapl/api/views_comissoes.py

@ -1,23 +1,16 @@
from django.apps.registry import apps 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 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 from sapl.materia.models import MateriaEmTramitacao
ApiViewSetConstrutor.build_class([apps.get_app_config("comissoes")])
ApiViewSetConstrutor.build_class(
[
apps.get_app_config('comissoes')
]
)
@customize(Comissao) @customize(Comissao)
class _ComissaoViewSet: class _ComissaoViewSet:
@action(detail=True) @action(detail=True)
def materiaemtramitacao(self, request, *args, **kwargs): def materiaemtramitacao(self, request, *args, **kwargs):
return self.get_materiaemtramitacao(**kwargs) return self.get_materiaemtramitacao(**kwargs)
@ -25,5 +18,5 @@ class _ComissaoViewSet:
@wrapper_queryset_response_for_drf_action(model=MateriaEmTramitacao) @wrapper_queryset_response_for_drf_action(model=MateriaEmTramitacao)
def get_materiaemtramitacao(self, **kwargs): def get_materiaemtramitacao(self, **kwargs):
return self.get_queryset().filter( return self.get_queryset().filter(
unidade_tramitacao_atual__comissao=kwargs['pk'], unidade_tramitacao_atual__comissao=kwargs["pk"],
) )

12
sapl/api/views_compilacao.py

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

55
sapl/api/views_materia.py

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

12
sapl/api/views_norma.py

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

11
sapl/api/views_painel.py

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

63
sapl/api/views_parlamentares.py

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

40
sapl/api/views_protocoloadm.py

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

23
sapl/api/views_sessao.py

@ -1,26 +1,19 @@
from django.apps.registry import apps from django.apps.registry import apps
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \ from drfautoapi.drfautoapi import (ApiViewSetConstrutor, customize,
customize, wrapper_queryset_response_for_drf_action wrapper_queryset_response_for_drf_action)
from sapl.api.serializers import ChoiceSerializer,\ from sapl.api.serializers import (ChoiceSerializer,
SessaoPlenariaECidadaniaSerializer SessaoPlenariaECidadaniaSerializer)
from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao from sapl.sessao.models import ExpedienteSessao, SessaoPlenaria
from sapl.utils import choice_anos_com_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) @customize(SessaoPlenaria)
class _SessaoPlenariaViewSet: class _SessaoPlenariaViewSet:
@action(detail=False) @action(detail=False)
def years(self, request, *args, **kwargs): def years(self, request, *args, **kwargs):
years = choice_anos_com_sessaoplenaria() years = choice_anos_com_sessaoplenaria()
@ -34,14 +27,14 @@ class _SessaoPlenariaViewSet:
@wrapper_queryset_response_for_drf_action(model=ExpedienteSessao) @wrapper_queryset_response_for_drf_action(model=ExpedienteSessao)
def get_expedientes(self): 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) @action(detail=True)
def ecidadania(self, request, *args, **kwargs): def ecidadania(self, request, *args, **kwargs):
self.serializer_class = SessaoPlenariaECidadaniaSerializer self.serializer_class = SessaoPlenariaECidadaniaSerializer
return self.retrieve(request, *args, **kwargs) 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): def ecidadania_list(self, request, *args, **kwargs):
self.serializer_class = SessaoPlenariaECidadaniaSerializer self.serializer_class = SessaoPlenariaECidadaniaSerializer
return self.list(request, *args, **kwargs) return self.list(request, *args, **kwargs)

6
sapl/audiencia/apps.py

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

228
sapl/audiencia/forms.py

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

194
sapl/audiencia/models.py

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

83
sapl/audiencia/tests/test_audiencia.py

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

11
sapl/audiencia/urls.py

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

75
sapl/audiencia/views.py

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

4
sapl/base/admin.py

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

7
sapl/base/apps.py

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

257
sapl/base/email_utils.py

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

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

499
sapl/base/models.py

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

269
sapl/base/receivers.py

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

135
sapl/base/search_indexes.py

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

5
sapl/base/templatetags/base_tags.py

@ -1,4 +1,3 @@
from django import template from django import template
register = template.Library() register = template.Library()
@ -6,4 +5,6 @@ register = template.Library()
@register.filter @register.filter
def tipoautor_contenttype_list(tipo): 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"
)

122
sapl/base/templatetags/common_tags.py

@ -7,22 +7,24 @@ from django.utils.safestring import mark_safe
from webpack_loader import utils from webpack_loader import utils
from sapl.base.models import AppConfig from sapl.base.models import AppConfig
from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa, Proposicao from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa,
Proposicao)
from sapl.norma.models import NormaJuridica from sapl.norma.models import NormaJuridica
from sapl.parlamentares.models import Filiacao from sapl.parlamentares.models import Filiacao
from sapl.sessao.models import SessaoPlenaria 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() register = template.Library()
def get_class(class_string): def get_class(class_string):
if not hasattr(class_string, '__bases__'): if not hasattr(class_string, "__bases__"):
class_string = str(class_string) class_string = str(class_string)
dot = class_string.rindex('.') dot = class_string.rindex(".")
mod_name, class_name = class_string[:dot], class_string[dot + 1:] mod_name, class_name = class_string[:dot], class_string[dot + 1 :]
if class_name: if class_name:
return getattr(__import__(mod_name, {}, {}, [str('')]), class_name) return getattr(__import__(mod_name, {}, {}, [str("")]), class_name)
@register.simple_tag @register.simple_tag
@ -66,7 +68,13 @@ def model_verbose_name_plural(class_name):
@register.filter @register.filter
def obfuscate_value(value, key): 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 "***************"
return value return value
@ -98,7 +106,7 @@ def meta_model_value(instance, attr):
try: try:
return getattr(instance._meta, attr) return getattr(instance._meta, attr)
except: except:
return '' return ""
@register.filter @register.filter
@ -121,15 +129,16 @@ def sort_by_keys(value, key):
transformed = [] transformed = []
id_props = [x.id for x in value] id_props = [x.id for x in value]
qs = Proposicao.objects.filter(pk__in=id_props) qs = Proposicao.objects.filter(pk__in=id_props)
key_descricao = {'1': 'data_envio', key_descricao = {
'-1': '-data_envio', "1": "data_envio",
'2': 'tipo', "-1": "-data_envio",
'-2': '-tipo', "2": "tipo",
'3': 'descricao', "-2": "-tipo",
'-3': '-descricao', "3": "descricao",
'4': 'autor', "-3": "-descricao",
'-4': '-autor' "4": "autor",
} "-4": "-autor",
}
transformed = qs.order_by(key_descricao[key]) transformed = qs.order_by(key_descricao[key])
return transformed return transformed
@ -176,7 +185,7 @@ def isinst(value, class_str):
@register.filter @register.filter
@stringfilter @stringfilter
def strip_hash(value): def strip_hash(value):
vet = value.split('/') vet = value.split("/")
if len(vet) == 2: if len(vet) == 2:
return vet[0][1:] return vet[0][1:]
else: else:
@ -193,7 +202,7 @@ def get_add_perm(value, arg):
except AttributeError: except AttributeError:
return None return None
nome_model = view.__class__.model.__name__.lower() nome_model = view.__class__.model.__name__.lower()
can_add = '.add_' + nome_model can_add = ".add_" + nome_model
return perm.__contains__(nome_app + can_add) return perm.__contains__(nome_app + can_add)
@ -208,7 +217,7 @@ def get_change_perm(value, arg):
except AttributeError: except AttributeError:
return None return None
nome_model = view.__class__.model.__name__.lower() nome_model = view.__class__.model.__name__.lower()
can_change = '.change_' + nome_model can_change = ".change_" + nome_model
return perm.__contains__(nome_app + can_change) return perm.__contains__(nome_app + can_change)
@ -223,7 +232,7 @@ def get_delete_perm(value, arg):
except AttributeError: except AttributeError:
return None return None
nome_model = view.__class__.model.__name__.lower() nome_model = view.__class__.model.__name__.lower()
can_delete = '.delete_' + nome_model can_delete = ".delete_" + nome_model
return perm.__contains__(nome_app + can_delete) return perm.__contains__(nome_app + can_delete)
@ -232,8 +241,9 @@ def get_delete_perm(value, arg):
def ultima_filiacao(value): def ultima_filiacao(value):
parlamentar = value parlamentar = value
ultima_filiacao = Filiacao.objects.filter( ultima_filiacao = (
parlamentar=parlamentar).order_by('-data').first() Filiacao.objects.filter(parlamentar=parlamentar).order_by("-data").first()
)
if ultima_filiacao: if ultima_filiacao:
return ultima_filiacao.partido return ultima_filiacao.partido
@ -249,28 +259,27 @@ def get_config_attr(attribute):
@register.filter @register.filter
def str2intabs(value): def str2intabs(value):
if not isinstance(value, str): if not isinstance(value, str):
return '' return ""
try: try:
v = int(value) v = int(value)
v = abs(v) v = abs(v)
return v return v
except: except:
return '' return ""
@register.filter @register.filter
def has_iframe(request): def has_iframe(request):
iframe = request.session.get("iframe", False)
iframe = request.session.get('iframe', False) if not iframe and "iframe" in request.GET:
if not iframe and 'iframe' in request.GET: ival = request.GET["iframe"]
ival = request.GET['iframe']
if ival and int(ival) == 1: if ival and int(ival) == 1:
request.session['iframe'] = True request.session["iframe"] = True
return True return True
elif 'iframe' in request.GET: elif "iframe" in request.GET:
ival = request.GET['iframe'] ival = request.GET["iframe"]
if ival and int(ival) == 0: if ival and int(ival) == 0:
del request.session['iframe'] del request.session["iframe"]
return False return False
return iframe return iframe
@ -278,7 +287,7 @@ def has_iframe(request):
@register.filter @register.filter
def url(value): def url(value):
if value.startswith('http://') or value.startswith('https://'): if value.startswith("http://") or value.startswith("https://"):
return True return True
return False return False
@ -308,33 +317,37 @@ def youtube_url(value):
@register.filter @register.filter
def facebook_url(value): def facebook_url(value):
value = value.lower() 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) r = re.findall(facebook_pattern, value)
return True if r else False return True if r else False
@register.filter @register.filter
def youtube_id(value): def youtube_id(value):
from urllib.parse import urlparse, parse_qs from urllib.parse import parse_qs, urlparse
u_pars = urlparse(value) 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: if quer_v:
return quer_v[0] return quer_v[0]
return '' return ""
@register.filter @register.filter
def file_extension(value): def file_extension(value):
import pathlib import pathlib
return pathlib.Path(value).suffix.replace('.', '')
return pathlib.Path(value).suffix.replace(".", "")
@register.filter @register.filter
def cronometro_to_seconds(value): def cronometro_to_seconds(value):
if not AppConfig.attr('cronometro_' + value): if not AppConfig.attr("cronometro_" + value):
return 0 return 0
return AppConfig.attr('cronometro_' + value).seconds return AppConfig.attr("cronometro_" + value).seconds
@register.filter @register.filter
@ -345,27 +358,25 @@ def to_list_pk(object_list):
@register.filter @register.filter
def search_get_model(object): def search_get_model(object):
if type(object) == MateriaLegislativa: if type(object) == MateriaLegislativa:
return 'm' return "m"
elif type(object) == DocumentoAcessorio: elif type(object) == DocumentoAcessorio:
return 'd' return "d"
elif type(object) == NormaJuridica: elif type(object) == NormaJuridica:
return 'n' return "n"
elif type(object) == SessaoPlenaria: elif type(object) == SessaoPlenaria:
return 's' return "s"
return None return None
@register.filter @register.filter
def urldetail_content_type(obj, value): def urldetail_content_type(obj, value):
return '%s:%s_detail' % ( return "%s:%s_detail" % (value._meta.app_config.name, obj.content_type.model)
value._meta.app_config.name, obj.content_type.model)
@register.filter @register.filter
def urldetail(obj): def urldetail(obj):
return '%s:%s_detail' % ( return "%s:%s_detail" % (obj._meta.app_config.name, obj._meta.model_name)
obj._meta.app_config.name, obj._meta.model_name)
@register.filter @register.filter
@ -373,7 +384,7 @@ def filiacao_data_filter(parlamentar, data_inicio):
try: try:
filiacao = filiacao_data(parlamentar, data_inicio) filiacao = filiacao_data(parlamentar, data_inicio)
except Exception: except Exception:
filiacao = '' filiacao = ""
return filiacao return filiacao
@ -382,7 +393,7 @@ def filiacao_intervalo_filter(parlamentar, date_range):
try: try:
filiacao = filiacao_data(parlamentar, date_range[0], date_range[1]) filiacao = filiacao_data(parlamentar, date_range[0], date_range[1])
except Exception: except Exception:
filiacao = '' filiacao = ""
return filiacao return filiacao
@ -390,10 +401,11 @@ def filiacao_intervalo_filter(parlamentar, date_range):
def render_chunk_vendors(extension=None): def render_chunk_vendors(extension=None):
try: try:
tags = utils.get_as_tags( tags = utils.get_as_tags(
'chunk-vendors', extension=extension, config='DEFAULT', attrs='') "chunk-vendors", extension=extension, config="DEFAULT", attrs=""
return mark_safe('\n'.join(tags)) )
return mark_safe("\n".join(tags))
except: except:
return '' return ""
@register.filter(is_safe=True) @register.filter(is_safe=True)
@ -416,4 +428,4 @@ def is_report_visible(request, url_path=None):
@register.filter @register.filter
def sort_by_index(queryset, index): def sort_by_index(queryset, index):
return sorted(queryset, key=lambda x: x[index]) return sorted(queryset, key=lambda x: x[index])

128
sapl/base/templatetags/menus.py

@ -1,27 +1,26 @@
import logging import logging
import yaml
from django import template from django import template
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import yaml
register = template.Library() register = template.Library()
logger = logging.getLogger(__name__) 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): def menu(context, path=None):
return nav_run(context, path) 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): def subnav(context, path=None):
return nav_run(context, path) 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): def navbar(context, path=None):
return nav_run(context, path) 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. será realizado o teste de permissão para renderizá-lo.
""" """
menu = None menu = None
root_pk = context.get('root_pk', None) root_pk = context.get("root_pk", None)
if not root_pk: if not root_pk:
obj = context.get('object', None) obj = context.get("object", None)
if obj: if obj:
root_pk = obj.pk root_pk = obj.pk
if root_pk or 'subnav_template_name' in context or path: if root_pk or "subnav_template_name" in context or path:
request = context['request'] request = context["request"]
""" """
As implementações das Views de Modelos que são dados auxiliares e 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 rm = request.resolver_match
app_template = rm.app_name.rsplit('.', 1)[-1] app_template = rm.app_name.rsplit(".", 1)[-1]
if path: if path:
yaml_path = path yaml_path = path
elif 'subnav_template_name' in context: elif "subnav_template_name" in context:
yaml_path = context['subnav_template_name'] yaml_path = context["subnav_template_name"]
else: else:
yaml_path = '%s/%s' % (app_template, 'subnav.yaml') yaml_path = "%s/%s" % (app_template, "subnav.yaml")
if not yaml_path: if not yaml_path:
return return
@ -88,44 +87,47 @@ def nav_run(context, path=None):
menu = yaml.load(rendered, yaml.Loader) menu = yaml.load(rendered, yaml.Loader)
resolve_urls_inplace(menu, root_pk, rm, context) resolve_urls_inplace(menu, root_pk, rm, context)
except Exception as e: 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: Erro:
%s %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): def resolve_urls_inplace(menu, pk, rm, context):
if isinstance(menu, list): if isinstance(menu, list):
list_active = '' list_active = ""
for item in menu: for item in menu:
menuactive = resolve_urls_inplace(item, pk, rm, context) menuactive = resolve_urls_inplace(item, pk, rm, context)
list_active = menuactive if menuactive else list_active list_active = menuactive if menuactive else list_active
if not isinstance(item, list): if not isinstance(item, list):
item['active'] = menuactive item["active"] = menuactive
return list_active return list_active
else: else:
if 'url' in menu: if "url" in menu:
url_name = menu["url"]
url_name = menu['url']
if "check_permission" in menu and not context["request"].user.has_perm(
if 'check_permission' in menu and not context[ menu["check_permission"]
'request'].user.has_perm(menu['check_permission']): ):
menu['url'] = '' menu["url"] = ""
menu['active'] = '' menu["active"] = ""
else: else:
if '/' in url_name: if "/" in url_name:
pass pass
elif ':' in url_name: elif ":" in url_name:
try: try:
menu['url'] = reverse('%s' % menu['url']) menu["url"] = reverse("%s" % menu["url"])
except: except:
try: try:
menu['url'] = reverse('%s' % menu['url'], menu["url"] = reverse("%s" % menu["url"], kwargs={"pk": pk})
kwargs={'pk': pk})
except: except:
# tem que ser root_pk pois quando está sendo # tem que ser root_pk pois quando está sendo
# renderizado um detail, update, delete # 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: 2) Se existe no contexto um desses itens:
- context['root_pk'] pk do master - context['root_pk'] pk do master
- context['object'] objeto do master - context['object'] objeto do master
""".format(menu['title'], menu['url']) """.format(
menu["title"], menu["url"]
)
logger.error(log) logger.error(log)
raise Exception(log) raise Exception(log)
else: else:
try: try:
menu['url'] = reverse('%s:%s' % ( menu["url"] = reverse("%s:%s" % (rm.app_name, menu["url"]))
rm.app_name, menu['url']))
except: except:
try: try:
menu['url'] = reverse('%s:%s' % ( menu["url"] = reverse(
rm.app_name, menu['url']), kwargs={'pk': pk}) "%s:%s" % (rm.app_name, menu["url"]), kwargs={"pk": pk}
)
except: except:
log = """Erro na construção do Menu: log = """Erro na construção do Menu:
menu: {} menu: {}
@ -169,13 +173,16 @@ def resolve_urls_inplace(menu, pk, rm, context):
2) Se existe no contexto um desses itens: 2) Se existe no contexto um desses itens:
- context['root_pk'] pk do master - context['root_pk'] pk do master
- context['object'] objeto do master - context['object'] objeto do master
""".format(menu['title'], menu['url']) """.format(
menu["title"], menu["url"]
)
logger.error(log) logger.error(log)
raise Exception(log) raise Exception(log)
menu['active'] = 'active'\ menu["active"] = (
if context['request'].path == menu['url'] else '' "active" if context["request"].path == menu["url"] else ""
if not menu['active']: )
if not menu["active"]:
""" """
Se não encontrada diretamente, Se não encontrada diretamente,
procura a url acionada dentro do crud, caso seja um. 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. - visualização de detalhes, adição, edição, remoção.
""" """
try: try:
if 'view' in context: if "view" in context:
view = context['view'] view = context["view"]
if hasattr(view, 'crud'): if hasattr(view, "crud"):
urls = view.crud.get_urls() urls = view.crud.get_urls()
for u in urls: for u in urls:
if (u.name == url_name or if (
'urls_extras' in menu and u.name == url_name
u.name in menu['urls_extras']): or "urls_extras" in menu
menu['active'] = 'active' and u.name in menu["urls_extras"]
):
menu["active"] = "active"
break break
except: except:
url_active = menu.get('url', '') url_active = menu.get("url", "")
logger.warning( logger.warning(
f'Não foi possível definir se url {url_active} é a url ativa.') 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']): elif "check_permission" in menu and not context["request"].user.has_perm(
menu['active'] = '' menu["check_permission"]
del menu['children'] ):
menu["active"] = ""
if 'children' in menu: del menu["children"]
menu['active'] = resolve_urls_inplace(
menu['children'], pk, rm, context) if "children" in menu:
return menu['active'] 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) @pytest.mark.django_db(transaction=False)
def test_casa_legislativa_model(): def test_casa_legislativa_model():
baker.make(CasaLegislativa, baker.make(
nome='Teste_Nome_Casa_Legislativa', CasaLegislativa,
sigla='TSCL', nome="Teste_Nome_Casa_Legislativa",
endereco='Teste_Endereço_Casa_Legislativa', sigla="TSCL",
cep='12345678', endereco="Teste_Endereço_Casa_Legislativa",
municipio='Teste_Municipio_Casa_Legislativa', cep="12345678",
uf='DF') municipio="Teste_Municipio_Casa_Legislativa",
uf="DF",
)
casa_legislativa = CasaLegislativa.objects.first() casa_legislativa = CasaLegislativa.objects.first()
assert casa_legislativa.nome == 'Teste_Nome_Casa_Legislativa' assert casa_legislativa.nome == "Teste_Nome_Casa_Legislativa"
assert casa_legislativa.sigla == 'TSCL' assert casa_legislativa.sigla == "TSCL"
assert casa_legislativa.endereco == 'Teste_Endereço_Casa_Legislativa' assert casa_legislativa.endereco == "Teste_Endereço_Casa_Legislativa"
assert casa_legislativa.cep == '12345678' assert casa_legislativa.cep == "12345678"
assert casa_legislativa.municipio == 'Teste_Municipio_Casa_Legislativa' assert casa_legislativa.municipio == "Teste_Municipio_Casa_Legislativa"
assert casa_legislativa.uf == 'DF' 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 errors = form.errors
assert errors['nome'] == [_('Este campo é obrigatório.')] assert errors["nome"] == [_("Este campo é obrigatório.")]
assert errors['sigla'] == [_('Este campo é obrigatório.')] assert errors["sigla"] == [_("Este campo é obrigatório.")]
assert errors['endereco'] == [_('Este campo é obrigatório.')] assert errors["endereco"] == [_("Este campo é obrigatório.")]
assert errors['cep'] == [_('Este campo é obrigatório.')] assert errors["cep"] == [_("Este campo é obrigatório.")]
assert errors['municipio'] == [_('Este campo é obrigatório.')] assert errors["municipio"] == [_("Este campo é obrigatório.")]
assert errors['uf'] == [_('Este campo é obrigatório.')] assert errors["uf"] == [_("Este campo é obrigatório.")]
assert len(errors) == 6 assert len(errors) == 6
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_casa_legislativa_form_invalido(): def test_casa_legislativa_form_invalido():
form = CasaLegislativaForm(data={'codigo': 'codigo', form = CasaLegislativaForm(
'nome': 'nome', data={
'sigla': 'sg', "codigo": "codigo",
'endereco': 'endereco', "nome": "nome",
'cep': '7000000', "sigla": "sg",
'municipio': 'municipio', "endereco": "endereco",
'uf': 'uf', "cep": "7000000",
'telefone': '33333333', "municipio": "municipio",
'fax': '33333333', "uf": "uf",
'logotipo': 'image', "telefone": "33333333",
'endereco_web': 'web', "fax": "33333333",
'email': 'email', "logotipo": "image",
'informacao_geral': 'informacao_geral' "endereco_web": "web",
}) "email": "email",
"informacao_geral": "informacao_geral",
}
)
assert not form.is_valid() assert not form.is_valid()

60
sapl/base/tests/test_login.py

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

1116
sapl/base/tests/test_view_base.py

File diff suppressed because it is too large

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 from sapl.urls import urlpatterns as original_patterns
ptrn = [path('zzzz', ptrn = [path("zzzz", TemplateView.as_view(template_name="index.html"), name="zzzz")]
TemplateView.as_view(
template_name='index.html'), name='zzzz')]
urlpatterns = original_patterns + ptrn urlpatterns = original_patterns + ptrn

304
sapl/base/urls.py

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

1254
sapl/base/views.py

File diff suppressed because it is too large

6
sapl/comissoes/apps.py

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

458
sapl/comissoes/forms.py

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

404
sapl/comissoes/models.py

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

165
sapl/comissoes/tests/test_comissoes.py

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

60
sapl/comissoes/urls.py

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

319
sapl/comissoes/views.py

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

8
sapl/compilacao/admin.py

@ -1,4 +1,5 @@
from django.contrib import admin from django.contrib import admin
from sapl.compilacao.models import TipoDispositivo from sapl.compilacao.models import TipoDispositivo
from sapl.utils import register_all_models_in_admin from sapl.utils import register_all_models_in_admin
@ -8,5 +9,8 @@ admin.site.unregister(TipoDispositivo)
@admin.register(TipoDispositivo) @admin.register(TipoDispositivo)
class TipoDispositivoAdmin(admin.ModelAdmin): class TipoDispositivoAdmin(admin.ModelAdmin):
readonly_fields = ("rotulo_prefixo_texto", "rotulo_sufixo_texto",) readonly_fields = (
list_display = [f.name for f in TipoDispositivo._meta.fields if f.name != 'id'] "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 import apps
from django.conf import settings from django.conf import settings
from django.db import connection, models from django.db import connection, models
@ -7,36 +6,39 @@ from django.utils.translation import gettext_lazy as _
class AppConfig(apps.AppConfig): class AppConfig(apps.AppConfig):
name = 'sapl.compilacao' name = "sapl.compilacao"
label = 'compilacao' label = "compilacao"
verbose_name = _('Compilação') verbose_name = _("Compilação")
@staticmethod @staticmethod
def import_pattern(): def import_pattern():
from django.contrib.contenttypes.models import ContentType
from unipath import Path
from sapl.compilacao.models import TipoTextoArticulado from sapl.compilacao.models import TipoTextoArticulado
from sapl.compilacao.utils import get_integrations_view_names 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) compilacao_app = Path(__file__).ancestor(1)
# print(compilacao_app) # 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 = f.readlines()
lines = [line.rstrip('\n') for line in lines] lines = [line.rstrip("\n") for line in lines]
with connection.cursor() as cursor: with connection.cursor() as cursor:
for line in lines: for line in lines:
line = line.strip() line = line.strip()
if not line or line.startswith('#'): if not line or line.startswith("#"):
continue continue
try: try:
cursor.execute(line) cursor.execute(line)
except IntegrityError as e: except IntegrityError as e:
if not settings.DEBUG: 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: except Exception as ee:
print(ee) print(ee)
@ -46,12 +48,12 @@ class AppConfig(apps.AppConfig):
verbose_name = verbose_name.upper().split() verbose_name = verbose_name.upper().split()
if len(verbose_name) == 1: if len(verbose_name) == 1:
verbose_name = verbose_name[0] verbose_name = verbose_name[0]
sigla = '' sigla = ""
for letra in verbose_name: for letra in verbose_name:
if letra in 'BCDFGHJKLMNPQRSTVWXYZ': if letra in "BCDFGHJKLMNPQRSTVWXYZ":
sigla += letra sigla += letra
else: else:
sigla = ''.join([palavra[0] for palavra in verbose_name]) sigla = "".join([palavra[0] for palavra in verbose_name])
return sigla[:3] return sigla[:3]
for view in integrations_view_names: for view in integrations_view_names:
@ -60,28 +62,31 @@ class AppConfig(apps.AppConfig):
tipo.sigla = cria_sigla( tipo.sigla = cria_sigla(
view.model._meta.verbose_name view.model._meta.verbose_name
if 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.descricao = view.model._meta.verbose_name
tipo.content_type = ContentType.objects.get_by_natural_key( 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() tipo.save()
except IntegrityError as e: except IntegrityError as e:
if not settings.DEBUG: 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): if app_config != AppConfig and not isinstance(app_config, AppConfig):
return return
from sapl.compilacao.models import TipoDispositivo from sapl.compilacao.models import TipoDispositivo
if not TipoDispositivo.objects.exists():
print('') if not TipoDispositivo.objects.exists():
print("\033[93m\033[1m{}\033[0m".format(_('Iniciando Textos Articulados...'))) print("")
print("\033[93m\033[1m{}\033[0m".format(_("Iniciando Textos Articulados...")))
AppConfig.import_pattern() AppConfig.import_pattern()
models.signals.post_migrate.connect( models.signals.post_migrate.connect(receiver=init_compilacao_base)
receiver=init_compilacao_base)

1801
sapl/compilacao/forms.py

File diff suppressed because it is too large

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

73
sapl/compilacao/tests/test_compilacao.py

@ -1,104 +1,85 @@
import pytest import pytest
from model_bakery import baker from model_bakery import baker
from sapl.compilacao.models import PerfilEstruturalTextoArticulado from sapl.compilacao.models import (PerfilEstruturalTextoArticulado,
from sapl.compilacao.models import TipoTextoArticulado TextoArticulado, TipoDispositivo,
from sapl.compilacao.models import TextoArticulado, TipoNota TipoDispositivoRelationship, TipoNota,
from sapl.compilacao.models import TipoVide, TipoDispositivo TipoTextoArticulado, TipoVide)
from sapl.compilacao.models import TipoDispositivoRelationship
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_perfil_estrutural_texto_articulado_model(): def test_perfil_estrutural_texto_articulado_model():
perfil_estrutural_texto_articulado = baker.make( perfil_estrutural_texto_articulado = baker.make(
PerfilEstruturalTextoArticulado, PerfilEstruturalTextoArticulado, nome="Teste_Nome_Perfil", sigla="TSPETA"
nome='Teste_Nome_Perfil', )
sigla='TSPETA')
assert perfil_estrutural_texto_articulado.nome == 'Teste_Nome_Perfil' assert perfil_estrutural_texto_articulado.nome == "Teste_Nome_Perfil"
assert perfil_estrutural_texto_articulado.sigla == 'TSPETA' assert perfil_estrutural_texto_articulado.sigla == "TSPETA"
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_tipo_texto_articulado_model(): def test_tipo_texto_articulado_model():
tipo_texto_articulado = baker.make( tipo_texto_articulado = baker.make(
TipoTextoArticulado, TipoTextoArticulado, sigla="TTP", descricao="T_Desc_Tipo_Texto_Articulado"
sigla='TTP',
descricao='T_Desc_Tipo_Texto_Articulado'
) )
assert tipo_texto_articulado.sigla == 'TTP' assert tipo_texto_articulado.sigla == "TTP"
assert tipo_texto_articulado.descricao == 'T_Desc_Tipo_Texto_Articulado' assert tipo_texto_articulado.descricao == "T_Desc_Tipo_Texto_Articulado"
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_texto_articulado_model(): def test_texto_articulado_model():
texto_articulado = baker.make( texto_articulado = baker.make(
TextoArticulado, TextoArticulado,
ementa='Teste_Ementa_Texto_Articulado', ementa="Teste_Ementa_Texto_Articulado",
numero='12345678', numero="12345678",
ano=2016, ano=2016,
) )
assert texto_articulado.ementa == 'Teste_Ementa_Texto_Articulado' assert texto_articulado.ementa == "Teste_Ementa_Texto_Articulado"
assert texto_articulado.numero == '12345678' assert texto_articulado.numero == "12345678"
assert texto_articulado.ano == 2016 assert texto_articulado.ano == 2016
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_tipo_nota_model(): def test_tipo_nota_model():
tipo_nota = baker.make( tipo_nota = baker.make(TipoNota, sigla="TTN", nome="Teste_Nome_Tipo_Nota")
TipoNota,
sigla='TTN',
nome='Teste_Nome_Tipo_Nota'
)
assert tipo_nota.sigla == 'TTN' assert tipo_nota.sigla == "TTN"
assert tipo_nota.nome == 'Teste_Nome_Tipo_Nota' assert tipo_nota.nome == "Teste_Nome_Tipo_Nota"
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_tipo_vide_model(): def test_tipo_vide_model():
tipo_vide = baker.make( tipo_vide = baker.make(TipoVide, sigla="TTV", nome="Teste_Nome_Tipo_Vide")
TipoVide,
sigla='TTV',
nome='Teste_Nome_Tipo_Vide'
)
assert tipo_vide.sigla == 'TTV' assert tipo_vide.sigla == "TTV"
assert tipo_vide.nome == 'Teste_Nome_Tipo_Vide' assert tipo_vide.nome == "Teste_Nome_Tipo_Vide"
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_tipo_dispositivo_model(): def test_tipo_dispositivo_model():
tipo_dispositivo = baker.make( tipo_dispositivo = baker.make(
TipoDispositivo, TipoDispositivo, nome="Teste_Nome_Tipo_Dispositivo", rotulo_ordinal=0
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 assert tipo_dispositivo.rotulo_ordinal == 0
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_tipo_dispositivo_relationship_model(): def test_tipo_dispositivo_relationship_model():
tipo_dispositivo_pai = baker.make( tipo_dispositivo_pai = baker.make(
TipoDispositivo, TipoDispositivo, nome="Tipo_Dispositivo_Pai", rotulo_ordinal=0
nome='Tipo_Dispositivo_Pai',
rotulo_ordinal=0
) )
t_dispositivo_filho = baker.make( t_dispositivo_filho = baker.make(
TipoDispositivo, TipoDispositivo, nome="Tipo_Dispositivo_Filho", rotulo_ordinal=0
nome='Tipo_Dispositivo_Filho',
rotulo_ordinal=0
) )
p_e_texto_articulado = baker.make( p_e_texto_articulado = baker.make(
PerfilEstruturalTextoArticulado, PerfilEstruturalTextoArticulado, nome="Teste_Nome_Perfil", sigla="TSPETA"
nome='Teste_Nome_Perfil', )
sigla='TSPETA')
tipo_dispositivo_relationship = baker.make( tipo_dispositivo_relationship = baker.make(
TipoDispositivoRelationship, 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 errors = form.errors
assert errors['sigla'] == [_('Este campo é obrigatório.')] assert errors["sigla"] == [_("Este campo é obrigatório.")]
assert errors['descricao'] == [_('Este campo é obrigatório.')] assert errors["descricao"] == [_("Este campo é obrigatório.")]
assert errors['participacao_social'] == [_('Este campo é obrigatório.')] assert errors["participacao_social"] == [_("Este campo é obrigatório.")]
assert errors['publicacao_func'] == [_('Este campo é obrigatório.')] assert errors["publicacao_func"] == [_("Este campo é obrigatório.")]
assert len(errors) == 4 assert len(errors) == 4
@ -29,12 +29,12 @@ def test_valida_campos_obrigatorios_nota_form():
errors = form.errors errors = form.errors
assert errors['texto'] == [_('Este campo é obrigatório')] assert errors["texto"] == [_("Este campo é obrigatório")]
assert errors['publicidade'] == [_('Este campo é obrigatório.')] assert errors["publicidade"] == [_("Este campo é obrigatório.")]
assert errors['tipo'] == [_('Este campo é obrigatório.')] assert errors["tipo"] == [_("Este campo é obrigatório.")]
assert errors['publicacao'] == [_('Este campo é obrigatório')] assert errors["publicacao"] == [_("Este campo é obrigatório")]
assert errors['efetividade'] == [_('Este campo é obrigatório')] assert errors["efetividade"] == [_("Este campo é obrigatório")]
assert errors['dispositivo'] == [_('Este campo é obrigatório.')] assert errors["dispositivo"] == [_("Este campo é obrigatório.")]
assert len(errors) == 6 assert len(errors) == 6
@ -43,15 +43,18 @@ def test_valida_campos_obrigatorios_nota_form():
def test_nota_form_invalido(): def test_nota_form_invalido():
tipo = baker.make(TipoNota) tipo = baker.make(TipoNota)
form = forms.NotaForm(data={'titulo': 'titulo', form = forms.NotaForm(
'texto': 'teste', data={
'url_externa': 'www.test.com', "titulo": "titulo",
'publicidade': 'publicidade', "texto": "teste",
'tipo': str(tipo.pk), "url_externa": "www.test.com",
'publicacao': '10/05/2017', "publicidade": "publicidade",
'efetividade': '10/05/2017', "tipo": str(tipo.pk),
'dispositivo': 'dispositivo', "publicacao": "10/05/2017",
'pk': 'pk' "efetividade": "10/05/2017",
}) "dispositivo": "dispositivo",
"pk": "pk",
}
)
assert not form.is_valid() assert not form.is_valid()

218
sapl/compilacao/urls.py

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

57
sapl/compilacao/utils.py

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

3142
sapl/compilacao/views.py

File diff suppressed because it is too large

40
sapl/context_processors.py

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

287
sapl/crispy_layout_mixin.py

@ -1,5 +1,6 @@
from math import ceil from math import ceil
import yaml
from crispy_forms.bootstrap import FormActions from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Div, Fieldset, Layout, Submit 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 import formats
from django.utils.encoding import force_str from django.utils.encoding import force_str
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
import yaml
def heads_and_tails(list_of_lists): def heads_and_tails(list_of_lists):
@ -19,11 +19,11 @@ def heads_and_tails(list_of_lists):
def to_column(name_span): def to_column(name_span):
fieldname, span = 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): 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): def to_fieldsets(fields):
@ -36,21 +36,28 @@ def to_fieldsets(fields):
yield field yield field
def form_actions(more=[Div(css_class='clearfix')], def form_actions(
label=_('Salvar'), name='salvar', more=[Div(css_class="clearfix")],
css_class='float-right', disabled=True): label=_("Salvar"),
name="salvar",
if disabled and force_str(label) != 'Pesquisar': css_class="float-right",
doubleclick = 'this.form.submit();this.disabled=true;' disabled=True,
):
if disabled and force_str(label) != "Pesquisar":
doubleclick = "this.form.submit();this.disabled=true;"
else: else:
doubleclick = 'return true;' doubleclick = "return true;"
return FormActions( return FormActions(
*more, *more,
Submit(name, label, css_class=css_class, Submit(
# para impedir resubmissão do form name,
onclick=doubleclick), label,
css_class='form-group row justify-content-between' css_class=css_class,
# para impedir resubmissão do form
onclick=doubleclick,
),
css_class="form-group row justify-content-between",
) )
@ -85,16 +92,22 @@ class SaplFormHelper(FormHelper):
class SaplFormLayout(Layout): class SaplFormLayout(Layout):
def __init__(
def __init__(self, *fields, cancel_label=_('Cancelar'), self, *fields, cancel_label=_("Cancelar"), save_label=_("Salvar"), actions=None
save_label=_('Salvar'), actions=None): ):
buttons = actions buttons = actions
if not buttons: if not buttons:
buttons = form_actions(label=save_label, more=[ buttons = form_actions(
HTML('<a href="{{ view.cancel_url }}"' label=save_label,
' class="btn btn-dark">%s</a>' % cancel_label) more=[
if cancel_label else None]) HTML(
'<a href="{{ view.cancel_url }}"'
' class="btn btn-dark">%s</a>' % cancel_label
)
if cancel_label
else None
],
)
_fields = list(to_fieldsets(fields)) _fields = list(to_fieldsets(fields))
if buttons: if buttons:
@ -103,26 +116,25 @@ class SaplFormLayout(Layout):
def get_field_display(obj, fieldname): def get_field_display(obj, fieldname):
field = '' field = ""
try: try:
field = obj._meta.get_field(fieldname) field = obj._meta.get_field(fieldname)
except Exception as e: except Exception as e:
""" nos casos que o fieldname não é um field_model, """nos casos que o fieldname não é um field_model,
ele pode ser um aggregate, annotate, um property, um manager, ele pode ser um aggregate, annotate, um property, um manager,
ou mesmo uma método no model. ou mesmo uma método no model.
""" """
value = getattr(obj, fieldname) value = getattr(obj, fieldname)
try: try:
verbose_name = value.model._meta.verbose_name verbose_name = value.model._meta.verbose_name
except AttributeError: except AttributeError:
verbose_name = '' verbose_name = ""
else: else:
verbose_name = str(field.verbose_name)\ verbose_name = str(field.verbose_name) if hasattr(field, "verbose_name") else ""
if hasattr(field, 'verbose_name') else ''
if hasattr(field, 'choices') and field.choices: if hasattr(field, "choices") and field.choices:
value = getattr(obj, 'get_%s_display' % fieldname)() value = getattr(obj, "get_%s_display" % fieldname)()
else: else:
value = getattr(obj, fieldname) value = getattr(obj, fieldname)
@ -130,95 +142,100 @@ def get_field_display(obj, fieldname):
str_type_from_field = str(type(field)) str_type_from_field = str(type(field))
if value is None: if value is None:
display = '' display = ""
elif '.date' in str_type_from_value: elif ".date" in str_type_from_value:
display = formats.date_format(value, "SHORT_DATE_FORMAT") display = formats.date_format(value, "SHORT_DATE_FORMAT")
elif 'bool' in str_type_from_value: elif "bool" in str_type_from_value:
display = _('Sim') if value else _('Não') display = _("Sim") if value else _("Não")
elif 'ImageFieldFile' in str(type(value)): elif "ImageFieldFile" in str(type(value)):
if value: if value:
display = '<img src="{}" />'.format(value.url) display = '<img src="{}" />'.format(value.url)
else: else:
display = '' display = ""
elif 'FieldFile' in str_type_from_value: elif "FieldFile" in str_type_from_value:
if value: if value:
display = '<a href="{}">{}</a>'.format( display = '<a href="{}">{}</a>'.format(
value.url, value.url, value.name.split("/")[-1:][0]
value.name.split('/')[-1:][0]) )
else: else:
display = '' display = ""
elif 'ManyRelatedManager' in str_type_from_value\ elif (
or 'RelatedManager' in str_type_from_value\ "ManyRelatedManager" in str_type_from_value
or 'GenericRelatedObjectManager' in str_type_from_value: or "RelatedManager" in str_type_from_value
display = '<ul>' or "GenericRelatedObjectManager" in str_type_from_value
):
display = "<ul>"
for v in value.all(): for v in value.all():
display += '<li>%s</li>' % str(v) display += "<li>%s</li>" % str(v)
display += '</ul>' display += "</ul>"
if not verbose_name: if not verbose_name:
if hasattr(field, 'related_model'): if hasattr(field, "related_model"):
verbose_name = str( verbose_name = str(field.related_model._meta.verbose_name_plural)
field.related_model._meta.verbose_name_plural) elif hasattr(field, "model"):
elif hasattr(field, 'model'):
verbose_name = str(field.model._meta.verbose_name_plural) 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( display = '<a href="{}">{}</a>'.format(
reverse( reverse(
'%s:%s_detail' % ( "%s:%s_detail" % (value._meta.app_config.name, obj.content_type.model),
value._meta.app_config.name, obj.content_type.model), args=(value.id,),
args=(value.id,)), ),
value) value,
elif 'TextField' in str_type_from_field: )
display = value.replace('\n', '<br/>') elif "TextField" in str_type_from_field:
display = value.replace("\n", "<br/>")
display = '<div class="dont-break-out">{}</div>'.format(display) display = '<div class="dont-break-out">{}</div>'.format(display)
else: else:
display = str(value) display = str(value)
return verbose_name, display or '&nbsp;' return verbose_name, display or "&nbsp;"
class CrispyLayoutFormMixin: class CrispyLayoutFormMixin:
@property @property
def layout_key(self): def layout_key(self):
if hasattr(super(CrispyLayoutFormMixin, self), 'layout_key'): if hasattr(super(CrispyLayoutFormMixin, self), "layout_key"):
return super(CrispyLayoutFormMixin, self).layout_key return super(CrispyLayoutFormMixin, self).layout_key
else: else:
return self.model.__name__ return self.model.__name__
@property @property
def layout_key_set(self): 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 return super(CrispyLayoutFormMixin, self).layout_key_set
else: else:
obj = self.crud if hasattr(self, 'crud') else self obj = self.crud if hasattr(self, "crud") else self
return getattr(obj.model, return getattr(obj.model, obj.model_set).field.model.__name__
obj.model_set).field.model.__name__
def get_layout(self, yaml_layout=None): def get_layout(self, yaml_layout=None):
if not yaml_layout: 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) return read_layout_from_yaml(yaml_layout, self.layout_key)
def get_layout_set(self): def get_layout_set(self):
obj = self.crud if hasattr(self, 'crud') else self obj = self.crud if hasattr(self, "crud") else self
yaml_layout = '%s/layouts.yaml' % getattr( yaml_layout = (
obj.model, obj.model_set).field.model._meta.app_config.label "%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) return read_layout_from_yaml(yaml_layout, self.layout_key_set)
@property @property
def fields(self): def fields(self):
if hasattr(self, 'form_class') and self.form_class: if hasattr(self, "form_class") and self.form_class:
return None return None
else: else:
'''Returns all fields in the layout''' """Returns all fields in the layout"""
return [fieldname for legend_rows in self.get_layout() return [
for row in legend_rows[1:] fieldname
for fieldname, span in row] for legend_rows in self.get_layout()
for row in legend_rows[1:]
for fieldname, span in row
]
def get_form(self, form_class=None): def get_form(self, form_class=None):
super_get_form = getattr(super(CrispyLayoutFormMixin, self), 'get_form', None) super_get_form = getattr(super(CrispyLayoutFormMixin, self), "get_form", None)
if super_get_form is None: if super_get_form is None:
# Either raise, or (if you want to support non-form views) construct a form when form_class exists. # Either raise, or (if you want to support non-form views) construct a form when form_class exists.
if getattr(self, 'form_class', None): if getattr(self, "form_class", None):
form_class = self.get_form_class() form_class = self.get_form_class()
form = form_class(**self.get_form_kwargs()) form = form_class(**self.get_form_kwargs())
else: else:
@ -236,24 +253,24 @@ class CrispyLayoutFormMixin:
@property @property
def list_field_names(self): 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 This base implementation returns the field names
in the first fieldset of the layout. in the first fieldset of the layout.
''' """
obj = self.crud if hasattr(self, 'crud') else self obj = self.crud if hasattr(self, "crud") else self
if hasattr(obj, 'list_field_names') and obj.list_field_names: if hasattr(obj, "list_field_names") and obj.list_field_names:
return obj.list_field_names return obj.list_field_names
rows = self.get_layout()[0][1:] rows = self.get_layout()[0][1:]
return [fieldname for row in rows for fieldname, __ in row] return [fieldname for row in rows for fieldname, __ in row]
@property @property
def list_field_names_set(self): 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 This base implementation returns the field names
in the first fieldset of the layout. in the first fieldset of the layout.
''' """
rows = self.get_layout_set()[0][1:] rows = self.get_layout_set()[0][1:]
return [fieldname for row in rows for fieldname, __ in row] return [fieldname for row in rows for fieldname, __ in row]
@ -261,65 +278,65 @@ class CrispyLayoutFormMixin:
obj = self.get_object() obj = self.get_object()
func = None func = None
if '|' in fieldname: if "|" in fieldname:
fieldname, func = tuple(fieldname.split('|')) fieldname, func = tuple(fieldname.split("|"))
try: try:
verbose_name, field_display = get_field_display(obj, fieldname) verbose_name, field_display = get_field_display(obj, fieldname)
except: except:
verbose_name, field_display = '', '' verbose_name, field_display = "", ""
if func: if func:
verbose_name, field_display = getattr(self, func)(obj, fieldname) verbose_name, field_display = getattr(self, func)(obj, fieldname)
hook_fieldname = 'hook_%s' % fieldname hook_fieldname = "hook_%s" % fieldname
if hasattr(self, hook_fieldname): if hasattr(self, hook_fieldname):
try: try:
verbose_name, field_display = getattr( verbose_name, field_display = getattr(self, hook_fieldname)(
self, hook_fieldname)(obj, verbose_name=verbose_name, field_display=field_display) obj, verbose_name=verbose_name, field_display=field_display
)
except: except:
verbose_name, field_display = getattr( verbose_name, field_display = getattr(self, hook_fieldname)(obj)
self, hook_fieldname)(obj)
elif not func: elif not func:
verbose_name, field_display = get_field_display(obj, fieldname) verbose_name, field_display = get_field_display(obj, fieldname)
return { return {
'id': fieldname, "id": fieldname,
'span': span, "span": span,
'verbose_name': verbose_name, "verbose_name": verbose_name,
'text': field_display, "text": field_display,
} }
def fk_urlify_for_detail(self, obj, fieldname): def fk_urlify_for_detail(self, obj, fieldname):
field = obj._meta.get_field(fieldname) field = obj._meta.get_field(fieldname)
value = getattr(obj, fieldname) value = getattr(obj, fieldname)
display = '<a href="{}">{}</a>'.format( display = '<a href="{}">{}</a>'.format(
reverse( reverse(
'%s:%s_detail' % ( "%s:%s_detail" % (value._meta.app_config.name, value._meta.model_name),
value._meta.app_config.name, value._meta.model_name), args=(value.id,),
args=(value.id,)), ),
value) value,
)
return field.verbose_name, display return field.verbose_name, display
def fk_urlify_for_list(self, obj, field): def fk_urlify_for_list(self, obj, field):
value = getattr(obj, field) value = getattr(obj, field)
return reverse( return (
'%s:%s_detail' % ( reverse(
value._meta.app_config.name, "%s:%s_detail" % (value._meta.app_config.name, value._meta.model_name),
value._meta.model_name), kwargs={"pk": value.id},
kwargs={'pk': value.id}), ),
)
def m2m_urlize_for_detail(self, obj, fieldname): def m2m_urlize_for_detail(self, obj, fieldname):
manager, fieldname = tuple(fieldname.split("__"))
manager, fieldname = tuple(fieldname.split('__'))
manager = getattr(obj, manager) manager = getattr(obj, manager)
verbose_name = manager.model._meta.verbose_name verbose_name = manager.model._meta.verbose_name
display = '' display = ""
for item in manager.all(): for item in manager.all():
obj_m2m = getattr(item, fieldname) obj_m2m = getattr(item, fieldname)
@ -330,44 +347,50 @@ class CrispyLayoutFormMixin:
display += '<li><a href="{}">{}</a></li>'.format( display += '<li><a href="{}">{}</a></li>'.format(
reverse( reverse(
'%s:%s_detail' % ( "%s:%s_detail"
obj_m2m._meta.app_config.name, obj_m2m._meta.model_name), % (obj_m2m._meta.app_config.name, obj_m2m._meta.model_name),
args=(obj_m2m.id,)), args=(obj_m2m.id,),
obj_m2m) ),
obj_m2m,
)
display += '' display += ""
if display: if display:
display = '<ul>%s</ul>' % display display = "<ul>%s</ul>" % display
else: else:
verbose_name = '' verbose_name = ""
return verbose_name, display return verbose_name, display
def widget__signs(self, obj, fieldname): def widget__signs(self, obj, fieldname):
from sapl.base.models import Metadata from sapl.base.models import Metadata
try: try:
md = Metadata.objects.get( md = Metadata.objects.get(
content_type=ContentType.objects.get_for_model( content_type=ContentType.objects.get_for_model(obj._meta.model),
obj._meta.model), object_id=obj.id,
object_id=obj.id,) )
autores = md.metadata['signs'][fieldname]['autores'] autores = md.metadata["signs"][fieldname]["autores"]
t = template.loader.get_template('base/widget__signs.html') t = template.loader.get_template("base/widget__signs.html")
rendered = str(t.render(context={'signs': autores})) rendered = str(t.render(context={"signs": autores}))
except Exception as e: except Exception as e:
return '', '' return "", ""
return 'Assinaturas Eletrônicas', rendered return "Assinaturas Eletrônicas", rendered
@property @property
def layout_display(self): def layout_display(self):
return [ return [
{'legend': legend, {
'rows': [[self.get_column(fieldname, span) "legend": legend,
for fieldname, span in row] "rows": [
for row in rows] [self.get_column(fieldname, span) for fieldname, span in row]
} for legend, rows in heads_and_tails(self.get_layout())] for row in rows
],
}
for legend, rows in heads_and_tails(self.get_layout())
]
def read_yaml_from_file(yaml_layout): def read_yaml_from_file(yaml_layout):
@ -387,7 +410,7 @@ def read_layout_from_yaml(yaml_layout, key):
base = yaml[key] base = yaml[key]
def line_to_namespans(line): 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] 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) remaining = 12 - sum(s for n, s in namespans)
nondefined = [ns for ns in namespans if not ns[1]] nondefined = [ns for ns in namespans if not ns[1]]
@ -398,5 +421,7 @@ def read_layout_from_yaml(yaml_layout, key):
remaining = remaining - span remaining = remaining - span
return list(map(tuple, namespans)) return list(map(tuple, namespans))
return [[legend] + [line_to_namespans(l) for l in lines] return [
for legend, lines in base.items()] [legend] + [line_to_namespans(l) for l in lines]
for legend, lines in base.items()
]

1104
sapl/crud/base.py

File diff suppressed because it is too large

74
sapl/crud/tests/settings.py

@ -3,68 +3,72 @@ from os.path import dirname, join
BASE_DIR = dirname(dirname(dirname(__file__))) BASE_DIR = dirname(dirname(dirname(__file__)))
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': ':memory:', "NAME": ":memory:",
}, },
} }
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.sessions', "django.contrib.sessions",
'crud.tests.stub_app', "crud.tests.stub_app",
'crispy_forms', "crispy_forms",
) )
ROOT_URLCONF = 'crud.tests.stub_app.urls' ROOT_URLCONF = "crud.tests.stub_app.urls"
USE_TZ = True USE_TZ = True
SECRET_KEY = 'zzz...' SECRET_KEY = "zzz..."
TEMPLATES = [{ TEMPLATES = [
'BACKEND': 'django.template.backends.django.DjangoTemplates', {
'DIRS': [join(BASE_DIR, 'crud/tests/stub_app/templates'), "BACKEND": "django.template.backends.django.DjangoTemplates",
join(BASE_DIR, 'templates')], "DIRS": [
'APP_DIRS': True, join(BASE_DIR, "crud/tests/stub_app/templates"),
'OPTIONS': { join(BASE_DIR, "templates"),
'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',
], ],
}, "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",
],
},
}
]
STATIC_URL = '/static/' STATIC_URL = "/static/"
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
# 'django.middleware.locale.LocaleMiddleware', # 'django.middleware.locale.LocaleMiddleware',
# 'django.middleware.common.CommonMiddleware', # 'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
# 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', # 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
# 'django.middleware.clickjacking.XFrameOptionsMiddleware', # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 'django.middleware.security.SecurityMiddleware', # 'django.middleware.security.SecurityMiddleware',
) )
SILENCED_SYSTEM_CHECKS = [ 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_I18N = True
USE_L10N = False USE_L10N = False
USE_TZ = True USE_TZ = True
# DATE_FORMAT = 'N j, Y' # DATE_FORMAT = 'N j, Y'
DATE_FORMAT = 'd/m/Y' DATE_FORMAT = "d/m/Y"
SHORT_DATE_FORMAT = 'd/m/Y' SHORT_DATE_FORMAT = "d/m/Y"
DATE_INPUT_FORMATS = ('%d/%m/%Y', '%m-%d-%Y', '%Y-%m-%d') 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): class Country(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
continent = models.ForeignKey(Continent, on_delete=models.CASCADE) continent = models.ForeignKey(Continent, on_delete=models.CASCADE)
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) population = models.PositiveIntegerField(blank=True, null=True)
description = models.TextField(blank=True) description = models.TextField(blank=True)
class Meta: class Meta:
verbose_name = 'Country' verbose_name = "Country"
verbose_name_plural = 'Countries' verbose_name_plural = "Countries"
def __str__(self): def __str__(self):
return self.name return self.name

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

@ -3,6 +3,5 @@ from django.urls import include, path
from .views import CityCrud, CountryCrud from .views import CityCrud, CountryCrud
urlpatterns = [ urlpatterns = [
path('country/', include( path("country/", include(CountryCrud.get_urls() + CityCrud.get_urls(), "stub_app")),
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): class CountryCrud(Crud):
model = Country model = Country
help_topic = 'help_topic', help_topic = ("help_topic",)
class ListView(CrudListView): class ListView(CrudListView):
paginate_by = 10 paginate_by = 10
@ -14,4 +14,4 @@ class CountryCrud(Crud):
class CityCrud(MasterDetailCrud): class CityCrud(MasterDetailCrud):
model = City model = City
help_topic = 'help_topic', help_topic = ("help_topic",)

304
sapl/crud/tests/test_base.py

@ -1,7 +1,7 @@
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from model_bakery import baker 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.models import Continent, Country
from sapl.crud.tests.stub_app.views import CountryCrud from sapl.crud.tests.stub_app.views import CountryCrud
@ -13,10 +13,12 @@ __ = None # for test readability
@pytest.mark.parametrize( @pytest.mark.parametrize(
"index, num_pages, result", "index, num_pages, result",
[(i, k, from_to(1, k)) [
for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] (i, k, from_to(1, k))
for k in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
] + [ for k in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
]
+ [
(11, 11, [1, 2, 3, 4, 5, 6, 7, __, 10, (11)]), (11, 11, [1, 2, 3, 4, 5, 6, 7, __, 10, (11)]),
(10, 11, [1, 2, 3, 4, 5, 6, __, 9, (10), 11]), (10, 11, [1, 2, 3, 4, 5, 6, __, 9, (10), 11]),
(9, 11, [1, 2, 3, 4, 5, __, 8, (9), 10, 11]), (9, 11, [1, 2, 3, 4, 5, __, 8, (9), 10, 11]),
@ -28,7 +30,6 @@ __ = None # for test readability
(3, 11, [1, 2, (3), 4, 5, 6, 7, __, 10, 11]), (3, 11, [1, 2, (3), 4, 5, 6, 7, __, 10, 11]),
(2, 11, [1, (2), 3, 4, 5, 6, 7, __, 10, 11]), (2, 11, [1, (2), 3, 4, 5, 6, 7, __, 10, 11]),
(1, 11, [(1), 2, 3, 4, 5, 6, 7, __, 10, 11]), (1, 11, [(1), 2, 3, 4, 5, 6, 7, __, 10, 11]),
(12, 12, [1, 2, 3, 4, 5, 6, 7, __, 11, (12)]), (12, 12, [1, 2, 3, 4, 5, 6, 7, __, 11, (12)]),
(11, 12, [1, 2, 3, 4, 5, 6, __, 10, (11), 12]), (11, 12, [1, 2, 3, 4, 5, 6, __, 10, (11), 12]),
(10, 12, [1, 2, 3, 4, 5, __, 9, (10), 11, 12]), (10, 12, [1, 2, 3, 4, 5, __, 9, (10), 11, 12]),
@ -41,12 +42,12 @@ __ = None # for test readability
(3, 12, [1, 2, (3), 4, 5, 6, 7, __, 11, 12]), (3, 12, [1, 2, (3), 4, 5, 6, 7, __, 11, 12]),
(2, 12, [1, (2), 3, 4, 5, 6, 7, __, 11, 12]), (2, 12, [1, (2), 3, 4, 5, 6, 7, __, 11, 12]),
(1, 12, [(1), 2, 3, 4, 5, 6, 7, __, 11, 12]), (1, 12, [(1), 2, 3, 4, 5, 6, 7, __, 11, 12]),
# some random entries # some random entries
(8, 22, [1, 2, 3, __, 7, (8), 9, __, 21, 22]), (8, 22, [1, 2, 3, __, 7, (8), 9, __, 21, 22]),
(1, 17, [(1), 2, 3, 4, 5, 6, 7, __, 16, 17]), (1, 17, [(1), 2, 3, 4, 5, 6, 7, __, 16, 17]),
(22, 25, [1, 2, 3, 4, __, 21, (22), 23, 24, 25]), (22, 25, [1, 2, 3, 4, __, 21, (22), 23, 24, 25]),
]) ],
)
def test_make_pagination(index, num_pages, result): def test_make_pagination(index, num_pages, result):
assert num_pages < 10 or len(result) == 10 assert num_pages < 10 or len(result) == 10
assert make_pagination(index, num_pages) == result assert make_pagination(index, num_pages) == result
@ -54,29 +55,32 @@ def test_make_pagination(index, num_pages, result):
def test_get_field_display(): def test_get_field_display():
stub = baker.prepare(Country, is_cold=True) stub = baker.prepare(Country, is_cold=True)
assert get_field_display(stub, 'name')[1] == stub.name assert get_field_display(stub, "name")[1] == stub.name
assert get_field_display(stub, 'continent')[1] == str(stub.continent) assert get_field_display(stub, "continent")[1] == str(stub.continent)
# must return choice display, not the value # must return choice display, not the value
assert stub.is_cold is True assert stub.is_cold is True
assert get_field_display(stub, 'is_cold')[1] == 'Yes' assert get_field_display(stub, "is_cold")[1] == "Yes"
# None is displayed as an empty string # None is displayed as an empty string
assert stub.population is None assert stub.population is None
assert get_field_display(stub, 'population')[1] == '' assert get_field_display(stub, "population")[1] == ""
@pytest.mark.parametrize("_layout, result", [ @pytest.mark.parametrize(
([['Dados Complementares']], []), # missing rows definition "_layout, result",
[
([['Basic', [('name', 9), ('population', 3)]], ([["Dados Complementares"]], []), # missing rows definition
['More Details', [('description', 12)]], (
], [
['name', 'population']), ["Basic", [("name", 9), ("population", 3)]],
]) ["More Details", [("description", 12)]],
],
["name", "population"],
),
],
)
def test_layout_fieldnames(_layout, result): def test_layout_fieldnames(_layout, result):
class StubMixin(CrispyLayoutFormMixin): class StubMixin(CrispyLayoutFormMixin):
def get_layout(self): def get_layout(self):
return _layout return _layout
@ -85,21 +89,20 @@ def test_layout_fieldnames(_layout, result):
def test_layout_detail_fieldsets(): def test_layout_detail_fieldsets():
stub = baker.make(
stub = baker.make(Country, Country, name="Brazil", continent__name="South America", is_cold=False
name='Brazil', )
continent__name='South America',
is_cold=False)
class StubMixin(CrispyLayoutFormMixin): class StubMixin(CrispyLayoutFormMixin):
def get_layout(self): def get_layout(self):
return [['Basic Data', return [
[('name', 9), ('continent', 3)], [
[('population', 6), ('is_cold', 6)] "Basic Data",
], [("name", 9), ("continent", 3)],
['More Details', [('description', 12)]], [("population", 6), ("is_cold", 6)],
] ],
["More Details", [("description", 12)]],
]
def get_object(self): def get_object(self):
return stub return stub
@ -110,154 +113,183 @@ def test_layout_detail_fieldsets():
assert stub.population is None assert stub.population is None
assert view.layout_display == [ assert view.layout_display == [
{'legend': 'Basic Data', {
'rows': [[{'id': 'name', "legend": "Basic Data",
'span': 9, "rows": [
'text': stub.name, [
'verbose_name': 'name'}, {
{'id': 'continent', "id": "name",
'span': 3, "span": 9,
'text': stub.continent.name, "text": stub.name,
'verbose_name': 'continent'} "verbose_name": "name",
], },
{
[{'id': 'population', "id": "continent",
'span': 6, "span": 3,
'text': '', "text": stub.continent.name,
'verbose_name': 'population'}, "verbose_name": "continent",
{'id': 'is_cold', },
'span': 6, ],
'text': 'No', [
'verbose_name': 'is cold'}]]}, {
{'legend': 'More Details', "id": "population",
'rows': [[{'id': 'description', "span": 6,
'span': 12, "text": "",
'text': '', "verbose_name": "population",
'verbose_name': 'description'}]]}] },
{
"id": "is_cold",
"span": 6,
"text": "No",
"verbose_name": "is cold",
},
],
],
},
{
"legend": "More Details",
"rows": [
[
{
"id": "description",
"span": 12,
"text": "",
"verbose_name": "description",
}
]
],
},
]
def test_reverse(): def test_reverse():
assert '/country/' == reverse('sapl.stub_app:country_list') assert "/country/" == reverse("sapl.stub_app:country_list")
assert '/country/create' == reverse('sapl.stub_app:country_create') assert "/country/create" == reverse("sapl.stub_app:country_create")
assert '/country/2' == reverse('sapl.stub_app:country_detail', args=(2,)) assert "/country/2" == reverse("sapl.stub_app:country_detail", args=(2,))
assert '/country/2/edit' == reverse( assert "/country/2/edit" == reverse("sapl.stub_app:country_update", args=(2,))
'sapl.stub_app:country_update', args=(2,)) assert "/country/2/delete" == reverse("sapl.stub_app:country_delete", args=(2,))
assert '/country/2/delete' == reverse(
'sapl.stub_app:country_delete', args=(2,))
def assert_h1(res, title): def assert_h1(res, title):
assert res.html.find('main').find('h1').text.strip() == title assert res.html.find("main").find("h1").text.strip() == title
NO_ENTRIES_MSG = str(CrudListView.no_entries_msg) # "unlazy" NO_ENTRIES_MSG = str(CrudListView.no_entries_msg) # "unlazy"
def assert_on_list_page(res): def assert_on_list_page(res):
assert_h1(res, 'Countries') assert_h1(res, "Countries")
assert 'Adicionar Country' in res assert "Adicionar Country" in res
assert res.html.find('table') or NO_ENTRIES_MSG in res assert res.html.find("table") or NO_ENTRIES_MSG in res
# XXX ... characterize better # XXX ... characterize better
def assert_on_create_page(res): def assert_on_create_page(res):
assert_h1(res, 'Adicionar Country') assert_h1(res, "Adicionar Country")
form = res.form form = res.form
assert not any( assert not any(form[k].value for k in form.fields if k != "csrfmiddlewaretoken")
form[k].value for k in form.fields if k != 'csrfmiddlewaretoken')
def assert_on_detail_page(res, stub_name): def assert_on_detail_page(res, stub_name):
assert_h1(res, stub_name) assert_h1(res, stub_name)
assert not res.forms assert not res.forms
assert 'Editar' in res assert "Editar" in res
assert 'Excluir' in res assert "Excluir" in res
@pytest.mark.parametrize("num_entries, page_size, ranges, page_list", [
(0, 6, [], []),
(5, 5, [(0, 5)], []),
(10, 5, [(0, 5), (5, 10)], ['Anterior', '1', '2', 'Próxima']),
(9, 4, [(0, 4), (4, 8), (8, 9)], ['Anterior', '1', '2', '3', 'Próxima']),
])
def test_flux_list_paginate_detail(
app, num_entries, page_size, ranges, page_list):
@pytest.mark.parametrize(
"num_entries, page_size, ranges, page_list",
[
(0, 6, [], []),
(5, 5, [(0, 5)], []),
(10, 5, [(0, 5), (5, 10)], ["Anterior", "1", "2", "Próxima"]),
(9, 4, [(0, 4), (4, 8), (8, 9)], ["Anterior", "1", "2", "3", "Próxima"]),
],
)
def test_flux_list_paginate_detail(app, num_entries, page_size, ranges, page_list):
entries_labels = [] entries_labels = []
for i in range(num_entries): for i in range(num_entries):
name, continent = 'name %s' % i, 'continent %s' % i name, continent = "name %s" % i, "continent %s" % i
population, is_cold = i, i % 2 == 0 population, is_cold = i, i % 2 == 0
entries_labels.append([ entries_labels.append(
name, continent, str(population), 'Yes' if is_cold else 'No']) [name, continent, str(population), "Yes" if is_cold else "No"]
baker.make(Country, )
name=name, baker.make(
continent__name=continent, Country,
population=population, name=name,
is_cold=is_cold) continent__name=continent,
population=population,
is_cold=is_cold,
)
CountryCrud.ListView.paginate_by = page_size CountryCrud.ListView.paginate_by = page_size
res = app.get('/country/') res = app.get("/country/")
if num_entries == 0: if num_entries == 0:
assert_on_list_page(res) assert_on_list_page(res)
assert NO_ENTRIES_MSG in res assert NO_ENTRIES_MSG in res
# no table # no table
assert not res.html.find('table') assert not res.html.find("table")
# no pagination # no pagination
assert not res.html.find('ul', {'class': 'pagination'}) assert not res.html.find("ul", {"class": "pagination"})
else: else:
def assert_at_page(res, i): def assert_at_page(res, i):
assert_on_list_page(res) assert_on_list_page(res)
table = res.html.find('table') table = res.html.find("table")
assert table assert table
header_trs = table.findAll('tr') header_trs = table.findAll("tr")
header, trs = header_trs[0], header_trs[1:] header, trs = header_trs[0], header_trs[1:]
assert [c.text for c in header.findChildren('th')] == [ assert [c.text for c in header.findChildren("th")] == [
'name', 'continent', 'population', 'is cold'] "name",
rows = [[td.text.strip() for td in tr.findAll('td')] "continent",
for tr in trs] "population",
"is cold",
]
rows = [[td.text.strip() for td in tr.findAll("td")] for tr in trs]
start, end = ranges[i - 1] start, end = ranges[i - 1]
assert entries_labels[start:end] == rows assert entries_labels[start:end] == rows
paginator = res.html.find('ul', {'class': 'pagination'}) paginator = res.html.find("ul", {"class": "pagination"})
if page_list: if page_list:
assert paginator assert paginator
assert paginator.text.strip().split() == page_list assert paginator.text.strip().split() == page_list
assert_at_page(res, 1) assert_at_page(res, 1)
res_detail = res.click('name 1') res_detail = res.click("name 1")
assert_on_detail_page(res_detail, 'name 1') assert_on_detail_page(res_detail, "name 1")
if len(ranges) > 1: if len(ranges) > 1:
res = res.click('2', href='page=2') res = res.click("2", href="page=2")
assert_at_page(res, 2) assert_at_page(res, 2)
fist_entry_on_2nd_page = 'name %s' % page_size fist_entry_on_2nd_page = "name %s" % page_size
res_detail = res.click(fist_entry_on_2nd_page) res_detail = res.click(fist_entry_on_2nd_page)
assert_on_detail_page(res_detail, fist_entry_on_2nd_page) assert_on_detail_page(res_detail, fist_entry_on_2nd_page)
res = res.click('1', href='page=1') res = res.click("1", href="page=1")
assert_at_page(res, 1) assert_at_page(res, 1)
res_detail = res.click('name 1') res_detail = res.click("name 1")
assert_on_detail_page(res_detail, 'name 1') assert_on_detail_page(res_detail, "name 1")
@pytest.mark.parametrize("cancel, make_invalid_submit", [ @pytest.mark.parametrize(
(a, b) for a in (True, False) for b in (True, False)]) "cancel, make_invalid_submit",
[(a, b) for a in (True, False) for b in (True, False)],
)
def test_flux_list_create_detail(app, cancel, make_invalid_submit): def test_flux_list_create_detail(app, cancel, make_invalid_submit):
# to have a couple an option for continent field # to have a couple an option for continent field
stub_continent = baker.make(Continent) stub_continent = baker.make(Continent)
res = app.get('/country/') res = app.get("/country/")
# on list page # on list page
assert_on_list_page(res) assert_on_list_page(res)
res = res.click('Adicionar Country') res = res.click("Adicionar Country")
previous_objects = set(Country.objects.all()) previous_objects = set(Country.objects.all())
# on create page # on create page
@ -265,7 +297,7 @@ def test_flux_list_create_detail(app, cancel, make_invalid_submit):
# test bifurcation ! # test bifurcation !
if cancel: if cancel:
res = res.click('Cancelar') res = res.click("Cancelar")
# back to list page # back to list page
assert_on_list_page(res) assert_on_list_page(res)
# db has not changed # db has not changed
@ -275,35 +307,35 @@ def test_flux_list_create_detail(app, cancel, make_invalid_submit):
if make_invalid_submit: if make_invalid_submit:
# some fields are required => validation error # some fields are required => validation error
res = res.form.submit() res = res.form.submit()
'Formulário inválido. O registro não foi criado.' in res "Formulário inválido. O registro não foi criado." in res
assert_on_create_page(res) assert_on_create_page(res)
# db has not changed # db has not changed
assert previous_objects == set(Country.objects.all()) assert previous_objects == set(Country.objects.all())
# now fill out some fields # now fill out some fields
form = res.form form = res.form
stub_name = '### name ###' stub_name = "### name ###"
form['name'] = stub_name form["name"] = stub_name
form['continent'] = stub_continent.id form["continent"] = stub_continent.id
form['population'] = 23000 form["population"] = 23000
form['is_cold'] = True form["is_cold"] = True
res = form.submit() res = form.submit()
# on redirect to detail page # on redirect to detail page
created = Country.objects.get(name=stub_name) created = Country.objects.get(name=stub_name)
assert res.url.endswith('/country/%s' % created.id) assert res.url.endswith("/country/%s" % created.id)
res = res.follow() res = res.follow()
# on detail page # on detail page
assert_on_detail_page(res, stub_name) assert_on_detail_page(res, stub_name)
assert 'Registro criado com sucesso!' in res assert "Registro criado com sucesso!" in res
[new_obj] = list(set(Country.objects.all()) - previous_objects) [new_obj] = list(set(Country.objects.all()) - previous_objects)
assert new_obj.name == stub_name assert new_obj.name == stub_name
def get_detail_page(app): def get_detail_page(app):
stub = baker.make(Country, name='Country Stub') stub = baker.make(Country, name="Country Stub")
res = app.get('/country/%s' % stub.id) res = app.get("/country/%s" % stub.id)
# on detail page # on detail page
assert_on_detail_page(res, stub.name) assert_on_detail_page(res, stub.name)
return stub, res return stub, res
@ -312,46 +344,46 @@ def get_detail_page(app):
@pytest.mark.parametrize("cancel", [True, False]) @pytest.mark.parametrize("cancel", [True, False])
def test_flux_detail_update_detail(app, cancel): def test_flux_detail_update_detail(app, cancel):
stub, res = get_detail_page(app) stub, res = get_detail_page(app)
res = res.click('Editar') res = res.click("Editar")
# on update page # on update page
assert_h1(res, stub.name) assert_h1(res, stub.name)
# test bifurcation ! # test bifurcation !
if cancel: if cancel:
res = res.click('Cancelar') res = res.click("Cancelar")
# back to detail page # back to detail page
assert_on_detail_page(res, stub.name) assert_on_detail_page(res, stub.name)
assert Country.objects.get(pk=stub.pk).name == stub.name assert Country.objects.get(pk=stub.pk).name == stub.name
else: else:
form = res.form form = res.form
new_name = '### New Name ###' new_name = "### New Name ###"
form['name'] = new_name form["name"] = new_name
res = form.submit() res = form.submit()
# on redirect to detail page # on redirect to detail page
assert res.url.endswith('/country/%s' % stub.id) assert res.url.endswith("/country/%s" % stub.id)
res = res.follow() res = res.follow()
# back to detail page # back to detail page
assert_on_detail_page(res, new_name) assert_on_detail_page(res, new_name)
assert 'Registro alterado com sucesso!' in res assert "Registro alterado com sucesso!" in res
assert Country.objects.get(pk=stub.pk).name == new_name assert Country.objects.get(pk=stub.pk).name == new_name
@pytest.mark.parametrize("cancel", [True, False]) @pytest.mark.parametrize("cancel", [True, False])
def test_flux_detail_delete_list(app, cancel): def test_flux_detail_delete_list(app, cancel):
stub, res = get_detail_page(app) stub, res = get_detail_page(app)
res = res.click('Excluir') res = res.click("Excluir")
# on delete page # on delete page
assert 'Confirma exclusão de' in res assert "Confirma exclusão de" in res
assert stub.name in res assert stub.name in res
# test bifurcation ! # test bifurcation !
if cancel: if cancel:
res = res.click('Cancelar') res = res.click("Cancelar")
# back to detail page # back to detail page
assert_on_detail_page(res, stub.name) assert_on_detail_page(res, stub.name)
@ -360,10 +392,10 @@ def test_flux_detail_delete_list(app, cancel):
res = res.form.submit() res = res.form.submit()
# on redirect to list page # on redirect to list page
assert res.url.endswith('/country/') assert res.url.endswith("/country/")
res = res.follow() res = res.follow()
# on list page # on list page
assert 'Registro excluído com sucesso!' in res assert "Registro excluído com sucesso!" in res
assert_h1(res, 'Countries') assert_h1(res, "Countries")
assert not Country.objects.filter(pk=stub.pk) assert not Country.objects.filter(pk=stub.pk)

17
sapl/crud/tests/test_masterdetail.py

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

2
sapl/crud/urls.py

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

23
sapl/decorators.py

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

33
sapl/endpoint_restriction_middleware.py

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

6
sapl/hashers.py

@ -6,7 +6,7 @@ from django.utils.encoding import force_bytes
def to_base64(source): def to_base64(source):
return base64.b64encode(source).decode('utf-8') return base64.b64encode(source).decode("utf-8")
class ZopeSHA1PasswordHasher(PBKDF2PasswordHasher): class ZopeSHA1PasswordHasher(PBKDF2PasswordHasher):
@ -40,14 +40,14 @@ def get_salt_from_zope_sha1(data):
return to_base64(salt) return to_base64(salt)
ZOPE_SHA1_PREFIX = '{SSHA}' ZOPE_SHA1_PREFIX = "{SSHA}"
def zope_encoded_password_to_django(encoded): def zope_encoded_password_to_django(encoded):
"Migra um hash de senha do zope para uso com o ZopeSHA1PasswordHasher" "Migra um hash de senha do zope para uso com o ZopeSHA1PasswordHasher"
if encoded and encoded.startswith(ZOPE_SHA1_PREFIX): if encoded and encoded.startswith(ZOPE_SHA1_PREFIX):
data = encoded[len(ZOPE_SHA1_PREFIX):] data = encoded[len(ZOPE_SHA1_PREFIX) :]
salt = get_salt_from_zope_sha1(data) salt = get_salt_from_zope_sha1(data)
hasher = ZopeSHA1PasswordHasher() hasher = ZopeSHA1PasswordHasher()
return super(ZopeSHA1PasswordHasher, hasher).encode(data, salt) return super(ZopeSHA1PasswordHasher, hasher).encode(data, salt)

358
sapl/lexml/OAIServer.py

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

6
sapl/lexml/apps.py

@ -3,6 +3,6 @@ from django.utils.translation import gettext_lazy as _
class AppConfig(apps.AppConfig): class AppConfig(apps.AppConfig):
name = 'sapl.lexml' name = "sapl.lexml"
label = 'lexml' label = "lexml"
verbose_name = _('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.core.exceptions import ValidationError
from django.forms import ModelForm from django.forms import ModelForm
from sapl.settings import PROJECT_DIR
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from io import StringIO
from lxml import etree from lxml import etree
import os
import re from sapl.settings import PROJECT_DIR
import xml.dom.minidom as dom
from .models import LexmlProvedor from .models import LexmlProvedor
@ -21,7 +22,7 @@ class LexmlProvedorForm(ModelForm):
"id_responsavel", "id_responsavel",
"nome_responsavel", "nome_responsavel",
"email_responsavel", "email_responsavel",
"xml" "xml",
] ]
def clean(self): def clean(self):
@ -44,14 +45,17 @@ def validar_xml(xml):
try: try:
dom.parse(xml) dom.parse(xml)
except Exception as e: except Exception as e:
raise ValidationError(_(F"XML mal formatado. Error: {e}")) raise ValidationError(_(f"XML mal formatado. Error: {e}"))
def validar_schema(xml): 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_root = etree.XML(xml_schema)
schema = etree.XMLSchema(schema_root) schema = etree.XMLSchema(schema_root)
parser = etree.XMLParser(schema=schema) parser = etree.XMLParser(schema=schema)
try: try:
root = etree.fromstring(xml.encode(), parser) root = etree.fromstring(xml.encode(), parser)
except Exception as e: 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 gettext_lazy as _
class LexmlProvedor(models.Model): # LexmlRegistroProvedor class LexmlProvedor(models.Model): # LexmlRegistroProvedor
id_provedor = models.PositiveIntegerField(verbose_name=_('Id do provedor')) id_provedor = models.PositiveIntegerField(verbose_name=_("Id do provedor"))
nome = models.CharField(max_length=255, verbose_name=_('Nome do provedor')) nome = models.CharField(max_length=255, verbose_name=_("Nome do provedor"))
sigla = models.CharField(max_length=15) sigla = models.CharField(max_length=15)
email_responsavel = models.EmailField( email_responsavel = models.EmailField(
max_length=50, max_length=50, blank=True, verbose_name=_("E-mail do responsável")
blank=True, )
verbose_name=_('E-mail do responsável'))
nome_responsavel = models.CharField( nome_responsavel = models.CharField(
max_length=255, max_length=255, blank=True, verbose_name=_("Nome do responsável")
blank=True, )
verbose_name=_('Nome do responsável'))
tipo = models.CharField(max_length=50) tipo = models.CharField(max_length=50)
id_responsavel = models.PositiveIntegerField( 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( xml = models.TextField(
blank=True, blank=True, verbose_name=_("XML fornecido pela equipe do LexML:")
verbose_name=_('XML fornecido pela equipe do LexML:')) )
@property @property
def pretty_xml(self): def pretty_xml(self):
import html import html
safe_xml = html.escape(self.xml) safe_xml = html.escape(self.xml)
return safe_xml.replace('\\n', '<br/>').replace(' ', '&nbsp;') return safe_xml.replace("\\n", "<br/>").replace(" ", "&nbsp;")
class Meta: class Meta:
verbose_name = _('Provedor Lexml') verbose_name = _("Provedor Lexml")
verbose_name_plural = _('Provedores Lexml') verbose_name_plural = _("Provedores Lexml")
ordering = ('id',) ordering = ("id",)
def __str__(self): def __str__(self):
return self.nome return self.nome
class LexmlPublicador(models.Model): class LexmlPublicador(models.Model):
id_publicador = models.PositiveIntegerField( id_publicador = models.PositiveIntegerField(verbose_name=_("Id do publicador"))
verbose_name=_('Id do publicador')) nome = models.CharField(max_length=255, verbose_name=_("Nome do publicador"))
nome = models.CharField(
max_length=255, verbose_name=_('Nome do publicador'))
email_responsavel = models.EmailField( email_responsavel = models.EmailField(
max_length=50, max_length=50, blank=True, verbose_name=_("E-mail do responsável")
blank=True, )
verbose_name=_('E-mail do responsável'))
sigla = models.CharField( sigla = models.CharField(
max_length=255, max_length=255, blank=True, verbose_name=_("Sigla do Publicador")
blank=True, )
verbose_name=_('Sigla do Publicador'))
nome_responsavel = models.CharField( nome_responsavel = models.CharField(
max_length=255, max_length=255, blank=True, verbose_name=_("Nome do responsável")
blank=True, )
verbose_name=_('Nome do responsável'))
tipo = models.CharField(max_length=50) tipo = models.CharField(max_length=50)
id_responsavel = models.PositiveIntegerField( id_responsavel = models.PositiveIntegerField(verbose_name=_("Id do responsável"))
verbose_name=_('Id do responsável'))
class Meta: class Meta:
verbose_name = _('Publicador Lexml') verbose_name = _("Publicador Lexml")
verbose_name_plural = _('Publicadores Lexml') verbose_name_plural = _("Publicadores Lexml")
ordering = ('id',) ordering = ("id",)
def __str__(self): def __str__(self):
return self.nome return self.nome

18
sapl/lexml/urls.py

@ -1,17 +1,19 @@
from django.urls import include, path, re_path from django.urls import include, path, re_path
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 from .apps import AppConfig
app_name = AppConfig.name app_name = AppConfig.name
urlpatterns = [ urlpatterns = [
path('sistema/lexml/provedor/', path("sistema/lexml/provedor/", include(LexmlProvedorCrud.get_urls())),
include(LexmlProvedorCrud.get_urls())), path("sistema/lexml/publicador/", include(LexmlPublicadorCrud.get_urls())),
path('sistema/lexml/publicador/', re_path(
include(LexmlPublicadorCrud.get_urls())), r"^sistema/lexml/request_search/(?P<keyword>[\w\-]+)/",
re_path(r'^sistema/lexml/request_search/(?P<keyword>[\w\-]+)/', request_search, name='lexml_search'), request_search,
re_path(r'^sistema/lexml/oai', lexml_request, name='lexml_endpoint'), name="lexml_search",
),
re_path(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.http import HttpResponse
from django.shortcuts import render 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.lexml.OAIServer import OAIServerFactory, get_config
from sapl.rules import RP_DETAIL, RP_LIST from sapl.rules import RP_DETAIL, RP_LIST
from .models import LexmlProvedor, LexmlPublicador
from .forms import LexmlProvedorForm from .forms import LexmlProvedorForm
from .models import LexmlProvedor, LexmlPublicador
LexmlPublicadorCrud = CrudAux.build(LexmlPublicador, 'lexml_publicador') LexmlPublicadorCrud = CrudAux.build(LexmlPublicador, "lexml_publicador")
class LexmlProvedorCrud(Crud): class LexmlProvedorCrud(Crud):
model = LexmlProvedor model = LexmlProvedor
help_topic = 'lexml_provedor' help_topic = "lexml_provedor"
public = [RP_LIST, RP_DETAIL] public = [RP_LIST, RP_DETAIL]
class CreateView(Crud.CreateView): class CreateView(Crud.CreateView):
@ -24,19 +23,19 @@ class LexmlProvedorCrud(Crud):
form_class = LexmlProvedorForm form_class = LexmlProvedorForm
class DetailView(Crud.DetailView): class DetailView(Crud.DetailView):
layout_key = 'LexmlProvedorDetail' layout_key = "LexmlProvedorDetail"
def lexml_request(request): def lexml_request(request):
request_dict = request.GET.copy() request_dict = request.GET.copy()
if request_dict.get('batch_size'): if request_dict.get("batch_size"):
del request_dict['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) oai_server = OAIServerFactory(config)
r = oai_server.handleRequest(request_dict) r = oai_server.handleRequest(request_dict)
response = r.decode('UTF-8') response = r.decode("UTF-8")
return HttpResponse(response, content_type='text/xml') return HttpResponse(response, content_type="text/xml")
def request_search(request, keyword): def request_search(request, keyword):

3
sapl/materia/admin.py

@ -13,7 +13,6 @@ register_all_models_in_admin(__name__)
if not settings.DEBUG: if not settings.DEBUG:
class RestricaoAdmin(admin.ModelAdmin): class RestricaoAdmin(admin.ModelAdmin):
def has_add_permission(self, request, obj=None): def has_add_permission(self, request, obj=None):
return False return False
@ -29,7 +28,7 @@ if not settings.DEBUG:
TipoComissao, TipoComissao,
TipoAfastamento, TipoAfastamento,
SituacaoMilitar, SituacaoMilitar,
TipoDependente TipoDependente,
) )
for model in models: for model in models:

6
sapl/materia/apps.py

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

3063
sapl/materia/forms.py

File diff suppressed because it is too large

1235
sapl/materia/models.py

File diff suppressed because it is too large

89
sapl/materia/tests/test_email_templates.py

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

961
sapl/materia/tests/test_materia.py

File diff suppressed because it is too large

155
sapl/materia/tests/test_materia_form.py

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

23
sapl/materia/tests/test_materia_urls.py

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

338
sapl/materia/urls.py

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

2683
sapl/materia/views.py

File diff suppressed because it is too large

12
sapl/middleware.py

@ -11,11 +11,13 @@ class CheckWeakPasswordMiddleware:
self.get_response = get_response self.get_response = get_response
def __call__(self, request): def __call__(self, request):
if request.user.is_authenticated and \ if (
request.session.get('weak_password', False) and \ request.user.is_authenticated
request.path != reverse('sapl.base:alterar_senha') and \ and request.session.get("weak_password", False)
request.path != reverse('sapl.base:logout'): and request.path != reverse("sapl.base:alterar_senha")
and request.path != reverse("sapl.base:logout")
):
logging.warning(f"Usuário {request.user.username} possui senha fraca.") logging.warning(f"Usuário {request.user.username} possui senha fraca.")
return redirect('sapl.base:alterar_senha') return redirect("sapl.base:alterar_senha")
return self.get_response(request) return self.get_response(request)

6
sapl/norma/apps.py

@ -3,6 +3,6 @@ from django.utils.translation import gettext_lazy as _
class AppConfig(apps.AppConfig): class AppConfig(apps.AppConfig):
name = 'sapl.norma' name = "sapl.norma"
label = 'norma' label = "norma"
verbose_name = _('Norma Jurídica') verbose_name = _("Norma Jurídica")

595
sapl/norma/forms.py

@ -1,54 +1,54 @@
import logging import logging
import re import re
from crispy_forms.layout import (Button, Fieldset, HTML, Layout) import django_filters
from crispy_forms.layout import HTML, Button, Fieldset, Layout
from django import forms from django import forms
from django.contrib.postgres.search import SearchVector from django.contrib.postgres.search import SearchVector
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db.models import Q, F, Func, Value from django.db.models import F, Func, Q, Value
from django.forms import ModelChoiceField, ModelForm, widgets from django.forms import ModelChoiceField, ModelForm, widgets
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import django_filters
from sapl.base.models import TipoAutor from sapl.base.models import TipoAutor
from sapl.crispy_layout_mixin import form_actions, SaplFormHelper, to_row from sapl.crispy_layout_mixin import SaplFormHelper, form_actions, to_row
from sapl.materia.models import (MateriaLegislativa, from sapl.materia.models import (MateriaLegislativa, Orgao,
TipoMateriaLegislativa, Orgao) TipoMateriaLegislativa)
from sapl.parlamentares.models import Partido from sapl.parlamentares.models import Partido
from sapl.utils import (autor_label, autor_modal, ANO_CHOICES, choice_anos_com_normas, from sapl.utils import (ANO_CHOICES, FileFieldCheckMixin,
FileFieldCheckMixin, FilterOverridesMetaMixin, FilterOverridesMetaMixin, NormaPesquisaOrderingFilter,
NormaPesquisaOrderingFilter, validar_arquivo) autor_label, autor_modal, choice_anos_com_normas,
validar_arquivo)
from .models import (AnexoNormaJuridica, AssuntoNorma, AutoriaNorma, from .models import (AnexoNormaJuridica, AssuntoNorma, AutoriaNorma,
NormaJuridica, NormaRelacionada, TipoNormaJuridica) NormaJuridica, NormaRelacionada, TipoNormaJuridica)
def get_esferas(): def get_esferas():
return [('E', 'Estadual'), return [("E", "Estadual"), ("F", "Federal"), ("M", "Municipal")]
('F', 'Federal'),
('M', 'Municipal')]
YES_NO_CHOICES = [('', '---------'), YES_NO_CHOICES = [("", "---------"), (True, _("Sim")), (False, _("Não"))]
(True, _('Sim')),
(False, _('Não'))]
ORDENACAO_CHOICES = [('', '---------'), ORDENACAO_CHOICES = [
('tipo,ano,numero', _('Tipo/Ano/Número')), ("", "---------"),
('data,tipo,ano,numero', _('Data/Tipo/Ano/Número'))] ("tipo,ano,numero", _("Tipo/Ano/Número")),
("data,tipo,ano,numero", _("Data/Tipo/Ano/Número")),
]
class AssuntoNormaFilterSet(django_filters.FilterSet): class AssuntoNormaFilterSet(django_filters.FilterSet):
assunto = django_filters.CharFilter(label=_("Assunto"), assunto = django_filters.CharFilter(label=_("Assunto"), method="multifield_filter")
method='multifield_filter')
class Meta: class Meta:
model = AssuntoNorma model = AssuntoNorma
fields = ["assunto"] fields = ["assunto"]
def multifield_filter(self, queryset, name, value): def multifield_filter(self, queryset, name, value):
return queryset.filter(Q(assunto__icontains=value) | Q(descricao__icontains=value)) return queryset.filter(
Q(assunto__icontains=value) | Q(descricao__icontains=value)
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -60,226 +60,292 @@ class AssuntoNormaFilterSet(django_filters.FilterSet):
self.form.helper.layout = Layout( self.form.helper.layout = Layout(
Fieldset( Fieldset(
_("Pesquisa de Assunto de Norma Jurídica"), _("Pesquisa de Assunto de Norma Jurídica"),
row0, form_actions(label="Pesquisar")) row0,
form_actions(label="Pesquisar"),
)
) )
class NormaFilterSet(django_filters.FilterSet): class NormaFilterSet(django_filters.FilterSet):
ano = django_filters.ChoiceFilter(
required=False, label="Ano", choices=choice_anos_com_normas
)
ano = django_filters.ChoiceFilter(required=False, numero = django_filters.CharFilter(method="filter_numero", label=_("Número"))
label='Ano',
choices=choice_anos_com_normas)
numero = django_filters.CharFilter(
method='filter_numero',
label=_('Número'))
ementa = django_filters.CharFilter( ementa = django_filters.CharFilter(
method='filter_ementa', method="filter_ementa", label=_("Pesquisar expressões na ementa da norma")
label=_('Pesquisar expressões na ementa da norma')) )
indexacao = django_filters.CharFilter(method='filter_indexacao', indexacao = django_filters.CharFilter(
label=_('Indexação')) method="filter_indexacao", label=_("Indexação")
)
assuntos = django_filters.ModelChoiceFilter( assuntos = django_filters.ModelChoiceFilter(queryset=AssuntoNorma.objects.all())
queryset=AssuntoNorma.objects.all())
autorianorma__autor = django_filters.CharFilter(widget=forms.HiddenInput()) autorianorma__autor = django_filters.CharFilter(widget=forms.HiddenInput())
autorianorma__primeiro_autor = django_filters.BooleanFilter( autorianorma__primeiro_autor = django_filters.BooleanFilter(
required=False, required=False, label=_("Primeiro Autor")
label=_('Primeiro Autor')) )
autorianorma__autor__parlamentar_set__filiacao__partido = django_filters.ModelChoiceFilter( autorianorma__autor__parlamentar_set__filiacao__partido = (
queryset=Partido.objects.all(), django_filters.ModelChoiceFilter(
label=_('Normas por Partido')) queryset=Partido.objects.all(), label=_("Normas por Partido")
)
)
o = NormaPesquisaOrderingFilter(help_text='') o = NormaPesquisaOrderingFilter(help_text="")
class Meta(FilterOverridesMetaMixin): class Meta(FilterOverridesMetaMixin):
model = NormaJuridica model = NormaJuridica
fields = ['orgao', 'tipo', 'numero', 'ano', 'data', fields = [
'data_vigencia', 'data_publicacao', 'ementa', 'assuntos', "orgao",
'autorianorma__autor', 'autorianorma__primeiro_autor', 'autorianorma__autor__tipo'] "tipo",
"numero",
"ano",
"data",
"data_vigencia",
"data_publicacao",
"ementa",
"assuntos",
"autorianorma__autor",
"autorianorma__primeiro_autor",
"autorianorma__autor__tipo",
]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(NormaFilterSet, self).__init__(*args, **kwargs) super(NormaFilterSet, self).__init__(*args, **kwargs)
self.filters['autorianorma__autor__tipo'].label = _('Tipo de Autor') self.filters["autorianorma__autor__tipo"].label = _("Tipo de Autor")
row1 = to_row([('tipo', 4), ('numero', 4), ('ano', 4)]) row1 = to_row([("tipo", 4), ("numero", 4), ("ano", 4)])
row2 = to_row([('data', 6), ('data_publicacao', 6)]) row2 = to_row([("data", 6), ("data_publicacao", 6)])
row3 = to_row([('ementa', 6), ('assuntos', 6)]) row3 = to_row([("ementa", 6), ("assuntos", 6)])
row4 = to_row([('data_vigencia', 6), ('orgao', 6), ]) row4 = to_row(
row5 = to_row([('o', 6), ('indexacao', 6)]) [
row6 = to_row([ ("data_vigencia", 6),
('autorianorma__autor', 0), ("orgao", 6),
(Button('pesquisar', ]
'Pesquisar Autor', )
css_class='btn btn-primary btn-sm'), 2), row5 = to_row([("o", 6), ("indexacao", 6)])
(Button('limpar', row6 = to_row(
'Limpar Autor', [
css_class='btn btn-primary btn-sm'), 2), ("autorianorma__autor", 0),
('autorianorma__primeiro_autor', 2), (
('autorianorma__autor__tipo', 3), Button(
('autorianorma__autor__parlamentar_set__filiacao__partido', 3) "pesquisar",
]) "Pesquisar Autor",
css_class="btn btn-primary btn-sm",
),
2,
),
(
Button(
"limpar", "Limpar Autor", css_class="btn btn-primary btn-sm"
),
2,
),
("autorianorma__primeiro_autor", 2),
("autorianorma__autor__tipo", 3),
("autorianorma__autor__parlamentar_set__filiacao__partido", 3),
]
)
self.form.helper = SaplFormHelper() self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET' self.form.helper.form_method = "GET"
self.form.helper.layout = Layout( self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Norma'), Fieldset(
row1, row2, row3, row4, row5, _("Pesquisa de Norma"),
Fieldset(_('Pesquisa Avançada'), row1,
row6, row2,
HTML(autor_label), row3,
HTML(autor_modal)), row4,
form_actions(label='Pesquisar')) row5,
Fieldset(
_("Pesquisa Avançada"), row6, HTML(autor_label), HTML(autor_modal)
),
form_actions(label="Pesquisar"),
)
) )
def filter_numero(self, queryset, name, value): def filter_numero(self, queryset, name, value):
p = r'(\W|_)' p = r"(\W|_)"
value = re.sub(p, '', value, flags=re.IGNORECASE) value = re.sub(p, "", value, flags=re.IGNORECASE)
return queryset.annotate( return queryset.annotate(
numero_clean=Func( numero_clean=Func(
F('numero'), F("numero"), Value(p), Value(""), Value("g"), function="REGEXP_REPLACE"
Value(p),
Value(''),
Value('g'),
function='REGEXP_REPLACE'
) )
).filter(numero_clean=value) ).filter(numero_clean=value)
def filter_ementa(self, queryset, name, value): def filter_ementa(self, queryset, name, value):
return queryset.annotate(search=SearchVector('ementa', return queryset.annotate(
config='portuguese')).filter(search=value) search=SearchVector("ementa", config="portuguese")
).filter(search=value)
def filter_indexacao(self, queryset, name, value): def filter_indexacao(self, queryset, name, value):
return queryset.annotate(search=SearchVector('indexacao', return queryset.annotate(
config='portuguese')).filter(search=value) search=SearchVector("indexacao", config="portuguese")
).filter(search=value)
def filter_autoria(self, queryset, name, value): def filter_autoria(self, queryset, name, value):
return queryset.filter(**{ return queryset.filter(
name: value, **{
}) name: value,
}
)
class NormaJuridicaForm(FileFieldCheckMixin, ModelForm): class NormaJuridicaForm(FileFieldCheckMixin, ModelForm):
# Campos de MateriaLegislativa # Campos de MateriaLegislativa
tipo_materia = forms.ModelChoiceField( tipo_materia = forms.ModelChoiceField(
label='Matéria', label="Matéria",
required=False, required=False,
queryset=TipoMateriaLegislativa.objects.all(), queryset=TipoMateriaLegislativa.objects.all(),
empty_label='Selecione', empty_label="Selecione",
widget=forms.Select(attrs={'autocomplete': 'off'}) widget=forms.Select(attrs={"autocomplete": "off"}),
) )
numero_materia = forms.CharField( numero_materia = forms.CharField(
label='Número Matéria', label="Número Matéria",
required=False, required=False,
widget=forms.TextInput(attrs={'autocomplete': 'off'}) widget=forms.TextInput(attrs={"autocomplete": "off"}),
) )
ano_materia = forms.ChoiceField( ano_materia = forms.ChoiceField(
label='Ano Matéria', label="Ano Matéria",
required=False, required=False,
choices=ANO_CHOICES, choices=ANO_CHOICES,
widget=forms.Select(attrs={'autocomplete': 'off'}) widget=forms.Select(attrs={"autocomplete": "off"}),
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Meta: class Meta:
model = NormaJuridica model = NormaJuridica
fields = ['tipo', fields = [
'numero', "tipo",
'ano', "numero",
'orgao', "ano",
'data', "orgao",
'esfera_federacao', "data",
'complemento', "esfera_federacao",
'tipo_materia', "complemento",
'numero_materia', "tipo_materia",
'ano_materia', "numero_materia",
'data_publicacao', "ano_materia",
'data_vigencia', "data_publicacao",
'veiculo_publicacao', "data_vigencia",
'pagina_inicio_publicacao', "veiculo_publicacao",
'pagina_fim_publicacao', "pagina_inicio_publicacao",
'ementa', "pagina_fim_publicacao",
'indexacao', "ementa",
'observacao', "indexacao",
'texto_integral', "observacao",
'assuntos', "texto_integral",
'user', "assuntos",
'ip', "user",
'ultima_edicao'] "ip",
"ultima_edicao",
widgets = {'assuntos': widgets.CheckboxSelectMultiple, ]
'user': forms.HiddenInput(),
'ip': forms.HiddenInput(),
'ultima_edicao': forms.HiddenInput()}
def clean(self): widgets = {
"assuntos": widgets.CheckboxSelectMultiple,
"user": forms.HiddenInput(),
"ip": forms.HiddenInput(),
"ultima_edicao": forms.HiddenInput(),
}
def clean(self):
cleaned_data = super(NormaJuridicaForm, self).clean() cleaned_data = super(NormaJuridicaForm, self).clean()
if not self.is_valid(): if not self.is_valid():
return cleaned_data return cleaned_data
import re import re
has_digits = re.sub(r'[^0-9]', '', cleaned_data['numero'])
has_digits = re.sub(r"[^0-9]", "", cleaned_data["numero"])
if not has_digits: if not has_digits:
self.logger.error("Número de norma ({}) não pode conter somente letras.".format( self.logger.error(
cleaned_data['numero'])) "Número de norma ({}) não pode conter somente letras.".format(
raise ValidationError( cleaned_data["numero"]
'Número de norma não pode conter somente letras') )
)
raise ValidationError("Número de norma não pode conter somente letras")
if self.instance.numero != cleaned_data['numero']: if self.instance.numero != cleaned_data["numero"]:
params = { params = {
'ano': cleaned_data['ano'], "ano": cleaned_data["ano"],
'numero': cleaned_data['numero'], "numero": cleaned_data["numero"],
'tipo': cleaned_data['tipo'], "tipo": cleaned_data["tipo"],
} }
params['orgao'] = cleaned_data['orgao'] params["orgao"] = cleaned_data["orgao"]
norma = NormaJuridica.objects.filter(**params).exists() norma = NormaJuridica.objects.filter(**params).exists()
if norma: if norma:
self.logger.warning("Já existe uma norma de mesmo Tipo ({}), Ano ({}) " self.logger.warning(
"e Número ({}) no sistema." "Já existe uma norma de mesmo Tipo ({}), Ano ({}) "
.format(cleaned_data['tipo'], cleaned_data['ano'], cleaned_data['numero'])) "e Número ({}) no sistema.".format(
raise ValidationError("Já existe uma norma de mesmo Tipo, Ano, Órgão " cleaned_data["tipo"],
"e Número no sistema") cleaned_data["ano"],
if (cleaned_data['tipo_materia'] and cleaned_data["numero"],
cleaned_data['numero_materia'] and )
cleaned_data['ano_materia']): )
raise ValidationError(
"Já existe uma norma de mesmo Tipo, Ano, Órgão "
"e Número no sistema"
)
if (
cleaned_data["tipo_materia"]
and cleaned_data["numero_materia"]
and cleaned_data["ano_materia"]
):
try: try:
self.logger.debug("Tentando obter objeto MateriaLegislativa com tipo={}, numero={}, ano={}." self.logger.debug(
.format(cleaned_data['tipo_materia'], cleaned_data['numero_materia'], cleaned_data['ano_materia'])) "Tentando obter objeto MateriaLegislativa com tipo={}, numero={}, ano={}.".format(
cleaned_data["tipo_materia"],
cleaned_data["numero_materia"],
cleaned_data["ano_materia"],
)
)
materia = MateriaLegislativa.objects.get( materia = MateriaLegislativa.objects.get(
tipo_id=cleaned_data['tipo_materia'], tipo_id=cleaned_data["tipo_materia"],
numero=cleaned_data['numero_materia'], numero=cleaned_data["numero_materia"],
ano=cleaned_data['ano_materia']) ano=cleaned_data["ano_materia"],
)
except ObjectDoesNotExist: except ObjectDoesNotExist:
self.logger.error("Matéria Legislativa %s/%s (%s) é inexistente." % ( self.logger.error(
self.cleaned_data['numero_materia'], "Matéria Legislativa %s/%s (%s) é inexistente."
self.cleaned_data['ano_materia'], % (
cleaned_data['tipo_materia'].descricao)) self.cleaned_data["numero_materia"],
self.cleaned_data["ano_materia"],
cleaned_data["tipo_materia"].descricao,
)
)
raise forms.ValidationError( raise forms.ValidationError(
_("Matéria Legislativa %s/%s (%s) é inexistente." % ( _(
self.cleaned_data['numero_materia'], "Matéria Legislativa %s/%s (%s) é inexistente."
self.cleaned_data['ano_materia'], % (
cleaned_data['tipo_materia'].descricao))) self.cleaned_data["numero_materia"],
self.cleaned_data["ano_materia"],
cleaned_data["tipo_materia"].descricao,
)
)
)
else: else:
self.logger.info("MateriaLegislativa com tipo={}, numero={}, ano={} obtida com sucesso." self.logger.info(
.format(cleaned_data['tipo_materia'], cleaned_data['numero_materia'], cleaned_data['ano_materia'])) "MateriaLegislativa com tipo={}, numero={}, ano={} obtida com sucesso.".format(
cleaned_data['materia'] = materia cleaned_data["tipo_materia"],
cleaned_data["numero_materia"],
cleaned_data["ano_materia"],
)
)
cleaned_data["materia"] = materia
else: else:
cleaned_data['materia'] = None cleaned_data["materia"] = None
return cleaned_data return cleaned_data
def clean_texto_integral(self): def clean_texto_integral(self):
super(NormaJuridicaForm, self).clean() super(NormaJuridicaForm, self).clean()
texto_integral = self.cleaned_data.get('texto_integral', False) texto_integral = self.cleaned_data.get("texto_integral", False)
if texto_integral: if texto_integral:
validar_arquivo(texto_integral, "Texto Original") validar_arquivo(texto_integral, "Texto Original")
@ -289,49 +355,56 @@ class NormaJuridicaForm(FileFieldCheckMixin, ModelForm):
def save(self, commit=False): def save(self, commit=False):
norma = self.instance norma = self.instance
norma.timestamp = timezone.now() norma.timestamp = timezone.now()
norma.materia = self.cleaned_data['materia'] norma.materia = self.cleaned_data["materia"]
norma = super(NormaJuridicaForm, self).save(commit=True) norma = super(NormaJuridicaForm, self).save(commit=True)
return norma return norma
class AutoriaNormaForm(ModelForm): class AutoriaNormaForm(ModelForm):
tipo_autor = ModelChoiceField(
label=_("Tipo Autor"),
required=False,
queryset=TipoAutor.objects.all(),
empty_label=_("Selecione"),
)
tipo_autor = ModelChoiceField(label=_('Tipo Autor'), data_relativa = forms.DateField(widget=forms.HiddenInput(), required=False)
required=False,
queryset=TipoAutor.objects.all(),
empty_label=_('Selecione'),)
data_relativa = forms.DateField(
widget=forms.HiddenInput(), required=False)
legislatura_anterior = forms.BooleanField(label=_('Legislatura Anterior'), legislatura_anterior = forms.BooleanField(
required=False) label=_("Legislatura Anterior"), required=False
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(AutoriaNormaForm, self).__init__(*args, **kwargs) super(AutoriaNormaForm, self).__init__(*args, **kwargs)
row1 = to_row([('tipo_autor', 4), row1 = to_row([("tipo_autor", 4), ("autor", 4), ("primeiro_autor", 4)])
('autor', 4),
('primeiro_autor', 4)])
self.helper = SaplFormHelper() self.helper = SaplFormHelper()
self.helper.layout = Layout( self.helper.layout = Layout(
Fieldset(_('Autoria'), Fieldset(
row1, 'data_relativa', _("Autoria"),
form_actions(label='Salvar'), row1,
to_row([('legislatura_anterior', 12)]))) "data_relativa",
form_actions(label="Salvar"),
to_row([("legislatura_anterior", 12)]),
)
)
if not self.instance: if not self.instance:
self.fields['autor'].choices = [] self.fields["autor"].choices = []
class Meta: class Meta:
model = AutoriaNorma model = AutoriaNorma
fields = ['tipo_autor', 'autor', fields = [
'primeiro_autor', 'data_relativa', "tipo_autor",
'legislatura_anterior'] "autor",
"primeiro_autor",
"data_relativa",
"legislatura_anterior",
]
def clean(self): def clean(self):
cd = super(AutoriaNormaForm, self).clean() cd = super(AutoriaNormaForm, self).clean()
@ -340,32 +413,27 @@ class AutoriaNormaForm(ModelForm):
return self.cleaned_data return self.cleaned_data
autorias = AutoriaNorma.objects.filter( autorias = AutoriaNorma.objects.filter(
norma=self.instance.norma, autor=cd['autor']) norma=self.instance.norma, autor=cd["autor"]
)
pk = self.instance.pk pk = self.instance.pk
if ((not pk and autorias.exists()) or if (not pk and autorias.exists()) or (pk and autorias.exclude(pk=pk).exists()):
(pk and autorias.exclude(pk=pk).exists())): self.logger.error("Autor ({}) já foi cadastrado.".format(cd["autor"]))
self.logger.error( raise ValidationError(_("Esse Autor já foi cadastrado."))
"Autor ({}) já foi cadastrado.".format(cd['autor']))
raise ValidationError(_('Esse Autor já foi cadastrado.'))
return cd return cd
class AnexoNormaJuridicaForm(FileFieldCheckMixin, ModelForm): class AnexoNormaJuridicaForm(FileFieldCheckMixin, ModelForm):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
anexo_arquivo = forms.FileField( anexo_arquivo = forms.FileField(required=True, label="Arquivo Anexo")
required=True,
label="Arquivo Anexo"
)
class Meta: class Meta:
model = AnexoNormaJuridica model = AnexoNormaJuridica
fields = ['norma', 'anexo_arquivo', 'assunto_anexo'] fields = ["norma", "anexo_arquivo", "assunto_anexo"]
widgets = { widgets = {
'norma': forms.HiddenInput(), "norma": forms.HiddenInput(),
} }
def clean(self): def clean(self):
@ -374,7 +442,7 @@ class AnexoNormaJuridicaForm(FileFieldCheckMixin, ModelForm):
if not self.is_valid(): if not self.is_valid():
return cleaned_data return cleaned_data
anexo_arquivo = self.cleaned_data.get('anexo_arquivo', False) anexo_arquivo = self.cleaned_data.get("anexo_arquivo", False)
if anexo_arquivo: if anexo_arquivo:
validar_arquivo(anexo_arquivo, "Arquivo Anexo") validar_arquivo(anexo_arquivo, "Arquivo Anexo")
@ -383,44 +451,40 @@ class AnexoNormaJuridicaForm(FileFieldCheckMixin, ModelForm):
def save(self, commit=False): def save(self, commit=False):
anexo = self.instance anexo = self.instance
anexo.ano = self.cleaned_data['norma'].ano anexo.ano = self.cleaned_data["norma"].ano
anexo.norma = self.cleaned_data['norma'] anexo.norma = self.cleaned_data["norma"]
anexo.assunto_anexo = self.cleaned_data['assunto_anexo'] anexo.assunto_anexo = self.cleaned_data["assunto_anexo"]
anexo.anexo_arquivo = self.cleaned_data['anexo_arquivo'] anexo.anexo_arquivo = self.cleaned_data["anexo_arquivo"]
anexo = super(AnexoNormaJuridicaForm, self).save(commit=True) anexo = super(AnexoNormaJuridicaForm, self).save(commit=True)
return anexo return anexo
class NormaRelacionadaForm(ModelForm): class NormaRelacionadaForm(ModelForm):
orgao = forms.ModelChoiceField( orgao = forms.ModelChoiceField(
label='Órgão', label="Órgão",
required=False, required=False,
queryset=Orgao.objects.all(), queryset=Orgao.objects.all(),
empty_label='----------', empty_label="----------",
) )
tipo = forms.ModelChoiceField( tipo = forms.ModelChoiceField(
label='Tipo', label="Tipo",
required=True, required=True,
queryset=TipoNormaJuridica.objects.all(), queryset=TipoNormaJuridica.objects.all(),
empty_label='----------', empty_label="----------",
) )
numero = forms.CharField(label='Número', required=True) numero = forms.CharField(label="Número", required=True)
ano = forms.CharField(label='Ano', required=True) ano = forms.CharField(label="Ano", required=True)
ementa = forms.CharField( ementa = forms.CharField(
required=False, required=False, widget=forms.Textarea(attrs={"disabled": "disabled"})
widget=forms.Textarea(attrs={'disabled': 'disabled'})) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Meta: class Meta:
model = NormaRelacionada model = NormaRelacionada
fields = ['orgao', 'tipo', 'numero', 'ano', fields = ["orgao", "tipo", "numero", "ano", "resumo", "ementa", "tipo_vinculo"]
'resumo', 'ementa', 'tipo_vinculo']
widgets = { widgets = {"resumo": forms.Textarea(attrs={"id": "texto-rico"})}
'resumo': forms.Textarea(
attrs={'id': 'texto-rico'})}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(NormaRelacionadaForm, self).__init__(*args, **kwargs) super(NormaRelacionadaForm, self).__init__(*args, **kwargs)
@ -433,30 +497,51 @@ class NormaRelacionadaForm(ModelForm):
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
try: try:
self.logger.debug("Tentando obter objeto NormaJuridica com numero={}, ano={}, tipo={}, orgao={}.".format( self.logger.debug(
cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo'], cleaned_data['orgao'])) "Tentando obter objeto NormaJuridica com numero={}, ano={}, tipo={}, orgao={}.".format(
cleaned_data["numero"],
cleaned_data["ano"],
cleaned_data["tipo"],
cleaned_data["orgao"],
)
)
norma_relacionada = NormaJuridica.objects.get( norma_relacionada = NormaJuridica.objects.get(
numero=cleaned_data['numero'], numero=cleaned_data["numero"],
ano=cleaned_data['ano'], ano=cleaned_data["ano"],
tipo=cleaned_data['tipo'], tipo=cleaned_data["tipo"],
orgao=cleaned_data['orgao']) orgao=cleaned_data["orgao"],
)
except ObjectDoesNotExist: except ObjectDoesNotExist:
self.logger.info("NormaJuridica com numero={}, ano={}, tipo={}, orgao={} não existe.".format( self.logger.info(
cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo'], cleaned_data['orgao'])) "NormaJuridica com numero={}, ano={}, tipo={}, orgao={} não existe.".format(
msg = _('A norma a ser relacionada não existe.') cleaned_data["numero"],
cleaned_data["ano"],
cleaned_data["tipo"],
cleaned_data["orgao"],
)
)
msg = _("A norma a ser relacionada não existe.")
raise ValidationError(msg) raise ValidationError(msg)
else: else:
self.logger.info("NormaJuridica com numero={}, ano={}, tipo={} , orgao={} obtida com sucesso.".format( self.logger.info(
cleaned_data['numero'], cleaned_data['ano'], cleaned_data['tipo'], cleaned_data['orgao'])) "NormaJuridica com numero={}, ano={}, tipo={} , orgao={} obtida com sucesso.".format(
cleaned_data['norma_relacionada'] = norma_relacionada cleaned_data["numero"],
cleaned_data["ano"],
cleaned_data["tipo"],
cleaned_data["orgao"],
)
)
cleaned_data["norma_relacionada"] = norma_relacionada
return cleaned_data return cleaned_data
def save(self, commit=False): def save(self, commit=False):
relacionada = super(NormaRelacionadaForm, self).save(commit) relacionada = super(NormaRelacionadaForm, self).save(commit)
relacionada.norma_relacionada = self.cleaned_data['norma_relacionada'] relacionada.norma_relacionada = self.cleaned_data["norma_relacionada"]
if relacionada.tipo_vinculo.revoga_integralmente: if relacionada.tipo_vinculo.revoga_integralmente:
relacionada.norma_relacionada.data_vigencia = relacionada.norma_principal.data relacionada.norma_relacionada.data_vigencia = (
relacionada.norma_principal.data
)
relacionada.norma_relacionada.save() relacionada.norma_relacionada.save()
relacionada.save() relacionada.save()
return relacionada return relacionada
@ -467,45 +552,33 @@ class NormaPesquisaSimplesForm(forms.Form):
label=TipoNormaJuridica._meta.verbose_name, label=TipoNormaJuridica._meta.verbose_name,
queryset=TipoNormaJuridica.objects.all(), queryset=TipoNormaJuridica.objects.all(),
required=False, required=False,
empty_label='Selecione') empty_label="Selecione",
)
data_inicial = forms.DateField( data_inicial = forms.DateField(
label='Data Inicial', label="Data Inicial", required=False, widget=forms.DateInput(format="%d/%m/%Y")
required=False,
widget=forms.DateInput(format='%d/%m/%Y')
) )
data_final = forms.DateField( data_final = forms.DateField(
label='Data Final', label="Data Final", required=False, widget=forms.DateInput(format="%d/%m/%Y")
required=False,
widget=forms.DateInput(format='%d/%m/%Y')
) )
titulo = forms.CharField( titulo = forms.CharField(
label='Título do Relatório', label="Título do Relatório", required=False, max_length=150
required=False, )
max_length=150)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
row1 = to_row( row1 = to_row([("tipo_norma", 6), ("data_inicial", 3), ("data_final", 3)])
[('tipo_norma', 6),
('data_inicial', 3),
('data_final', 3)])
row2 = to_row( row2 = to_row([("titulo", 12)])
[('titulo', 12)])
self.helper = SaplFormHelper() self.helper = SaplFormHelper()
self.helper.layout = Layout( self.helper.layout = Layout(
Fieldset( Fieldset("Índice de Normas", row1, row2, form_actions(label="Pesquisar"))
'Índice de Normas',
row1, row2,
form_actions(label='Pesquisar')
)
) )
def clean(self): def clean(self):
@ -515,19 +588,29 @@ class NormaPesquisaSimplesForm(forms.Form):
return self.cleaned_data return self.cleaned_data
cleaned_data = self.cleaned_data cleaned_data = self.cleaned_data
data_inicial = cleaned_data['data_inicial'] data_inicial = cleaned_data["data_inicial"]
data_final = cleaned_data['data_final'] data_final = cleaned_data["data_final"]
if data_inicial or data_final: if data_inicial or data_final:
if not(data_inicial and data_final): if not (data_inicial and data_final):
self.logger.error("Caso pesquise por data, os campos de Data Inicial e " self.logger.error(
"Data Final devem ser preenchidos obrigatoriamente") "Caso pesquise por data, os campos de Data Inicial e "
raise ValidationError(_('Caso pesquise por data, os campos de Data Inicial e ' "Data Final devem ser preenchidos obrigatoriamente"
'Data Final devem ser preenchidos obrigatoriamente')) )
raise ValidationError(
_(
"Caso pesquise por data, os campos de Data Inicial e "
"Data Final devem ser preenchidos obrigatoriamente"
)
)
elif data_inicial > data_final: elif data_inicial > data_final:
self.logger.error("Data Final ({}) menor que a Data Inicial ({}).".format( self.logger.error(
data_final, data_inicial)) "Data Final ({}) menor que a Data Inicial ({}).".format(
data_final, data_inicial
)
)
raise ValidationError( raise ValidationError(
_('A Data Final não pode ser menor que a Data Inicial')) _("A Data Final não pode ser menor que a Data Inicial")
)
return cleaned_data return cleaned_data

522
sapl/norma/models.py

@ -8,22 +8,21 @@ from model_utils import Choices
from sapl.base.models import Autor from sapl.base.models import Autor
from sapl.compilacao.models import TextoArticulado from sapl.compilacao.models import TextoArticulado
from sapl.materia.models import MateriaLegislativa, Orgao from sapl.materia.models import MateriaLegislativa, Orgao
from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, from sapl.utils import (RANGE_ANOS, YES_NO_CHOICES, OverwriteStorage,
restringe_tipos_de_arquivo_txt,
texto_upload_path,
get_settings_auth_user_model, get_settings_auth_user_model,
OverwriteStorage) restringe_tipos_de_arquivo_txt, texto_upload_path)
class AssuntoNorma(models.Model): class AssuntoNorma(models.Model):
assunto = models.CharField(max_length=50, verbose_name=_('Assunto')) assunto = models.CharField(max_length=50, verbose_name=_("Assunto"))
descricao = models.CharField( descricao = models.CharField(
max_length=250, blank=True, verbose_name=_('Descrição')) max_length=250, blank=True, verbose_name=_("Descrição")
)
class Meta: class Meta:
verbose_name = _('Assunto de Norma Jurídica') verbose_name = _("Assunto de Norma Jurídica")
verbose_name_plural = _('Assuntos de Normas Jurídicas') verbose_name_plural = _("Assuntos de Normas Jurídicas")
ordering = ['assunto'] ordering = ["assunto"]
def __str__(self): def __str__(self):
return self.assunto return self.assunto
@ -31,33 +30,37 @@ class AssuntoNorma(models.Model):
class TipoNormaJuridica(models.Model): class TipoNormaJuridica(models.Model):
# TODO transform into Domain Model and use an FK for the field # TODO transform into Domain Model and use an FK for the field
EQUIVALENTE_LEXML_CHOICES = ((name, name) for name in EQUIVALENTE_LEXML_CHOICES = (
('constituicao', (name, name)
'ementa.constitucional', for name in (
'lei.complementar', "constituicao",
'lei.delegada', "ementa.constitucional",
'lei', "lei.complementar",
'decreto.lei', "lei.delegada",
'medida.provisoria', "lei",
'decreto', "decreto.lei",
'lei.organica', "medida.provisoria",
'emenda.lei.organica', "decreto",
'decreto.legislativo', "lei.organica",
'resolucao', "emenda.lei.organica",
'regimento.interno', "decreto.legislativo",
)) "resolucao",
"regimento.interno",
)
)
equivalente_lexml = models.CharField( equivalente_lexml = models.CharField(
max_length=50, max_length=50,
blank=True, blank=True,
verbose_name=_('Equivalente LexML'), verbose_name=_("Equivalente LexML"),
choices=EQUIVALENTE_LEXML_CHOICES) choices=EQUIVALENTE_LEXML_CHOICES,
sigla = models.CharField(max_length=3, verbose_name=_('Sigla')) )
descricao = models.CharField(max_length=50, verbose_name=_('Descrição')) sigla = models.CharField(max_length=3, verbose_name=_("Sigla"))
descricao = models.CharField(max_length=50, verbose_name=_("Descrição"))
class Meta: class Meta:
verbose_name = _('Tipo de Norma Jurídica') verbose_name = _("Tipo de Norma Jurídica")
verbose_name_plural = _('Tipos de Norma Jurídica') verbose_name_plural = _("Tipos de Norma Jurídica")
ordering = ['descricao'] ordering = ["descricao"]
def __str__(self): def __str__(self):
return self.descricao return self.descricao
@ -68,7 +71,6 @@ def norma_upload_path(instance, filename):
class NormaJuridicaManager(models.Manager): class NormaJuridicaManager(models.Manager):
use_for_related_fields = True use_for_related_fields = True
def normas_sem_textos_articulados(self): def normas_sem_textos_articulados(self):
@ -81,19 +83,16 @@ class NormaJuridicaManager(models.Manager):
qs = qs.filter( qs = qs.filter(
texto_articulado__editable_only_by_owners=False, texto_articulado__editable_only_by_owners=False,
texto_articulado__privacidade=0, texto_articulado__privacidade=0,
texto_articulado__isnull=False texto_articulado__isnull=False,
) )
return qs return qs
def normas_com_textos_articulados_pendentes(self): def normas_com_textos_articulados_pendentes(self):
qs = self.get_queryset() qs = self.get_queryset()
qs = qs.filter( qs = qs.filter(texto_articulado__editable_only_by_owners=False)
texto_articulado__editable_only_by_owners=False)
q = models.Q( q = models.Q(texto_articulado__privacidade=0) | models.Q(
texto_articulado__privacidade=0
) | models.Q(
texto_articulado__isnull=True texto_articulado__isnull=True
) )
qs = qs.exclude(q) qs = qs.exclude(q)
@ -109,14 +108,15 @@ class NormaJuridicaManager(models.Manager):
count = 0 count = 0
elif count == 3: elif count == 3:
ds = ta.dispositivos_set.all() ds = ta.dispositivos_set.all()
if ds[1].auto_inserido and \ if (
not d[2].dispositivo_pai and\ ds[1].auto_inserido
d[2].tipo_dispositivo.dispositivo_de_articulacao: and not d[2].dispositivo_pai
and d[2].tipo_dispositivo.dispositivo_de_articulacao
):
count = 0 count = 0
if not count: if not count:
ta.dispositivos_set.filter( ta.dispositivos_set.filter(dispositivo_pai__isnull=False).delete()
dispositivo_pai__isnull=False).delete()
ta.publicacao_set.all().delete() ta.publicacao_set.all().delete()
ta.delete() ta.delete()
@ -124,13 +124,12 @@ class NormaJuridicaManager(models.Manager):
class NormaJuridica(models.Model): class NormaJuridica(models.Model):
objects = NormaJuridicaManager() objects = NormaJuridicaManager()
ESFERA_FEDERACAO_CHOICES = Choices( ESFERA_FEDERACAO_CHOICES = Choices(
('M', 'municipal', _('Municipal')), ("M", "municipal", _("Municipal")),
('E', 'estadual', _('Estadual')), ("E", "estadual", _("Estadual")),
('F', 'federal', _('Federal')), ("F", "federal", _("Federal")),
) )
texto_integral = models.FileField( texto_integral = models.FileField(
@ -138,132 +137,137 @@ class NormaJuridica(models.Model):
blank=True, blank=True,
null=True, null=True,
upload_to=norma_upload_path, upload_to=norma_upload_path,
verbose_name=_('Texto Original'), verbose_name=_("Texto Original"),
storage=OverwriteStorage(), storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt]) validators=[restringe_tipos_de_arquivo_txt],
)
tipo = models.ForeignKey( tipo = models.ForeignKey(
TipoNormaJuridica, TipoNormaJuridica,
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Tipo da Norma Jurídica')) verbose_name=_("Tipo da Norma Jurídica"),
)
materia = models.ForeignKey( materia = models.ForeignKey(
MateriaLegislativa, blank=True, null=True, MateriaLegislativa,
blank=True,
null=True,
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Matéria'), verbose_name=_("Matéria"),
related_name='normajuridica_set') related_name="normajuridica_set",
)
orgao = models.ForeignKey( orgao = models.ForeignKey(
Orgao, blank=True, null=True, Orgao, blank=True, null=True, on_delete=models.PROTECT, verbose_name=_("Órgão")
on_delete=models.PROTECT, verbose_name=_('Órgão')) )
numero = models.CharField( numero = models.CharField(max_length=8, verbose_name=_("Número"))
max_length=8, ano = models.PositiveSmallIntegerField(verbose_name=_("Ano"), choices=RANGE_ANOS)
verbose_name=_('Número'))
ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'),
choices=RANGE_ANOS)
esfera_federacao = models.CharField( esfera_federacao = models.CharField(
max_length=1, max_length=1,
verbose_name=_('Esfera Federação'), verbose_name=_("Esfera Federação"),
choices=ESFERA_FEDERACAO_CHOICES) choices=ESFERA_FEDERACAO_CHOICES,
data = models.DateField(blank=False, null=True, verbose_name=_('Data')) )
data = models.DateField(blank=False, null=True, verbose_name=_("Data"))
data_publicacao = models.DateField( data_publicacao = models.DateField(
blank=True, null=True, verbose_name=_('Data de Publicação')) blank=True, null=True, verbose_name=_("Data de Publicação")
)
veiculo_publicacao = models.CharField( veiculo_publicacao = models.CharField(
max_length=200, max_length=200, blank=True, verbose_name=_("Veículo de Publicação")
blank=True, )
verbose_name=_('Veículo de Publicação'))
pagina_inicio_publicacao = models.PositiveIntegerField( pagina_inicio_publicacao = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Pg. Início')) blank=True, null=True, verbose_name=_("Pg. Início")
)
pagina_fim_publicacao = models.PositiveIntegerField( pagina_fim_publicacao = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_('Pg. Fim')) blank=True, null=True, verbose_name=_("Pg. Fim")
ementa = models.TextField(verbose_name=_('Ementa')) )
indexacao = models.TextField( ementa = models.TextField(verbose_name=_("Ementa"))
blank=True, verbose_name=_('Indexação')) indexacao = models.TextField(blank=True, verbose_name=_("Indexação"))
observacao = models.TextField( observacao = models.TextField(blank=True, verbose_name=_("Observação"))
blank=True, verbose_name=_('Observação'))
complemento = models.BooleanField( complemento = models.BooleanField(
null=True, null=True,
blank=True, blank=True,
default=False, default=False,
verbose_name=_('Complementar ?'), verbose_name=_("Complementar ?"),
choices=YES_NO_CHOICES choices=YES_NO_CHOICES,
) )
# XXX was a CharField (attention on migrate) # XXX was a CharField (attention on migrate)
assuntos = models.ManyToManyField( assuntos = models.ManyToManyField(
AssuntoNorma, blank=True, AssuntoNorma, blank=True, verbose_name=_("Assuntos")
verbose_name=_('Assuntos')) )
data_vigencia = models.DateField( data_vigencia = models.DateField(
blank=True, null=True, verbose_name=_('Data Fim Vigência')) blank=True, null=True, verbose_name=_("Data Fim Vigência")
)
timestamp = models.DateTimeField(null=True) timestamp = models.DateTimeField(null=True)
texto_articulado = GenericRelation( texto_articulado = GenericRelation(
TextoArticulado, related_query_name='texto_articulado') TextoArticulado, related_query_name="texto_articulado"
)
data_ultima_atualizacao = models.DateTimeField( data_ultima_atualizacao = models.DateTimeField(
blank=True, null=True, blank=True, null=True, auto_now=True, verbose_name=_("Data")
auto_now=True, )
verbose_name=_('Data'))
autores = models.ManyToManyField( autores = models.ManyToManyField(
Autor, Autor,
through='AutoriaNorma', through="AutoriaNorma",
through_fields=('norma', 'autor'), through_fields=("norma", "autor"),
symmetrical=False) symmetrical=False,
)
user = models.ForeignKey( user = models.ForeignKey(
get_settings_auth_user_model(), get_settings_auth_user_model(),
verbose_name=_('Usuário'), verbose_name=_("Usuário"),
on_delete=models.PROTECT, on_delete=models.PROTECT,
null=True, null=True,
blank=True
)
ip = models.CharField(
verbose_name=_('IP'),
max_length=60,
blank=True, blank=True,
default=''
) )
ip = models.CharField(verbose_name=_("IP"), max_length=60, blank=True, default="")
ultima_edicao = models.DateTimeField( ultima_edicao = models.DateTimeField(
verbose_name=_('Data e Hora da Edição'), verbose_name=_("Data e Hora da Edição"), blank=True, null=True
blank=True, null=True
) )
class Meta: class Meta:
verbose_name = _('Norma Jurídica') verbose_name = _("Norma Jurídica")
verbose_name_plural = _('Normas Jurídicas') verbose_name_plural = _("Normas Jurídicas")
ordering = ['-data', '-numero'] ordering = ["-data", "-numero"]
def get_normas_relacionadas(self): def get_normas_relacionadas(self):
principais = NormaRelacionada.objects.\ principais = (
select_related('tipo_vinculo', NormaRelacionada.objects.select_related(
'norma_principal', "tipo_vinculo",
'norma_relacionada', "norma_principal",
'norma_principal__tipo', "norma_relacionada",
'norma_relacionada__tipo').\ "norma_principal__tipo",
filter(norma_principal=self.id).order_by('norma_principal__data', "norma_relacionada__tipo",
'norma_relacionada__data') )
relacionadas = NormaRelacionada.objects.\ .filter(norma_principal=self.id)
select_related('tipo_vinculo', .order_by("norma_principal__data", "norma_relacionada__data")
'norma_principal', )
'norma_relacionada', relacionadas = (
'norma_principal__tipo', NormaRelacionada.objects.select_related(
'norma_relacionada__tipo').\ "tipo_vinculo",
filter(norma_relacionada=self.id).order_by('norma_principal__data', "norma_principal",
'norma_relacionada__data') "norma_relacionada",
"norma_principal__tipo",
"norma_relacionada__tipo",
)
.filter(norma_relacionada=self.id)
.order_by("norma_principal__data", "norma_relacionada__data")
)
return (principais, relacionadas) return (principais, relacionadas)
def get_anexos_norma_juridica(self): def get_anexos_norma_juridica(self):
anexos = AnexoNormaJuridica.objects.filter( anexos = AnexoNormaJuridica.objects.filter(norma=self.id)
norma=self.id)
return anexos return anexos
def __str__(self): def __str__(self):
numero_norma = self.numero numero_norma = self.numero
if numero_norma.isnumeric(): if numero_norma.isnumeric():
numero_norma = '{0:,}'.format(int(self.numero)).replace(',', '.') numero_norma = "{0:,}".format(int(self.numero)).replace(",", ".")
return _('%(tipo)s%(orgao_sigla)s%(numero)s, de %(data)s') % { return _("%(tipo)s%(orgao_sigla)s%(numero)s, de %(data)s") % {
'tipo': self.tipo, "tipo": self.tipo,
'orgao_sigla': f'-{self.orgao.sigla}' if self.orgao else '', "orgao_sigla": f"-{self.orgao.sigla}" if self.orgao else "",
'numero': numero_norma, "numero": numero_norma,
'data': defaultfilters.date(self.data, r"d \d\e F \d\e Y").lower()} "data": defaultfilters.date(self.data, r"d \d\e F \d\e Y").lower(),
}
@property @property
def epigrafe(self): def epigrafe(self):
@ -273,12 +277,13 @@ class NormaJuridica(models.Model):
def epigrafe_simplificada(self): def epigrafe_simplificada(self):
numero_norma = self.numero numero_norma = self.numero
if numero_norma.isnumeric(): if numero_norma.isnumeric():
numero_norma = '{0:,}'.format(int(self.numero)).replace(',', '.') numero_norma = "{0:,}".format(int(self.numero)).replace(",", ".")
return _('%(tipo)s%(numero)s, de %(data)s') % { return _("%(tipo)s%(numero)s, de %(data)s") % {
'tipo': self.tipo, "tipo": self.tipo,
'numero': numero_norma, "numero": numero_norma,
'data': defaultfilters.date(self.data, r"d \d\e F \d\e Y").lower()} "data": defaultfilters.date(self.data, r"d \d\e F \d\e Y").lower(),
}
def delete(self, using=None, keep_parents=False): def delete(self, using=None, keep_parents=False):
texto_integral = self.texto_integral texto_integral = self.texto_integral
@ -289,22 +294,28 @@ class NormaJuridica(models.Model):
return result return result
def save(self, force_insert=False, force_update=False, using=None, def save(
update_fields=None): self, force_insert=False, force_update=False, using=None, update_fields=None
):
if not self.pk and self.texto_integral: if not self.pk and self.texto_integral:
texto_integral = self.texto_integral texto_integral = self.texto_integral
self.texto_integral = None self.texto_integral = None
models.Model.save(self, force_insert=force_insert, models.Model.save(
force_update=force_update, self,
using=using, force_insert=force_insert,
update_fields=update_fields) force_update=force_update,
using=using,
update_fields=update_fields,
)
self.texto_integral = texto_integral self.texto_integral = texto_integral
return models.Model.save(self, force_insert=force_insert, return models.Model.save(
force_update=force_update, self,
using=using, force_insert=force_insert,
update_fields=update_fields) force_update=force_update,
using=using,
update_fields=update_fields,
)
def get_ano_atual(): def get_ano_atual():
@ -313,50 +324,51 @@ def get_ano_atual():
class NormaEstatisticas(models.Model): class NormaEstatisticas(models.Model):
usuario = models.CharField(max_length=50) usuario = models.CharField(max_length=50)
horario_acesso = models.DateTimeField( horario_acesso = models.DateTimeField(blank=True, null=True)
blank=True, null=True) ano = models.PositiveSmallIntegerField(
ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'), verbose_name=_("Ano"), choices=RANGE_ANOS, default=get_ano_atual
choices=RANGE_ANOS, default=get_ano_atual) )
norma = models.ForeignKey(NormaJuridica, norma = models.ForeignKey(NormaJuridica, on_delete=models.CASCADE)
on_delete=models.CASCADE)
class Meta: class Meta:
ordering = ('id',) ordering = ("id",)
def __str__(self): def __str__(self):
return _('Usuário: %(usuario)s, Norma: %(norma)s') % { return _("Usuário: %(usuario)s, Norma: %(norma)s") % {
'usuario': self.usuario, 'norma': self.norma} "usuario": self.usuario,
"norma": self.norma,
}
class ViewNormasEstatisticas(models.Model): class ViewNormasEstatisticas(models.Model):
mais_acessadas = models.PositiveSmallIntegerField( mais_acessadas = models.PositiveSmallIntegerField(verbose_name=_("Mais Acessadas"))
verbose_name=_('Mais Acessadas'))
ano_est = models.PositiveSmallIntegerField( ano_est = models.PositiveSmallIntegerField(
verbose_name=_('Ano do Registro de Acesso')) verbose_name=_("Ano do Registro de Acesso")
)
mes_est = models.PositiveSmallIntegerField( mes_est = models.PositiveSmallIntegerField(
verbose_name=_('Mês do Registro de Acesso')) verbose_name=_("Mês do Registro de Acesso")
)
norma_id = models.BigIntegerField(verbose_name=_('Id da Norma')) norma_id = models.BigIntegerField(verbose_name=_("Id da Norma"))
norma_count = models.PositiveSmallIntegerField( norma_count = models.PositiveSmallIntegerField(
verbose_name=_('Mês do Registro de Acesso')) verbose_name=_("Mês do Registro de Acesso")
)
norma_numero = models.CharField( norma_numero = models.CharField(max_length=8, verbose_name=_("Número da Norma"))
max_length=8, verbose_name=_('Número da Norma'))
norma_ano = models.PositiveSmallIntegerField( norma_ano = models.PositiveSmallIntegerField(verbose_name=_("Ano da Norma"))
verbose_name=_('Ano da Norma')) norma_ementa = models.TextField(verbose_name=_("Ementa"))
norma_ementa = models.TextField(verbose_name=_('Ementa')) norma_observacao = models.TextField(blank=True, verbose_name=_("Observação"))
norma_observacao = models.TextField(
blank=True, verbose_name=_('Observação'))
norma_tipo_sigla = models.CharField( norma_tipo_sigla = models.CharField(
max_length=3, max_length=3, verbose_name=_("Sigla do Tipo da Norma")
verbose_name=_('Sigla do Tipo da Norma')) )
norma_tipo_descricao = models.CharField( norma_tipo_descricao = models.CharField(
max_length=50, verbose_name=_('Descrição do Tipo da Norma')) max_length=50, verbose_name=_("Descrição do Tipo da Norma")
)
norma_data = models.DateField(verbose_name=_('Data da Norma')) norma_data = models.DateField(verbose_name=_("Data da Norma"))
class Meta: class Meta:
managed = False managed = False
@ -364,79 +376,70 @@ class ViewNormasEstatisticas(models.Model):
class AutoriaNorma(models.Model): class AutoriaNorma(models.Model):
autor = models.ForeignKey(Autor, autor = models.ForeignKey(Autor, verbose_name=_("Autor"), on_delete=models.CASCADE)
verbose_name=_('Autor'),
on_delete=models.CASCADE)
norma = models.ForeignKey( norma = models.ForeignKey(
NormaJuridica, on_delete=models.CASCADE, NormaJuridica, on_delete=models.CASCADE, verbose_name=_("Matéria Legislativa")
verbose_name=_('Matéria Legislativa')) )
primeiro_autor = models.BooleanField(verbose_name=_('Primeiro Autor'), primeiro_autor = models.BooleanField(
choices=YES_NO_CHOICES, verbose_name=_("Primeiro Autor"), choices=YES_NO_CHOICES, default=False
default=False) )
class Meta: class Meta:
verbose_name = _('Autoria') verbose_name = _("Autoria")
verbose_name_plural = _('Autorias') verbose_name_plural = _("Autorias")
unique_together = (('autor', 'norma'), ) unique_together = (("autor", "norma"),)
ordering = ('-primeiro_autor', 'autor__nome') ordering = ("-primeiro_autor", "autor__nome")
def __str__(self): def __str__(self):
return _('Autoria: %(autor)s - %(norma)s') % { return _("Autoria: %(autor)s - %(norma)s") % {
'autor': self.autor, 'norma': self.norma} "autor": self.autor,
"norma": self.norma,
}
class LegislacaoCitada(models.Model): class LegislacaoCitada(models.Model):
materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE) materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE)
norma = models.ForeignKey(NormaJuridica, on_delete=models.CASCADE) norma = models.ForeignKey(NormaJuridica, on_delete=models.CASCADE)
disposicoes = models.CharField( disposicoes = models.CharField(
max_length=15, blank=True, verbose_name=_('Disposição')) max_length=15, blank=True, verbose_name=_("Disposição")
parte = models.CharField( )
max_length=8, blank=True, verbose_name=_('Parte')) parte = models.CharField(max_length=8, blank=True, verbose_name=_("Parte"))
livro = models.CharField( livro = models.CharField(max_length=7, blank=True, verbose_name=_("Livro"))
max_length=7, blank=True, verbose_name=_('Livro')) titulo = models.CharField(max_length=7, blank=True, verbose_name=_("Título"))
titulo = models.CharField( capitulo = models.CharField(max_length=7, blank=True, verbose_name=_("Capítulo"))
max_length=7, blank=True, verbose_name=_('Título')) secao = models.CharField(max_length=7, blank=True, verbose_name=_("Seção"))
capitulo = models.CharField( subsecao = models.CharField(max_length=7, blank=True, verbose_name=_("Subseção"))
max_length=7, blank=True, verbose_name=_('Capítulo')) artigo = models.CharField(max_length=4, blank=True, verbose_name=_("Artigo"))
secao = models.CharField( paragrafo = models.CharField(max_length=3, blank=True, verbose_name=_("Parágrafo"))
max_length=7, blank=True, verbose_name=_('Seção')) inciso = models.CharField(max_length=10, blank=True, verbose_name=_("Inciso"))
subsecao = models.CharField( alinea = models.CharField(max_length=3, blank=True, verbose_name=_("Alínea"))
max_length=7, blank=True, verbose_name=_('Subseção')) item = models.CharField(max_length=3, blank=True, verbose_name=_("Item"))
artigo = models.CharField(
max_length=4, blank=True, verbose_name=_('Artigo'))
paragrafo = models.CharField(
max_length=3, blank=True, verbose_name=_('Parágrafo'))
inciso = models.CharField(
max_length=10, blank=True, verbose_name=_('Inciso'))
alinea = models.CharField(
max_length=3, blank=True, verbose_name=_('Alínea'))
item = models.CharField(
max_length=3, blank=True, verbose_name=_('Item'))
class Meta: class Meta:
verbose_name = _('Legislação') verbose_name = _("Legislação")
verbose_name_plural = _('Legislações') verbose_name_plural = _("Legislações")
ordering = ('id',) ordering = ("id",)
def __str__(self): def __str__(self):
return str(self.norma) return str(self.norma)
class TipoVinculoNormaJuridica(models.Model): class TipoVinculoNormaJuridica(models.Model):
sigla = models.CharField( sigla = models.CharField(max_length=1, blank=True, verbose_name=_("Sigla"))
max_length=1, blank=True, verbose_name=_('Sigla'))
descricao_ativa = models.CharField( descricao_ativa = models.CharField(
max_length=50, blank=True, verbose_name=_('Descrição Ativa')) max_length=50, blank=True, verbose_name=_("Descrição Ativa")
)
descricao_passiva = models.CharField( descricao_passiva = models.CharField(
max_length=50, blank=True, verbose_name=_('Descrição Passiva')) max_length=50, blank=True, verbose_name=_("Descrição Passiva")
revoga_integralmente = models.BooleanField(verbose_name=_('Revoga Integralmente?'), )
choices=YES_NO_CHOICES, revoga_integralmente = models.BooleanField(
default=False) verbose_name=_("Revoga Integralmente?"), choices=YES_NO_CHOICES, default=False
)
class Meta: class Meta:
verbose_name = _('Tipo de Vínculo entre Normas Jurídicas') verbose_name = _("Tipo de Vínculo entre Normas Jurídicas")
verbose_name_plural = _('Tipos de Vínculos entre Normas Jurídicas') verbose_name_plural = _("Tipos de Vínculos entre Normas Jurídicas")
ordering = ('id',) ordering = ("id",)
def __str__(self): def __str__(self):
return self.descricao_ativa return self.descricao_ativa
@ -445,84 +448,95 @@ class TipoVinculoNormaJuridica(models.Model):
class NormaRelacionada(models.Model): class NormaRelacionada(models.Model):
norma_principal = models.ForeignKey( norma_principal = models.ForeignKey(
NormaJuridica, NormaJuridica,
related_name='norma_principal', related_name="norma_principal",
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Norma Principal')) verbose_name=_("Norma Principal"),
)
norma_relacionada = models.ForeignKey( norma_relacionada = models.ForeignKey(
NormaJuridica, NormaJuridica,
related_name='norma_relacionada', related_name="norma_relacionada",
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Norma Relacionada')) verbose_name=_("Norma Relacionada"),
)
tipo_vinculo = models.ForeignKey( tipo_vinculo = models.ForeignKey(
TipoVinculoNormaJuridica, TipoVinculoNormaJuridica,
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Tipo de Vínculo')) verbose_name=_("Tipo de Vínculo"),
)
resumo = models.TextField( resumo = models.TextField(
blank=True, blank=True,
default="", default="",
verbose_name=_('Resumo'), verbose_name=_("Resumo"),
) )
class Meta: class Meta:
verbose_name = _('Norma Relacionada') verbose_name = _("Norma Relacionada")
verbose_name_plural = _('Normas Relacionadas') verbose_name_plural = _("Normas Relacionadas")
ordering = ('norma_principal__data', 'norma_relacionada__data') ordering = ("norma_principal__data", "norma_relacionada__data")
def __str__(self): def __str__(self):
return _('Principal: %(norma_principal)s' return _(
' - Relacionada: %(norma_relacionada)s') % { "Principal: %(norma_principal)s" " - Relacionada: %(norma_relacionada)s"
'norma_principal': str(self.norma_principal), ) % {
'norma_relacionada': str(self.norma_relacionada)} "norma_principal": str(self.norma_principal),
"norma_relacionada": str(self.norma_relacionada),
}
class AnexoNormaJuridica(models.Model): class AnexoNormaJuridica(models.Model):
norma = models.ForeignKey( norma = models.ForeignKey(
NormaJuridica, NormaJuridica,
related_name='norma', related_name="norma",
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Norma Juridica')) verbose_name=_("Norma Juridica"),
)
assunto_anexo = models.TextField( assunto_anexo = models.TextField(
blank=True, blank=True, default="", verbose_name=_("Assunto do Anexo"), max_length=250
default="",
verbose_name=_('Assunto do Anexo'),
max_length=250
) )
anexo_arquivo = models.FileField( anexo_arquivo = models.FileField(
max_length=300, max_length=300,
blank=True, blank=True,
null=True, null=True,
upload_to=norma_upload_path, upload_to=norma_upload_path,
verbose_name=_('Arquivo Anexo'), verbose_name=_("Arquivo Anexo"),
storage=OverwriteStorage(), storage=OverwriteStorage(),
validators=[restringe_tipos_de_arquivo_txt]) validators=[restringe_tipos_de_arquivo_txt],
ano = models.PositiveSmallIntegerField(verbose_name=_('Ano'), )
choices=RANGE_ANOS) ano = models.PositiveSmallIntegerField(verbose_name=_("Ano"), choices=RANGE_ANOS)
class Meta: class Meta:
verbose_name = _('Anexo da Norma Juridica') verbose_name = _("Anexo da Norma Juridica")
verbose_name_plural = _('Anexos da Norma Juridica') verbose_name_plural = _("Anexos da Norma Juridica")
ordering = ('id',) ordering = ("id",)
def __str__(self): def __str__(self):
return _('Anexo: %(anexo)s da norma %(norma)s') % { return _("Anexo: %(anexo)s da norma %(norma)s") % {
'anexo': self.anexo_arquivo, 'norma': self.norma} "anexo": self.anexo_arquivo,
"norma": self.norma,
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.anexo_arquivo: if not self.pk and self.anexo_arquivo:
anexo_arquivo = self.anexo_arquivo anexo_arquivo = self.anexo_arquivo
self.anexo_arquivo = None self.anexo_arquivo = None
models.Model.save(self, force_insert=force_insert, models.Model.save(
force_update=force_update, self,
using=using, force_insert=force_insert,
update_fields=update_fields) force_update=force_update,
using=using,
update_fields=update_fields,
)
self.anexo_arquivo = anexo_arquivo self.anexo_arquivo = anexo_arquivo
return models.Model.save(self, force_insert=force_insert, return models.Model.save(
force_update=force_update, self,
using=using, force_insert=force_insert,
update_fields=update_fields) force_update=force_update,
using=using,
update_fields=update_fields,
)
def delete(self, using=None, keep_parents=False): def delete(self, using=None, keep_parents=False):
anexo_arquivo = self.anexo_arquivo anexo_arquivo = self.anexo_arquivo

181
sapl/norma/tests/test_norma.py

@ -1,7 +1,7 @@
import pytest
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from model_bakery import baker from model_bakery import baker
import pytest
from sapl.base.models import AppConfig from sapl.base.models import AppConfig
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
@ -13,48 +13,55 @@ from sapl.norma.models import NormaJuridica, TipoNormaJuridica
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_incluir_norma_submit(admin_client): def test_incluir_norma_submit(admin_client):
# Cria um tipo de norma # Cria um tipo de norma
tipo = baker.make(TipoNormaJuridica, tipo = baker.make(TipoNormaJuridica, sigla="T", descricao="Teste")
sigla='T',
descricao='Teste')
config = baker.make(AppConfig) config = baker.make(AppConfig)
# Testa POST # Testa POST
response = admin_client.post(reverse('sapl.norma:normajuridica_create'), response = admin_client.post(
{'tipo': tipo.pk, reverse("sapl.norma:normajuridica_create"),
'numero': '1', {
'ano': '2016', "tipo": tipo.pk,
'data': '2016-03-22', "numero": "1",
'esfera_federacao': 'E', "ano": "2016",
'ementa': 'Teste_Ementa', "data": "2016-03-22",
'salvar': 'salvar'}, "esfera_federacao": "E",
follow=True) "ementa": "Teste_Ementa",
"salvar": "salvar",
},
follow=True,
)
assert response.status_code == 200 assert response.status_code == 200
norma = NormaJuridica.objects.first() norma = NormaJuridica.objects.first()
assert norma.numero == '1' assert norma.numero == "1"
assert norma.ano == 2016 assert norma.ano == 2016
assert norma.tipo == tipo assert norma.tipo == tipo
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_incluir_norma_errors(admin_client): def test_incluir_norma_errors(admin_client):
response = admin_client.post(
response = admin_client.post(reverse('sapl.norma:normajuridica_create'), reverse("sapl.norma:normajuridica_create"), {"salvar": "salvar"}, follow=True
{'salvar': 'salvar'}, )
follow=True)
assert response.context_data["form"].errors["tipo"] == [
assert (response.context_data['form'].errors['tipo'] == _("Este campo é obrigatório.")
[_('Este campo é obrigatório.')]) ]
assert (response.context_data['form'].errors['numero'] == assert response.context_data["form"].errors["numero"] == [
[_('Este campo é obrigatório.')]) _("Este campo é obrigatório.")
assert (response.context_data['form'].errors['ano'] == ]
[_('Este campo é obrigatório.')]) assert response.context_data["form"].errors["ano"] == [
assert (response.context_data['form'].errors['data'] == _("Este campo é obrigatório.")
[_('Este campo é obrigatório.')]) ]
assert (response.context_data['form'].errors['esfera_federacao'] == assert response.context_data["form"].errors["data"] == [
[_('Este campo é obrigatório.')]) _("Este campo é obrigatório.")
assert (response.context_data['form'].errors['ementa'] == ]
[_('Este campo é obrigatório.')]) assert response.context_data["form"].errors["esfera_federacao"] == [
_("Este campo é obrigatório.")
]
assert response.context_data["form"].errors["ementa"] == [
_("Este campo é obrigatório.")
]
# TODO esse teste repete o teste acima (test_incluir_norma_errors) # TODO esse teste repete o teste acima (test_incluir_norma_errors)
@ -67,65 +74,69 @@ def test_norma_form_invalida():
errors = form.errors errors = form.errors
assert errors['tipo'] == [_('Este campo é obrigatório.')] assert errors["tipo"] == [_("Este campo é obrigatório.")]
assert errors['numero'] == [_('Este campo é obrigatório.')] assert errors["numero"] == [_("Este campo é obrigatório.")]
assert errors['ano'] == [_('Este campo é obrigatório.')] assert errors["ano"] == [_("Este campo é obrigatório.")]
assert errors['data'] == [_('Este campo é obrigatório.')] assert errors["data"] == [_("Este campo é obrigatório.")]
assert errors['esfera_federacao'] == [_('Este campo é obrigatório.')] assert errors["esfera_federacao"] == [_("Este campo é obrigatório.")]
assert errors['ementa'] == [_('Este campo é obrigatório.')] assert errors["ementa"] == [_("Este campo é obrigatório.")]
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_norma_juridica_materia_inexistente(): def test_norma_juridica_materia_inexistente():
tipo = baker.make(TipoNormaJuridica) tipo = baker.make(TipoNormaJuridica)
tipo_materia = baker.make(TipoMateriaLegislativa, descricao='VETO') tipo_materia = baker.make(TipoMateriaLegislativa, descricao="VETO")
# cria uma matéria qualquer em 2017 pois, no teste, o campo ano_materia # cria uma matéria qualquer em 2017 pois, no teste, o campo ano_materia
# está vazio # está vazio
materia = baker.make(MateriaLegislativa, materia = baker.make(
tipo=tipo_materia, MateriaLegislativa,
ano=2017, tipo=tipo_materia,
numero=1, ano=2017,
data_apresentacao='2017-03-05' numero=1,
) data_apresentacao="2017-03-05",
)
form = NormaJuridicaForm(data={'tipo': str(tipo.pk),
'numero': '1', form = NormaJuridicaForm(
'ano': '2017', data={
'data': '2017-12-12', "tipo": str(tipo.pk),
'esfera_federacao': 'F', "numero": "1",
'ementa': 'teste norma', "ano": "2017",
'tipo_materia': str(tipo_materia.pk), "data": "2017-12-12",
'numero_materia': '2', "esfera_federacao": "F",
'ano_materia': '2017' "ementa": "teste norma",
}) "tipo_materia": str(tipo_materia.pk),
"numero_materia": "2",
"ano_materia": "2017",
}
)
assert not form.is_valid() assert not form.is_valid()
assert form.errors['__all__'] == [ assert form.errors["__all__"] == [
_("Matéria Legislativa 2/2017 (VETO) é inexistente.")] _("Matéria Legislativa 2/2017 (VETO) é inexistente.")
]
@pytest.mark.django_db(transaction=False) @pytest.mark.django_db(transaction=False)
def test_norma_juridica_materia_existente(): def test_norma_juridica_materia_existente():
tipo = baker.make(TipoNormaJuridica) tipo = baker.make(TipoNormaJuridica)
tipo_materia = baker.make(TipoMateriaLegislativa) tipo_materia = baker.make(TipoMateriaLegislativa)
baker.make(MateriaLegislativa, baker.make(MateriaLegislativa, numero=2, ano=2017, tipo=tipo_materia)
numero=2,
ano=2017, form = NormaJuridicaForm(
tipo=tipo_materia) data={
"tipo": str(tipo.pk),
form = NormaJuridicaForm(data={'tipo': str(tipo.pk), "numero": "1",
'numero': '1', "ano": "2017",
'ano': '2017', "data": "2017-12-12",
'data': '2017-12-12', "esfera_federacao": "F",
'esfera_federacao': 'F', "ementa": "teste norma",
'ementa': 'teste norma', "tipo_materia": str(tipo_materia.pk),
'tipo_materia': str(tipo_materia.pk), "numero_materia": "2",
'numero_materia': '2', "ano_materia": "2017",
'ano_materia': '2017' }
}) )
assert form.is_valid() assert form.is_valid()
@ -137,10 +148,10 @@ def test_norma_relacionada_form_campos_obrigatorios():
errors = form.errors errors = form.errors
assert errors['tipo'] == [_('Este campo é obrigatório.')] assert errors["tipo"] == [_("Este campo é obrigatório.")]
assert errors['numero'] == [_('Este campo é obrigatório.')] assert errors["numero"] == [_("Este campo é obrigatório.")]
assert errors['ano'] == [_('Este campo é obrigatório.')] assert errors["ano"] == [_("Este campo é obrigatório.")]
assert errors['tipo_vinculo'] == [_('Este campo é obrigatório.')] assert errors["tipo_vinculo"] == [_("Este campo é obrigatório.")]
assert len(errors) == 4 assert len(errors) == 4
@ -149,10 +160,14 @@ def test_norma_relacionada_form_campos_obrigatorios():
def test_norma_pesquisa_form_datas_invalidas(): def test_norma_pesquisa_form_datas_invalidas():
tipo = baker.make(TipoNormaJuridica) tipo = baker.make(TipoNormaJuridica)
form = NormaPesquisaSimplesForm(data={'tipo_norma': str(tipo.pk), form = NormaPesquisaSimplesForm(
'data_inicial': '10/11/2017', data={
'data_final': '09/11/2017' "tipo_norma": str(tipo.pk),
}) "data_inicial": "10/11/2017",
"data_final": "09/11/2017",
}
)
assert not form.is_valid() assert not form.is_valid()
assert form.errors['__all__'] == [_('A Data Final não pode ser menor que ' assert form.errors["__all__"] == [
'a Data Inicial')] _("A Data Final não pode ser menor que " "a Data Inicial")
]

54
sapl/norma/urls.py

@ -2,42 +2,42 @@ from django.conf import settings
from django.urls import include, path, re_path from django.urls import include, path, re_path
from sapl.norma.views import (AnexoNormaJuridicaCrud, AssuntoNormaCrud, from sapl.norma.views import (AnexoNormaJuridicaCrud, AssuntoNormaCrud,
NormaCrud, NormaPesquisaView, AutoriaNormaCrud, NormaCrud, NormaPesquisaView,
NormaRelacionadaCrud, NormaTaView, TipoNormaCrud, NormaRelacionadaCrud, NormaTaView,
PesquisarAssuntoNormaView, TipoNormaCrud,
TipoVinculoNormaJuridicaCrud, recuperar_norma, TipoVinculoNormaJuridicaCrud, recuperar_norma,
recuperar_numero_norma, AutoriaNormaCrud, recuperar_numero_norma)
PesquisarAssuntoNormaView)
from .apps import AppConfig from .apps import AppConfig
app_name = AppConfig.name app_name = AppConfig.name
urlpatterns = [ urlpatterns = [
path('norma/', include(NormaCrud.get_urls() + path(
NormaRelacionadaCrud.get_urls() + "norma/",
AnexoNormaJuridicaCrud.get_urls() + include(
AutoriaNormaCrud.get_urls())), NormaCrud.get_urls()
+ NormaRelacionadaCrud.get_urls()
+ AnexoNormaJuridicaCrud.get_urls()
+ AutoriaNormaCrud.get_urls()
),
),
# Integração com Compilação # Integração com Compilação
path('norma/<int:pk>/ta', NormaTaView.as_view(), name='norma_ta'), path("norma/<int:pk>/ta", NormaTaView.as_view(), name="norma_ta"),
path('sistema/norma/tipo/', include(TipoNormaCrud.get_urls())), path("sistema/norma/tipo/", include(TipoNormaCrud.get_urls())),
path("sistema/norma/assunto/", include(AssuntoNormaCrud.get_urls())),
path('sistema/norma/assunto/', include(AssuntoNormaCrud.get_urls())),
re_path( re_path(
r'^sistema/norma/pesquisar-assunto-norma/', r"^sistema/norma/pesquisar-assunto-norma/",
PesquisarAssuntoNormaView.as_view(), name="pesquisar_assuntonorma" PesquisarAssuntoNormaView.as_view(),
name="pesquisar_assuntonorma",
),
path("sistema/norma/vinculo/", include(TipoVinculoNormaJuridicaCrud.get_urls())),
path("norma/pesquisar", NormaPesquisaView.as_view(), name="norma_pesquisa"),
path("norma/recuperar-norma", recuperar_norma, name="recuperar_norma"),
path(
"norma/recuperar-numero-norma",
recuperar_numero_norma,
name="recuperar_numero_norma",
), ),
path('sistema/norma/vinculo/', include(
TipoVinculoNormaJuridicaCrud.get_urls())),
path('norma/pesquisar',
NormaPesquisaView.as_view(), name='norma_pesquisa'),
path('norma/recuperar-norma', recuperar_norma, name="recuperar_norma"),
path('norma/recuperar-numero-norma', recuperar_numero_norma,
name="recuperar_numero_norma"),
] ]

488
sapl/norma/views.py

@ -1,7 +1,8 @@
from datetime import datetime
import logging import logging
import re import re
from datetime import datetime
import weasyprint
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q from django.db.models import Q
@ -17,33 +18,41 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView, UpdateView from django.views.generic import TemplateView, UpdateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django_filters.views import FilterView from django_filters.views import FilterView
import weasyprint
from sapl import settings
import sapl import sapl
from sapl import settings
from sapl.base.models import AppConfig from sapl.base.models import AppConfig
from sapl.compilacao.models import STATUS_TA_PUBLIC from sapl.compilacao.models import STATUS_TA_PUBLIC
from sapl.compilacao.views import IntegracaoTaView from sapl.compilacao.views import IntegracaoTaView
from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux, from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
MasterDetailCrud, make_pagination) MasterDetailCrud, make_pagination)
from sapl.materia.models import Orgao from sapl.materia.models import Orgao
from sapl.utils import show_results_filter_set, get_client_ip,\ from sapl.utils import (MultiFormatOutputMixin, get_client_ip, sapn_is_enabled,
sapn_is_enabled, MultiFormatOutputMixin show_results_filter_set)
from .forms import (AnexoNormaJuridicaForm, NormaFilterSet, NormaJuridicaForm,
NormaPesquisaSimplesForm, NormaRelacionadaForm,
AutoriaNormaForm, AssuntoNormaFilterSet)
from .models import (AnexoNormaJuridica, AssuntoNorma, NormaJuridica, NormaRelacionada,
TipoNormaJuridica, TipoVinculoNormaJuridica, AutoriaNorma, NormaEstatisticas)
from .forms import (AnexoNormaJuridicaForm, AssuntoNormaFilterSet,
AutoriaNormaForm, NormaFilterSet, NormaJuridicaForm,
NormaPesquisaSimplesForm, NormaRelacionadaForm)
from .models import (AnexoNormaJuridica, AssuntoNorma, AutoriaNorma,
NormaEstatisticas, NormaJuridica, NormaRelacionada,
TipoNormaJuridica, TipoVinculoNormaJuridica)
# LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '') # LegislacaoCitadaCrud = Crud.build(LegislacaoCitada, '')
TipoNormaCrud = CrudAux.build( TipoNormaCrud = CrudAux.build(
TipoNormaJuridica, 'tipo_norma_juridica', TipoNormaJuridica,
list_field_names=['sigla', 'descricao', 'equivalente_lexml']) "tipo_norma_juridica",
list_field_names=["sigla", "descricao", "equivalente_lexml"],
)
TipoVinculoNormaJuridicaCrud = CrudAux.build( TipoVinculoNormaJuridicaCrud = CrudAux.build(
TipoVinculoNormaJuridica, '', TipoVinculoNormaJuridica,
list_field_names=['sigla', 'descricao_ativa', 'descricao_passiva', 'revoga_integralmente']) "",
list_field_names=[
"sigla",
"descricao_ativa",
"descricao_passiva",
"revoga_integralmente",
],
)
class AssuntoNormaCrud(CrudAux): class AssuntoNormaCrud(CrudAux):
@ -54,7 +63,7 @@ class AssuntoNormaCrud(CrudAux):
class DeleteView(CrudAux.DeleteView): class DeleteView(CrudAux.DeleteView):
def get_success_url(self): def get_success_url(self):
return reverse('sapl.norma:pesquisar_assuntonorma') return reverse("sapl.norma:pesquisar_assuntonorma")
class PesquisarAssuntoNormaView(FilterView): class PesquisarAssuntoNormaView(FilterView):
@ -63,30 +72,26 @@ class PesquisarAssuntoNormaView(FilterView):
paginate_by = 20 paginate_by = 20
def get_filterset_kwargs(self, filterset_class): def get_filterset_kwargs(self, filterset_class):
super(PesquisarAssuntoNormaView, self).get_filterset_kwargs( super(PesquisarAssuntoNormaView, self).get_filterset_kwargs(filterset_class)
filterset_class
)
return ({ return {
"data": self.request.GET or None, "data": self.request.GET or None,
"queryset": self.get_queryset().order_by("assunto").distinct() "queryset": self.get_queryset().order_by("assunto").distinct(),
}) }
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(PesquisarAssuntoNormaView, self).get_context_data( context = super(PesquisarAssuntoNormaView, self).get_context_data(**kwargs)
**kwargs
)
paginator = context["paginator"] paginator = context["paginator"]
page_obj = context["page_obj"] page_obj = context["page_obj"]
context.update({ context.update(
"page_range": make_pagination( {
page_obj.number, paginator.num_pages "page_range": make_pagination(page_obj.number, paginator.num_pages),
), "NO_ENTRIES_MSG": "Nenhum assunto de norma jurídica encontrado!",
"NO_ENTRIES_MSG": "Nenhum assunto de norma jurídica encontrado!", "title": _("Assunto de Norma Jurídica"),
"title": _("Assunto de Norma Jurídica") }
}) )
return context return context
@ -95,38 +100,40 @@ class PesquisarAssuntoNormaView(FilterView):
data = self.filterset.data data = self.filterset.data
url = '' url = ""
if data: if data:
url = '&' + str(self.request.META["QUERY_STRING"]) url = "&" + str(self.request.META["QUERY_STRING"])
if url.startswith("&page"): if url.startswith("&page"):
url = '' url = ""
if 'assunto' in self.request.META['QUERY_STRING'] or\ if (
'page' in self.request.META['QUERY_STRING']: "assunto" in self.request.META["QUERY_STRING"]
or "page" in self.request.META["QUERY_STRING"]
):
resultados = self.object_list resultados = self.object_list
else: else:
resultados = [] resultados = []
context = self.get_context_data(filter=self.filterset, context = self.get_context_data(
object_list=resultados, filter=self.filterset,
filter_url=url, object_list=resultados,
numero_res=len(resultados) filter_url=url,
) numero_res=len(resultados),
)
context['show_results'] = show_results_filter_set( context["show_results"] = show_results_filter_set(self.request.GET.copy())
self.request.GET.copy())
return self.render_to_response(context) return self.render_to_response(context)
class NormaRelacionadaCrud(MasterDetailCrud): class NormaRelacionadaCrud(MasterDetailCrud):
model = NormaRelacionada model = NormaRelacionada
parent_field = 'norma_principal' parent_field = "norma_principal"
help_topic = 'norma_juridica' help_topic = "norma_juridica"
class BaseMixin(MasterDetailCrud.BaseMixin): class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['norma_relacionada', 'tipo_vinculo', 'resujmo'] list_field_names = ["norma_relacionada", "tipo_vinculo", "resujmo"]
class CreateView(MasterDetailCrud.CreateView): class CreateView(MasterDetailCrud.CreateView):
form_class = NormaRelacionadaForm form_class = NormaRelacionadaForm
@ -136,15 +143,14 @@ class NormaRelacionadaCrud(MasterDetailCrud):
def get_initial(self): def get_initial(self):
initial = super(UpdateView, self).get_initial() initial = super(UpdateView, self).get_initial()
initial['tipo'] = self.object.norma_relacionada.tipo.id initial["tipo"] = self.object.norma_relacionada.tipo.id
initial['numero'] = self.object.norma_relacionada.numero initial["numero"] = self.object.norma_relacionada.numero
initial['ano'] = self.object.norma_relacionada.ano initial["ano"] = self.object.norma_relacionada.ano
initial['ementa'] = self.object.norma_relacionada.ementa initial["ementa"] = self.object.norma_relacionada.ementa
return initial return initial
class DetailView(MasterDetailCrud.DetailView): class DetailView(MasterDetailCrud.DetailView):
layout_key = "NormaRelacionadaDetail"
layout_key = 'NormaRelacionadaDetail'
class NormaPesquisaView(MultiFormatOutputMixin, FilterView): class NormaPesquisaView(MultiFormatOutputMixin, FilterView):
@ -153,110 +159,116 @@ class NormaPesquisaView(MultiFormatOutputMixin, FilterView):
paginate_by = 50 paginate_by = 50
fields_base_report = [ fields_base_report = [
'id', 'ano', 'numero', 'tipo__sigla', 'tipo__descricao', 'texto_integral', 'ementa' "id",
"ano",
"numero",
"tipo__sigla",
"tipo__descricao",
"texto_integral",
"ementa",
] ]
fields_report = { fields_report = {
'csv': fields_base_report, "csv": fields_base_report,
'xlsx': fields_base_report, "xlsx": fields_base_report,
'json': fields_base_report, "json": fields_base_report,
} }
def hook_texto_integral(self, obj): def hook_texto_integral(self, obj):
url = self.request.build_absolute_uri('/')[:-1] url = self.request.build_absolute_uri("/")[:-1]
texto_integral = obj.texto_integral if not isinstance( texto_integral = (
obj, dict) else obj["texto_integral"] obj.texto_integral if not isinstance(obj, dict) else obj["texto_integral"]
)
return f'{url}/media/{texto_integral}' return f"{url}/media/{texto_integral}"
def get_queryset(self): def get_queryset(self):
qs = super().get_queryset() qs = super().get_queryset()
qs = qs.extra({ qs = qs.extra(
'nm_i': "CAST(regexp_replace(numero,'[^0-9]','', 'g') AS INTEGER)", {
'norma_letra': "regexp_replace(numero,'[^a-zA-Z]','', 'g')" "nm_i": "CAST(regexp_replace(numero,'[^0-9]','', 'g') AS INTEGER)",
}).order_by('-data', '-nm_i', 'norma_letra') "norma_letra": "regexp_replace(numero,'[^a-zA-Z]','', 'g')",
}
).order_by("-data", "-nm_i", "norma_letra")
return qs return qs
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(NormaPesquisaView, self).get_context_data(**kwargs) context = super(NormaPesquisaView, self).get_context_data(**kwargs)
context['title'] = _('Pesquisar Norma Jurídica') context["title"] = _("Pesquisar Norma Jurídica")
self.filterset.form.fields['o'].label = _('Ordenação') self.filterset.form.fields["o"].label = _("Ordenação")
qs = self.object_list qs = self.object_list
if 'o' in self.request.GET and not self.request.GET['o']: if "o" in self.request.GET and not self.request.GET["o"]:
qs = qs.order_by('-ano', 'tipo', '-numero') qs = qs.order_by("-ano", "tipo", "-numero")
qr = self.request.GET.copy() qr = self.request.GET.copy()
if 'page' in qr: if "page" in qr:
del qr['page'] del qr["page"]
paginator = context['paginator'] paginator = context["paginator"]
page_obj = context['page_obj'] page_obj = context["page_obj"]
context['page_range'] = make_pagination( context["page_range"] = make_pagination(page_obj.number, paginator.num_pages)
page_obj.number, paginator.num_pages)
context['filter_url'] = ('&' + qr.urlencode()) if len(qr) > 0 else '' context["filter_url"] = ("&" + qr.urlencode()) if len(qr) > 0 else ""
context['show_results'] = show_results_filter_set(qr) context["show_results"] = show_results_filter_set(qr)
return context return context
class AnexoNormaJuridicaCrud(MasterDetailCrud): class AnexoNormaJuridicaCrud(MasterDetailCrud):
model = AnexoNormaJuridica model = AnexoNormaJuridica
parent_field = 'norma' parent_field = "norma"
help_topic = 'anexonormajuridica' help_topic = "anexonormajuridica"
public = [RP_LIST, RP_DETAIL] public = [RP_LIST, RP_DETAIL]
class BaseMixin(MasterDetailCrud.BaseMixin): class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = ['id', 'anexo_arquivo', 'assunto_anexo'] list_field_names = ["id", "anexo_arquivo", "assunto_anexo"]
class CreateView(MasterDetailCrud.CreateView): class CreateView(MasterDetailCrud.CreateView):
form_class = AnexoNormaJuridicaForm form_class = AnexoNormaJuridicaForm
layout_key = 'AnexoNormaJuridica' layout_key = "AnexoNormaJuridica"
def get_initial(self): def get_initial(self):
initial = super(MasterDetailCrud.CreateView, self).get_initial() initial = super(MasterDetailCrud.CreateView, self).get_initial()
initial['norma'] = NormaJuridica.objects.get(id=self.kwargs['pk']) initial["norma"] = NormaJuridica.objects.get(id=self.kwargs["pk"])
return initial return initial
class UpdateView(MasterDetailCrud.UpdateView): class UpdateView(MasterDetailCrud.UpdateView):
form_class = AnexoNormaJuridicaForm form_class = AnexoNormaJuridicaForm
layout_key = 'AnexoNormaJuridica' layout_key = "AnexoNormaJuridica"
def get_initial(self): def get_initial(self):
initial = super(UpdateView, self).get_initial() initial = super(UpdateView, self).get_initial()
initial['norma'] = self.object.norma initial["norma"] = self.object.norma
initial['anexo_arquivo'] = self.object.anexo_arquivo initial["anexo_arquivo"] = self.object.anexo_arquivo
initial['assunto_anexo'] = self.object.assunto_anexo initial["assunto_anexo"] = self.object.assunto_anexo
initial['ano'] = self.object.ano initial["ano"] = self.object.ano
return initial return initial
class DetailView(MasterDetailCrud.DetailView): class DetailView(MasterDetailCrud.DetailView):
form_class = AnexoNormaJuridicaForm form_class = AnexoNormaJuridicaForm
layout_key = 'AnexoNormaJuridica' layout_key = "AnexoNormaJuridica"
class NormaTaView(IntegracaoTaView): class NormaTaView(IntegracaoTaView):
model = NormaJuridica model = NormaJuridica
model_type_foreignkey = TipoNormaJuridica model_type_foreignkey = TipoNormaJuridica
map_fields = { map_fields = {
'data': 'data', "data": "data",
'ementa': 'ementa', "ementa": "ementa",
'observacao': 'observacao', "observacao": "observacao",
'numero': 'numero', "numero": "numero",
'ano': 'ano', "ano": "ano",
'tipo': 'tipo', "tipo": "tipo",
} }
map_funcs = { map_funcs = {"publicacao_func": True}
'publicacao_func': True
}
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
""" """
@ -264,7 +276,7 @@ class NormaTaView(IntegracaoTaView):
este get foi implementado para tratar uma prerrogativa externa este get foi implementado para tratar uma prerrogativa externa
de usuário. de usuário.
""" """
if AppConfig.attr('texto_articulado_norma'): if AppConfig.attr("texto_articulado_norma"):
return IntegracaoTaView.get(self, request, *args, **kwargs) return IntegracaoTaView.get(self, request, *args, **kwargs)
else: else:
return self.get_redirect_deactivated() return self.get_redirect_deactivated()
@ -272,39 +284,46 @@ class NormaTaView(IntegracaoTaView):
class NormaCrud(Crud): class NormaCrud(Crud):
model = NormaJuridica model = NormaJuridica
help_topic = 'norma_juridica' help_topic = "norma_juridica"
public = [RP_LIST, RP_DETAIL] public = [RP_LIST, RP_DETAIL]
class BaseMixin(Crud.BaseMixin): class BaseMixin(Crud.BaseMixin):
list_field_names = ['epigrafe', 'ementa'] list_field_names = ["epigrafe", "ementa"]
list_url = '' list_url = ""
@property @property
def search_url(self): def search_url(self):
namespace = self.model._meta.app_config.name namespace = self.model._meta.app_config.name
return reverse('%s:%s' % (namespace, 'norma_pesquisa')) return reverse("%s:%s" % (namespace, "norma_pesquisa"))
class DetailView(Crud.DetailView): class DetailView(Crud.DetailView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
estatisticas_acesso_normas = AppConfig.objects.first().estatisticas_acesso_normas estatisticas_acesso_normas = (
if estatisticas_acesso_normas == 'S' and \ AppConfig.objects.first().estatisticas_acesso_normas
NormaJuridica.objects.filter(id=kwargs['pk']).exists(): )
NormaEstatisticas.objects.create(usuario=str(self.request.user), if (
norma_id=kwargs['pk'], estatisticas_acesso_normas == "S"
ano=timezone.now().year, and NormaJuridica.objects.filter(id=kwargs["pk"]).exists()
horario_acesso=timezone.now()) ):
NormaEstatisticas.objects.create(
if 'display' not in request.GET and \ usuario=str(self.request.user),
not request.user.has_perm('norma.change_normajuridica'): norma_id=kwargs["pk"],
ano=timezone.now().year,
horario_acesso=timezone.now(),
)
if "display" not in request.GET and not request.user.has_perm(
"norma.change_normajuridica"
):
ta = self.get_object().texto_articulado.first() ta = self.get_object().texto_articulado.first()
if ta and ta.privacidade == STATUS_TA_PUBLIC: if ta and ta.privacidade == STATUS_TA_PUBLIC:
return redirect(reverse('sapl.norma:norma_ta', return redirect(
kwargs={'pk': self.kwargs['pk']})) reverse("sapl.norma:norma_ta", kwargs={"pk": self.kwargs["pk"]})
)
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
class DeleteView(Crud.DeleteView): class DeleteView(Crud.DeleteView):
def get_success_url(self): def get_success_url(self):
return self.search_url return self.search_url
@ -320,95 +339,102 @@ class NormaCrud(Crud):
def get_initial(self): def get_initial(self):
initial = super().get_initial() initial = super().get_initial()
initial['user'] = self.request.user initial["user"] = self.request.user
initial['ip'] = get_client_ip(self.request) initial["ip"] = get_client_ip(self.request)
from django.utils import timezone from django.utils import timezone
initial['ultima_edicao'] = timezone.now()
initial["ultima_edicao"] = timezone.now()
username = self.request.user.username username = self.request.user.username
try: try:
self.logger.debug( self.logger.debug(
'user=' + username + '. Tentando obter objeto de modelo da esfera da federação.') "user="
esfera = sapl.base.models.AppConfig.objects.last( + username
).esfera_federacao + ". Tentando obter objeto de modelo da esfera da federação."
initial['esfera_federacao'] = esfera )
esfera = sapl.base.models.AppConfig.objects.last().esfera_federacao
initial["esfera_federacao"] = esfera
except: except:
self.logger.error( self.logger.error(
'user=' + username + '. Erro ao obter objeto de modelo da esfera da federação.') "user="
+ username
+ ". Erro ao obter objeto de modelo da esfera da federação."
)
pass pass
initial['complemento'] = False initial["complemento"] = False
return initial return initial
layout_key = 'NormaJuridicaCreate' layout_key = "NormaJuridicaCreate"
class ListView(Crud.ListView): class ListView(Crud.ListView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if AppConfig.attr('texto_articulado_norma'): if AppConfig.attr("texto_articulado_norma"):
self.status = self.request.GET.get('status', '') self.status = self.request.GET.get("status", "")
return Crud.ListView.get(self, request, *args, **kwargs) return Crud.ListView.get(self, request, *args, **kwargs)
else: else:
url = self.get_redirect_url(*args, **kwargs) url = self.get_redirect_url(*args, **kwargs)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
def hook_header_epigrafe(self, *args, **kwargs): def hook_header_epigrafe(self, *args, **kwargs):
return force_str(_('Epigrafe')) return force_str(_("Epigrafe"))
def hook_epigrafe(self, obj, ss, url): def hook_epigrafe(self, obj, ss, url):
return obj.epigrafe, reverse_lazy( return obj.epigrafe, reverse_lazy(
'sapl.norma:norma_ta', "sapl.norma:norma_ta", kwargs={"pk": obj.id}
kwargs={'pk': obj.id}) )
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
namespace = self.model._meta.app_config.name namespace = self.model._meta.app_config.name
return reverse('%s:%s' % (namespace, 'norma_pesquisa')) return reverse("%s:%s" % (namespace, "norma_pesquisa"))
def get_queryset(self): def get_queryset(self):
if self.status == 'pendente': if self.status == "pendente":
qs = NormaJuridica.objects.normas_com_textos_articulados_pendentes() qs = NormaJuridica.objects.normas_com_textos_articulados_pendentes()
elif self.status == 'publico': elif self.status == "publico":
qs = NormaJuridica.objects.normas_com_textos_articulados_publicados() qs = NormaJuridica.objects.normas_com_textos_articulados_publicados()
else: else:
qs = NormaJuridica.objects.normas_sem_textos_articulados() qs = NormaJuridica.objects.normas_sem_textos_articulados()
return qs.order_by('-texto_articulado__privacidade', '-ano', '-numero') return qs.order_by("-texto_articulado__privacidade", "-ano", "-numero")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = Crud.ListView.get_context_data(self, **kwargs) context = Crud.ListView.get_context_data(self, **kwargs)
if self.status == 'pendente': if self.status == "pendente":
context['title'] = 'Normas Jurídicas com Textos Articulados não publicados' context[
elif self.status == 'publico': "title"
context['title'] = 'Normas Jurídicas com Textos Articulados publicados' ] = "Normas Jurídicas com Textos Articulados não publicados"
elif self.status == "publico":
context["title"] = "Normas Jurídicas com Textos Articulados publicados"
else: else:
context['title'] = 'Normas Jurídicas sem Textos Articulados' context["title"] = "Normas Jurídicas sem Textos Articulados"
return context return context
@classmethod @classmethod
def get_url_regex(cls): def get_url_regex(cls):
return r'^check_compilacao$' return r"^check_compilacao$"
class UpdateView(Crud.UpdateView): class UpdateView(Crud.UpdateView):
form_class = NormaJuridicaForm form_class = NormaJuridicaForm
layout_key = 'NormaJuridicaCreate' layout_key = "NormaJuridicaCreate"
def get_initial(self): def get_initial(self):
initial = super().get_initial() initial = super().get_initial()
norma = NormaJuridica.objects.select_related( norma = NormaJuridica.objects.select_related("materia").get(
"materia").get(id=self.kwargs['pk']) id=self.kwargs["pk"]
)
if norma.materia: if norma.materia:
initial['tipo_materia'] = norma.materia.tipo initial["tipo_materia"] = norma.materia.tipo
initial['ano_materia'] = norma.materia.ano initial["ano_materia"] = norma.materia.ano
initial['numero_materia'] = norma.materia.numero initial["numero_materia"] = norma.materia.numero
initial['esfera_federacao'] = norma.esfera_federacao initial["esfera_federacao"] = norma.esfera_federacao
return initial return initial
def form_valid(self, form): def form_valid(self, form):
norma_antiga = NormaJuridica.objects.get(pk=self.kwargs['pk']) norma_antiga = NormaJuridica.objects.get(pk=self.kwargs["pk"])
# Feito desta forma para que sejam materializados os assuntos # Feito desta forma para que sejam materializados os assuntos
# antigos # antigos
@ -418,12 +444,25 @@ class NormaCrud(Crud):
self.object = form.save() self.object = form.save()
dict_objeto_novo = self.object.__dict__ dict_objeto_novo = self.object.__dict__
atributos = ['tipo_id', 'numero', 'ano', 'data', 'esfera_federacao', atributos = [
'complemento', 'materia_id', 'numero', "tipo_id",
'data_publicacao', 'data_vigencia', "numero",
'veiculo_publicacao', 'pagina_inicio_publicacao', "ano",
'pagina_fim_publicacao', 'ementa', 'indexacao', "data",
'observacao', 'texto_integral'] "esfera_federacao",
"complemento",
"materia_id",
"numero",
"data_publicacao",
"data_vigencia",
"veiculo_publicacao",
"pagina_inicio_publicacao",
"pagina_fim_publicacao",
"ementa",
"indexacao",
"observacao",
"texto_integral",
]
for atributo in atributos: for atributo in atributos:
if dict_objeto_antigo[atributo] != dict_objeto_novo[atributo]: if dict_objeto_antigo[atributo] != dict_objeto_novo[atributo]:
@ -431,6 +470,7 @@ class NormaCrud(Crud):
self.object.ip = get_client_ip(self.request) self.object.ip = get_client_ip(self.request)
from django.utils import timezone from django.utils import timezone
self.object.ultima_edicao = timezone.now() self.object.ultima_edicao = timezone.now()
self.object.save() self.object.save()
break break
@ -443,6 +483,7 @@ class NormaCrud(Crud):
self.object.ip = get_client_ip(self.request) self.object.ip = get_client_ip(self.request)
from django.utils import timezone from django.utils import timezone
self.object.ultima_edicao = timezone.now() self.object.ultima_edicao = timezone.now()
self.object.save() self.object.save()
@ -455,61 +496,70 @@ def recuperar_norma(request):
username = request.user.username username = request.user.username
orgao = None orgao = None
if 'orgao' in request.GET and request.GET['orgao']: if "orgao" in request.GET and request.GET["orgao"]:
orgao = Orgao.objects.get(pk=request.GET['orgao']) orgao = Orgao.objects.get(pk=request.GET["orgao"])
tipo = TipoNormaJuridica.objects.get(pk=request.GET['tipo']) tipo = TipoNormaJuridica.objects.get(pk=request.GET["tipo"])
numero = request.GET['numero'] numero = request.GET["numero"]
ano = request.GET['ano'] ano = request.GET["ano"]
try: try:
logger.info('user=' + username + '. Tentando obter NormaJuridica (tipo={}, ano={}, numero={}).' logger.info(
.format(tipo, ano, numero)) "user="
norma = NormaJuridica.objects.get(tipo=tipo, + username
ano=ano, + ". Tentando obter NormaJuridica (tipo={}, ano={}, numero={}).".format(
numero=numero, tipo, ano, numero
orgao=orgao) )
response = JsonResponse({'ementa': norma.ementa, )
'id': norma.id}) norma = NormaJuridica.objects.get(
tipo=tipo, ano=ano, numero=numero, orgao=orgao
)
response = JsonResponse({"ementa": norma.ementa, "id": norma.id})
except ObjectDoesNotExist: except ObjectDoesNotExist:
logger.warning('user=' + username + '. NormaJuridica buscada (tipo={}, ano={}, numero={}) não existe. ' logger.warning(
'Definida com ementa vazia e id 0.'.format(tipo, ano, numero)) "user="
response = JsonResponse({'ementa': '', 'id': 0}) + username
+ ". NormaJuridica buscada (tipo={}, ano={}, numero={}) não existe. "
"Definida com ementa vazia e id 0.".format(tipo, ano, numero)
)
response = JsonResponse({"ementa": "", "id": 0})
return response return response
def recuperar_numero_norma(request): def recuperar_numero_norma(request):
tipo = TipoNormaJuridica.objects.get(pk=request.GET['tipo']) tipo = TipoNormaJuridica.objects.get(pk=request.GET["tipo"])
ano = request.GET.get('ano', '') ano = request.GET.get("ano", "")
orgao = request.GET.get('orgao', '') orgao = request.GET.get("orgao", "")
param = {'tipo': tipo, param = {
'ano': ano if ano else timezone.now().year, "tipo": tipo,
} "ano": ano if ano else timezone.now().year,
}
if orgao: if orgao:
param['orgao'] = Orgao.objects.get(pk=orgao) param["orgao"] = Orgao.objects.get(pk=orgao)
norma = NormaJuridica.objects.filter(**param).order_by( norma = (
'tipo', 'ano', 'numero').values_list('numero', flat=True) NormaJuridica.objects.filter(**param)
.order_by("tipo", "ano", "numero")
.values_list("numero", flat=True)
)
if norma: if norma:
numeros = sorted([int(re.sub(r"[^0-9].*", '', n)) for n in norma]) numeros = sorted([int(re.sub(r"[^0-9].*", "", n)) for n in norma])
next_num = numeros.pop() + 1 next_num = numeros.pop() + 1
response = JsonResponse({'numero': next_num, response = JsonResponse({"numero": next_num, "ano": param["ano"]})
'ano': param['ano']})
else: else:
response = JsonResponse( response = JsonResponse({"numero": 1, "ano": param["ano"]})
{'numero': 1, 'ano': param['ano']})
return response return response
class AutoriaNormaCrud(MasterDetailCrud): class AutoriaNormaCrud(MasterDetailCrud):
model = AutoriaNorma model = AutoriaNorma
parent_field = 'norma' parent_field = "norma"
help_topic = 'despacho_autoria' help_topic = "despacho_autoria"
public = [RP_LIST, RP_DETAIL] public = [RP_LIST, RP_DETAIL]
list_field_names = ['autor', 'autor__tipo__descricao', 'primeiro_autor'] list_field_names = ["autor", "autor__tipo__descricao", "primeiro_autor"]
class LocalBaseMixin: class LocalBaseMixin:
form_class = AutoriaNormaForm form_class = AutoriaNormaForm
@ -519,69 +569,71 @@ class AutoriaNormaCrud(MasterDetailCrud):
return None return None
class CreateView(LocalBaseMixin, MasterDetailCrud.CreateView): class CreateView(LocalBaseMixin, MasterDetailCrud.CreateView):
def get_initial(self): def get_initial(self):
initial = super().get_initial() initial = super().get_initial()
norma = NormaJuridica.objects.get(id=self.kwargs['pk']) norma = NormaJuridica.objects.get(id=self.kwargs["pk"])
initial['data_relativa'] = norma.data initial["data_relativa"] = norma.data
initial['autor'] = [] initial["autor"] = []
return initial return initial
class UpdateView(LocalBaseMixin, MasterDetailCrud.UpdateView): class UpdateView(LocalBaseMixin, MasterDetailCrud.UpdateView):
def get_initial(self): def get_initial(self):
initial = super().get_initial() initial = super().get_initial()
initial.update({ initial.update(
'data_relativa': self.object.norma.data, {
'tipo_autor': self.object.autor.tipo.id, "data_relativa": self.object.norma.data,
}) "tipo_autor": self.object.autor.tipo.id,
}
)
return initial return initial
class ImpressosView(PermissionRequiredMixin, TemplateView): class ImpressosView(PermissionRequiredMixin, TemplateView):
template_name = 'materia/impressos/impressos.html' template_name = "materia/impressos/impressos.html"
permission_required = ('materia.can_access_impressos', ) permission_required = ("materia.can_access_impressos",)
def gerar_pdf_impressos(request, context, template_name): def gerar_pdf_impressos(request, context, template_name):
template = loader.get_template(template_name) template = loader.get_template(template_name)
html = template.render(context, request) html = template.render(context, request)
pdf = weasyprint.HTML( pdf = weasyprint.HTML(
string=html, base_url=request.build_absolute_uri()).write_pdf() string=html, base_url=request.build_absolute_uri()
).write_pdf()
response = HttpResponse(pdf, content_type='application/pdf') response = HttpResponse(pdf, content_type="application/pdf")
response['Content-Disposition'] = 'inline; filename="relatorio_impressos.pdf"' response["Content-Disposition"] = 'inline; filename="relatorio_impressos.pdf"'
response['Content-Transfer-Encoding'] = 'binary' response["Content-Transfer-Encoding"] = "binary"
return response return response
class NormaPesquisaSimplesView(PermissionRequiredMixin, FormView): class NormaPesquisaSimplesView(PermissionRequiredMixin, FormView):
form_class = NormaPesquisaSimplesForm form_class = NormaPesquisaSimplesForm
template_name = 'materia/impressos/impressos_form.html' template_name = "materia/impressos/impressos_form.html"
permission_required = ('materia.can_access_impressos', ) permission_required = ("materia.can_access_impressos",)
def form_valid(self, form): def form_valid(self, form):
template_norma = 'materia/impressos/normas_pdf.html' template_norma = "materia/impressos/normas_pdf.html"
titulo = form.cleaned_data['titulo'] titulo = form.cleaned_data["titulo"]
kwargs = {} kwargs = {}
if form.cleaned_data.get('tipo_norma'): if form.cleaned_data.get("tipo_norma"):
kwargs.update({'tipo': form.cleaned_data['tipo_norma']}) kwargs.update({"tipo": form.cleaned_data["tipo_norma"]})
if form.cleaned_data.get('data_inicial'): if form.cleaned_data.get("data_inicial"):
kwargs.update({'data__gte': form.cleaned_data['data_inicial'], kwargs.update(
'data__lte': form.cleaned_data['data_final']}) {
"data__gte": form.cleaned_data["data_inicial"],
"data__lte": form.cleaned_data["data_final"],
}
)
normas = NormaJuridica.objects.filter( normas = NormaJuridica.objects.filter(**kwargs).order_by("-numero", "ano")
**kwargs).order_by('-numero', 'ano')
quantidade_normas = normas.count() quantidade_normas = normas.count()
normas = normas[:2000] if quantidade_normas > 2000 else normas normas = normas[:2000] if quantidade_normas > 2000 else normas
context = {'quantidade': quantidade_normas, context = {"quantidade": quantidade_normas, "titulo": titulo, "normas": normas}
'titulo': titulo,
'normas': normas}
return gerar_pdf_impressos(self.request, context, template_norma) return gerar_pdf_impressos(self.request, context, template_norma)

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

Loading…
Cancel
Save