slthcore 0.4.6__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.

Files changed (115) hide show
  1. {slthcore-0.4.6/slthcore.egg-info → slthcore-0.4.8}/PKG-INFO +1 -1
  2. {slthcore-0.4.6 → slthcore-0.4.8}/setup.py +1 -2
  3. {slthcore-0.4.6 → slthcore-0.4.8}/slth/__init__.py +9 -14
  4. slthcore-0.4.8/slth/application.py +178 -0
  5. slthcore-0.4.8/slth/cmd/init/boilerplate/backend/api/__init__.py +27 -0
  6. {slthcore-0.4.6 → slthcore-0.4.8}/slth/db/models.py +1 -0
  7. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/__init__.py +21 -92
  8. {slthcore-0.4.6 → slthcore-0.4.8}/slth/forms.py +44 -21
  9. slthcore-0.4.8/slth/management/commands/api.py +67 -0
  10. {slthcore-0.4.6 → slthcore-0.4.8}/slth/models.py +7 -4
  11. {slthcore-0.4.6 → slthcore-0.4.8}/slth/notifications.py +3 -2
  12. {slthcore-0.4.6 → slthcore-0.4.8}/slth/oauth.py +5 -4
  13. {slthcore-0.4.6 → slthcore-0.4.8}/slth/queryset.py +7 -0
  14. {slthcore-0.4.6 → slthcore-0.4.8}/slth/static/.DS_Store +0 -0
  15. slthcore-0.4.8/slth/static/js/slth.min.js +251 -0
  16. {slthcore-0.4.6 → slthcore-0.4.8}/slth/templates/index.html +9 -9
  17. {slthcore-0.4.6 → slthcore-0.4.8}/slth/views.py +6 -5
  18. {slthcore-0.4.6 → slthcore-0.4.8/slthcore.egg-info}/PKG-INFO +1 -1
  19. {slthcore-0.4.6 → slthcore-0.4.8}/slthcore.egg-info/SOURCES.txt +2 -1
  20. slthcore-0.4.6/slth/cmd/init/boilerplate/backend/application.yml +0 -64
  21. slthcore-0.4.6/slth/migrations/__init__.py +0 -0
  22. slthcore-0.4.6/slth/static/js/slth.min.js +0 -240
  23. {slthcore-0.4.6 → slthcore-0.4.8}/MANIFEST.in +0 -0
  24. {slthcore-0.4.6 → slthcore-0.4.8}/setup.cfg +0 -0
  25. {slthcore-0.4.6 → slthcore-0.4.8}/slth/apps.py +0 -0
  26. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/configure/__main__.py +0 -0
  27. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/__main__.py +0 -0
  28. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/__pycache__/__main__.cpython-312.pyc +0 -0
  29. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/.DS_Store +0 -0
  30. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/.gitignore +0 -0
  31. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/asgi.py +0 -0
  32. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/endpoints/__init__.py +0 -0
  33. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/models.py +0 -0
  34. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/settings.py +0 -0
  35. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/tests.py +0 -0
  36. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/urls.py +0 -0
  37. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/api/wsgi.py +0 -0
  38. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/entrypoint.sh +0 -0
  39. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/manage.py +0 -0
  40. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/backend/requirements.txt +0 -0
  41. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/base.env +0 -0
  42. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/docker-compose.yml +0 -0
  43. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/frontend/package.json +0 -0
  44. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/frontend/src/main.jsx +0 -0
  45. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/frontend/vite.config.js +0 -0
  46. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/local.env +0 -0
  47. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/run.sh +0 -0
  48. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/selenium/run.sh +0 -0
  49. {slthcore-0.4.6 → slthcore-0.4.8}/slth/cmd/init/boilerplate/test.sh +0 -0
  50. {slthcore-0.4.6 → slthcore-0.4.8}/slth/components.py +0 -0
  51. {slthcore-0.4.6 → slthcore-0.4.8}/slth/db/__init__.py +0 -0
  52. {slthcore-0.4.6 → slthcore-0.4.8}/slth/db/generic.py +0 -0
  53. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/auth.py +0 -0
  54. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/deletion.py +0 -0
  55. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/dev.py +0 -0
  56. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/email.py +0 -0
  57. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/job.py +0 -0
  58. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/log.py +0 -0
  59. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/profile.py +0 -0
  60. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/pushsubscription.py +0 -0
  61. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/report.py +0 -0
  62. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/role.py +0 -0
  63. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/task.py +0 -0
  64. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/timezone.py +0 -0
  65. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/user.py +0 -0
  66. {slthcore-0.4.6 → slthcore-0.4.8}/slth/endpoints/whatsappnotification.py +0 -0
  67. {slthcore-0.4.6 → slthcore-0.4.8}/slth/exceptions.py +0 -0
  68. {slthcore-0.4.6 → slthcore-0.4.8}/slth/factory.py +0 -0
  69. {slthcore-0.4.6/slth/cmd/init/boilerplate/backend/api → slthcore-0.4.8/slth/management}/__init__.py +0 -0
  70. {slthcore-0.4.6/slth/management → slthcore-0.4.8/slth/management/commands}/__init__.py +0 -0
  71. {slthcore-0.4.6 → slthcore-0.4.8}/slth/management/commands/integration_test.py +0 -0
  72. {slthcore-0.4.6 → slthcore-0.4.8}/slth/management/commands/sync.py +0 -0
  73. {slthcore-0.4.6 → slthcore-0.4.8}/slth/management/commands/worker.py +0 -0
  74. {slthcore-0.4.6/slth/management/commands → slthcore-0.4.8/slth/middleware}/__init__.py +0 -0
  75. {slthcore-0.4.6 → slthcore-0.4.8}/slth/middleware/timezone.py +0 -0
  76. {slthcore-0.4.6 → slthcore-0.4.8}/slth/migrations/0001_initial.py +0 -0
  77. {slthcore-0.4.6 → slthcore-0.4.8}/slth/migrations/0002_email_role_pushsubscription_error.py +0 -0
  78. {slthcore-0.4.6 → slthcore-0.4.8}/slth/migrations/0003_rename_photo_profile_alter_profile_options.py +0 -0
  79. {slthcore-0.4.6 → slthcore-0.4.8}/slth/migrations/0004_alter_profile_photo.py +0 -0
  80. {slthcore-0.4.6 → slthcore-0.4.8}/slth/migrations/0005_alter_profile_photo.py +0 -0
  81. {slthcore-0.4.6 → slthcore-0.4.8}/slth/migrations/0006_user.py +0 -0
  82. {slthcore-0.4.6 → slthcore-0.4.8}/slth/migrations/0007_deletion_log.py +0 -0
  83. {slthcore-0.4.6 → slthcore-0.4.8}/slth/migrations/0008_alter_deletion_datetime_alter_log_datetime.py +0 -0
  84. {slthcore-0.4.6 → slthcore-0.4.8}/slth/migrations/0009_remove_email_from_email_email_action_email_attempt_and_more.py +0 -0
  85. {slthcore-0.4.6 → slthcore-0.4.8}/slth/migrations/0010_email_key_alter_email_action_alter_email_attempt_and_more.py +0 -0
  86. {slthcore-0.4.6 → slthcore-0.4.8}/slth/migrations/0011_usertimezone.py +0 -0
  87. {slthcore-0.4.6 → slthcore-0.4.8}/slth/migrations/0012_timezone_remove_usertimezone_key_and_more.py +0 -0
  88. {slthcore-0.4.6 → slthcore-0.4.8}/slth/migrations/0013_whatsappnotification.py +0 -0
  89. {slthcore-0.4.6/slth/middleware → slthcore-0.4.8/slth/migrations}/__init__.py +0 -0
  90. {slthcore-0.4.6 → slthcore-0.4.8}/slth/pdf/__init__.py +0 -0
  91. {slthcore-0.4.6 → slthcore-0.4.8}/slth/pdf/tests.py +0 -0
  92. {slthcore-0.4.6 → slthcore-0.4.8}/slth/permissions.py +0 -0
  93. {slthcore-0.4.6 → slthcore-0.4.8}/slth/printer.py +0 -0
  94. {slthcore-0.4.6 → slthcore-0.4.8}/slth/roles.py +0 -0
  95. {slthcore-0.4.6 → slthcore-0.4.8}/slth/selenium/__init__.py +0 -0
  96. {slthcore-0.4.6 → slthcore-0.4.8}/slth/selenium/browser.py +0 -0
  97. {slthcore-0.4.6 → slthcore-0.4.8}/slth/serializer.py +0 -0
  98. {slthcore-0.4.6 → slthcore-0.4.8}/slth/static/css/.DS_Store +0 -0
  99. {slthcore-0.4.6 → slthcore-0.4.8}/slth/static/css/slth.css +0 -0
  100. {slthcore-0.4.6 → slthcore-0.4.8}/slth/static/images/placeholder.png +0 -0
  101. {slthcore-0.4.6 → slthcore-0.4.8}/slth/static/js/index.min.js +0 -0
  102. {slthcore-0.4.6 → slthcore-0.4.8}/slth/static/js/react.min.js +0 -0
  103. {slthcore-0.4.6 → slthcore-0.4.8}/slth/statistics.py +0 -0
  104. {slthcore-0.4.6 → slthcore-0.4.8}/slth/tasks.py +0 -0
  105. {slthcore-0.4.6 → slthcore-0.4.8}/slth/templates/email.html +0 -0
  106. {slthcore-0.4.6 → slthcore-0.4.8}/slth/templates/report.html +0 -0
  107. {slthcore-0.4.6 → slthcore-0.4.8}/slth/templates/service-worker.js +0 -0
  108. {slthcore-0.4.6 → slthcore-0.4.8}/slth/templates/signature.html +0 -0
  109. {slthcore-0.4.6 → slthcore-0.4.8}/slth/tests.py +0 -0
  110. {slthcore-0.4.6 → slthcore-0.4.8}/slth/threadlocal.py +0 -0
  111. {slthcore-0.4.6 → slthcore-0.4.8}/slth/tz.py +0 -0
  112. {slthcore-0.4.6 → slthcore-0.4.8}/slth/urls.py +0 -0
  113. {slthcore-0.4.6 → slthcore-0.4.8}/slth/utils.py +0 -0
  114. {slthcore-0.4.6 → slthcore-0.4.8}/slthcore.egg-info/dependency_links.txt +0 -0
  115. {slthcore-0.4.6 → slthcore-0.4.8}/slthcore.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: slthcore
