slthcore 0.2.0__tar.gz → 0.2.2__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.2.0/slthcore.egg-info → slthcore-0.2.2}/PKG-INFO +1 -1
- {slthcore-0.2.0 → slthcore-0.2.2}/setup.py +1 -1
- slthcore-0.2.2/slth/apps.py +9 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/db/models.py +14 -1
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/endpoints.py +102 -73
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/forms.py +3 -2
- slthcore-0.2.2/slth/middleware/timezone.py +25 -0
- slthcore-0.2.2/slth/migrations/0008_alter_deletion_datetime_alter_log_datetime.py +24 -0
- slthcore-0.2.2/slth/migrations/0009_remove_email_from_email_email_action_email_attempt_and_more.py +43 -0
- slthcore-0.2.2/slth/migrations/__init__.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/models.py +42 -15
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/pdf/__init__.py +8 -5
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/queryset.py +7 -5
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/serializer.py +9 -6
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/static/js/slth.min.js +15 -15
- slthcore-0.2.2/slth/templates/email.html +155 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/urls.py +6 -2
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/views.py +2 -1
- {slthcore-0.2.0 → slthcore-0.2.2/slthcore.egg-info}/PKG-INFO +1 -1
- {slthcore-0.2.0 → slthcore-0.2.2}/slthcore.egg-info/SOURCES.txt +6 -1
- slthcore-0.2.0/slth/middleware.py +0 -24
- {slthcore-0.2.0 → slthcore-0.2.2}/MANIFEST.in +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/setup.cfg +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/__init__.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/configure/__main__.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/__main__.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/.DS_Store +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/.gitignore +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/__init__.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/asgi.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/endpoints.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/models.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/settings.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/tests.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/urls.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/wsgi.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/application.yml +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/entrypoint.sh +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/manage.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/requirements.txt +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/base.env +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/docker-compose.yml +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/frontend/package.json +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/frontend/src/main.jsx +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/frontend/vite.config.js +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/local.env +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/run.sh +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/selenium/run.sh +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/test.sh +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/components.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/db/__init__.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/db/generic.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/exceptions.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/factory.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/management/__init__.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/management/commands/__init__.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/management/commands/integration_test.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/management/commands/sync.py +0 -0
- {slthcore-0.2.0/slth/migrations → slthcore-0.2.2/slth/middleware}/__init__.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0001_initial.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0002_email_role_pushsubscription_error.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0003_rename_photo_profile_alter_profile_options.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0004_alter_profile_photo.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0005_alter_profile_photo.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0006_user.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0007_deletion_log.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/notifications.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/oauth.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/pdf/tests.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/permissions.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/printer.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/roles.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/selenium/__init__.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/selenium/browser.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/static/.DS_Store +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/static/css/.DS_Store +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/static/css/slth.css +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/static/js/index.min.js +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/static/js/react.min.js +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/statistics.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/tasks.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/templates/index.html +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/templates/report.html +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/templates/service-worker.js +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/templates/signature.html +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/tests.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/threadlocal.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slth/utils.py +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slthcore.egg-info/dependency_links.txt +0 -0
- {slthcore-0.2.0 → slthcore-0.2.2}/slthcore.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from uuid import uuid1
|
|
2
|
-
|
|
2
|
+
import pytz
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
from django.utils import timezone
|
|
5
|
+
from django.db.models import Model as DjangoModel, fields as DjangoFields
|
|
3
6
|
from django.db.models import *
|
|
4
7
|
from django.utils.translation import gettext_lazy as _
|
|
5
8
|
from . import generic
|
|
@@ -107,6 +110,16 @@ class TextField(TextField):
|
|
|
107
110
|
self.formatted= kwargs.pop('formatted', False)
|
|
108
111
|
super().__init__(*args, **kwargs)
|
|
109
112
|
|
|
113
|
+
|
|
114
|
+
class DateTimeField(DateTimeField):
|
|
115
|
+
|
|
116
|
+
def get_db_prep_value(self, value, *args, **kwargs):
|
|
117
|
+
return pytz.timezone(timezone.get_current_timezone_name()).localize(value).astimezone(timezone.get_default_timezone()).replace(tzinfo=None) if value else None
|
|
118
|
+
|
|
119
|
+
def from_db_value(self, value, *args, **kwargs):
|
|
120
|
+
return pytz.timezone(timezone.get_default_timezone_name()).localize(value).astimezone(timezone.get_current_timezone()).replace(tzinfo=None) if value else None
|
|
121
|
+
|
|
122
|
+
|
|
110
123
|
class FileField(FileField):
|
|
111
124
|
def __init__(self, *args, extensions=('pdf',), max_size=5, **kwargs):
|
|
112
125
|
self.extensions= extensions
|
|
@@ -34,7 +34,7 @@ from .components import (
|
|
|
34
34
|
)
|
|
35
35
|
from .exceptions import JsonResponseException, ReadyResponseException
|
|
36
36
|
from .utils import build_url, append_url
|
|
37
|
-
from .models import PushSubscription, Profile, User, Log
|
|
37
|
+
from .models import PushSubscription, Profile, User, Log, Email
|
|
38
38
|
from slth.queryset import QuerySet
|
|
39
39
|
from slth import APPLICATON, ENDPOINTS
|
|
40
40
|
from . import oauth
|
|
@@ -62,41 +62,9 @@ class EnpointMetaclass(type):
|
|
|
62
62
|
name not in ("Endpoint", "ChildEndpoint")
|
|
63
63
|
and "_ChildEndpoint" not in bases_names
|
|
64
64
|
):
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
items = (
|
|
69
|
-
("Cadastrar", AddEndpoint[model], "plus", "add"),
|
|
70
|
-
("Editar", EditEndpoint[model], "pen", "edit"),
|
|
71
|
-
("Visualizar", ViewEndpoint[model], "eye", "view"),
|
|
72
|
-
("Excluir", DeleteEndpoint[model], "trash", "delete"),
|
|
73
|
-
)
|
|
74
|
-
for prefix, base, icon, action in items:
|
|
75
|
-
endpoint = types.new_class(f"{prefix}{model.__name__}", (base,), {})
|
|
76
|
-
endpoint.__admin__ = cls
|
|
77
|
-
endpoint.__action__ = action
|
|
78
|
-
endpoint.check_permission = lambda self: (
|
|
79
|
-
getattr(self.__admin__.instantiate(self.request, self), f'check_{self.__action__}_permission')()
|
|
80
|
-
)
|
|
81
|
-
endpoint.Meta = type(
|
|
82
|
-
"Meta",
|
|
83
|
-
(),
|
|
84
|
-
dict(
|
|
85
|
-
icon=icon,
|
|
86
|
-
modal=prefix != "Visualizar",
|
|
87
|
-
verbose_name=f"{prefix} {model._meta.verbose_name}",
|
|
88
|
-
),
|
|
89
|
-
)
|
|
90
|
-
if "Meta" not in attrs:
|
|
91
|
-
cls.Meta = type(
|
|
92
|
-
"Meta",
|
|
93
|
-
(),
|
|
94
|
-
dict(
|
|
95
|
-
icon=getattr(model._meta, "icon", None),
|
|
96
|
-
modal=False,
|
|
97
|
-
verbose_name=f"{model._meta.verbose_name_plural}",
|
|
98
|
-
),
|
|
99
|
-
)
|
|
65
|
+
module = cls.__module__.split('.')[-1]
|
|
66
|
+
key = cls.__name__.lower() if module == 'endpoints' else f'{module}.{cls.__name__.lower()}'
|
|
67
|
+
ENDPOINTS[key] = cls
|
|
100
68
|
return cls
|
|
101
69
|
|
|
102
70
|
|
|
@@ -203,7 +171,7 @@ class Endpoint(metaclass=EnpointMetaclass):
|
|
|
203
171
|
data = form
|
|
204
172
|
elif isinstance(data, Form) or isinstance(data, ModelForm):
|
|
205
173
|
data = data.settitle(title)
|
|
206
|
-
elif self.request.method == "POST" and not data:
|
|
174
|
+
elif self.request.method == "POST":# and not data:
|
|
207
175
|
return self.post()
|
|
208
176
|
return data
|
|
209
177
|
|
|
@@ -230,12 +198,34 @@ class Endpoint(metaclass=EnpointMetaclass):
|
|
|
230
198
|
@classmethod
|
|
231
199
|
def get_api_name(cls):
|
|
232
200
|
return cls.__name__.lower()
|
|
201
|
+
|
|
202
|
+
@classmethod
|
|
203
|
+
def get_key_name(cls):
|
|
204
|
+
module = cls.__module__.split('.')[-1]
|
|
205
|
+
if module == 'endpoints':
|
|
206
|
+
return cls.get_api_name()
|
|
207
|
+
else:
|
|
208
|
+
return "{}.{}".format(module, cls.get_api_name())
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def get_default_actions(cls):
|
|
212
|
+
actions = ['add', 'view', 'edit', 'delete']
|
|
213
|
+
module = cls.__module__.split('.')[-1]
|
|
214
|
+
if module == 'endpoints':
|
|
215
|
+
return actions
|
|
216
|
+
else:
|
|
217
|
+
return [f'{module}.{name}' for name in actions if f'{module}.{name}' in ENDPOINTS]
|
|
233
218
|
|
|
234
219
|
@classmethod
|
|
235
220
|
def get_api_url(cls, arg=None):
|
|
221
|
+
module = cls.__module__.split('.')[-1]
|
|
222
|
+
if module == 'endpoints':
|
|
223
|
+
url = "/api/{}/".format(cls.get_api_name())
|
|
224
|
+
else:
|
|
225
|
+
url = "/api/{}/{}/".format(module, cls.get_api_name())
|
|
236
226
|
if arg:
|
|
237
|
-
|
|
238
|
-
return
|
|
227
|
+
url = f"{url}{arg}/"
|
|
228
|
+
return url
|
|
239
229
|
|
|
240
230
|
@classmethod
|
|
241
231
|
def get_pretty_name(cls):
|
|
@@ -246,10 +236,6 @@ class Endpoint(metaclass=EnpointMetaclass):
|
|
|
246
236
|
name.append(c)
|
|
247
237
|
return "".join(name)
|
|
248
238
|
|
|
249
|
-
@classmethod
|
|
250
|
-
def get_qualified_name(cls):
|
|
251
|
-
return "{}.{}".format(cls.__module__, cls.__name__).lower()
|
|
252
|
-
|
|
253
239
|
@classmethod
|
|
254
240
|
def instantiate(cls, request, source):
|
|
255
241
|
args = ()
|
|
@@ -266,28 +252,31 @@ class Endpoint(metaclass=EnpointMetaclass):
|
|
|
266
252
|
@classmethod
|
|
267
253
|
def get_api_url_pattern(cls):
|
|
268
254
|
args = inspect.getfullargspec(cls.__init__).args[1:]
|
|
269
|
-
|
|
255
|
+
module = cls.__module__.split('.')[-1]
|
|
256
|
+
if module == 'endpoints':
|
|
257
|
+
pattern = "{}/".format(cls.get_api_name())
|
|
258
|
+
else:
|
|
259
|
+
pattern = "{}/{}/".format(module, cls.get_api_name())
|
|
270
260
|
for arg in args:
|
|
271
261
|
pattern = "{}{}/".format(pattern, "<str:{}>".format(arg))
|
|
272
262
|
return pattern
|
|
273
263
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
url = append_url(base_url, f"action={cls.get_api_name()}")
|
|
264
|
+
def get_api_metadata(self, request, base_url, pk=None):
|
|
265
|
+
action_name = self.get_verbose_name()
|
|
266
|
+
icon = self.get_metadata("icon")
|
|
267
|
+
modal = self.get_metadata("modal")
|
|
268
|
+
if self.is_child():
|
|
269
|
+
url = append_url(base_url, f"action={self.get_key_name()}")
|
|
281
270
|
url = f"{url}&id={pk}" if pk else url
|
|
282
271
|
else:
|
|
283
|
-
url = build_url(request,
|
|
272
|
+
url = build_url(request, self.get_api_url())
|
|
284
273
|
url = f"{url}{pk}/" if pk else url
|
|
285
274
|
return dict(
|
|
286
275
|
type="action",
|
|
287
276
|
title=action_name,
|
|
288
277
|
name=action_name,
|
|
289
278
|
url=url,
|
|
290
|
-
key=
|
|
279
|
+
key=self.get_api_name(),
|
|
291
280
|
icon=icon,
|
|
292
281
|
modal=modal,
|
|
293
282
|
)
|
|
@@ -299,8 +288,6 @@ class Endpoint(metaclass=EnpointMetaclass):
|
|
|
299
288
|
if metaclass:
|
|
300
289
|
value = getattr(metaclass, key, None)
|
|
301
290
|
if value is None:
|
|
302
|
-
if key == "verbose_name":
|
|
303
|
-
value = cls.get_pretty_name()
|
|
304
291
|
if key == "modal":
|
|
305
292
|
value = (
|
|
306
293
|
issubclass(cls, EditEndpoint)
|
|
@@ -313,6 +300,9 @@ class Endpoint(metaclass=EnpointMetaclass):
|
|
|
313
300
|
def get_verbose_name(self):
|
|
314
301
|
return self.get_metadata("verbose_name")
|
|
315
302
|
|
|
303
|
+
def get_icon(self):
|
|
304
|
+
return self.get_metadata("icon")
|
|
305
|
+
|
|
316
306
|
def get_instance(self):
|
|
317
307
|
return None
|
|
318
308
|
|
|
@@ -362,6 +352,9 @@ class ModelEndpoint(Endpoint):
|
|
|
362
352
|
self.model = self.objects(self.model).get(pk=self.pk)
|
|
363
353
|
super().__init__()
|
|
364
354
|
|
|
355
|
+
def get_icon(self):
|
|
356
|
+
return self.model._meta.icon or super().get_icon()
|
|
357
|
+
|
|
365
358
|
|
|
366
359
|
class AdminEndpoint(Generic[T], ModelEndpoint):
|
|
367
360
|
|
|
@@ -385,12 +378,33 @@ class AdminEndpoint(Generic[T], ModelEndpoint):
|
|
|
385
378
|
return self.check_permission()
|
|
386
379
|
|
|
387
380
|
|
|
381
|
+
class QuerySetEndpoint(Generic[T], ModelEndpoint):
|
|
382
|
+
class Meta:
|
|
383
|
+
modal = False
|
|
384
|
+
|
|
385
|
+
def get(self) -> QuerySet:
|
|
386
|
+
return self.model.objects.contextualize(self.request)
|
|
387
|
+
|
|
388
|
+
def get_verbose_name(self):
|
|
389
|
+
return self.get_metadata('verbose_name', self.model._meta.verbose_name_plural)
|
|
390
|
+
|
|
391
|
+
|
|
388
392
|
class ListEndpoint(Generic[T], ModelEndpoint):
|
|
393
|
+
class Meta:
|
|
394
|
+
modal = False
|
|
395
|
+
|
|
389
396
|
def get(self) -> QuerySet:
|
|
390
|
-
return self.model.objects
|
|
397
|
+
return self.model.objects.all().contextualize(self.request).actions(*self.get_default_actions())
|
|
398
|
+
|
|
399
|
+
def get_verbose_name(self):
|
|
400
|
+
return self.get_metadata('verbose_name', self.model._meta.verbose_name_plural)
|
|
391
401
|
|
|
392
402
|
|
|
393
403
|
class AddEndpoint(Generic[T], ModelEndpoint):
|
|
404
|
+
class Meta:
|
|
405
|
+
icon = 'plus'
|
|
406
|
+
modal = True
|
|
407
|
+
|
|
394
408
|
def __init__(self):
|
|
395
409
|
super().__init__()
|
|
396
410
|
self.instance = self.model()
|
|
@@ -403,6 +417,9 @@ class AddEndpoint(Generic[T], ModelEndpoint):
|
|
|
403
417
|
|
|
404
418
|
def formfactory(self):
|
|
405
419
|
return self.instance.formfactory()
|
|
420
|
+
|
|
421
|
+
def get_verbose_name(self):
|
|
422
|
+
return f'Cadastrar {self.model._meta.verbose_name}'
|
|
406
423
|
|
|
407
424
|
|
|
408
425
|
class ModelInstanceEndpoint(ModelEndpoint):
|
|
@@ -428,21 +445,28 @@ class ViewEndpoint(Generic[T], ModelInstanceEndpoint):
|
|
|
428
445
|
class Meta:
|
|
429
446
|
icon = "eye"
|
|
430
447
|
modal = False
|
|
431
|
-
verbose_name =
|
|
448
|
+
verbose_name = 'Visualizar'
|
|
432
449
|
|
|
433
450
|
def get(self) -> Serializer:
|
|
434
451
|
return self.get_instance().serializer().contextualize(self.request)
|
|
435
452
|
|
|
436
|
-
def get_verbose_name(self):
|
|
437
|
-
return str(self.get_instance())
|
|
438
|
-
|
|
439
453
|
|
|
440
454
|
class EditEndpoint(Generic[T], ModelInstanceEndpoint):
|
|
455
|
+
class Meta:
|
|
456
|
+
modal = True
|
|
457
|
+
icon = 'pen'
|
|
458
|
+
verbose_name = 'Editar'
|
|
459
|
+
|
|
441
460
|
def get(self) -> FormFactory:
|
|
442
461
|
return self.get_instance().formfactory()
|
|
443
462
|
|
|
444
463
|
|
|
445
464
|
class DeleteEndpoint(Generic[T], ModelInstanceEndpoint):
|
|
465
|
+
class Meta:
|
|
466
|
+
modal = True
|
|
467
|
+
icon = 'trash'
|
|
468
|
+
verbose_name = 'Excluir'
|
|
469
|
+
|
|
446
470
|
def get(self) -> FormFactory:
|
|
447
471
|
return self.formfactory(self.get_instance()).fields()
|
|
448
472
|
|
|
@@ -741,28 +765,28 @@ class Dashboard(Endpoint):
|
|
|
741
765
|
serializer = Serializer(request=self.request)
|
|
742
766
|
if APPLICATON["dashboard"]["boxes"]:
|
|
743
767
|
boxes = Boxes("Acesso Rápido")
|
|
744
|
-
for
|
|
745
|
-
cls = ENDPOINTS[
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
768
|
+
for name in APPLICATON["dashboard"]["boxes"]:
|
|
769
|
+
cls = ENDPOINTS[name]
|
|
770
|
+
endpoint = cls().contextualize(self.request)
|
|
771
|
+
if endpoint.check_permission():
|
|
772
|
+
icon = endpoint.get_icon() or "check"
|
|
773
|
+
label = endpoint.get_verbose_name()
|
|
749
774
|
url = build_url(self.request, cls.get_api_url())
|
|
750
775
|
boxes.append(icon, label, url)
|
|
751
776
|
serializer.append("Acesso Rápido", boxes)
|
|
752
777
|
if APPLICATON["dashboard"]["top"]:
|
|
753
778
|
group = serializer.group("Top")
|
|
754
|
-
for
|
|
755
|
-
cls = ENDPOINTS[
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
).check_permission():
|
|
779
|
+
for name in APPLICATON["dashboard"]["top"]:
|
|
780
|
+
cls = ENDPOINTS[name]
|
|
781
|
+
endpoint = cls.instantiate(self.request, self.request.user)
|
|
782
|
+
if endpoint.check_permission():
|
|
759
783
|
group.endpoint(
|
|
760
784
|
cls.get_metadata("verbose_name"), cls, wrap=False
|
|
761
785
|
)
|
|
762
786
|
group.parent()
|
|
763
787
|
if APPLICATON["dashboard"]["center"]:
|
|
764
|
-
for
|
|
765
|
-
cls = ENDPOINTS[
|
|
788
|
+
for name in APPLICATON["dashboard"]["center"]:
|
|
789
|
+
cls = ENDPOINTS[name]
|
|
766
790
|
serializer.endpoint(
|
|
767
791
|
cls.get_metadata("verbose_name"), cls, wrap=False
|
|
768
792
|
)
|
|
@@ -891,7 +915,12 @@ class PushSubscribe(Endpoint):
|
|
|
891
915
|
return True
|
|
892
916
|
|
|
893
917
|
|
|
894
|
-
class
|
|
918
|
+
class Emails(AdminEndpoint[Email]):
|
|
919
|
+
class Meta:
|
|
920
|
+
verbose_name = "E-mails"
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
class PushSubscriptions(AdminEndpoint[PushSubscription]):
|
|
895
924
|
class Meta:
|
|
896
925
|
verbose_name = "Notificações"
|
|
897
926
|
|
|
@@ -386,9 +386,10 @@ class FormMixin:
|
|
|
386
386
|
data["onchange"] = absolute_url(self.request, f"on_change={name}")
|
|
387
387
|
if name in self._actions:
|
|
388
388
|
cls = ENDPOINTS[self._actions[name]]
|
|
389
|
-
|
|
389
|
+
endpoint = cls.instantiate(self.request, self)
|
|
390
|
+
if endpoint.check_permission():
|
|
390
391
|
data.update(
|
|
391
|
-
action=
|
|
392
|
+
action=endpoint.get_api_metadata(
|
|
392
393
|
self.request, absolute_url(self.request)
|
|
393
394
|
)
|
|
394
395
|
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import pytz
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from django.utils import timezone
|
|
4
|
+
|
|
5
|
+
class Middleware:
|
|
6
|
+
def __init__(self, get_response):
|
|
7
|
+
self.get_response = get_response
|
|
8
|
+
|
|
9
|
+
def __call__(self, request):
|
|
10
|
+
tz = request.path[0] == '/' and (request.META.get('HTTP_TZ') or request.META.get('TZ'))
|
|
11
|
+
if tz:
|
|
12
|
+
timezone.activate(pytz.timezone(tz))
|
|
13
|
+
response = self.get_response(request)
|
|
14
|
+
if tz:
|
|
15
|
+
timezone.deactivate()
|
|
16
|
+
return response
|
|
17
|
+
|
|
18
|
+
def now():
|
|
19
|
+
return datetime.now().astimezone(timezone.get_current_timezone()).replace(tzinfo=None)
|
|
20
|
+
|
|
21
|
+
def today():
|
|
22
|
+
return now().date()
|
|
23
|
+
|
|
24
|
+
def local_datetime(dt, tz):
|
|
25
|
+
pass
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Generated by Django 4.2.6 on 2024-09-09 07:10
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
import slth.db.models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('slth', '0007_deletion_log'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AlterField(
|
|
15
|
+
model_name='deletion',
|
|
16
|
+
name='datetime',
|
|
17
|
+
field=slth.db.models.DateTimeField(null=True, verbose_name='Data/Hora'),
|
|
18
|
+
),
|
|
19
|
+
migrations.AlterField(
|
|
20
|
+
model_name='log',
|
|
21
|
+
name='datetime',
|
|
22
|
+
field=slth.db.models.DateTimeField(null=True, verbose_name='Data/Hora'),
|
|
23
|
+
),
|
|
24
|
+
]
|
slthcore-0.2.2/slth/migrations/0009_remove_email_from_email_email_action_email_attempt_and_more.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Generated by Django 5.1.1 on 2024-09-16 11:01
|
|
2
|
+
|
|
3
|
+
import slth.db.models
|
|
4
|
+
from django.db import migrations
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('slth', '0008_alter_deletion_datetime_alter_log_datetime'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.RemoveField(
|
|
15
|
+
model_name='email',
|
|
16
|
+
name='from_email',
|
|
17
|
+
),
|
|
18
|
+
migrations.AddField(
|
|
19
|
+
model_name='email',
|
|
20
|
+
name='action',
|
|
21
|
+
field=slth.db.models.CharField(max_length=255, null=True, verbose_name='Ação'),
|
|
22
|
+
),
|
|
23
|
+
migrations.AddField(
|
|
24
|
+
model_name='email',
|
|
25
|
+
name='attempt',
|
|
26
|
+
field=slth.db.models.IntegerField(default=0, verbose_name='Tentativa de Envio'),
|
|
27
|
+
),
|
|
28
|
+
migrations.AddField(
|
|
29
|
+
model_name='email',
|
|
30
|
+
name='error',
|
|
31
|
+
field=slth.db.models.TextField(null=True, verbose_name='Erro'),
|
|
32
|
+
),
|
|
33
|
+
migrations.AddField(
|
|
34
|
+
model_name='email',
|
|
35
|
+
name='send_at',
|
|
36
|
+
field=slth.db.models.DateTimeField(null=True, verbose_name='Data/Hora'),
|
|
37
|
+
),
|
|
38
|
+
migrations.AddField(
|
|
39
|
+
model_name='email',
|
|
40
|
+
name='url',
|
|
41
|
+
field=slth.db.models.CharField(max_length=255, null=True, verbose_name='URL'),
|
|
42
|
+
),
|
|
43
|
+
]
|
|
File without changes
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import json
|
|
3
3
|
import binascii
|
|
4
|
+
import traceback
|
|
4
5
|
from .db import models, meta
|
|
5
6
|
from django.conf import settings
|
|
6
7
|
from django.utils.translation import gettext_lazy as _
|
|
@@ -10,6 +11,7 @@ from django.apps import apps
|
|
|
10
11
|
from datetime import datetime
|
|
11
12
|
from django.utils.html import strip_tags
|
|
12
13
|
from django.core import serializers
|
|
14
|
+
from django.template.loader import render_to_string
|
|
13
15
|
from django.core.mail import EmailMultiAlternatives
|
|
14
16
|
from .notifications import send_push_web_notification
|
|
15
17
|
from slth import APPLICATON
|
|
@@ -103,15 +105,6 @@ class Role(models.Model):
|
|
|
103
105
|
return '{} - {}'.format(self.get_verbose_name(), scope_value) if scope_value else self.get_verbose_name()
|
|
104
106
|
|
|
105
107
|
|
|
106
|
-
class EmailManager(models.Manager):
|
|
107
|
-
def all(self):
|
|
108
|
-
return self.order_by('-id')
|
|
109
|
-
|
|
110
|
-
def send(self, to, subject, content, from_email=None):
|
|
111
|
-
to = [to] if isinstance(to, str) else list(to)
|
|
112
|
-
return self.create(from_email=from_email or 'no-replay@mail.com', to=', '.join(to), subject=subject, content=content)
|
|
113
|
-
|
|
114
|
-
|
|
115
108
|
class PushSubscription(models.Model):
|
|
116
109
|
user = models.ForeignKey('auth.user', verbose_name='Usuário', on_delete=models.CASCADE)
|
|
117
110
|
device = models.CharField(verbose_name='Dispositivo')
|
|
@@ -144,12 +137,27 @@ class Error(models.Model):
|
|
|
144
137
|
verbose_name_plural = 'Erros'
|
|
145
138
|
|
|
146
139
|
|
|
140
|
+
class EmailManager(models.Manager):
|
|
141
|
+
def all(self):
|
|
142
|
+
return self.order_by('-id')
|
|
143
|
+
|
|
144
|
+
def send(self, to, subject, content, send_at=None, action=None, url=None):
|
|
145
|
+
to = [to] if isinstance(to, str) else list(to)
|
|
146
|
+
return self.create(to=', '.join(to), subject=subject, content=content, send_at=send_at, action=action, url=url)
|
|
147
|
+
|
|
148
|
+
|
|
147
149
|
class Email(models.Model):
|
|
148
|
-
from_email = models.EmailField('Remetente')
|
|
149
150
|
to = models.TextField('Destinatário', help_text='Separar endereços de e-mail por ",".')
|
|
150
151
|
subject = models.CharField('Assunto')
|
|
151
152
|
content = models.TextField('Conteúdo', formatted=True)
|
|
153
|
+
send_at = models.DateTimeField('Data/Hora', null=True)
|
|
152
154
|
sent_at = models.DateTimeField('Data/Hora', null=True)
|
|
155
|
+
attempt = models.IntegerField('Tentativa de Envio', default=0)
|
|
156
|
+
|
|
157
|
+
action = models.CharField('Ação', null=True)
|
|
158
|
+
url = models.CharField('URL', null=True)
|
|
159
|
+
|
|
160
|
+
error = models.TextField('Erro', null=True)
|
|
153
161
|
|
|
154
162
|
objects = EmailManager()
|
|
155
163
|
|
|
@@ -161,13 +169,32 @@ class Email(models.Model):
|
|
|
161
169
|
return self.subject
|
|
162
170
|
|
|
163
171
|
def save(self, *args, **kwargs):
|
|
172
|
+
self.from_email = settings.DEFAULT_FROM_EMAIL
|
|
173
|
+
if 1 or self.sent_at is None:
|
|
174
|
+
self.send()
|
|
175
|
+
super().save(*args, **kwargs)
|
|
176
|
+
|
|
177
|
+
def send(self):
|
|
164
178
|
to = [email.strip() for email in self.to.split(',')]
|
|
165
179
|
msg = EmailMultiAlternatives(self.subject, strip_tags(self.content), self.from_email, to)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
180
|
+
content = render_to_string('email.html', dict(email=self, title=APPLICATON['title']))
|
|
181
|
+
msg.attach_alternative(content, "text/html")
|
|
182
|
+
self.send_at = datetime.now()
|
|
183
|
+
self.attempt = self.attempt + 1
|
|
184
|
+
try:
|
|
185
|
+
msg.send(fail_silently=False)
|
|
186
|
+
self.sent_at = self.send_at
|
|
187
|
+
except Exception as e:
|
|
188
|
+
self.error = str(e)
|
|
189
|
+
traceback.print_exc()
|
|
190
|
+
super().save()
|
|
191
|
+
|
|
192
|
+
def formfactory(self):
|
|
193
|
+
return super().formfactory().fieldset(
|
|
194
|
+
'Dados Gerais', ('subject', 'to', 'content')
|
|
195
|
+
).fieldset(
|
|
196
|
+
'Botão', ('action', 'url')
|
|
197
|
+
)
|
|
171
198
|
|
|
172
199
|
|
|
173
200
|
class Profile(models.Model):
|
|
@@ -28,6 +28,9 @@ class PdfWriter:
|
|
|
28
28
|
def render(self, template_name, context):
|
|
29
29
|
context.update(base_url=settings.SITE_URL)
|
|
30
30
|
html = render_to_string(template_name, context)
|
|
31
|
+
self.write(html)
|
|
32
|
+
|
|
33
|
+
def write(self, html):
|
|
31
34
|
self.pdf.write_html(html, tag_styles=STYLE, font_family="Courier")
|
|
32
35
|
|
|
33
36
|
def save(self, path):
|
|
@@ -45,14 +48,14 @@ class PdfSigner:
|
|
|
45
48
|
self.offset = len(self.content)
|
|
46
49
|
self.byterange = []
|
|
47
50
|
self.path = path.split('.')[0]
|
|
48
|
-
self.startxref = [int(x[9:].strip()) for x in re.findall(b'startxref[\n\r ]
|
|
51
|
+
self.startxref = [int(x[9:].strip()) for x in re.findall(b'startxref[\n\r ]*\\d+', self.content)]
|
|
49
52
|
self.objects = {}
|
|
50
|
-
for xref in re.findall(b'xref[\n\r
|
|
51
|
-
for offset in sum([[int(n) for n in re.findall(b'
|
|
53
|
+
for xref in re.findall(b'xref[\n\r\\w\\d ]*trailer', self.content[self.startxref[0]:]):
|
|
54
|
+
for offset in sum([[int(n) for n in re.findall(b'\\d{10}', xref) if int(n)]], []):
|
|
52
55
|
self.objects[b' '.join(self.content[offset:offset + 15].split()[0:2]).decode()] = offset
|
|
53
56
|
self.trailer = self.content[self.startxref[-1]:].split(b'trailer')[1].split(b'startxref')[0].strip()
|
|
54
|
-
self.root = root = re.findall(b'/Root
|
|
55
|
-
self.size = int(re.findall(b'/Size
|
|
57
|
+
self.root = root = re.findall(b'/Root \\d+ \\d+ R', self.trailer)[-1][6:-2].decode()
|
|
58
|
+
self.size = int(re.findall(b'/Size \\d+', self.trailer)[-1][5:].strip())
|
|
56
59
|
self.page()
|
|
57
60
|
|
|
58
61
|
def obj(self, key=None):
|
|
@@ -261,9 +261,10 @@ class QuerySet(models.QuerySet):
|
|
|
261
261
|
queryset_actions.append(cls)
|
|
262
262
|
|
|
263
263
|
for cls in queryset_actions:
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
action
|
|
264
|
+
endpoint = cls.instantiate(self.request, self)
|
|
265
|
+
if endpoint.check_permission():
|
|
266
|
+
action = endpoint.get_api_metadata(self.request, base_url)
|
|
267
|
+
#action['name'] = action['name'].replace(" {}".format(self.model._meta.verbose_name.title()), "")
|
|
267
268
|
if relations and cls.get_metadata('modal'):
|
|
268
269
|
params = '&'.join(f'{k}={v}' for k, v in relations.items())
|
|
269
270
|
action['url'] = append_url(action['url'], params)
|
|
@@ -357,8 +358,9 @@ class QuerySet(models.QuerySet):
|
|
|
357
358
|
serializer.obj = obj
|
|
358
359
|
serialized = serializer.serialize(forward_exception=True)
|
|
359
360
|
for cls in instance_actions:
|
|
360
|
-
|
|
361
|
-
|
|
361
|
+
endpoint = cls.instantiate(self.request, obj)
|
|
362
|
+
if endpoint.check_permission():
|
|
363
|
+
action = endpoint.get_api_metadata(self.request, base_url, obj.pk)
|
|
362
364
|
action['name'] = action['name'].replace(" {}".format(self.model._meta.verbose_name), "")
|
|
363
365
|
if relations and cls.get_metadata('modal'):
|
|
364
366
|
params = '&'.join(f'{k}={v}' for k, v in relations.items())
|