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.
- {slthcore-0.4.4/slthcore.egg-info → slthcore-0.4.6}/PKG-INFO +1 -1
- {slthcore-0.4.4 → slthcore-0.4.6}/setup.py +1 -1
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/__init__.py +20 -4
- slthcore-0.4.6/slth/endpoints/whatsappnotification.py +52 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/factory.py +3 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/forms.py +6 -2
- slthcore-0.4.6/slth/management/commands/worker.py +28 -0
- slthcore-0.4.6/slth/migrations/0013_whatsappnotification.py +35 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/models.py +110 -25
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/notifications.py +12 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/queryset.py +0 -1
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/serializer.py +6 -3
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/static/css/slth.css +8 -0
- slthcore-0.4.6/slth/static/images/placeholder.png +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/static/js/slth.min.js +6 -6
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/views.py +3 -13
- {slthcore-0.4.4 → slthcore-0.4.6/slthcore.egg-info}/PKG-INFO +1 -1
- {slthcore-0.4.4 → slthcore-0.4.6}/slthcore.egg-info/SOURCES.txt +3 -0
- slthcore-0.4.4/slth/management/commands/worker.py +0 -19
- {slthcore-0.4.4 → slthcore-0.4.6}/MANIFEST.in +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/setup.cfg +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/__init__.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/apps.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/configure/__main__.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/__main__.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/__pycache__/__main__.cpython-312.pyc +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/.DS_Store +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/.gitignore +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/__init__.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/asgi.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/endpoints/__init__.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/models.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/settings.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/tests.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/urls.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/api/wsgi.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/application.yml +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/entrypoint.sh +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/manage.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/backend/requirements.txt +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/base.env +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/docker-compose.yml +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/frontend/package.json +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/frontend/src/main.jsx +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/frontend/vite.config.js +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/local.env +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/run.sh +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/selenium/run.sh +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/cmd/init/boilerplate/test.sh +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/components.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/db/__init__.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/db/generic.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/db/models.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/auth.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/deletion.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/dev.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/email.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/job.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/log.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/profile.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/pushsubscription.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/report.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/role.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/task.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/timezone.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/endpoints/user.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/exceptions.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/management/__init__.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/management/commands/__init__.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/management/commands/integration_test.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/management/commands/sync.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/middleware/__init__.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/middleware/timezone.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0001_initial.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0002_email_role_pushsubscription_error.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0003_rename_photo_profile_alter_profile_options.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0004_alter_profile_photo.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0005_alter_profile_photo.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0006_user.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0007_deletion_log.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0008_alter_deletion_datetime_alter_log_datetime.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0009_remove_email_from_email_email_action_email_attempt_and_more.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0010_email_key_alter_email_action_alter_email_attempt_and_more.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0011_usertimezone.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/0012_timezone_remove_usertimezone_key_and_more.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/migrations/__init__.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/oauth.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/pdf/__init__.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/pdf/tests.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/permissions.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/printer.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/roles.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/selenium/__init__.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/selenium/browser.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/static/.DS_Store +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/static/css/.DS_Store +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/static/js/index.min.js +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/static/js/react.min.js +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/statistics.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/tasks.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/templates/email.html +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/templates/index.html +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/templates/report.html +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/templates/service-worker.js +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/templates/signature.html +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/tests.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/threadlocal.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/tz.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/urls.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slth/utils.py +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slthcore.egg-info/dependency_links.txt +0 -0
- {slthcore-0.4.4 → slthcore-0.4.6}/slthcore.egg-info/top_level.txt +0 -0
|
@@ -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
|
-
|
|
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") ==
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
|
|
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']
|
|
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)
|
|
Binary file
|