Browse Source

Reformata código com isort e black

pull/3789/head
Edward Ribeiro 3 months ago
parent
commit
6eb348aa6a
  1. 9
      conftest.py
  2. 31
      docker/startup_scripts/create_admin.py
  3. 13
      docker/startup_scripts/genkey.py
  4. 3
      docker/startup_scripts/gunicorn.conf.py
  5. 176
      docker/startup_scripts/solr_cli.py
  6. 205
      drfautoapi/drfautoapi.py
  7. 2
      sapl/api/__init__.py
  8. 6
      sapl/api/apps.py
  9. 8
      sapl/api/deprecated.py
  10. 136
      sapl/api/forms.py
  11. 127
      sapl/api/pagination.py
  12. 51
      sapl/api/permissions.py
  13. 221
      sapl/api/serializers.py
  14. 46
      sapl/api/urls.py
  15. 40
      sapl/api/views.py
  16. 12
      sapl/api/views_audiencia.py
  17. 70
      sapl/api/views_base.py
  18. 22
      sapl/api/views_comissoes.py
  19. 15
      sapl/api/views_compilacao.py
  20. 62
      sapl/api/views_materia.py
  21. 15
      sapl/api/views_norma.py
  22. 14
      sapl/api/views_painel.py
  23. 68
      sapl/api/views_parlamentares.py
  24. 45
      sapl/api/views_protocoloadm.py
  25. 25
      sapl/api/views_sessao.py
  26. 2
      sapl/audiencia/__init__.py
  27. 6
      sapl/audiencia/apps.py
  28. 235
      sapl/audiencia/forms.py
  29. 199
      sapl/audiencia/models.py
  30. 86
      sapl/audiencia/tests/test_audiencia.py
  31. 10
      sapl/audiencia/urls.py
  32. 75
      sapl/audiencia/views.py
  33. 2
      sapl/base/__init__.py
  34. 4
      sapl/base/admin.py
  35. 6
      sapl/base/apps.py
  36. 257
      sapl/base/email_utils.py
  37. 1267
      sapl/base/forms.py
  38. 10
      sapl/base/management/commands/backfill_auditlog.py
  39. 505
      sapl/base/models.py
  40. 274
      sapl/base/receivers.py
  41. 142
      sapl/base/search_indexes.py
  42. 5
      sapl/base/templatetags/base_tags.py
  43. 118
      sapl/base/templatetags/common_tags.py
  44. 128
      sapl/base/templatetags/menus.py
  45. 28
      sapl/base/tests/test_base.py
  46. 43
      sapl/base/tests/test_form.py
  47. 60
      sapl/base/tests/test_login.py
  48. 1133
      sapl/base/tests/test_view_base.py
  49. 4
      sapl/base/tests/teststub_urls.py
  50. 317
      sapl/base/urls.py
  51. 1272
      sapl/base/views.py
  52. 2
      sapl/comissoes/__init__.py
  53. 6
      sapl/comissoes/apps.py
  54. 463
      sapl/comissoes/forms.py
  55. 409
      sapl/comissoes/models.py
  56. 164
      sapl/comissoes/tests/test_comissoes.py
  57. 69
      sapl/comissoes/urls.py
  58. 360
      sapl/comissoes/views.py
  59. 2
      sapl/compilacao/__init__.py
  60. 8
      sapl/compilacao/admin.py
  61. 55
      sapl/compilacao/apps.py
  62. 1845
      sapl/compilacao/forms.py
  63. 1481
      sapl/compilacao/models.py
  64. 179
      sapl/compilacao/templatetags/compilacao_filters.py
  65. 78
      sapl/compilacao/tests/test_compilacao.py
  66. 43
      sapl/compilacao/tests/test_tipo_texto_articulado_form.py
  67. 239
      sapl/compilacao/urls.py
  68. 57
      sapl/compilacao/utils.py
  69. 3213
      sapl/compilacao/views.py
  70. 39
      sapl/context_processors.py
  71. 283
      sapl/crispy_layout_mixin.py
  72. 1113
      sapl/crud/base.py
  73. 74
      sapl/crud/tests/settings.py
  74. 6
      sapl/crud/tests/stub_app/models.py
  75. 5
      sapl/crud/tests/stub_app/urls.py
  76. 4
      sapl/crud/tests/stub_app/views.py
  77. 2
      sapl/crud/tests/test_base.py
  78. 17
      sapl/crud/tests/test_masterdetail.py
  79. 2
      sapl/crud/urls.py
  80. 23
      sapl/decorators.py
  81. 33
      sapl/endpoint_restriction_middleware.py
  82. 6
      sapl/hashers.py
  83. 358
      sapl/lexml/OAIServer.py
  84. 2
      sapl/lexml/__init__.py
  85. 6
      sapl/lexml/apps.py
  86. 24
      sapl/lexml/forms.py
  87. 60
      sapl/lexml/models.py
  88. 22
      sapl/lexml/urls.py
  89. 21
      sapl/lexml/views.py
  90. 2
      sapl/materia/__init__.py
  91. 6
      sapl/materia/admin.py
  92. 6
      sapl/materia/apps.py
  93. 3105
      sapl/materia/forms.py
  94. 1251
      sapl/materia/models.py
  95. 89
      sapl/materia/tests/test_email_templates.py
  96. 989
      sapl/materia/tests/test_materia.py
  97. 155
      sapl/materia/tests/test_materia_form.py
  98. 23
      sapl/materia/tests/test_materia_urls.py
  99. 401
      sapl/materia/urls.py
  100. 2749
      sapl/materia/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())

