slthcore 0.4.4__tar.gz → 0.4.6__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 (112) hide show
  1. {slthcore-0.4.4/slthcore.egg-info → slthcore-0.4.6}/PKG-INFO +1 -1
  2. {slthcore-0.4.4 → slthcore-0.4.6}/setup.py +1 -1
  3. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/__init__.py +20 -4
  4. slthcore-0.4.6/slth/endpoints/whatsappnotification.py +52 -0
  5. {slthcore-0.4.4 → slthcore-0.4.6}/slth/factory.py +3 -0
  6. {slthcore-0.4.4 → slthcore-0.4.6}/slth/forms.py +6 -2
  7. slthcore-0.4.6/slth/management/commands/worker.py +28 -0
  8. slthcore-0.4.6/slth/migrations/0013_whatsappnotification.py +35 -0
  9. {slthcore-0.4.4 → slthcore-0.4.6}/slth/models.py +110 -25
  10. {slthcore-0.4.4 → slthcore-0.4.6}/slth/notifications.py +12 -0
  11. {slthcore-0.4.4 → slthcore-0.4.6}/slth/queryset.py +0 -1
  12. {slthcore-0.4.4 → slthcore-0.4.6}/slth/serializer.py +6 -3
  13. {slthcore-0.4.4 → slthcore-0.4.6}/slth/static/css/slth.css +8 -0
  14. slthcore-0.4.6/slth/static/images/placeholder.png +0 -0
  15. {slthcore-0.4.4 → slthcore-0.4.6}/slth/static/js/slth.min.js +6 -6
  16. {slthcore-0.4.4 → slthcore-0.4.6}/slth/views.py +3 -13
  17. {slthcore-0.4.4 → slthcore-0.4.6/slthcore.egg-info}/PKG-INFO +1 -1
  18. {slthcore-0.4.4 → slthcore-0.4.6}/slthcore.egg-info/SOURCES.txt +3 -0
  19. slthcore-0.4.4/slth/management/commands/worker.py +0 -19
  20. {slthcore-0.4.4 → slthcore-0.4.6}/MANIFEST.in +0 -0
  21. {slthcore-0.4.4 → slthcore-0.4.6}/setup.cfg +0 -0
  22. {slthcore-0.4.4 → slthcore-0.4.6}/slth/__init__.py +0 -0
  23. {slthcore-0.4.4 → slthcore-0.4.6}/slth/apps.py +0 -0
  24. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/configure/__main__.py +0 -0
  25. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/__main__.py +0 -0
  26. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/__pycache__/__main__.cpython-312.pyc +0 -0
  27. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/.DS_Store +0 -0
  28. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/.gitignore +0 -0
  29. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/__init__.py +0 -0
  30. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/asgi.py +0 -0
  31. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/endpoints/__init__.py +0 -0
  32. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/models.py +0 -0
  33. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/settings.py +0 -0
  34. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/tests.py +0 -0
  35. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/urls.py +0 -0
  36. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/wsgi.py +0 -0
  37. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/application.yml +0 -0
  38. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/entrypoint.sh +0 -0
  39. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/manage.py +0 -0
  40. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/requirements.txt +0 -0
  41. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/base.env +0 -0
  42. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/docker-compose.yml +0 -0
  43. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/frontend/package.json +0 -0
  44. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/frontend/src/main.jsx +0 -0
  45. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/frontend/vite.config.js +0 -0
  46. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/local.env +0 -0
  47. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/run.sh +0 -0
  48. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/selenium/run.sh +0 -0
  49. {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/test.sh +0 -0
  50. {slthcore-0.4.4 → slthcore-0.4.6}/slth/components.py +0 -0
  51. {slthcore-0.4.4 → slthcore-0.4.6}/slth/db/__init__.py +0 -0
  52. {slthcore-0.4.4 → slthcore-0.4.6}/slth/db/generic.py +0 -0
  53. {slthcore-0.4.4 → slthcore-0.4.6}/slth/db/models.py +0 -0
  54. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/auth.py +0 -0
  55. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/deletion.py +0 -0
  56. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/dev.py +0 -0
  57. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/email.py +0 -0
  58. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/job.py +0 -0
  59. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/log.py +0 -0
  60. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/profile.py +0 -0
  61. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/pushsubscription.py +0 -0
  62. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/report.py +0 -0
  63. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/role.py +0 -0
  64. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/task.py +0 -0
  65. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/timezone.py +0 -0
  66. {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/user.py +0 -0
  67. {slthcore-0.4.4 → slthcore-0.4.6}/slth/exceptions.py +0 -0
  68. {slthcore-0.4.4 → slthcore-0.4.6}/slth/management/__init__.py +0 -0
  69. {slthcore-0.4.4 → slthcore-0.4.6}/slth/management/commands/__init__.py +0 -0
  70. {slthcore-0.4.4 → slthcore-0.4.6}/slth/management/commands/integration_test.py +0 -0
  71. {slthcore-0.4.4 → slthcore-0.4.6}/slth/management/commands/sync.py +0 -0
  72. {slthcore-0.4.4 → slthcore-0.4.6}/slth/middleware/__init__.py +0 -0
  73. {slthcore-0.4.4 → slthcore-0.4.6}/slth/middleware/timezone.py +0 -0
  74. {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0001_initial.py +0 -0
  75. {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0002_email_role_pushsubscription_error.py +0 -0
  76. {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0003_rename_photo_profile_alter_profile_options.py +0 -0
  77. {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0004_alter_profile_photo.py +0 -0
  78. {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0005_alter_profile_photo.py +0 -0
  79. {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0006_user.py +0 -0
  80. {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0007_deletion_log.py +0 -0
  81. {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0008_alter_deletion_datetime_alter_log_datetime.py +0 -0
  82. {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0009_remove_email_from_email_email_action_email_attempt_and_more.py +0 -0
  83. {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0010_email_key_alter_email_action_alter_email_attempt_and_more.py +0 -0
  84. {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0011_usertimezone.py +0 -0
  85. {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0012_timezone_remove_usertimezone_key_and_more.py +0 -0
  86. {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/__init__.py +0 -0
  87. {slthcore-0.4.4 → slthcore-0.4.6}/slth/oauth.py +0 -0
  88. {slthcore-0.4.4 → slthcore-0.4.6}/slth/pdf/__init__.py +0 -0
  89. {slthcore-0.4.4 → slthcore-0.4.6}/slth/pdf/tests.py +0 -0
  90. {slthcore-0.4.4 → slthcore-0.4.6}/slth/permissions.py +0 -0
  91. {slthcore-0.4.4 → slthcore-0.4.6}/slth/printer.py +0 -0
  92. {slthcore-0.4.4 → slthcore-0.4.6}/slth/roles.py +0 -0
  93. {slthcore-0.4.4 → slthcore-0.4.6}/slth/selenium/__init__.py +0 -0
  94. {slthcore-0.4.4 → slthcore-0.4.6}/slth/selenium/browser.py +0 -0
  95. {slthcore-0.4.4 → slthcore-0.4.6}/slth/static/.DS_Store +0 -0
  96. {slthcore-0.4.4 → slthcore-0.4.6}/slth/static/css/.DS_Store +0 -0
  97. {slthcore-0.4.4 → slthcore-0.4.6}/slth/static/js/index.min.js +0 -0
  98. {slthcore-0.4.4 → slthcore-0.4.6}/slth/static/js/react.min.js +0 -0
  99. {slthcore-0.4.4 → slthcore-0.4.6}/slth/statistics.py +0 -0
  100. {slthcore-0.4.4 → slthcore-0.4.6}/slth/tasks.py +0 -0
  101. {slthcore-0.4.4 → slthcore-0.4.6}/slth/templates/email.html +0 -0
  102. {slthcore-0.4.4 → slthcore-0.4.6}/slth/templates/index.html +0 -0
  103. {slthcore-0.4.4 → slthcore-0.4.6}/slth/templates/report.html +0 -0
  104. {slthcore-0.4.4 → slthcore-0.4.6}/slth/templates/service-worker.js +0 -0
  105. {slthcore-0.4.4 → slthcore-0.4.6}/slth/templates/signature.html +0 -0
  106. {slthcore-0.4.4 → slthcore-0.4.6}/slth/tests.py +0 -0
  107. {slthcore-0.4.4 → slthcore-0.4.6}/slth/threadlocal.py +0 -0
  108. {slthcore-0.4.4 → slthcore-0.4.6}/slth/tz.py +0 -0
  109. {slthcore-0.4.4 → slthcore-0.4.6}/slth/urls.py +0 -0
  110. {slthcore-0.4.4 → slthcore-0.4.6}/slth/utils.py +0 -0
  111. {slthcore-0.4.4 → slthcore-0.4.6}/slthcore.egg-info/dependency_links.txt +0 -0
  112. {slthcore-0.4.4 → slthcore-0.4.6}/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.4
3
+ Version: 0.4.6
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.4.4',
8
+ version='0.4.6',
9
9
  packages=find_packages(),
10
10
  install_requires=install_requires,
11
11
  include_package_data=True,
@@ -1,4 +1,5 @@
1
1
  import io
2
+ import json
2
3
  import inspect
3
4
  from ..models import Log
4
5
  from django.apps import apps
@@ -11,6 +12,7 @@ from django.http import JsonResponse, HttpResponse
11
12
  from ..factory import FormFactory
12
13
  from django.core.exceptions import ValidationError
13
14
  from slth import forms
15
+ from slth.models import Role
14
16
  from django.db.models import Model
15
17
  from datetime import datetime
16
18
  from django.template.loader import render_to_string
@@ -45,6 +47,7 @@ class ApiResponse(JsonResponse):
45
47
  self["Access-Control-Allow-Headers"] = "*"
46
48
  self["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS, PUT, DELETE, PATCH"
47
49
  self["Access-Control-Max-Age"] = "600"
50
+ # print(json.dumps(args[0], indent=1, ensure_ascii=False))
48
51
 
49
52
 
50
53
  class EnpointMetaclass(type):
@@ -82,7 +85,7 @@ class Endpoint(metaclass=EnpointMetaclass):
82
85
  return self
83
86
 
84
87
  def objects(self, model):
85
- return apps.get_model(model).objects
88
+ return apps.get_model(model).objects.contextualize(self.request)
86
89
 
87
90
  def get(self):
88
91
  fields = []
@@ -105,7 +108,7 @@ class Endpoint(metaclass=EnpointMetaclass):
105
108
  return True
106
109
  for name in names:
107
110
  if (
108
- self.objects("slth.role")
111
+ Role.objects
109
112
  .filter(username=self.request.user.username, name=name)
110
113
  .exists()
111
114
  ):
@@ -151,7 +154,7 @@ class Endpoint(metaclass=EnpointMetaclass):
151
154
  data = data.contextualize(self.request).settitle(title)
152
155
  elif isinstance(data, FormFactory):
153
156
  form = self.getform(data.settitle(title).form(self))
154
- if self.request.method == "POST" or (title and self.request.GET.get("form") == title):
157
+ if self.request.method == "POST" or (title and self.request.GET.get("form") == form._key):
155
158
  try:
156
159
  if isinstance(self, DeleteEndpoint):
157
160
  return self.post()
@@ -177,6 +180,7 @@ class Endpoint(metaclass=EnpointMetaclass):
177
180
  output = self.process()
178
181
  if isinstance(output, QuerySet) or isinstance(output, Serializer):
179
182
  output.base_url = self.base_url
183
+ output = output.contextualize(self.request)
180
184
  if isinstance(output, Task):
181
185
  job = Job.objects.create(task=output)
182
186
  output = Response(f'Tarefa {job.id} iniciada.', task=job.id)
@@ -291,7 +295,16 @@ class Endpoint(metaclass=EnpointMetaclass):
291
295
  return default if value is None else value
292
296
 
293
297
  def get_verbose_name(self):
294
- return self.get_metadata("verbose_name")
298
+ return self.get_metadata("verbose_name", self.get_default_verbose_name())
299
+
300
+ def get_submit_label(self):
301
+ return self.get_metadata("submit_label", "Enviar")
302
+
303
+ def get_submit_icon(self):
304
+ return self.get_metadata("submit_icon", "chevron-right")
305
+
306
+ def get_default_verbose_name(self):
307
+ return type(self).__name__
295
308
 
296
309
  def get_icon(self):
297
310
  return self.get_metadata("icon")
@@ -337,6 +350,9 @@ class ModelEndpoint(Endpoint):
337
350
 
338
351
  def get_icon(self):
339
352
  return super().get_icon() or getattr(self.model._meta, 'icon', None)
353
+
354
+ def get_default_verbose_name(self):
355
+ return type(self).__name__.replace(self.model.__name__, f' {self.model._meta.verbose_name}')
340
356
 
341
357
 
342
358
  class QuerySetEndpoint(Generic[T], ModelEndpoint):
@@ -0,0 +1,52 @@
1
+ from slth import endpoints
2
+ from ..models import WhatsappNotification
3
+
4
+
5
+ class WhatsappNotifications(endpoints.ListEndpoint[WhatsappNotification]):
6
+ def get(self):
7
+ return super().get().actions('whatsappnotification.send')
8
+ def check_permission(self):
9
+ return self.check_role()
10
+
11
+
12
+ class Add(endpoints.AddEndpoint[WhatsappNotification]):
13
+ class Meta:
14
+ modal = True
15
+ icon = 'message'
16
+
17
+ def check_permission(self):
18
+ return self.check_role()
19
+
20
+ def get_verbose_name(self):
21
+ return 'Enviar Whatsapp'
22
+
23
+
24
+ class Edit(endpoints.EditEndpoint[WhatsappNotification]):
25
+ def check_permission(self):
26
+ return self.check_role()
27
+
28
+
29
+ class Delete(endpoints.DeleteEndpoint[WhatsappNotification]):
30
+ def check_permission(self):
31
+ return self.check_role()
32
+
33
+
34
+ class View(endpoints.ViewEndpoint[WhatsappNotification]):
35
+ def check_permission(self):
36
+ return self.check_role()
37
+
38
+
39
+ class Send(endpoints.InstanceEndpoint[WhatsappNotification]):
40
+ class Meta:
41
+ icon = "chevron-right"
42
+ verbose_name = "Send"
43
+
44
+ def check_permission(self):
45
+ return self.check_role()
46
+
47
+ def get(self):
48
+ return self.formfactory().fields()
49
+
50
+ def post(self):
51
+ self.instance.send()
52
+ return super().post()
@@ -39,6 +39,7 @@ class FormFactory:
39
39
  return self
40
40
 
41
41
  def fields(self, *names, **values) -> 'FormFactory':
42
+ names = names + tuple(values.keys())
42
43
  not_str = {name for name in names if not isinstance(name, str)}
43
44
  if not_str:
44
45
  self.fieldset('Dados Gerais', names)
@@ -126,6 +127,8 @@ class FormFactory:
126
127
  form._dispose = self._dispose
127
128
  form._image = self._image
128
129
  form._autosubmit = self._autosubmit
130
+ form._submit_label = endpoint.get_submit_label()
131
+ form._submit_icon = endpoint.get_submit_icon()
129
132
  for name in self._fieldlist:
130
133
  if name not in form.fields:
131
134
  form.fields[name] = getattr(endpoint, name)
@@ -4,6 +4,7 @@ import datetime
4
4
  from django.forms import *
5
5
  from django.utils.translation import gettext_lazy as _
6
6
  from django.forms.models import ModelChoiceIterator, ModelMultipleChoiceField
7
+ from django.forms import fields
7
8
  from django.db.models import Model, QuerySet, Manager
8
9
  from .models import Token, Profile
9
10
  from django.db import transaction
@@ -156,7 +157,6 @@ class FormMixin:
156
157
  field_name = self.request.GET.get("on_change")
157
158
  if field_name:
158
159
  raise JsonResponseException(self.controller.on_change(field_name))
159
-
160
160
  data = dict(
161
161
  type="form",
162
162
  key=self._key,
@@ -168,6 +168,8 @@ class FormMixin:
168
168
  info=self._info,
169
169
  image=self._image,
170
170
  autosubmit=self._autosubmit,
171
+ submit_label=self._submit_label,
172
+ submit_icon=self._submit_icon,
171
173
  )
172
174
  data.update(
173
175
  controls=self.controller.controls, width=self.get_metadata("width", "100%")
@@ -340,7 +342,7 @@ class FormMixin:
340
342
  if word in name:
341
343
  data.update(type="password")
342
344
  pick = getattr(field, "pick", False)
343
- if isinstance(field, CharField) or isinstance(field, IntegerField):
345
+ if isinstance(field, fields.CharField) or isinstance(field, fields.IntegerField):
344
346
  mask = getattr(field, "mask", None)
345
347
  if mask:
346
348
  data.update(mask=mask)
@@ -528,6 +530,8 @@ class Form(DjangoForm, FormMixin):
528
530
  self._method = "POST"
529
531
  self._key = self._title.lower()
530
532
  self._autosubmit = None
533
+ self._submit_label = "Enviar"
534
+ self._submit_icon = "chevron-right"
531
535
 
532
536
  self.fieldsets = {}
533
537
  self.fields = {}
@@ -0,0 +1,28 @@
1
+ import time
2
+ from django.apps import apps
3
+ from django.conf import settings
4
+ from django.core.management.commands.test import Command
5
+
6
+
7
+ class Command(Command):
8
+ def add_arguments(self, parser):
9
+ super().add_arguments(parser)
10
+ parser.add_argument("--preview", action="store_true", help="Preview")
11
+
12
+ def handle(self, *args, **options):
13
+ seconds = 10 if settings.DEBUG else 60
14
+ preview = options.get("preview")
15
+ try:
16
+ while True:
17
+ print('Checking whatsapp notifications...')
18
+ apps.get_model("slth", "whatsappnotification").objects.send(preview)
19
+ print('Checking email notifications...')
20
+ apps.get_model("slth", "email").objects.send(preview)
21
+ print('Checking push notifications...')
22
+ apps.get_model("slth", "pushnotification").objects.send(preview)
23
+ print('Checking jobs...')
24
+ apps.get_model("slth", "job").objects.execute(preview)
25
+ print(f'Sleeping for {seconds} seconds...')
26
+ time.sleep(seconds)
27
+ except KeyboardInterrupt:
28
+ pass
@@ -0,0 +1,35 @@
1
+ # Generated by Django 5.1.3 on 2025-01-23 14:16
2
+
3
+ import slth
4
+ import slth.db.models
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ('slth', '0012_timezone_remove_usertimezone_key_and_more'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.CreateModel(
16
+ name='WhatsappNotification',
17
+ fields=[
18
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19
+ ('to', slth.db.models.CharField(max_length=255, verbose_name='To')),
20
+ ('title', slth.db.models.CharField(max_length=255, verbose_name='Subject')),
21
+ ('message', slth.db.models.TextField(verbose_name='Content')),
22
+ ('url', slth.db.models.CharField(blank=True, max_length=255, null=True, verbose_name='URL')),
23
+ ('send_at', slth.db.models.DateTimeField(blank=True, null=True, verbose_name='Send at')),
24
+ ('sent_at', slth.db.models.DateTimeField(blank=True, null=True, verbose_name='Sent at')),
25
+ ('attempt', slth.db.models.IntegerField(blank=True, default=0, verbose_name='Attempt')),
26
+ ('error', slth.db.models.TextField(blank=True, null=True, verbose_name='Error')),
27
+ ('key', slth.db.models.CharField(blank=True, db_index=True, max_length=255, null=True)),
28
+ ],
29
+ options={
30
+ 'verbose_name': 'Whatsapp Notification',
31
+ 'verbose_name_plural': 'Whatsapp Notifications',
32
+ },
33
+ bases=(models.Model, slth.ModelMixin),
34
+ ),
35
+ ]
@@ -16,7 +16,7 @@ from django.utils.html import strip_tags
16
16
  from django.core import serializers
17
17
  from django.template.loader import render_to_string
18
18
  from django.core.mail import EmailMultiAlternatives
19
- from .notifications import send_push_web_notification
19
+ from .notifications import send_push_web_notification, send_whatsapp_notification
20
20
  from .components import HtmlContent
21
21
  from slth import APPLICATON
22
22
  from django.utils import timezone
@@ -222,18 +222,19 @@ class JobManager(models.Manager):
222
222
  task_runner = super().create(name=name or uuid1().hex, task=task, start=start)
223
223
  return task_runner
224
224
 
225
- def execute(self):
225
+ def execute(self, preview=False):
226
226
  qs = self.filter(finish__isnull=True, attempt__lte=3, canceled=False)
227
227
  qs = qs.filter(start__isnull=True) | qs.filter(start__lt=datetime.now())
228
228
  for obj in qs:
229
229
  try:
230
230
  obj.task.job = obj
231
231
  print(f'Executing job {obj.id}...')
232
- obj.start = datetime.now()
233
- obj.attempt += 1
234
- obj.task.run()
235
- obj.finish = datetime.now()
236
- print(f'Job {obj.id} completed with success.')
232
+ if not preview:
233
+ obj.start = datetime.now()
234
+ obj.attempt += 1
235
+ obj.task.run()
236
+ obj.finish = datetime.now()
237
+ print(f'Job {obj.id} completed with success.')
237
238
  except Exception as e:
238
239
  traceback.print_exc()
239
240
  obj.error = str(e)
@@ -243,7 +244,8 @@ class JobManager(models.Manager):
243
244
  obj.finish = datetime.now()
244
245
  print(f'Job {obj.id} completed with error ({obj.error}).')
245
246
  finally:
246
- obj.save()
247
+ if not preview:
248
+ obj.save()
247
249
 
248
250
 
249
251
  class Job(models.Model):
@@ -280,18 +282,23 @@ class Job(models.Model):
280
282
 
281
283
  class PushNotificationQuerySet(models.QuerySet):
282
284
  def all(self):
283
- return self
285
+ return self.fields('title', 'to', 'message', 'send_at', 'sent_at').search('title', 'message', 'to').filters('send_at__gte', 'send_at__lte', 'sent_at__gte', 'sent_at__lte').rows().order_by('-id')
284
286
 
285
287
  def create(self, to, title, message, send_at=None, action=None, url=None, key=None):
286
- return super().create(
288
+ if key:
289
+ self.filter(key=key, sent_at__isnull=False).delete()
290
+ notification = super().create(
287
291
  to=to, title=title, message=message, send_at=send_at, url=url, key=key
288
292
  )
293
+ if send_at and send_at <= datetime.now():
294
+ notification.send()
295
+ return notification
289
296
 
290
- def send(self):
297
+ def send(self, preview=False):
291
298
  qs = self.filter(attempt__lte=3, sent_at__isnull=True)
292
299
  qs = qs.filter(send_at__isnull=True) | qs.filter(send_at__lte=datetime.now())
293
300
  for obj in qs:
294
- obj.send()
301
+ obj.send(preview=preview)
295
302
 
296
303
 
297
304
  class PushNotification(models.Model):
@@ -316,19 +323,92 @@ class PushNotification(models.Model):
316
323
  def __str__(self):
317
324
  return f'Push Notification {self.title} to {self.to}'
318
325
 
319
- def send(self):
326
+ def send(self, preview=False):
320
327
  self.attempt = self.attempt + 1
321
328
  try:
322
329
  print(f'Sending notification "{self.title}" to "{self.to}"...')
323
- send_push_web_notification(self.to, self.title, self.message, url=self.url)
324
- self.sent_at = datetime.now()
325
- print(f'Notification "{self.title}" to "{self.to}" sent with success.')
330
+ if not preview:
331
+ send_push_web_notification(self.to, self.title, self.message, url=self.url)
332
+ self.sent_at = datetime.now()
333
+ print(f'Notification "{self.title}" to "{self.to}" sent with success.')
326
334
  except Exception as e:
327
335
  self.error = str(e)
328
336
  print(f'Notification "{self.title}" to "{self.to}" failed during attempt {self.attempt}.')
329
337
  traceback.print_exc()
330
338
  finally:
331
- super().save()
339
+ if not preview:
340
+ super().save()
341
+
342
+
343
+ class WhatsappNotificationQuerySet(models.QuerySet):
344
+ def all(self):
345
+ return self.fields('title', 'to', 'message', 'send_at', 'sent_at').search('title', 'message', 'to').filters('send_at__gte', 'send_at__lte', 'sent_at__gte', 'sent_at__lte').rows().order_by('-id')
346
+
347
+ @meta('Enviadas')
348
+ def sent(self):
349
+ return self.filter(send_at__isnull=False)
350
+
351
+ @meta('Não-Enviadas')
352
+ def unsent(self):
353
+ return self.filter(send_at__isnull=True)
354
+
355
+ def create(self, to, title, message, send_at=None, url=None, key=None):
356
+ if key:
357
+ self.filter(key=key, sent_at__isnull=False).delete()
358
+ notification = super().create(
359
+ to=to, title=title, message=message, send_at=send_at, url=url, key=key
360
+ )
361
+ if send_at and send_at <= datetime.now():
362
+ notification.send()
363
+ return notification
364
+
365
+ def send(self, preview=False):
366
+ qs = self.filter(attempt__lte=3, sent_at__isnull=True)
367
+ qs = qs.filter(send_at__isnull=True) | qs.filter(send_at__lte=datetime.now())
368
+ for obj in qs:
369
+ obj.send(preview=preview)
370
+
371
+
372
+ class WhatsappNotification(models.Model):
373
+ to = models.CharField(verbose_name='To')
374
+ title = models.CharField("Subject")
375
+ message = models.TextField("Content")
376
+ url = models.CharField("URL", null=True, blank=True)
377
+
378
+ send_at = models.DateTimeField("Send at", null=True, blank=True)
379
+ sent_at = models.DateTimeField("Sent at", null=True, blank=True)
380
+ attempt = models.IntegerField("Attempt", default=0, blank=True)
381
+
382
+ error = models.TextField("Error", null=True, blank=True)
383
+ key = models.CharField(max_length=255, null=True, blank=True, db_index=True)
384
+
385
+ class Meta:
386
+ verbose_name = 'Whatsapp Notification'
387
+ verbose_name_plural = 'Whatsapp Notifications'
388
+
389
+ objects = PushNotificationQuerySet()
390
+
391
+ def __str__(self):
392
+ return f'Push Notification {self.title} to {self.to}'
393
+
394
+ def send(self, preview=False):
395
+ self.attempt = self.attempt + 1
396
+ try:
397
+ print(f'Sending notification "{self.title}" to "{self.to}"...')
398
+ if not preview:
399
+ send_whatsapp_notification(self.to, self.title, self.message, url=self.url)
400
+ self.sent_at = datetime.now()
401
+ print(f'Notification "{self.title}" to "{self.to}" sent with success.')
402
+ except Exception as e:
403
+ self.error = str(e)
404
+ print(f'Notification "{self.title}" to "{self.to}" failed during attempt {self.attempt}.')
405
+ traceback.print_exc()
406
+ finally:
407
+ if not preview:
408
+ super().save()
409
+
410
+ def serializer(self):
411
+ return super().serializer().fieldset("Dados Gerais", ('subject', 'to', 'content', 'url', 'send_at'))
332
412
 
333
413
 
334
414
  class EmailManager(models.Manager):
@@ -339,15 +419,18 @@ class EmailManager(models.Manager):
339
419
  to = [to] if isinstance(to, str) else list(to)
340
420
  if key:
341
421
  self.filter(key=key, sent_at__isnull=False).delete()
342
- return super().create(
422
+ email = super().create(
343
423
  to=" ".join(to), subject=subject, content=content, send_at=send_at, action=action, url=url, key=key
344
424
  )
425
+ if send_at and send_at <= datetime.now():
426
+ email.send()
427
+ return email
345
428
 
346
- def send(self):
429
+ def send(self, preview=False):
347
430
  qs = self.filter(attempt__lte=3, sent_at__isnull=True)
348
431
  qs = qs.filter(send_at__isnull=True) | qs.filter(send_at__lte=datetime.now())
349
432
  for obj in qs:
350
- obj.send()
433
+ obj.send(preview=preview)
351
434
 
352
435
 
353
436
  class Email(models.Model):
@@ -373,7 +456,7 @@ class Email(models.Model):
373
456
  def __str__(self):
374
457
  return self.subject
375
458
 
376
- def send(self):
459
+ def send(self, preview=False):
377
460
  to = [email.strip() for email in self.to.split()]
378
461
  msg = EmailMultiAlternatives(self.subject, strip_tags(self.content), settings.DEFAULT_FROM_EMAIL, to)
379
462
  html = render_to_string('email.html', dict(email=self, title=APPLICATON['title']))
@@ -381,15 +464,17 @@ class Email(models.Model):
381
464
  self.attempt = self.attempt + 1
382
465
  try:
383
466
  print(f'Sending e-mail "{self.subject}" to "{self.to}"...')
384
- msg.send(fail_silently=False)
385
- self.sent_at = datetime.now()
386
- print(f'E-mail "{self.subject}" to "{self.to}" sent with success.')
467
+ if not preview:
468
+ msg.send(fail_silently=False)
469
+ self.sent_at = datetime.now()
470
+ print(f'E-mail "{self.subject}" to "{self.to}" sent with success.')
387
471
  except Exception as e:
388
472
  self.error = str(e)
389
473
  print(f'Email "{self.subject}" to "{self.to}" failed during attempt {self.attempt}.')
390
474
  traceback.print_exc()
391
475
  finally:
392
- super().save()
476
+ if not preview:
477
+ super().save()
393
478
 
394
479
  def formfactory(self):
395
480
  return super().formfactory().fieldset(
@@ -1,8 +1,10 @@
1
1
  import json
2
2
  import os
3
+ import requests
3
4
  from pywebpush import webpush
4
5
  from slth import APPLICATON
5
6
 
7
+
6
8
  def send_push_web_notification(user, title, message, url=None, icon=None):
7
9
  icon = icon or APPLICATON['icon']
8
10
  for subscription in user.pushsubscription_set.all():
@@ -14,3 +16,13 @@ def send_push_web_notification(user, title, message, url=None, icon=None):
14
16
  vapid_claims={"sub": "mailto:admin@admin.com"}
15
17
  )
16
18
  print(data)
19
+
20
+
21
+ def send_whatsapp_notification(to, title, messsage, url=None):
22
+ api_url = 'https://whatsapp.aplicativo.click/send'
23
+ headers={"Content-Type": "application/json", "Authorization": "Token undefined"}
24
+ to = to.replace('(', '').replace(')', '').replace('-', '').replace(' ', '')
25
+ to = '55{}{}@c.us'.format(to[0:2], to[3:])
26
+ data = {"to": to, "message": "*{}*\n{}\n{}".format(title, messsage, url or "")}
27
+ response = requests.post(api_url, headers=headers, json=data)
28
+ response.status_code == 200
@@ -168,7 +168,6 @@ class QuerySet(models.QuerySet):
168
168
  raise JsonResponseException(endpoint.serialize())
169
169
  raise Exception()
170
170
 
171
-
172
171
  filters = qs.metadata.get('filters', ())
173
172
  for lookup in filters:
174
173
  if request and lookup in request.GET and lookup != choices_field_name:
@@ -9,7 +9,7 @@ from django.db.models import Model, QuerySet, Manager
9
9
  from django.utils.text import slugify
10
10
  from .exceptions import JsonResponseException
11
11
  from .utils import absolute_url, build_url
12
- from .components import Image, FileLink, FileViewer
12
+ from .components import Image, FileLink, FileViewer, Banner
13
13
  from django.db.models.fields.files import ImageFieldFile, FieldFile
14
14
 
15
15
 
@@ -36,7 +36,7 @@ def serialize(obj, primitive=False, request=None, logging=False):
36
36
  return None
37
37
  elif isinstance(obj, dict):
38
38
  if request:
39
- if isinstance(obj, Image):
39
+ if isinstance(obj, Image) or isinstance(obj, Banner):
40
40
  if isinstance(obj['src'], ImageFieldFile):
41
41
  obj['src'] = build_url(request, obj['src'].url) if obj['src'] else None
42
42
  elif isinstance(obj, FileLink) or isinstance(obj, FileViewer):
@@ -316,12 +316,15 @@ class Serializer:
316
316
  returned = endpoint.process()
317
317
  if isinstance(returned, QuerySet) or isinstance(returned, Serializer):
318
318
  returned.base_url = endpoint.base_url
319
+ returned = returned.contextualize(self.request)
319
320
  path = self.path + [key]
320
321
  if wrap:
321
322
  data = dict(type='fieldset', key=key, title=title, url=None, data=serialize(returned))
322
323
  else:
323
324
  data = serialize(returned)
324
- data['title'] = title
325
+ #if 'title' in data and data['title'] is None:
326
+ if lazy:
327
+ data['title'] = title
325
328
  if isinstance(data, dict):
326
329
  data['url'] = absolute_url(self.request, 'only={}'.format('__'.join(path)))
327
330
  if leaf: raise JsonResponseException(data)
@@ -70,6 +70,14 @@ input[type="checkbox"], input[type="radio"]{
70
70
  text-align: center;
71
71
  }
72
72
 
73
+ #output h1{
74
+ font-size: 1.5rem;
75
+ }
76
+
77
+ .tabContent h1{
78
+ display: none;
79
+ }
80
+
73
81
 
74
82
  /* .object-viewer .object-fieldset {
75
83
  border: solid 1px #2c3e50;