slthcore 0.4.5__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.5/slthcore.egg-info → slthcore-0.4.6}/PKG-INFO +1 -1
  2. {slthcore-0.4.5 → slthcore-0.4.6}/setup.py +1 -1
  3. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/__init__.py +17 -2
  4. slthcore-0.4.6/slth/endpoints/whatsappnotification.py +52 -0
  5. {slthcore-0.4.5 → slthcore-0.4.6}/slth/factory.py +3 -0
  6. {slthcore-0.4.5 → slthcore-0.4.6}/slth/forms.py +4 -0
  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.5 → slthcore-0.4.6}/slth/models.py +110 -25
  10. {slthcore-0.4.5 → slthcore-0.4.6}/slth/notifications.py +12 -0
  11. {slthcore-0.4.5 → slthcore-0.4.6}/slth/serializer.py +2 -2
  12. slthcore-0.4.6/slth/static/images/placeholder.png +0 -0
  13. {slthcore-0.4.5 → slthcore-0.4.6}/slth/static/js/slth.min.js +6 -6
  14. {slthcore-0.4.5 → slthcore-0.4.6}/slth/views.py +2 -12
  15. {slthcore-0.4.5 → slthcore-0.4.6/slthcore.egg-info}/PKG-INFO +1 -1
  16. {slthcore-0.4.5 → slthcore-0.4.6}/slthcore.egg-info/SOURCES.txt +3 -0
  17. slthcore-0.4.5/slth/management/commands/worker.py +0 -19
  18. {slthcore-0.4.5 → slthcore-0.4.6}/MANIFEST.in +0 -0
  19. {slthcore-0.4.5 → slthcore-0.4.6}/setup.cfg +0 -0
  20. {slthcore-0.4.5 → slthcore-0.4.6}/slth/__init__.py +0 -0
  21. {slthcore-0.4.5 → slthcore-0.4.6}/slth/apps.py +0 -0
  22. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/configure/__main__.py +0 -0
  23. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/__main__.py +0 -0
  24. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/__pycache__/__main__.cpython-312.pyc +0 -0
  25. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/.DS_Store +0 -0
  26. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/.gitignore +0 -0
  27. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/__init__.py +0 -0
  28. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/asgi.py +0 -0
  29. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/endpoints/__init__.py +0 -0
  30. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/models.py +0 -0
  31. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/settings.py +0 -0
  32. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/tests.py +0 -0
  33. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/urls.py +0 -0
  34. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/wsgi.py +0 -0
  35. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/application.yml +0 -0
  36. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/entrypoint.sh +0 -0
  37. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/manage.py +0 -0
  38. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/requirements.txt +0 -0
  39. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/base.env +0 -0
  40. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/docker-compose.yml +0 -0
  41. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/frontend/package.json +0 -0
  42. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/frontend/src/main.jsx +0 -0
  43. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/frontend/vite.config.js +0 -0
  44. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/local.env +0 -0
  45. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/run.sh +0 -0
  46. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/selenium/run.sh +0 -0
  47. {slthcore-0.4.5 → slthcore-0.4.6}/slth/cmd/init/boilerplate/test.sh +0 -0
  48. {slthcore-0.4.5 → slthcore-0.4.6}/slth/components.py +0 -0
  49. {slthcore-0.4.5 → slthcore-0.4.6}/slth/db/__init__.py +0 -0
  50. {slthcore-0.4.5 → slthcore-0.4.6}/slth/db/generic.py +0 -0
  51. {slthcore-0.4.5 → slthcore-0.4.6}/slth/db/models.py +0 -0
  52. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/auth.py +0 -0
  53. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/deletion.py +0 -0
  54. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/dev.py +0 -0
  55. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/email.py +0 -0
  56. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/job.py +0 -0
  57. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/log.py +0 -0
  58. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/profile.py +0 -0
  59. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/pushsubscription.py +0 -0
  60. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/report.py +0 -0
  61. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/role.py +0 -0
  62. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/task.py +0 -0
  63. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/timezone.py +0 -0
  64. {slthcore-0.4.5 → slthcore-0.4.6}/slth/endpoints/user.py +0 -0
  65. {slthcore-0.4.5 → slthcore-0.4.6}/slth/exceptions.py +0 -0
  66. {slthcore-0.4.5 → slthcore-0.4.6}/slth/management/__init__.py +0 -0
  67. {slthcore-0.4.5 → slthcore-0.4.6}/slth/management/commands/__init__.py +0 -0
  68. {slthcore-0.4.5 → slthcore-0.4.6}/slth/management/commands/integration_test.py +0 -0
  69. {slthcore-0.4.5 → slthcore-0.4.6}/slth/management/commands/sync.py +0 -0
  70. {slthcore-0.4.5 → slthcore-0.4.6}/slth/middleware/__init__.py +0 -0
  71. {slthcore-0.4.5 → slthcore-0.4.6}/slth/middleware/timezone.py +0 -0
  72. {slthcore-0.4.5 → slthcore-0.4.6}/slth/migrations/0001_initial.py +0 -0
  73. {slthcore-0.4.5 → slthcore-0.4.6}/slth/migrations/0002_email_role_pushsubscription_error.py +0 -0
  74. {slthcore-0.4.5 → slthcore-0.4.6}/slth/migrations/0003_rename_photo_profile_alter_profile_options.py +0 -0
  75. {slthcore-0.4.5 → slthcore-0.4.6}/slth/migrations/0004_alter_profile_photo.py +0 -0
  76. {slthcore-0.4.5 → slthcore-0.4.6}/slth/migrations/0005_alter_profile_photo.py +0 -0
  77. {slthcore-0.4.5 → slthcore-0.4.6}/slth/migrations/0006_user.py +0 -0
  78. {slthcore-0.4.5 → slthcore-0.4.6}/slth/migrations/0007_deletion_log.py +0 -0
  79. {slthcore-0.4.5 → slthcore-0.4.6}/slth/migrations/0008_alter_deletion_datetime_alter_log_datetime.py +0 -0
  80. {slthcore-0.4.5 → slthcore-0.4.6}/slth/migrations/0009_remove_email_from_email_email_action_email_attempt_and_more.py +0 -0
  81. {slthcore-0.4.5 → slthcore-0.4.6}/slth/migrations/0010_email_key_alter_email_action_alter_email_attempt_and_more.py +0 -0
  82. {slthcore-0.4.5 → slthcore-0.4.6}/slth/migrations/0011_usertimezone.py +0 -0
  83. {slthcore-0.4.5 → slthcore-0.4.6}/slth/migrations/0012_timezone_remove_usertimezone_key_and_more.py +0 -0
  84. {slthcore-0.4.5 → slthcore-0.4.6}/slth/migrations/__init__.py +0 -0
  85. {slthcore-0.4.5 → slthcore-0.4.6}/slth/oauth.py +0 -0
  86. {slthcore-0.4.5 → slthcore-0.4.6}/slth/pdf/__init__.py +0 -0
  87. {slthcore-0.4.5 → slthcore-0.4.6}/slth/pdf/tests.py +0 -0
  88. {slthcore-0.4.5 → slthcore-0.4.6}/slth/permissions.py +0 -0
  89. {slthcore-0.4.5 → slthcore-0.4.6}/slth/printer.py +0 -0
  90. {slthcore-0.4.5 → slthcore-0.4.6}/slth/queryset.py +0 -0
  91. {slthcore-0.4.5 → slthcore-0.4.6}/slth/roles.py +0 -0
  92. {slthcore-0.4.5 → slthcore-0.4.6}/slth/selenium/__init__.py +0 -0
  93. {slthcore-0.4.5 → slthcore-0.4.6}/slth/selenium/browser.py +0 -0
  94. {slthcore-0.4.5 → slthcore-0.4.6}/slth/static/.DS_Store +0 -0
  95. {slthcore-0.4.5 → slthcore-0.4.6}/slth/static/css/.DS_Store +0 -0
  96. {slthcore-0.4.5 → slthcore-0.4.6}/slth/static/css/slth.css +0 -0
  97. {slthcore-0.4.5 → slthcore-0.4.6}/slth/static/js/index.min.js +0 -0
  98. {slthcore-0.4.5 → slthcore-0.4.6}/slth/static/js/react.min.js +0 -0
  99. {slthcore-0.4.5 → slthcore-0.4.6}/slth/statistics.py +0 -0
  100. {slthcore-0.4.5 → slthcore-0.4.6}/slth/tasks.py +0 -0
  101. {slthcore-0.4.5 → slthcore-0.4.6}/slth/templates/email.html +0 -0
  102. {slthcore-0.4.5 → slthcore-0.4.6}/slth/templates/index.html +0 -0
  103. {slthcore-0.4.5 → slthcore-0.4.6}/slth/templates/report.html +0 -0
  104. {slthcore-0.4.5 → slthcore-0.4.6}/slth/templates/service-worker.js +0 -0
  105. {slthcore-0.4.5 → slthcore-0.4.6}/slth/templates/signature.html +0 -0
  106. {slthcore-0.4.5 → slthcore-0.4.6}/slth/tests.py +0 -0
  107. {slthcore-0.4.5 → slthcore-0.4.6}/slth/threadlocal.py +0 -0
  108. {slthcore-0.4.5 → slthcore-0.4.6}/slth/tz.py +0 -0
  109. {slthcore-0.4.5 → slthcore-0.4.6}/slth/urls.py +0 -0
  110. {slthcore-0.4.5 → slthcore-0.4.6}/slth/utils.py +0 -0
  111. {slthcore-0.4.5 → slthcore-0.4.6}/slthcore.egg-info/dependency_links.txt +0 -0
  112. {slthcore-0.4.5 → 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.5
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.5',
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):
@@ -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
  ):
@@ -292,7 +295,16 @@ class Endpoint(metaclass=EnpointMetaclass):
292
295
  return default if value is None else value
293
296
 
294
297
  def get_verbose_name(self):
295
- 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__
296
308
 
297
309
  def get_icon(self):
298
310
  return self.get_metadata("icon")
@@ -338,6 +350,9 @@ class ModelEndpoint(Endpoint):
338
350
 
339
351
  def get_icon(self):
340
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}')
341
356
 
342
357
 
343
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)
@@ -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%")
@@ -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
@@ -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):