3
docker/startup_scripts/gunicorn.conf.py

@ -41,7 +41,7 @@ accesslog = "/var/log/sapl/access.log"
errorlog = "/var/log/sapl/error.log" errorlog = "/var/log/sapl/error.log"
# 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
# Worker/process lifecycle # Worker/process lifecycle
workers = NUM_WORKERS workers = NUM_WORKERS
@ -74,6 +74,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:

205
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):
@ -212,13 +210,11 @@ class ApiViewSetConstrutor():
router = router_class() router = router_class()
for app, built_sets in cls._built_sets.items(): for app, built_sets in cls._built_sets.items():
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}', viewset)
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 = {}
@ -229,35 +225,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:
@ -268,45 +267,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):
@ -317,11 +321,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:
@ -351,17 +354,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
@ -369,41 +371,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

2
sapl/api/__init__.py

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

6
sapl/api/apps.py

@ -3,9 +3,9 @@ from django.utils.translation import ugettext_lazy as _
class AppConfig(apps.AppConfig): 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)

46
sapl/api/urls.py

@ -1,16 +1,16 @@
from django.conf.urls import include, url from django.conf.urls import include, url
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, \ from drf_spectacular.views import (
SpectacularRedocView SpectacularAPIView,
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, recria_token
SaplApiViewSetConstrutor
from .apps import AppConfig from .apps import AppConfig
app_name = AppConfig.name app_name = AppConfig.name
router = SaplApiViewSetConstrutor.router() router = SaplApiViewSetConstrutor.router()
@ -19,26 +19,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 = [
url('^schema/swagger-ui/', url(
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"),
url('^schema/redoc/', name="swagger_ui_schema_api",
SpectacularRedocView.as_view(url_name='sapl.api:schema_api'), ),
name='redoc_schema_api'), url(
url('^schema/', SpectacularAPIView.as_view(), name='schema_api'), "^schema/redoc/",
SpectacularRedocView.as_view(url_name="sapl.api:schema_api"),
name="redoc_schema_api",
),
url("^schema/", SpectacularAPIView.as_view(), name="schema_api"),
] ]
urlpatterns = [ urlpatterns = [
url(r'^api/', include(urlpatterns_api_doc)), url(r"^api/", include(urlpatterns_api_doc)),
url(r'^api/', include(urlpatterns_router)), url(r"^api/", include(urlpatterns_router)),
url(r"^api/version", AppVersionView.as_view()),
url(r'^api/version', AppVersionView.as_view()), url(r"^api/auth/token$", obtain_auth_token),
url(r'^api/auth/token$', obtain_auth_token), url(r"^api/recriar-token/(?P<pk>\d*)$", recria_token, name="recria_token"),
url(r'^api/recriar-token/(?P<pk>\d*)$', recria_token, name="recria_token"),
] ]

40
sapl/api/views.py

@ -3,7 +3,7 @@ import logging
from django.conf import settings from 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",
]
)
""" """

