Browse Source

Formata código com isort e black

upgrade-sapl
Edward Ribeiro 2 months ago
parent
commit
989f640243
  1. 9
      conftest.py
  2. 31
      docker/startup_scripts/create_admin.py
  3. 13
      docker/startup_scripts/genkey.py
  4. 1
      docker/startup_scripts/gunicorn.conf.py
  5. 152
      docker/startup_scripts/solr_cli.py
  6. 208
      drfautoapi/drfautoapi.py
  7. 6
      sapl/api/apps.py
  8. 6
      sapl/api/deprecated.py
  9. 132
      sapl/api/forms.py
  10. 131
      sapl/api/pagination.py
  11. 51
      sapl/api/permissions.py
  12. 215
      sapl/api/serializers.py
  13. 44
      sapl/api/urls.py
  14. 40
      sapl/api/views.py
  15. 9
      sapl/api/views_audiencia.py
  16. 70
      sapl/api/views_base.py
  17. 17
      sapl/api/views_comissoes.py
  18. 12
      sapl/api/views_compilacao.py
  19. 55
      sapl/api/views_materia.py
  20. 12
      sapl/api/views_norma.py
  21. 11
      sapl/api/views_painel.py
  22. 63
      sapl/api/views_parlamentares.py
  23. 40
      sapl/api/views_protocoloadm.py
  24. 23
      sapl/api/views_sessao.py
  25. 6
      sapl/audiencia/apps.py
  26. 226
      sapl/audiencia/forms.py
  27. 180
      sapl/audiencia/models.py
  28. 79
      sapl/audiencia/tests/test_audiencia.py
  29. 9
      sapl/audiencia/urls.py
  30. 71
      sapl/audiencia/views.py
  31. 4
      sapl/base/admin.py
  32. 7
      sapl/base/apps.py
  33. 177
      sapl/base/email_utils.py
  34. 1222
      sapl/base/forms.py
  35. 10
      sapl/base/management/commands/backfill_auditlog.py
  36. 489
      sapl/base/models.py
  37. 257
      sapl/base/receivers.py
  38. 131
      sapl/base/search_indexes.py
  39. 5
      sapl/base/templatetags/base_tags.py
  40. 118
      sapl/base/templatetags/common_tags.py
  41. 128
      sapl/base/templatetags/menus.py
  42. 28
      sapl/base/tests/test_base.py
  43. 43
      sapl/base/tests/test_form.py
  44. 56
      sapl/base/tests/test_login.py
  45. 376
      sapl/base/tests/test_view_base.py
  46. 4
      sapl/base/tests/teststub_urls.py
  47. 270
      sapl/base/urls.py
  48. 1208
      sapl/base/views.py
  49. 6
      sapl/comissoes/apps.py
  50. 450
      sapl/comissoes/forms.py
  51. 386
      sapl/comissoes/models.py
  52. 161
      sapl/comissoes/tests/test_comissoes.py
  53. 60
      sapl/comissoes/urls.py
  54. 297
      sapl/comissoes/views.py
  55. 8
      sapl/compilacao/admin.py
  56. 55
      sapl/compilacao/apps.py
  57. 1741
      sapl/compilacao/forms.py
  58. 1450
      sapl/compilacao/models.py
  59. 179
      sapl/compilacao/templatetags/compilacao_filters.py
  60. 73
      sapl/compilacao/tests/test_compilacao.py
  61. 43
      sapl/compilacao/tests/test_tipo_texto_articulado_form.py
  62. 218
      sapl/compilacao/urls.py
  63. 57
      sapl/compilacao/utils.py
  64. 3038
      sapl/compilacao/views.py
  65. 40
      sapl/context_processors.py
  66. 279
      sapl/crispy_layout_mixin.py
  67. 1076
      sapl/crud/base.py
  68. 68
      sapl/crud/tests/settings.py
  69. 6
      sapl/crud/tests/stub_app/models.py
  70. 3
      sapl/crud/tests/stub_app/urls.py
  71. 4
      sapl/crud/tests/stub_app/views.py
  72. 282
      sapl/crud/tests/test_base.py
  73. 17
      sapl/crud/tests/test_masterdetail.py
  74. 2
      sapl/crud/urls.py
  75. 23
      sapl/decorators.py
  76. 33
      sapl/endpoint_restriction_middleware.py
  77. 6
      sapl/hashers.py
  78. 334
      sapl/lexml/OAIServer.py
  79. 6
      sapl/lexml/apps.py
  80. 24
      sapl/lexml/forms.py
  81. 60
      sapl/lexml/models.py
  82. 18
      sapl/lexml/urls.py
  83. 21
      sapl/lexml/views.py
  84. 3
      sapl/materia/admin.py
  85. 6
      sapl/materia/apps.py
  86. 2921
      sapl/materia/forms.py
  87. 1167
      sapl/materia/models.py
  88. 69
      sapl/materia/tests/test_email_templates.py
  89. 925
      sapl/materia/tests/test_materia.py
  90. 153
      sapl/materia/tests/test_materia_form.py
  91. 23
      sapl/materia/tests/test_materia_urls.py
  92. 338
      sapl/materia/urls.py
  93. 2629
      sapl/materia/views.py
  94. 12
      sapl/middleware.py
  95. 6
      sapl/norma/apps.py
  96. 589
      sapl/norma/forms.py
  97. 506
      sapl/norma/models.py
  98. 169
      sapl/norma/tests/test_norma.py
  99. 54
      sapl/norma/urls.py
  100. 476
      sapl/norma/views.py

