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.

Files changed (90) hide show
  1. {slthcore-0.2.0/slthcore.egg-info → slthcore-0.2.2}/PKG-INFO +1 -1
  2. {slthcore-0.2.0 → slthcore-0.2.2}/setup.py +1 -1
  3. slthcore-0.2.2/slth/apps.py +9 -0
  4. {slthcore-0.2.0 → slthcore-0.2.2}/slth/db/models.py +14 -1
  5. {slthcore-0.2.0 → slthcore-0.2.2}/slth/endpoints.py +102 -73
  6. {slthcore-0.2.0 → slthcore-0.2.2}/slth/forms.py +3 -2
  7. slthcore-0.2.2/slth/middleware/timezone.py +25 -0
  8. slthcore-0.2.2/slth/migrations/0008_alter_deletion_datetime_alter_log_datetime.py +24 -0
  9. slthcore-0.2.2/slth/migrations/0009_remove_email_from_email_email_action_email_attempt_and_more.py +43 -0
  10. slthcore-0.2.2/slth/migrations/__init__.py +0 -0
  11. {slthcore-0.2.0 → slthcore-0.2.2}/slth/models.py +42 -15
  12. {slthcore-0.2.0 → slthcore-0.2.2}/slth/pdf/__init__.py +8 -5
  13. {slthcore-0.2.0 → slthcore-0.2.2}/slth/queryset.py +7 -5
  14. {slthcore-0.2.0 → slthcore-0.2.2}/slth/serializer.py +9 -6
  15. {slthcore-0.2.0 → slthcore-0.2.2}/slth/static/js/slth.min.js +15 -15
  16. slthcore-0.2.2/slth/templates/email.html +155 -0
  17. {slthcore-0.2.0 → slthcore-0.2.2}/slth/urls.py +6 -2
  18. {slthcore-0.2.0 → slthcore-0.2.2}/slth/views.py +2 -1
  19. {slthcore-0.2.0 → slthcore-0.2.2/slthcore.egg-info}/PKG-INFO +1 -1
  20. {slthcore-0.2.0 → slthcore-0.2.2}/slthcore.egg-info/SOURCES.txt +6 -1
  21. slthcore-0.2.0/slth/middleware.py +0 -24
  22. {slthcore-0.2.0 → slthcore-0.2.2}/MANIFEST.in +0 -0
  23. {slthcore-0.2.0 → slthcore-0.2.2}/setup.cfg +0 -0
  24. {slthcore-0.2.0 → slthcore-0.2.2}/slth/__init__.py +0 -0
  25. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/configure/__main__.py +0 -0
  26. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/__main__.py +0 -0
  27. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/.DS_Store +0 -0
  28. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/.gitignore +0 -0
  29. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/__init__.py +0 -0
  30. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/asgi.py +0 -0
  31. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/endpoints.py +0 -0
  32. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/models.py +0 -0
  33. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/settings.py +0 -0
  34. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/tests.py +0 -0
  35. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/urls.py +0 -0
  36. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/api/wsgi.py +0 -0
  37. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/application.yml +0 -0
  38. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/entrypoint.sh +0 -0
  39. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/manage.py +0 -0
  40. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/backend/requirements.txt +0 -0
  41. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/base.env +0 -0
  42. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/docker-compose.yml +0 -0
  43. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/frontend/package.json +0 -0
  44. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/frontend/src/main.jsx +0 -0
  45. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/frontend/vite.config.js +0 -0
  46. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/local.env +0 -0
  47. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/run.sh +0 -0
  48. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/selenium/run.sh +0 -0
  49. {slthcore-0.2.0 → slthcore-0.2.2}/slth/cmd/init/boilerplate/test.sh +0 -0
  50. {slthcore-0.2.0 → slthcore-0.2.2}/slth/components.py +0 -0
  51. {slthcore-0.2.0 → slthcore-0.2.2}/slth/db/__init__.py +0 -0
  52. {slthcore-0.2.0 → slthcore-0.2.2}/slth/db/generic.py +0 -0
  53. {slthcore-0.2.0 → slthcore-0.2.2}/slth/exceptions.py +0 -0
  54. {slthcore-0.2.0 → slthcore-0.2.2}/slth/factory.py +0 -0
  55. {slthcore-0.2.0 → slthcore-0.2.2}/slth/management/__init__.py +0 -0
  56. {slthcore-0.2.0 → slthcore-0.2.2}/slth/management/commands/__init__.py +0 -0
  57. {slthcore-0.2.0 → slthcore-0.2.2}/slth/management/commands/integration_test.py +0 -0
  58. {slthcore-0.2.0 → slthcore-0.2.2}/slth/management/commands/sync.py +0 -0
  59. {slthcore-0.2.0/slth/migrations → slthcore-0.2.2/slth/middleware}/__init__.py +0 -0
  60. {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0001_initial.py +0 -0
  61. {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0002_email_role_pushsubscription_error.py +0 -0
  62. {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0003_rename_photo_profile_alter_profile_options.py +0 -0
  63. {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0004_alter_profile_photo.py +0 -0
  64. {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0005_alter_profile_photo.py +0 -0
  65. {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0006_user.py +0 -0
  66. {slthcore-0.2.0 → slthcore-0.2.2}/slth/migrations/0007_deletion_log.py +0 -0
  67. {slthcore-0.2.0 → slthcore-0.2.2}/slth/notifications.py +0 -0
  68. {slthcore-0.2.0 → slthcore-0.2.2}/slth/oauth.py +0 -0
  69. {slthcore-0.2.0 → slthcore-0.2.2}/slth/pdf/tests.py +0 -0
  70. {slthcore-0.2.0 → slthcore-0.2.2}/slth/permissions.py +0 -0
  71. {slthcore-0.2.0 → slthcore-0.2.2}/slth/printer.py +0 -0
  72. {slthcore-0.2.0 → slthcore-0.2.2}/slth/roles.py +0 -0
  73. {slthcore-0.2.0 → slthcore-0.2.2}/slth/selenium/__init__.py +0 -0
  74. {slthcore-0.2.0 → slthcore-0.2.2}/slth/selenium/browser.py +0 -0
  75. {slthcore-0.2.0 → slthcore-0.2.2}/slth/static/.DS_Store +0 -0
  76. {slthcore-0.2.0 → slthcore-0.2.2}/slth/static/css/.DS_Store +0 -0
  77. {slthcore-0.2.0 → slthcore-0.2.2}/slth/static/css/slth.css +0 -0
  78. {slthcore-0.2.0 → slthcore-0.2.2}/slth/static/js/index.min.js +0 -0
  79. {slthcore-0.2.0 → slthcore-0.2.2}/slth/static/js/react.min.js +0 -0
  80. {slthcore-0.2.0 → slthcore-0.2.2}/slth/statistics.py +0 -0
  81. {slthcore-0.2.0 → slthcore-0.2.2}/slth/tasks.py +0 -0
  82. {slthcore-0.2.0 → slthcore-0.2.2}/slth/templates/index.html +0 -0
  83. {slthcore-0.2.0 → slthcore-0.2.2}/slth/templates/report.html +0 -0
  84. {slthcore-0.2.0 → slthcore-0.2.2}/slth/templates/service-worker.js +0 -0
  85. {slthcore-0.2.0 → slthcore-0.2.2}/slth/templates/signature.html +0 -0
  86. {slthcore-0.2.0 → slthcore-0.2.2}/slth/tests.py +0 -0
  87. {slthcore-0.2.0 → slthcore-0.2.2}/slth/threadlocal.py +0 -0
  88. {slthcore-0.2.0 → slthcore-0.2.2}/slth/utils.py +0 -0
  89. {slthcore-0.2.0 → slthcore-0.2.2}/slthcore.egg-info/dependency_links.txt +0 -0
  90. {slthcore-0.2.0 → slthcore-0.2.2}/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.2.0
3
+ Version: 0.2.2
4
4
  Summary: API generator based on yml file
5
5
  Home-page: https://github.com/brenokcc
6
6
  Author: Breno Silva
@@ -5,7 +5,7 @@ install_requires = []
5
5
 
6
6
  setup(
7
7
  name='slthcore',
8
- version='0.2.0',
8
+ version='0.2.2',
9
9
  packages=find_packages(),
10
10
  install_requires=install_requires,
11
11
  include_package_data=True,
@@ -0,0 +1,9 @@
1
+ from django.conf import settings
2
+ from django.apps import AppConfig
3
+
4
+
5
+ class AppConfig(AppConfig):
6
+ name = 'slth'
7
+
8
+ def ready(self):
9
+ settings.SLOTH = True
@@ -1,5 +1,8 @@
1
1
  from uuid import uuid1
2
- from django.db.models import Model as DjangoModel
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
- ENDPOINTS[cls.__name__.lower()] = cls
66
- if "AdminEndpoint" in bases_names[0:1]:
67
- model = cls.__orig_bases__[0].__args__[0]
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
- return f"/api/{cls.get_api_name()}/{arg}/"
238
- return f"/api/{cls.get_api_name()}/"
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
- pattern = "{}/".format(cls.get_api_name())
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
- @classmethod
275
- def get_api_metadata(cls, request, base_url, pk=None):
276
- action_name = cls.get_metadata("verbose_name")
277
- icon = cls.get_metadata("icon")
278
- modal = cls.get_metadata("modal")
279
- if cls.is_child():
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, f"/api/{cls.get_api_name()}/")
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=cls.get_api_name(),
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#.contextualize(self.request)
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 = "Visualizar"
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 endpoint in APPLICATON["dashboard"]["boxes"]:
745
- cls = ENDPOINTS[endpoint]
746
- if cls().contextualize(self.request).check_permission():
747
- icon = cls.get_metadata("icon", "check")
748
- label = cls.get_metadata("verbose_name")
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 endpoint in APPLICATON["dashboard"]["top"]:
755
- cls = ENDPOINTS[endpoint]
756
- if cls.instantiate(
757
- self.request, self.request.user
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 endpoint in APPLICATON["dashboard"]["center"]:
765
- cls = ENDPOINTS[endpoint]
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 PushSubscriptions(ListEndpoint[PushSubscription]):
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
- if cls.instantiate(self.request, self).check_permission():
389
+ endpoint = cls.instantiate(self.request, self)
390
+ if endpoint.check_permission():
390
391
  data.update(
391
- action=cls.get_api_metadata(
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
+ ]
@@ -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
- msg.attach_alternative(self.content, "text/html")
167
- if self.sent_at is None:
168
- msg.send(fail_silently=True)
169
- self.sent_at = datetime.now()
170
- super().save(*args, **kwargs)
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 ]*\d+', self.content)]
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\w\d ]*trailer', self.content[self.startxref[0]:]):
51
- for offset in sum([[int(n) for n in re.findall(b'\d{10}', xref) if int(n)]], []):
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 \d+ \d+ R', self.trailer)[-1][6:-2].decode()
55
- self.size = int(re.findall(b'/Size \d+', self.trailer)[-1][5:].strip())
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
- if cls.instantiate(self.request, self).check_permission():
265
- action = cls.get_api_metadata(self.request, base_url)
266
- action['name'] = action['name'].replace(" {}".format(self.model._meta.verbose_name.title()), "")
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
- if cls.instantiate(self.request, obj).check_permission():
361
- action = cls.get_api_metadata(self.request, base_url, obj.pk)
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())