12
sapl/api/views_audiencia.py

@ -1,11 +1,11 @@
from django.apps.registry import apps from django.apps.registry import apps
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \ from drfautoapi.drfautoapi import (
customize, wrapper_queryset_response_for_drf_action ApiViewSetConstrutor,
customize,
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:

22
sapl/api/views_comissoes.py

@ -1,23 +1,19 @@
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 +21,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"],
) )

15
sapl/api/views_compilacao.py

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

62
sapl/api/views_materia.py

@ -1,22 +1,23 @@
from django.apps.registry import apps from django.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 (
customize, wrapper_queryset_response_for_drf_action ApiViewSetConstrutor,
customize,
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,
ApiViewSetConstrutor.build_class( Tramitacao,
[
apps.get_app_config('materia')
]
) )
ApiViewSetConstrutor.build_class([apps.get_app_config("materia")])
@customize(Proposicao) @customize(Proposicao)
class _ProposicaoViewSet: class _ProposicaoViewSet:
@ -46,9 +47,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 +68,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 +75,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 +84,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 +111,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)

15
sapl/api/views_norma.py

@ -1,14 +1,11 @@
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 (
customize, wrapper_queryset_response_for_drf_action ApiViewSetConstrutor,
customize,
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')
]
)

14
sapl/api/views_painel.py

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

68
sapl/api/views_parlamentares.py