9
conftest.py

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

31
docker/startup_scripts/create_admin.py

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

13
docker/startup_scripts/genkey.py

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

1
docker/startup_scripts/gunicorn.conf.py

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

152
docker/startup_scripts/solr_cli.py

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

208
drfautoapi/drfautoapi.py

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

6
sapl/api/apps.py

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

6
sapl/api/deprecated.py

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

132
sapl/api/forms.py

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

131
sapl/api/pagination.py

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

51
sapl/api/permissions.py

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

215
sapl/api/serializers.py

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

44
sapl/api/urls.py

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

40
sapl/api/views.py

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

9
sapl/api/views_audiencia.py

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

70
sapl/api/views_base.py

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

17
sapl/api/views_comissoes.py

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

12
sapl/api/views_compilacao.py

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

55
sapl/api/views_materia.py

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

12
sapl/api/views_norma.py

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

11
sapl/api/views_painel.py

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

63
sapl/api/views_parlamentares.py

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

40
sapl/api/views_protocoloadm.py

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

23
sapl/api/views_sessao.py

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

6
sapl/audiencia/apps.py

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

226
sapl/audiencia/forms.py

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

180
sapl/audiencia/models.py

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

79
sapl/audiencia/tests/test_audiencia.py

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

9
sapl/audiencia/urls.py

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

71
sapl/audiencia/views.py

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

4
sapl/base/admin.py

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

7
sapl/base/apps.py

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

177
sapl/base/email_utils.py

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

1222
sapl/base/forms.py

File diff suppressed because it is too large

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

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

489
sapl/base/models.py

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

257
sapl/base/receivers.py

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

131
sapl/base/search_indexes.py

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

5
sapl/base/templatetags/base_tags.py

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

118
sapl/base/templatetags/common_tags.py

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

128
sapl/base/templatetags/menus.py

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

28
sapl/base/tests/test_base.py

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

43
sapl/base/tests/test_form.py

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

56
sapl/base/tests/test_login.py

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

376
sapl/base/tests/test_view_base.py

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

4
sapl/base/tests/teststub_urls.py

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

270
sapl/base/urls.py

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

1208
sapl/base/views.py

File diff suppressed because it is too large

6
sapl/comissoes/apps.py

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

450
sapl/comissoes/forms.py

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

386
sapl/comissoes/models.py

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

161
sapl/comissoes/tests/test_comissoes.py

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

60
sapl/comissoes/urls.py

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

297
sapl/comissoes/views.py

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

8
sapl/compilacao/admin.py

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

55
sapl/compilacao/apps.py

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

1741
sapl/compilacao/forms.py

File diff suppressed because it is too large

1450
sapl/compilacao/models.py

File diff suppressed because it is too large

179
sapl/compilacao/templatetags/compilacao_filters.py

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

73
sapl/compilacao/tests/test_compilacao.py

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

43
sapl/compilacao/tests/test_tipo_texto_articulado_form.py

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

218
sapl/compilacao/urls.py

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

57
sapl/compilacao/utils.py

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

3038
sapl/compilacao/views.py

File diff suppressed because it is too large

40
sapl/context_processors.py

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

279
sapl/crispy_layout_mixin.py

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

1076
sapl/crud/base.py

File diff suppressed because it is too large

68
sapl/crud/tests/settings.py

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

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

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

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

@ -3,6 +3,5 @@ from django.urls import include, path
from .views import CityCrud, CountryCrud
urlpatterns = [
path('country/', include(
CountryCrud.get_urls() + CityCrud.get_urls(), 'stub_app')),
path("country/", include(CountryCrud.get_urls() + CityCrud.get_urls(), "stub_app")),
]

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

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

282
sapl/crud/tests/test_base.py

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

17
sapl/crud/tests/test_masterdetail.py

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

2
sapl/crud/urls.py

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

23
sapl/decorators.py

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

33
sapl/endpoint_restriction_middleware.py

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

6
sapl/hashers.py

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

334
sapl/lexml/OAIServer.py

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

6
sapl/lexml/apps.py

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

24
sapl/lexml/forms.py

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

60
sapl/lexml/models.py

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

18
sapl/lexml/urls.py

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

21
sapl/lexml/views.py

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

3
sapl/materia/admin.py

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

6
sapl/materia/apps.py

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

2921
sapl/materia/forms.py

File diff suppressed because it is too large

1167
sapl/materia/models.py

File diff suppressed because it is too large

69
sapl/materia/tests/test_email_templates.py

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

925
sapl/materia/tests/test_materia.py

File diff suppressed because it is too large

153
sapl/materia/tests/test_materia_form.py

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

23
sapl/materia/tests/test_materia_urls.py

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

338
sapl/materia/urls.py

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

2629
sapl/materia/views.py

File diff suppressed because it is too large

12
sapl/middleware.py

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

6
sapl/norma/apps.py

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

589
sapl/norma/forms.py

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

506
sapl/norma/models.py

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

169
sapl/norma/tests/test_norma.py

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

54
sapl/norma/urls.py

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

476
sapl/norma/views.py

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

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

Loading…
Cancel
Save