3
- Version: 0.4.6
3
+ Version: 0.4.8
4
4
  Summary: API generator based on yml file
5
5
  Home-page: https://github.com/brenokcc
6
6
  Author: Breno Silva
@@ -1,11 +1,10 @@
1
- import os
2
1
  from setuptools import find_packages, setup
3
2
 
4
3
  install_requires = []
5
4
 
6
5
  setup(
7
6
  name='slthcore',
8
- version='0.4.6',
7
+ version='0.4.8',
9
8
  packages=find_packages(),
10
9
  install_requires=install_requires,
11
10
  include_package_data=True,
@@ -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
- b = getattr(self, field.name)
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
- b = getattr(self, field.name)
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"})
@@ -36,6 +36,7 @@ class IntegerField(IntegerField):
36
36
  field.pick = self.pick
37
37
  return field
38
38
 
39
+
39
40
  class ForeignKey(ForeignKey):
40
41
  def __init__(self, to, on_delete=None, **kwargs):
41
42
  self.pick = kwargs.pop('pick', False)
@@ -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 APPLICATON, ENDPOINTS
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
- if options is None and APPLICATON["dashboard"]["search"]:
511
+ application = ApplicationConfig.get_instance()
512
+ if options is None and application.dashboard.search:
515
513
  options = []
516
- for name in APPLICATON["dashboard"]["search"]:
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
- cls = ENDPOINTS[APPLICATON["index"]]
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 APPLICATON["dashboard"]["boxes"]:
550
+ if application.dashboard.boxes:
551
551
  boxes = Boxes("Acesso Rápido")
552
- for name in APPLICATON["dashboard"]["boxes"]:
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 APPLICATON["dashboard"]["top"]:
561
+ if application.dashboard.top:
562
562
  group = serializer.group("Top")
563
- for name in APPLICATON["dashboard"]["top"]:
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 APPLICATON["dashboard"]["center"]:
572
- for name in APPLICATON["dashboard"]["center"]:
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
- user = None
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": APPLICATON["title"],
671
- "short_name": APPLICATON["title"],
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, APPLICATON["icon"]),
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
- return dict(version=APPLICATON["version"])
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={name}")
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
- pk = self.data.get(inline_form_field_name)
423
- if pk:
424
- pk = int(pk)
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.get(
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 inline_form.is_valid():
452
- if isinstance(inline_form, DjangoModelForm):
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
- errors.update(
458
- {
459
- f"{prefix}__{name}": error
460
- for name, error in inline_form.errors.items()
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)