@ -1,34 +1,30 @@
from django.apps.registry import apps from django.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 (
wrapper_queryset_response_for_drf_action ApiViewSetConstrutor,
customize,
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 +33,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 +54,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 +89,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

45
sapl/api/views_protocoloadm.py

@ -1,29 +1,28 @@
from django.apps.registry import apps from django.apps.registry import apps
from drfautoapi.drfautoapi import ApiViewSetConstrutor, \ from drfautoapi.drfautoapi import (
customize, wrapper_queryset_response_for_drf_action ApiViewSetConstrutor,
customize,
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 (
DocumentoAcessorioAdministrativo, TramitacaoAdministrativo, Anexado Anexado,
DocumentoAcessorioAdministrativo,
DocumentoAdministrativo,
ApiViewSetConstrutor.build_class( TramitacaoAdministrativo,
[
apps.get_app_config('protocoloadm')
]
) )
ApiViewSetConstrutor.build_class([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 +53,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 +71,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 +89,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()

25
sapl/api/views_sessao.py

@ -1,26 +1,21 @@
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 (
customize, wrapper_queryset_response_for_drf_action ApiViewSetConstrutor,
from sapl.api.serializers import ChoiceSerializer,\ customize,
SessaoPlenariaECidadaniaSerializer wrapper_queryset_response_for_drf_action,
from sapl.sessao.models import SessaoPlenaria, ExpedienteSessao )
from sapl.api.serializers import ChoiceSerializer, SessaoPlenariaECidadaniaSerializer
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 +29,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)

2
sapl/audiencia/__init__.py

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

6
sapl/audiencia/apps.py

@ -3,6 +3,6 @@ from django.utils.translation import ugettext_lazy as _
class AppConfig(apps.AppConfig): 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")

235
sapl/audiencia/forms.py

@ -1,19 +1,26 @@
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 ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from crispy_forms.layout import Button, Column, Fieldset, HTML, Layout from sapl.audiencia.models import (
AnexoAudienciaPublica,
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica, AnexoAudienciaPublica AudienciaPublica,
from sapl.crispy_layout_mixin import form_actions, SaplFormHelper, SaplFormLayout, to_row TipoAudienciaPublica,
)
from sapl.crispy_layout_mixin import (
SaplFormHelper,
SaplFormLayout,
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 +29,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 +115,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 +248,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 +269,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")

199
sapl/audiencia/models.py

@ -2,130 +2,152 @@ from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from model_utils import Choices from model_utils import Choices
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 +170,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 +229,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 +240,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,
)

86
sapl/audiencia/tests/test_audiencia.py

@ -1,57 +1,62 @@
import pytest
import datetime import datetime
from model_bakery import baker
import pytest
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext 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 (
from sapl.audiencia.models import TipoAudienciaPublica, AudienciaPublica AnexoAudienciaPublica,
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 +70,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 +85,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()

10
sapl/audiencia/urls.py

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

2
sapl/base/__init__.py

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

4
sapl/base/admin.py

@ -7,8 +7,8 @@ from sapl.utils import register_all_models_in_admin
register_all_models_in_admin(__name__) 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):

6
sapl/base/apps.py

@ -3,9 +3,9 @@ from django.utils.translation import ugettext_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 ugettext_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 ugettext_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()

1267
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")

505
sapl/base/models.py

@ -9,40 +9,49 @@ from django.db.models.signals import post_migrate
from django.db.utils import DEFAULT_DB_ALIAS from django.db.utils import DEFAULT_DB_ALIAS
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from sapl.utils import (LISTA_DE_UFS, YES_NO_CHOICES, from sapl.utils import (
get_settings_auth_user_model, models_with_gr_for_model) LISTA_DE_UFS,
YES_NO_CHOICES,
get_settings_auth_user_model,
models_with_gr_for_model,
)
DOC_ADM_OSTENSIVO = 'O' DOC_ADM_OSTENSIVO = "O"
DOC_ADM_RESTRITIVO = 'R' DOC_ADM_RESTRITIVO = "R"
TIPO_DOCUMENTO_ADMINISTRATIVO = ((DOC_ADM_OSTENSIVO, _('Ostensiva')), TIPO_DOCUMENTO_ADMINISTRATIVO = (
(DOC_ADM_RESTRITIVO, _('Restritiva'))) (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 +59,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 +104,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 +120,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 +161,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 +290,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 +342,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 +377,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 +407,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}"

274
sapl/base/receivers.py

@ -1,37 +1,41 @@
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_migrate, pre_save, pre_migrate post_delete,
post_migrate,
post_save,
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 ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from PyPDF4.pdf import PdfFileReader
from sapl.base.email_utils import do_envia_email_tramitacao from sapl.base.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 +47,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 +58,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 +73,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 +99,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 +118,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 +131,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 +150,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 +215,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 +299,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 +322,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 +380,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 +434,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 +461,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")

142
sapl/base/search_indexes.py

@ -11,8 +11,11 @@ from haystack.fields import CharField
from haystack.indexes import SearchIndex from haystack.indexes import SearchIndex
from haystack.utils import get_model_ct_tuple from haystack.utils import get_model_ct_tuple
from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC, from sapl.compilacao.models import (
STATUS_TA_PUBLIC, Dispositivo) STATUS_TA_IMMUTABLE_PUBLIC,
STATUS_TA_PUBLIC,
Dispositivo,
)
from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa
from sapl.norma.models import NormaJuridica from sapl.norma.models import NormaJuridica
from sapl.sessao.models import SessaoPlenaria from sapl.sessao.models import SessaoPlenaria
@ -21,7 +24,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 +32,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 +74,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 +110,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 +150,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"
)

118
sapl/base/templatetags/common_tags.py

@ -11,18 +11,18 @@ from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa, Proposic
from sapl.norma.models import NormaJuridica from sapl.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 +66,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 +104,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 +127,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 +183,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 +200,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 +215,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 +230,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 +239,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 +257,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 +285,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 +315,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 +356,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 +382,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 +391,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 +399,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 +426,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 ugettext_lazy as _ from django.utils.translation import ugettext_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

1133
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 = [url(r'^zzzz$', ptrn = [url(r"^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

317
sapl/base/urls.py

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

1272
sapl/base/views.py

File diff suppressed because it is too large

2
sapl/comissoes/__init__.py

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

6
sapl/comissoes/apps.py

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

463
sapl/comissoes/forms.py

@ -1,34 +1,35 @@
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 ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_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 (
DocumentoAcessorio, Participacao, Comissao,
Periodo, Reuniao) Composicao,
from sapl.crispy_layout_mixin import (form_actions, SaplFormHelper, DocumentoAcessorio,
to_row) Participacao,
Periodo,
Reuniao,
)
from sapl.crispy_layout_mixin import SaplFormHelper, form_actions, 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, validar_arquivo
from sapl.utils import (FileFieldCheckMixin,
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 +38,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 +104,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 +163,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 +189,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 +256,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 +271,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 +304,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 +339,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 +368,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 +467,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 +478,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 +493,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 +522,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 +585,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 +600,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 +615,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")

409
sapl/comissoes/models.py

@ -4,261 +4,281 @@ 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 (
restringe_tipos_de_arquivo_txt, texto_upload_path, YES_NO_CHOICES,
OverwriteStorage) OverwriteStorage,
SaplGenericRelation,
restringe_tipos_de_arquivo_txt,
texto_upload_path,
)
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 +301,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 +373,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,
)

164
sapl/comissoes/tests/test_comissoes.py

@ -3,61 +3,54 @@ from django.urls import reverse
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext 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 +58,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 +134,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 +147,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

69
sapl/comissoes/urls.py

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

360
sapl/comissoes/views.py

@ -2,59 +2,82 @@ 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 ugettext_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 ugettext_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
from sapl.comissoes.apps import AppConfig from sapl.comissoes.apps import AppConfig
from sapl.comissoes.forms import (ComissaoForm, ComposicaoForm, from sapl.comissoes.forms import (
DocumentoAcessorioCreateForm, ComissaoForm,
DocumentoAcessorioEditForm, ComposicaoForm,
ParticipacaoCreateForm, DocumentoAcessorioCreateForm,
ParticipacaoEditForm, DocumentoAcessorioEditForm,
PautaReuniaoFilterSet, PautaReuniaoForm, ParticipacaoCreateForm,
PeriodoForm, ReuniaoForm) ParticipacaoEditForm,
from sapl.crud.base import (Crud, CrudAux, MasterDetailCrud, PautaReuniaoFilterSet,
PermissionRequiredForAppCrudMixin, RP_DETAIL, PautaReuniaoForm,
RP_LIST) PeriodoForm,
from sapl.materia.models import (MateriaEmTramitacao, MateriaLegislativa, ReuniaoForm,
PautaReuniao, Tramitacao) )
from sapl.crud.base import (
RP_DETAIL,
RP_LIST,
Crud,
CrudAux,
MasterDetailCrud,
PermissionRequiredForAppCrudMixin,
)
from sapl.materia.models import (
MateriaEmTramitacao,
MateriaLegislativa,
PautaReuniao,
Tramitacao,
)
from sapl.utils import show_results_filter_set from sapl.utils import show_results_filter_set
from .models import (CargoComissao, Comissao, Composicao, DocumentoAcessorio, from .models import (
Participacao, Periodo, Reuniao, TipoComissao) CargoComissao,
Comissao,
Composicao,
DocumentoAcessorio,
Participacao,
Periodo,
Reuniao,
TipoComissao,
)
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 +94,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 +150,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 +173,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 +191,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 +234,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 +244,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 +271,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 +297,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 +318,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)

2
sapl/compilacao/__init__.py

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

8
sapl/compilacao/admin.py

@ -1,4 +1,5 @@
from django.contrib import admin from 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 ugettext_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)

1845
sapl/compilacao/forms.py

File diff suppressed because it is too large

1481
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

78
sapl/compilacao/tests/test_compilacao.py

@ -1,104 +1,90 @@
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 (
from sapl.compilacao.models import TipoTextoArticulado PerfilEstruturalTextoArticulado,
from sapl.compilacao.models import TextoArticulado, TipoNota TextoArticulado,
from sapl.compilacao.models import TipoVide, TipoDispositivo TipoDispositivo,
from sapl.compilacao.models import TipoDispositivoRelationship TipoDispositivoRelationship,
TipoNota,
TipoTextoArticulado,
TipoVide,
)
@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()

239
sapl/compilacao/urls.py

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

57
sapl/compilacao/utils.py

@ -1,29 +1,31 @@
import sys 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

3213
sapl/compilacao/views.py

File diff suppressed because it is too large

39
sapl/context_processors.py

@ -2,13 +2,15 @@ import logging
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_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 +21,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),
} }

