slthcore 0.4.7__tar.gz → 0.4.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of slthcore might be problematic. Click here for more details.
- {slthcore-0.4.7/slthcore.egg-info → slthcore-0.4.8}/PKG-INFO +1 -1
- {slthcore-0.4.7 → slthcore-0.4.8}/setup.py +1 -2
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/__init__.py +9 -14
- slthcore-0.4.8/slth/application.py +178 -0
- slthcore-0.4.8/slth/cmd/init/boilerplate/backend/api/__init__.py +27 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/db/models.py +1 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/__init__.py +21 -92
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/forms.py +44 -21
- slthcore-0.4.8/slth/management/commands/api.py +67 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/models.py +7 -4
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/notifications.py +3 -2
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/oauth.py +5 -4
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/queryset.py +7 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/static/.DS_Store +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/static/js/slth.min.js +64 -53
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/templates/index.html +9 -9
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/views.py +6 -5
- {slthcore-0.4.7 → slthcore-0.4.8/slthcore.egg-info}/PKG-INFO +1 -1
- {slthcore-0.4.7 → slthcore-0.4.8}/slthcore.egg-info/SOURCES.txt +2 -1
- slthcore-0.4.7/slth/cmd/init/boilerplate/backend/application.yml +0 -64
- slthcore-0.4.7/slth/migrations/__init__.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/MANIFEST.in +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/setup.cfg +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/apps.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/configure/__main__.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/__main__.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/__pycache__/__main__.cpython-312.pyc +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/.DS_Store +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/.gitignore +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/asgi.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/endpoints/__init__.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/models.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/settings.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/tests.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/urls.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/wsgi.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/entrypoint.sh +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/manage.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/requirements.txt +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/base.env +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/docker-compose.yml +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/frontend/package.json +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/frontend/src/main.jsx +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/frontend/vite.config.js +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/local.env +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/run.sh +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/selenium/run.sh +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/cmd/init/boilerplate/test.sh +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/components.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/db/__init__.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/db/generic.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/auth.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/deletion.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/dev.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/email.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/job.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/log.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/profile.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/pushsubscription.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/report.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/role.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/task.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/timezone.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/user.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/endpoints/whatsappnotification.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/exceptions.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/factory.py +0 -0
- {slthcore-0.4.7/slth/cmd/init/boilerplate/backend/api → slthcore-0.4.8/slth/management}/__init__.py +0 -0
- {slthcore-0.4.7/slth/management → slthcore-0.4.8/slth/management/commands}/__init__.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/management/commands/integration_test.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/management/commands/sync.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/management/commands/worker.py +0 -0
- {slthcore-0.4.7/slth/management/commands → slthcore-0.4.8/slth/middleware}/__init__.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/middleware/timezone.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/migrations/0001_initial.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/migrations/0002_email_role_pushsubscription_error.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/migrations/0003_rename_photo_profile_alter_profile_options.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/migrations/0004_alter_profile_photo.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/migrations/0005_alter_profile_photo.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/migrations/0006_user.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/migrations/0007_deletion_log.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/migrations/0008_alter_deletion_datetime_alter_log_datetime.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/migrations/0009_remove_email_from_email_email_action_email_attempt_and_more.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/migrations/0010_email_key_alter_email_action_alter_email_attempt_and_more.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/migrations/0011_usertimezone.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/migrations/0012_timezone_remove_usertimezone_key_and_more.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/migrations/0013_whatsappnotification.py +0 -0
- {slthcore-0.4.7/slth/middleware → slthcore-0.4.8/slth/migrations}/__init__.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/pdf/__init__.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/pdf/tests.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/permissions.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/printer.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/roles.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/selenium/__init__.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/selenium/browser.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/serializer.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/static/css/.DS_Store +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/static/css/slth.css +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/static/images/placeholder.png +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/static/js/index.min.js +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/static/js/react.min.js +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/statistics.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/tasks.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/templates/email.html +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/templates/report.html +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/templates/service-worker.js +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/templates/signature.html +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/tests.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/threadlocal.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/tz.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/urls.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slth/utils.py +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slthcore.egg-info/dependency_links.txt +0 -0
- {slthcore-0.4.7 → slthcore-0.4.8}/slthcore.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import os
|
|
3
|
-
import yaml
|
|
4
1
|
import json
|
|
5
2
|
import warnings
|
|
6
3
|
from pathlib import Path
|
|
@@ -15,6 +12,7 @@ import django.db.models.options as options
|
|
|
15
12
|
from django.db.models.base import ModelBase
|
|
16
13
|
from django.db.models import Model
|
|
17
14
|
from django.core.exceptions import FieldDoesNotExist
|
|
15
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
18
16
|
from django.utils.autoreload import autoreload_started
|
|
19
17
|
from django.core import serializers
|
|
20
18
|
from django.utils import autoreload
|
|
@@ -29,15 +27,6 @@ FILENAME = 'application.yml'
|
|
|
29
27
|
ENDPOINTS = {}
|
|
30
28
|
PROXIED_MODELS = []
|
|
31
29
|
THREADS = []
|
|
32
|
-
APPLICATON = None
|
|
33
|
-
|
|
34
|
-
if APPLICATON is None and os.path.exists(FILENAME):
|
|
35
|
-
with open(FILENAME) as file:
|
|
36
|
-
content = file.read()
|
|
37
|
-
for variable in re.findall(r'\$[a-zA-z0-9_]+', content):
|
|
38
|
-
content = content.replace(variable, os.environ.get(variable[1:], ''))
|
|
39
|
-
APPLICATON = yaml.safe_load(content).get('application')
|
|
40
|
-
|
|
41
30
|
|
|
42
31
|
class JSONEncoder(json.JSONEncoder):
|
|
43
32
|
def default(self, obj):
|
|
@@ -185,13 +174,19 @@ def save_decorator(func):
|
|
|
185
174
|
if obj:
|
|
186
175
|
for field in self._meta.fields:
|
|
187
176
|
a = getattr(obj, field.name)
|
|
188
|
-
|
|
177
|
+
try:
|
|
178
|
+
b = getattr(self, field.name)
|
|
179
|
+
except ObjectDoesNotExist:
|
|
180
|
+
b = None
|
|
189
181
|
if a != b:
|
|
190
182
|
diff[field.verbose_name] = (serialize(a), serialize(b))
|
|
191
183
|
else:
|
|
192
184
|
action = 'add'
|
|
193
185
|
for field in self._meta.fields:
|
|
194
|
-
|
|
186
|
+
try:
|
|
187
|
+
b = getattr(self, field.name)
|
|
188
|
+
except ObjectDoesNotExist:
|
|
189
|
+
b = None
|
|
195
190
|
if b is not None:
|
|
196
191
|
diff[field.verbose_name] = (None, serialize(b))
|
|
197
192
|
func(self, *args, **kwargs)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
|
|
2
|
+
from slth import ENDPOINTS
|
|
3
|
+
from .utils import build_url
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from django.apps import apps
|
|
6
|
+
|
|
7
|
+
APPLICATION_CLASS = None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Style():
|
|
11
|
+
def __init__(self, color="black", background="inherite", border="none"):
|
|
12
|
+
self.update(color=color, background=background, border=border)
|
|
13
|
+
|
|
14
|
+
def update(self, color=None, background=None, border=None):
|
|
15
|
+
if color:
|
|
16
|
+
self.color = color
|
|
17
|
+
if background:
|
|
18
|
+
self.background = background
|
|
19
|
+
if border:
|
|
20
|
+
self.border = border
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Theme():
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.primary:Style = Style("#1351b4")
|
|
26
|
+
self.secondary:Style = Style("#071e41")
|
|
27
|
+
self.auxiliary:Style = Style("#2670e8")
|
|
28
|
+
self.highlight:Style = Style("#0c326f")
|
|
29
|
+
|
|
30
|
+
self.info:Style = Style("#1351b4", "#d4e5ff")
|
|
31
|
+
self.success:Style = Style("#1351b4")
|
|
32
|
+
self.warning:Style = Style("#fff5c2")
|
|
33
|
+
self.danger:Style = Style("#e52207")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Groups(dict):
|
|
37
|
+
def add(self, **kwargs):
|
|
38
|
+
self.update(**kwargs)
|
|
39
|
+
|
|
40
|
+
class Menu(dict):
|
|
41
|
+
def add(self, arg):
|
|
42
|
+
self.update(arg)
|
|
43
|
+
|
|
44
|
+
def process(self, request):
|
|
45
|
+
items = []
|
|
46
|
+
|
|
47
|
+
def get_item(k, v):
|
|
48
|
+
if isinstance(v, dict):
|
|
49
|
+
icon, label = k.split(":") if ":" in k else (None, k)
|
|
50
|
+
subitems = []
|
|
51
|
+
for k1, v1 in v.items():
|
|
52
|
+
subitem = get_item(k1, v1)
|
|
53
|
+
if subitem:
|
|
54
|
+
subitems.append(subitem)
|
|
55
|
+
if subitems:
|
|
56
|
+
return dict(dict(icon=icon, label=label, items=subitems))
|
|
57
|
+
else:
|
|
58
|
+
cls = ENDPOINTS.get(v)
|
|
59
|
+
if cls:
|
|
60
|
+
endpoint = cls().instantiate(request, None)
|
|
61
|
+
if endpoint.check_permission() and endpoint.contribute("menu"):
|
|
62
|
+
icon, label = k.split(":") if ":" in k else (None, k)
|
|
63
|
+
url = build_url(request, cls.get_api_url())
|
|
64
|
+
return dict(dict(label=label, url=url, icon=icon))
|
|
65
|
+
|
|
66
|
+
for k, v in self.items():
|
|
67
|
+
item = get_item(k, v)
|
|
68
|
+
if item:
|
|
69
|
+
items.append(item)
|
|
70
|
+
|
|
71
|
+
return items
|
|
72
|
+
|
|
73
|
+
class Oauth(list):
|
|
74
|
+
def add(self, name, client_id, client_secret, redirect_uri, authorize_url, access_token_url, user_data_url, user_logout_url, user_scope, user_create, user_username, user_email):
|
|
75
|
+
super().append(dict(name=name, client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, authorize_url=authorize_url, access_token_url=access_token_url, user_data_url=user_data_url, user_logout_url=user_logout_url, user_scope=user_scope, user_create=user_create, user_username=user_username, user_email=user_email))
|
|
76
|
+
|
|
77
|
+
def serialize(self):
|
|
78
|
+
data = []
|
|
79
|
+
for provider in self:
|
|
80
|
+
redirect_uri = "{}{}".format(settings.SITE_URL, provider['redirect_uri'])
|
|
81
|
+
authorize_url = '{}?response_type=code&client_id={}&redirect_uri={}'.format(
|
|
82
|
+
provider['authorize_url'], provider['client_id'], redirect_uri
|
|
83
|
+
)
|
|
84
|
+
if provider.get('scope'):
|
|
85
|
+
authorize_url = '{}&scope={}'.format(authorize_url, provider.get('scope'))
|
|
86
|
+
data.append(dict(label=f'Entrar com {provider["name"]}', url=authorize_url))
|
|
87
|
+
return data
|
|
88
|
+
|
|
89
|
+
class List(list):
|
|
90
|
+
|
|
91
|
+
def add(self, *names):
|
|
92
|
+
super().extend(names)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class Dashboard():
|
|
96
|
+
def __init__(self):
|
|
97
|
+
self.actions:List = List()
|
|
98
|
+
self.toolbar:List = List()
|
|
99
|
+
self.top:List = List()
|
|
100
|
+
self.center:List = List()
|
|
101
|
+
self.boxes:List = List()
|
|
102
|
+
self.search:List = List()
|
|
103
|
+
self.usermenu:List = List()
|
|
104
|
+
self.adder:List = List()
|
|
105
|
+
self.tools:List = List()
|
|
106
|
+
self.settings:List = List()
|
|
107
|
+
self.index = "dashboard"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ApplicationMetaclass(type):
|
|
111
|
+
|
|
112
|
+
def __new__(mcs, name, bases, attrs):
|
|
113
|
+
global APPLICATION_CLASS
|
|
114
|
+
cls = super().__new__(mcs, name, bases, attrs)
|
|
115
|
+
APPLICATION_CLASS = cls
|
|
116
|
+
return cls
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class Application(metaclass=ApplicationMetaclass):
|
|
120
|
+
|
|
121
|
+
def __init__(self):
|
|
122
|
+
self.lang = "pt-br"
|
|
123
|
+
self.title = "Sloth"
|
|
124
|
+
self.subtitle = "Take your time!"
|
|
125
|
+
self.icon = "/static/images/logo.png"
|
|
126
|
+
self.logo = "/static/images/logo.png"
|
|
127
|
+
self.version = "0.0.1"
|
|
128
|
+
self.oauth:Oauth = Oauth()
|
|
129
|
+
self.groups:Groups = Groups()
|
|
130
|
+
self.menu:Menu = Menu()
|
|
131
|
+
self.dashboard = Dashboard()
|
|
132
|
+
self.theme:Theme = Theme()
|
|
133
|
+
|
|
134
|
+
def load(self):
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
def serialize(self, request):
|
|
138
|
+
icon = build_url(request, self.icon)
|
|
139
|
+
logo = build_url(request, self.logo)
|
|
140
|
+
if request.user.is_authenticated:
|
|
141
|
+
user = request.user.username.split()[0].split("@")[0]
|
|
142
|
+
profile = apps.get_model("slth", "profile").objects.filter(user=request.user).first()
|
|
143
|
+
photo = profile and profile.photo and build_url(request, profile.photo.url) or None
|
|
144
|
+
else:
|
|
145
|
+
user = profile = photo = None
|
|
146
|
+
endpoints = {"actions": [], "usermenu": [], "adder": [], "settings": [], "tools": [], "toolbar": []}
|
|
147
|
+
for entrypoint in endpoints:
|
|
148
|
+
for endpoint_name in getattr(self.dashboard, entrypoint):
|
|
149
|
+
cls = ENDPOINTS[endpoint_name]
|
|
150
|
+
endpoint = cls().instantiate(request, None)
|
|
151
|
+
if endpoint.check_permission() and endpoint.contribute(entrypoint):
|
|
152
|
+
name = endpoint.get_verbose_name()
|
|
153
|
+
url = build_url(request, cls.get_api_url())
|
|
154
|
+
modal = cls.get_metadata("modal", False)
|
|
155
|
+
icon = cls.get_metadata("icon", None)
|
|
156
|
+
endpoints[entrypoint].append(dict(name=name, url=url, modal=modal, icon=icon))
|
|
157
|
+
return dict(
|
|
158
|
+
type="application",
|
|
159
|
+
icon=icon,
|
|
160
|
+
navbar=dict(
|
|
161
|
+
type="navbar", title=self.title, subtitle=self.subtitle, logo=logo, user=user, **endpoints
|
|
162
|
+
),
|
|
163
|
+
menu=dict(
|
|
164
|
+
type="menu", items=self.menu.process(request), user=user, image=photo
|
|
165
|
+
),
|
|
166
|
+
footer=dict(
|
|
167
|
+
type="footer", version=self.version
|
|
168
|
+
),
|
|
169
|
+
oauth=self.oauth.serialize(),
|
|
170
|
+
sponsors=[]
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
def get_instance():
|
|
175
|
+
instance = APPLICATION_CLASS()
|
|
176
|
+
instance.load()
|
|
177
|
+
return instance
|
|
178
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from slth.application import Application
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ApiApplication(Application):
|
|
5
|
+
def __init__(self):
|
|
6
|
+
super().__init__()
|
|
7
|
+
self.groups.add(administrador="Administrador", operador="Operador")
|
|
8
|
+
self.dashboard.usermenu.add(
|
|
9
|
+
"dev.icons",
|
|
10
|
+
"user.users",
|
|
11
|
+
"log.logs",
|
|
12
|
+
"email.emails",
|
|
13
|
+
"pushsubscription.pushsubscriptions",
|
|
14
|
+
"job.jobs",
|
|
15
|
+
"deletion.deletions",
|
|
16
|
+
"auth.logout",
|
|
17
|
+
)
|
|
18
|
+
self.dashboard.boxes.add("user.users")
|
|
19
|
+
self.dashboard.actions.add()
|
|
20
|
+
self.dashboard.toolbar.add()
|
|
21
|
+
self.dashboard.top.add()
|
|
22
|
+
self.dashboard.center.add()
|
|
23
|
+
self.dashboard.search.add()
|
|
24
|
+
self.dashboard.adder.add()
|
|
25
|
+
self.dashboard.tools.add()
|
|
26
|
+
self.dashboard.settings.add()
|
|
27
|
+
self.menu.add({"Sair": "auth.logout"})
|
|
@@ -19,19 +19,16 @@ from django.template.loader import render_to_string
|
|
|
19
19
|
from ..forms import ModelForm, Form
|
|
20
20
|
from ..serializer import serialize, Serializer
|
|
21
21
|
from ..components import (
|
|
22
|
-
Application as Application_,
|
|
23
|
-
Navbar,
|
|
24
|
-
Menu,
|
|
25
|
-
Footer,
|
|
26
22
|
Response,
|
|
27
23
|
Boxes,
|
|
28
24
|
TemplateContent,
|
|
29
25
|
)
|
|
26
|
+
from slth.application import Application as ApplicationConfig
|
|
30
27
|
from ..exceptions import JsonResponseException, ReadyResponseException
|
|
31
28
|
from ..utils import build_url, append_url
|
|
32
29
|
from ..models import Profile, Log, Job
|
|
33
30
|
from slth.queryset import QuerySet
|
|
34
|
-
from slth import
|
|
31
|
+
from slth import ENDPOINTS
|
|
35
32
|
from .. import oauth
|
|
36
33
|
from ..threadlocal import tl
|
|
37
34
|
from ..tasks import Task
|
|
@@ -511,9 +508,10 @@ class Search(Endpoint):
|
|
|
511
508
|
key = "_options_"
|
|
512
509
|
options = self.cache.get(key, [])
|
|
513
510
|
term = self.request.GET.get("term")
|
|
514
|
-
|
|
511
|
+
application = ApplicationConfig.get_instance()
|
|
512
|
+
if options is None and application.dashboard.search:
|
|
515
513
|
options = []
|
|
516
|
-
for name in
|
|
514
|
+
for name in application.dashboard.search:
|
|
517
515
|
cls = ENDPOINTS[name]
|
|
518
516
|
endpoint = cls.instantiate(self.request, self)
|
|
519
517
|
if endpoint.check_permission():
|
|
@@ -537,7 +535,8 @@ class Home(PublicEndpoint):
|
|
|
537
535
|
verbose_name = ""
|
|
538
536
|
|
|
539
537
|
def get(self):
|
|
540
|
-
|
|
538
|
+
application = ApplicationConfig.get_instance()
|
|
539
|
+
cls = ENDPOINTS[application.dashboard.index]
|
|
541
540
|
self.redirect(cls.get_api_url())
|
|
542
541
|
|
|
543
542
|
|
|
@@ -546,10 +545,11 @@ class Dashboard(Endpoint):
|
|
|
546
545
|
verbose_name = ""
|
|
547
546
|
|
|
548
547
|
def get(self):
|
|
548
|
+
application = ApplicationConfig.get_instance()
|
|
549
549
|
serializer = Serializer(request=self.request)
|
|
550
|
-
if
|
|
550
|
+
if application.dashboard.boxes:
|
|
551
551
|
boxes = Boxes("Acesso Rápido")
|
|
552
|
-
for name in
|
|
552
|
+
for name in application.dashboard.boxes:
|
|
553
553
|
cls = ENDPOINTS[name]
|
|
554
554
|
endpoint = cls().contextualize(self.request)
|
|
555
555
|
if endpoint.check_permission():
|
|
@@ -558,9 +558,9 @@ class Dashboard(Endpoint):
|
|
|
558
558
|
url = build_url(self.request, cls.get_api_url())
|
|
559
559
|
boxes.append(icon, label, url)
|
|
560
560
|
serializer.append("Acesso Rápido", boxes)
|
|
561
|
-
if
|
|
561
|
+
if application.dashboard.top:
|
|
562
562
|
group = serializer.group("Top")
|
|
563
|
-
for name in
|
|
563
|
+
for name in application.dashboard.top:
|
|
564
564
|
cls = ENDPOINTS[name]
|
|
565
565
|
endpoint = cls.instantiate(self.request, self)
|
|
566
566
|
if endpoint.check_permission():
|
|
@@ -568,8 +568,8 @@ class Dashboard(Endpoint):
|
|
|
568
568
|
endpoint.get_verbose_name(), cls, wrap=False
|
|
569
569
|
)
|
|
570
570
|
group.parent()
|
|
571
|
-
if
|
|
572
|
-
for name in
|
|
571
|
+
if application.dashboard.center:
|
|
572
|
+
for name in application.dashboard.center:
|
|
573
573
|
cls = ENDPOINTS[name]
|
|
574
574
|
endpoint = cls.instantiate(self.request, self)
|
|
575
575
|
if endpoint.check_permission():
|
|
@@ -584,79 +584,7 @@ class Dashboard(Endpoint):
|
|
|
584
584
|
|
|
585
585
|
class Application(PublicEndpoint):
|
|
586
586
|
def get(self):
|
|
587
|
-
|
|
588
|
-
photo = None
|
|
589
|
-
navbar = None
|
|
590
|
-
menu = None
|
|
591
|
-
icon = build_url(self.request, APPLICATON["logo"])
|
|
592
|
-
logo = build_url(self.request, APPLICATON["logo"])
|
|
593
|
-
if self.request.user.is_authenticated:
|
|
594
|
-
user = self.request.user.username.split()[0].split("@")[0]
|
|
595
|
-
profile = Profile.objects.filter(user=self.request.user).first()
|
|
596
|
-
photo = profile and profile.photo and build_url(self.request, profile.photo.url) or None
|
|
597
|
-
|
|
598
|
-
navbar = Navbar(
|
|
599
|
-
title=APPLICATON["title"],
|
|
600
|
-
subtitle=APPLICATON["subtitle"],
|
|
601
|
-
logo=logo,
|
|
602
|
-
user=user,
|
|
603
|
-
photo=photo,
|
|
604
|
-
search=False,
|
|
605
|
-
roles=' | '.join((str(role) for role in self.objects('slth.role').filter(username=self.request.user.username)))
|
|
606
|
-
)
|
|
607
|
-
for entrypoint in ["actions", "usermenu", "adder", "settings", "tools", "toolbar"]:
|
|
608
|
-
if APPLICATON["dashboard"][entrypoint]:
|
|
609
|
-
for endpoint_name in APPLICATON["dashboard"][entrypoint]:
|
|
610
|
-
cls = ENDPOINTS[endpoint_name]
|
|
611
|
-
endpoint = cls().instantiate(self.request, self)
|
|
612
|
-
if endpoint.check_permission() and endpoint.contribute(entrypoint):
|
|
613
|
-
label = endpoint.get_verbose_name()
|
|
614
|
-
url = build_url(self.request, cls.get_api_url())
|
|
615
|
-
modal = cls.get_metadata("modal", False)
|
|
616
|
-
icon = cls.get_metadata("icon", None)
|
|
617
|
-
navbar.add_action(entrypoint, label, url, modal, icon=icon)
|
|
618
|
-
|
|
619
|
-
if APPLICATON["menu"]:
|
|
620
|
-
items = []
|
|
621
|
-
|
|
622
|
-
def get_item(k, v):
|
|
623
|
-
if isinstance(v, dict):
|
|
624
|
-
icon, label = k.split(":") if ":" in k else (None, k)
|
|
625
|
-
subitems = []
|
|
626
|
-
for k1, v1 in v.items():
|
|
627
|
-
subitem = get_item(k1, v1)
|
|
628
|
-
if subitem:
|
|
629
|
-
subitems.append(subitem)
|
|
630
|
-
if subitems:
|
|
631
|
-
return dict(dict(icon=icon, label=label, items=subitems))
|
|
632
|
-
else:
|
|
633
|
-
cls = ENDPOINTS.get(v)
|
|
634
|
-
if cls:
|
|
635
|
-
endpoint = cls().instantiate(self.request, self)
|
|
636
|
-
if endpoint.check_permission() and endpoint.contribute("menu"):
|
|
637
|
-
icon, label = k.split(":") if ":" in k else (None, k)
|
|
638
|
-
url = build_url(self.request, cls.get_api_url())
|
|
639
|
-
return dict(dict(label=label, url=url, icon=icon))
|
|
640
|
-
|
|
641
|
-
for k, v in APPLICATON["menu"].items():
|
|
642
|
-
item = get_item(k, v)
|
|
643
|
-
if item:
|
|
644
|
-
items.append(item)
|
|
645
|
-
profile = (
|
|
646
|
-
Profile.objects.filter(user=self.request.user).first() if user else None
|
|
647
|
-
)
|
|
648
|
-
photo_url = (
|
|
649
|
-
profile.photo.url
|
|
650
|
-
if profile and profile.photo
|
|
651
|
-
else "/static/images/user.svg"
|
|
652
|
-
)
|
|
653
|
-
menu = Menu(items, user=user, image=build_url(self.request, photo_url))
|
|
654
|
-
|
|
655
|
-
footer = Footer(APPLICATON["version"])
|
|
656
|
-
return Application_(
|
|
657
|
-
icon=icon, navbar=navbar, menu=menu, footer=footer, oauth=oauth.providers(),
|
|
658
|
-
sponsors=APPLICATON.get("sponsors", ())
|
|
659
|
-
)
|
|
587
|
+
return ApplicationConfig.get_instance().serialize(self.request)
|
|
660
588
|
|
|
661
589
|
|
|
662
590
|
class Manifest(PublicEndpoint):
|
|
@@ -665,17 +593,18 @@ class Manifest(PublicEndpoint):
|
|
|
665
593
|
verbose_name = "Manifest"
|
|
666
594
|
|
|
667
595
|
def get(self):
|
|
596
|
+
application = ApplicationConfig.get_instance()
|
|
668
597
|
return dict(
|
|
669
598
|
{
|
|
670
|
-
"name":
|
|
671
|
-
"short_name":
|
|
599
|
+
"name": application.title,
|
|
600
|
+
"short_name": application.title,
|
|
672
601
|
"lang": "pt-BR",
|
|
673
602
|
"start_url": build_url(self.request, "/app/home/"),
|
|
674
603
|
"scope": build_url(self.request, "/app/"),
|
|
675
604
|
"display": "standalone",
|
|
676
605
|
"icons": [
|
|
677
606
|
{
|
|
678
|
-
"src": build_url(self.request,
|
|
607
|
+
"src": build_url(self.request, application.icon),
|
|
679
608
|
"sizes": "192x192",
|
|
680
609
|
"type": "image/png",
|
|
681
610
|
}
|
|
@@ -689,5 +618,5 @@ class Manifest(PublicEndpoint):
|
|
|
689
618
|
|
|
690
619
|
class About(PublicEndpoint):
|
|
691
620
|
def get(self):
|
|
692
|
-
|
|
693
|
-
|
|
621
|
+
application = ApplicationConfig.get_instance()
|
|
622
|
+
return dict(version=application.version)
|
|
@@ -25,11 +25,11 @@ DjangoFileField = FileField
|
|
|
25
25
|
DjangoImageField = ImageField
|
|
26
26
|
|
|
27
27
|
MASKS = dict(
|
|
28
|
-
cpf_cnpj="999.999.999-99|99.999.999/9999-99",
|
|
29
28
|
cpf="999.999.999-99",
|
|
30
29
|
cnpj="99.999.999/9999-99",
|
|
31
30
|
telefone="(99) 99999-9999",
|
|
32
|
-
cep='99.999-990'
|
|
31
|
+
cep='99.999-990',
|
|
32
|
+
cpf_cnpj="999.999.999-99|99.999.999/9999-99",
|
|
33
33
|
)
|
|
34
34
|
|
|
35
35
|
|
|
@@ -264,7 +264,7 @@ class FormMixin:
|
|
|
264
264
|
field.form(endpoint=self._endpoint).to_dict(prefix=f"{name}__n")
|
|
265
265
|
)
|
|
266
266
|
required = getattr(field, "required2", field.required)
|
|
267
|
-
label = field.label.title() if field.label else field.label
|
|
267
|
+
label = field.label.title() if field.label and field.label.islower() else field.label
|
|
268
268
|
data = dict(
|
|
269
269
|
type="inline",
|
|
270
270
|
min=field.min,
|
|
@@ -384,10 +384,15 @@ class FormMixin:
|
|
|
384
384
|
data.update(pick=append_url(choiceurl, f"choices={fname}") if choiceurl else absolute_url(
|
|
385
385
|
self.request, f"choices={fname}"
|
|
386
386
|
))
|
|
387
|
+
if ftype == "boolean":
|
|
388
|
+
data.update(choices=[
|
|
389
|
+
{"id": "true", "value": "Sim"}, {"id": "false", "value": "Não"}, {"id": "null", "value": "Não Informado"}
|
|
390
|
+
])
|
|
387
391
|
|
|
388
|
-
attr_name = f"on_{name}_change"
|
|
392
|
+
attr_name = f"on_{prefix}__{name}_change" if prefix else f"on_{name}_change"
|
|
393
|
+
on_change_name = f"{prefix}__{name}" if prefix else name
|
|
389
394
|
if hasattr(self._endpoint, attr_name):
|
|
390
|
-
data["onchange"] = absolute_url(self.request, f"on_change={
|
|
395
|
+
data["onchange"] = absolute_url(self.request, f"on_change={on_change_name}")
|
|
391
396
|
if name in self._actions:
|
|
392
397
|
cls = ENDPOINTS[self._actions[name]]
|
|
393
398
|
endpoint = cls.instantiate(self.request, self)
|
|
@@ -409,6 +414,7 @@ class FormMixin:
|
|
|
409
414
|
}
|
|
410
415
|
self.is_valid()
|
|
411
416
|
errors.update(self.errors)
|
|
417
|
+
inline_forms = []
|
|
412
418
|
for inline_field_name, inline_field in inline_fields.items():
|
|
413
419
|
data[inline_field_name] = []
|
|
414
420
|
for i in range(0, inline_field.max):
|
|
@@ -419,12 +425,21 @@ class FormMixin:
|
|
|
419
425
|
inline_form_field_name = f"{prefix}__id"
|
|
420
426
|
if inline_form_field_name in self.data:
|
|
421
427
|
inline_form_data = {}
|
|
422
|
-
|
|
423
|
-
if
|
|
424
|
-
pk = int(
|
|
428
|
+
inline_form_field_value = self.data.get(inline_form_field_name)
|
|
429
|
+
if inline_form_field_value or is_one_to_one:
|
|
430
|
+
pk = int(inline_form_field_value or 0)
|
|
425
431
|
for name in inline_field.form.base_fields:
|
|
432
|
+
inline_form_field = inline_field.form.base_fields[name]
|
|
426
433
|
inline_form_field_name = f"{prefix}__{name}"
|
|
427
|
-
inline_form_data[name] = self.data.
|
|
434
|
+
inline_form_data[name] = self.data.getlist(
|
|
435
|
+
inline_form_field_name
|
|
436
|
+
) if (
|
|
437
|
+
(
|
|
438
|
+
isinstance(inline_form_field, DjangoMultipleChoiceField)
|
|
439
|
+
or isinstance(inline_form_field, ModelMultipleChoiceField)
|
|
440
|
+
)
|
|
441
|
+
and not isinstance(inline_form_field, TypedChoiceField)
|
|
442
|
+
) else self.data.get(
|
|
428
443
|
inline_form_field_name
|
|
429
444
|
)
|
|
430
445
|
if (
|
|
@@ -448,18 +463,22 @@ class FormMixin:
|
|
|
448
463
|
instance=instance,
|
|
449
464
|
endpoint=self._endpoint,
|
|
450
465
|
)
|
|
451
|
-
if
|
|
452
|
-
|
|
453
|
-
data[inline_field_name].append(inline_form.instance)
|
|
454
|
-
else:
|
|
455
|
-
data[inline_field_name].append(inline_form.cleaned_data)
|
|
466
|
+
if is_one_to_one and not inline_field.required and not inline_form_field_value:
|
|
467
|
+
data[inline_field_name] = []
|
|
456
468
|
else:
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
469
|
+
if inline_form.is_valid():
|
|
470
|
+
if isinstance(inline_form, DjangoModelForm):
|
|
471
|
+
data[inline_field_name].append(inline_form.instance)
|
|
472
|
+
else:
|
|
473
|
+
data[inline_field_name].append(inline_form.cleaned_data)
|
|
474
|
+
inline_forms.append(inline_form)
|
|
475
|
+
else:
|
|
476
|
+
errors.update(
|
|
477
|
+
{
|
|
478
|
+
f"{prefix}__{name}": error
|
|
479
|
+
for name, error in inline_form.errors.items()
|
|
480
|
+
}
|
|
481
|
+
)
|
|
463
482
|
if errors:
|
|
464
483
|
raise JsonResponseException(
|
|
465
484
|
dict(type="error", text="Por favor, corrija os erros.", errors=errors)
|
|
@@ -481,7 +500,8 @@ class FormMixin:
|
|
|
481
500
|
fieldname = attr_name.replace('clean_', '')
|
|
482
501
|
raise JsonResponseException(dict(type="error", text="Por favor, corrija os erros.", errors={fieldname: ''.join(e.messages)}))
|
|
483
502
|
with transaction.atomic():
|
|
484
|
-
|
|
503
|
+
for inline_form in inline_forms:
|
|
504
|
+
inline_form.save()
|
|
485
505
|
if isinstance(self, DjangoModelForm):
|
|
486
506
|
self.instance.pre_save()
|
|
487
507
|
for inline_field_name in inline_fields:
|
|
@@ -568,6 +588,8 @@ class ModelForm(DjangoModelForm, FormMixin):
|
|
|
568
588
|
self._method = "POST"
|
|
569
589
|
self._key = self._title.lower()
|
|
570
590
|
self._autosubmit = None
|
|
591
|
+
self._submit_label = "Enviar"
|
|
592
|
+
self._submit_icon = "chevron-right"
|
|
571
593
|
|
|
572
594
|
self.fieldsets = {}
|
|
573
595
|
self.request = endpoint.request
|
|
@@ -740,6 +762,7 @@ FIELD_TYPES = {
|
|
|
740
762
|
"EmailField": "email",
|
|
741
763
|
"DecimalField": "decimal",
|
|
742
764
|
"BooleanField": "boolean",
|
|
765
|
+
"NullBooleanField": "boolean",
|
|
743
766
|
"DateTimeField": "datetime",
|
|
744
767
|
"DateField": "date",
|
|
745
768
|
"IntegerField": "number",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from django.apps import apps
|
|
3
|
+
from django.utils.text import slugify
|
|
4
|
+
from django.core.management.commands.test import Command
|
|
5
|
+
|
|
6
|
+
TEMPLATE = """from slth import endpoints
|
|
7
|
+
from ..models import *
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class {plural}(endpoints.ListEndpoint[{model}]):
|
|
11
|
+
class Meta:
|
|
12
|
+
verbose_name = '{verbose_name_plural}'
|
|
13
|
+
|
|
14
|
+
def get(self):
|
|
15
|
+
return (
|
|
16
|
+
super().get()
|
|
17
|
+
.actions('{lower}.cadastrar', '{lower}.editar', '{lower}.excluir')
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Cadastrar(endpoints.AddEndpoint[{model}]):
|
|
22
|
+
class Meta:
|
|
23
|
+
verbose_name = 'Cadastrar {verbose_name}'
|
|
24
|
+
|
|
25
|
+
def get(self):
|
|
26
|
+
return (
|
|
27
|
+
super().get()
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Editar(endpoints.EditEndpoint[{model}]):
|
|
32
|
+
class Meta:
|
|
33
|
+
verbose_name = 'Editar {verbose_name}'
|
|
34
|
+
|
|
35
|
+
def get(self):
|
|
36
|
+
return (
|
|
37
|
+
super().get()
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Excluir(endpoints.DeleteEndpoint[{model}]):
|
|
42
|
+
class Meta:
|
|
43
|
+
verbose_name = 'Excluir {verbose_name}'
|
|
44
|
+
|
|
45
|
+
def get(self):
|
|
46
|
+
return (
|
|
47
|
+
super().get()
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
class Command(Command):
|
|
53
|
+
|
|
54
|
+
def handle(self, *args, **options):
|
|
55
|
+
for model in apps.get_models():
|
|
56
|
+
if model._meta.app_label == "api":
|
|
57
|
+
content = TEMPLATE.format(
|
|
58
|
+
plural = slugify(model._meta.verbose_name_plural).replace('-de-', '-').replace('-', '').title(),
|
|
59
|
+
model = model.__name__,
|
|
60
|
+
lower = model.__name__.lower(),
|
|
61
|
+
verbose_name = model._meta.verbose_name,
|
|
62
|
+
verbose_name_plural = model._meta.verbose_name_plural,
|
|
63
|
+
)
|
|
64
|
+
file_path = f'api/endpoints/{model.__name__.lower()}.py'
|
|
65
|
+
if not os.path.exists(file_path):
|
|
66
|
+
with open(file_path, 'w') as file:
|
|
67
|
+
file.write(content)
|