283
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_text from django.utils.encoding import force_text
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext 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_text(label) != 'Pesquisar': css_class="float-right",
doubleclick = 'this.form.submit();this.disabled=true;' disabled=True,
):
if disabled and force_text(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"
) )
@ -84,16 +91,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:
@ -102,26 +115,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)
@ -129,89 +141,94 @@ 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):
try: try:
@ -230,24 +247,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]
@ -255,65 +272,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)
@ -324,44 +341,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):
@ -381,7 +404,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]]
@ -392,5 +415,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()
]

1113
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) continent = models.ForeignKey(Continent)
is_cold = models.BooleanField(choices=[(True, 'Yes'), (False, 'No')]) is_cold = models.BooleanField(choices=[(True, "Yes"), (False, "No")])
population = models.PositiveIntegerField(blank=True, null=True) 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

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

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

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

@ -6,7 +6,7 @@ from .models import City, Country
class CountryCrud(Crud): 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",)

2
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

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.conf.urls import include, url from django.conf.urls import include, url
urlpatterns = [ urlpatterns = [
url(r'', include('stub_app.urls')), url(r"", include("stub_app.urls")),
] ]

23
sapl/decorators.py

@ -18,26 +18,21 @@ def vigencia_atual(decorated_method):
def display_atual(self): 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'))

2
sapl/lexml/__init__.py

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

6
sapl/lexml/apps.py

@ -3,6 +3,6 @@ from django.utils.translation import ugettext_lazy as _
class AppConfig(apps.AppConfig): 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 ugettext_lazy as _ from django.utils.translation import ugettext_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 ugettext_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

22
sapl/lexml/urls.py

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

21
sapl/lexml/views.py

@ -1,20 +1,19 @@
from django.http import HttpResponse from django.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):

2
sapl/materia/__init__.py

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

6
sapl/materia/admin.py

@ -4,8 +4,7 @@ from django.contrib import admin
from sapl.base.models import TipoAutor from sapl.base.models import TipoAutor
from sapl.comissoes.models import TipoComissao from sapl.comissoes.models import TipoComissao
from sapl.materia.models import Proposicao from sapl.materia.models import Proposicao
from sapl.parlamentares.models import (SituacaoMilitar, TipoAfastamento, from sapl.parlamentares.models import SituacaoMilitar, TipoAfastamento, TipoDependente
TipoDependente)
from sapl.utils import register_all_models_in_admin from sapl.utils import register_all_models_in_admin
register_all_models_in_admin(__name__) register_all_models_in_admin(__name__)
@ -13,7 +12,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 +27,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 ugettext_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")

3105
sapl/materia/forms.py

File diff suppressed because it is too large

1251
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

989
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

401
sapl/materia/urls.py

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

2749
sapl/materia/views.py

File diff suppressed because it is too large

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

Loading…
Cancel
Save