slthcore 0.6.5__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.
- slthcore-0.6.5/MANIFEST.in +4 -0
- slthcore-0.6.5/PKG-INFO +16 -0
- slthcore-0.6.5/setup.cfg +4 -0
- slthcore-0.6.5/setup.py +27 -0
- slthcore-0.6.5/slth/__init__.py +270 -0
- slthcore-0.6.5/slth/application.py +252 -0
- slthcore-0.6.5/slth/apps.py +44 -0
- slthcore-0.6.5/slth/cmd/configure/__main__.py +88 -0
- slthcore-0.6.5/slth/cmd/init/__main__.py +24 -0
- slthcore-0.6.5/slth/cmd/init/__pycache__/__main__.cpython-312.pyc +0 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/.DS_Store +0 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/.gitignore +9 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/backend/api/__init__.py +28 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/backend/api/asgi.py +16 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/backend/api/endpoints/__init__.py +1 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/backend/api/models.py +1 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/backend/api/settings.py +175 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/backend/api/tests.py +8 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/backend/api/urls.py +14 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/backend/api/wsgi.py +16 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/backend/entrypoint.sh +3 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/backend/manage.py +22 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/backend/requirements.txt +2 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/base.env +15 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/docker-compose.yml +64 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/frontend/package.json +22 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/frontend/src/main.jsx +11 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/frontend/vite.config.js +22 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/local.env +3 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/run.sh +3 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/selenium/run.sh +2 -0
- slthcore-0.6.5/slth/cmd/init/boilerplate/test.sh +4 -0
- slthcore-0.6.5/slth/components.py +484 -0
- slthcore-0.6.5/slth/db/__init__.py +3 -0
- slthcore-0.6.5/slth/db/generic.py +98 -0
- slthcore-0.6.5/slth/db/models.py +179 -0
- slthcore-0.6.5/slth/endpoints/__init__.py +671 -0
- slthcore-0.6.5/slth/endpoints/auth.py +90 -0
- slthcore-0.6.5/slth/endpoints/deletion.py +21 -0
- slthcore-0.6.5/slth/endpoints/dev.py +18 -0
- slthcore-0.6.5/slth/endpoints/email.py +34 -0
- slthcore-0.6.5/slth/endpoints/job.py +22 -0
- slthcore-0.6.5/slth/endpoints/log.py +7 -0
- slthcore-0.6.5/slth/endpoints/profile.py +37 -0
- slthcore-0.6.5/slth/endpoints/pushsubscription.py +48 -0
- slthcore-0.6.5/slth/endpoints/report.py +16 -0
- slthcore-0.6.5/slth/endpoints/role.py +18 -0
- slthcore-0.6.5/slth/endpoints/settings.py +60 -0
- slthcore-0.6.5/slth/endpoints/task.py +12 -0
- slthcore-0.6.5/slth/endpoints/timezone.py +11 -0
- slthcore-0.6.5/slth/endpoints/user.py +63 -0
- slthcore-0.6.5/slth/endpoints/whatsappnotification.py +52 -0
- slthcore-0.6.5/slth/exceptions.py +11 -0
- slthcore-0.6.5/slth/factory.py +146 -0
- slthcore-0.6.5/slth/forms.py +791 -0
- slthcore-0.6.5/slth/geo/__init__.py +16 -0
- slthcore-0.6.5/slth/integrations/__init__.py +0 -0
- slthcore-0.6.5/slth/integrations/deepseek/__init__.py +36 -0
- slthcore-0.6.5/slth/integrations/google/__init__.py +0 -0
- slthcore-0.6.5/slth/integrations/google/gemini.py +23 -0
- slthcore-0.6.5/slth/integrations/google/places.py +41 -0
- slthcore-0.6.5/slth/integrations/google/vision.py +18 -0
- slthcore-0.6.5/slth/integrations/justvoip/__init__.py +15 -0
- slthcore-0.6.5/slth/integrations/mercadopago/__init__.py +48 -0
- slthcore-0.6.5/slth/integrations/openai/__init__.py +0 -0
- slthcore-0.6.5/slth/integrations/openai/chatgpt.py +30 -0
- slthcore-0.6.5/slth/integrations/viacep/__init__.py +6 -0
- slthcore-0.6.5/slth/integrations/whatsapp/__init__.py +9 -0
- slthcore-0.6.5/slth/management/__init__.py +0 -0
- slthcore-0.6.5/slth/management/commands/__init__.py +0 -0
- slthcore-0.6.5/slth/management/commands/api.py +90 -0
- slthcore-0.6.5/slth/management/commands/integration_test.py +27 -0
- slthcore-0.6.5/slth/management/commands/sync.py +38 -0
- slthcore-0.6.5/slth/management/commands/worker.py +28 -0
- slthcore-0.6.5/slth/middleware/__init__.py +0 -0
- slthcore-0.6.5/slth/middleware/timezone.py +15 -0
- slthcore-0.6.5/slth/migrations/0001_initial.py +41 -0
- slthcore-0.6.5/slth/migrations/0002_email_role_pushsubscription_error.py +78 -0
- slthcore-0.6.5/slth/migrations/0003_rename_photo_profile_alter_profile_options.py +23 -0
- slthcore-0.6.5/slth/migrations/0004_alter_profile_photo.py +18 -0
- slthcore-0.6.5/slth/migrations/0005_alter_profile_photo.py +19 -0
- slthcore-0.6.5/slth/migrations/0006_user.py +33 -0
- slthcore-0.6.5/slth/migrations/0007_deletion_log.py +49 -0
- slthcore-0.6.5/slth/migrations/0008_alter_deletion_datetime_alter_log_datetime.py +24 -0
- slthcore-0.6.5/slth/migrations/0009_remove_email_from_email_email_action_email_attempt_and_more.py +43 -0
- slthcore-0.6.5/slth/migrations/0010_email_key_alter_email_action_alter_email_attempt_and_more.py +103 -0
- slthcore-0.6.5/slth/migrations/0011_usertimezone.py +31 -0
- slthcore-0.6.5/slth/migrations/0012_timezone_remove_usertimezone_key_and_more.py +37 -0
- slthcore-0.6.5/slth/migrations/0013_whatsappnotification.py +35 -0
- slthcore-0.6.5/slth/migrations/0014_settings.py +40 -0
- slthcore-0.6.5/slth/migrations/0015_auto_20250412_1126.py +27 -0
- slthcore-0.6.5/slth/migrations/__init__.py +0 -0
- slthcore-0.6.5/slth/models.py +672 -0
- slthcore-0.6.5/slth/notifications.py +29 -0
- slthcore-0.6.5/slth/oauth.py +62 -0
- slthcore-0.6.5/slth/pdf/__init__.py +182 -0
- slthcore-0.6.5/slth/pdf/tests.py +70 -0
- slthcore-0.6.5/slth/permissions.py +56 -0
- slthcore-0.6.5/slth/printer.py +71 -0
- slthcore-0.6.5/slth/queryset.py +570 -0
- slthcore-0.6.5/slth/roles.py +103 -0
- slthcore-0.6.5/slth/selenium/__init__.py +244 -0
- slthcore-0.6.5/slth/selenium/browser.py +300 -0
- slthcore-0.6.5/slth/serializer.py +434 -0
- slthcore-0.6.5/slth/static/.DS_Store +0 -0
- slthcore-0.6.5/slth/static/css/.DS_Store +0 -0
- slthcore-0.6.5/slth/static/css/slth.css +98 -0
- slthcore-0.6.5/slth/static/images/placeholder.png +0 -0
- slthcore-0.6.5/slth/static/js/index.min.js +1 -0
- slthcore-0.6.5/slth/static/js/react.min.js +40 -0
- slthcore-0.6.5/slth/static/js/slth.min.js +270 -0
- slthcore-0.6.5/slth/statistics.py +191 -0
- slthcore-0.6.5/slth/storage/__init__.py +99 -0
- slthcore-0.6.5/slth/storage/s3.py +383 -0
- slthcore-0.6.5/slth/tasks.py +59 -0
- slthcore-0.6.5/slth/templates/email.html +155 -0
- slthcore-0.6.5/slth/templates/index.html +76 -0
- slthcore-0.6.5/slth/templates/report.html +23 -0
- slthcore-0.6.5/slth/templates/service-worker.js +39 -0
- slthcore-0.6.5/slth/templates/signature.html +45 -0
- slthcore-0.6.5/slth/tests.py +179 -0
- slthcore-0.6.5/slth/threadlocal.py +3 -0
- slthcore-0.6.5/slth/timezone.py +33 -0
- slthcore-0.6.5/slth/urls.py +31 -0
- slthcore-0.6.5/slth/utils.py +46 -0
- slthcore-0.6.5/slth/views.py +86 -0
- slthcore-0.6.5/slthcore.egg-info/PKG-INFO +16 -0
- slthcore-0.6.5/slthcore.egg-info/SOURCES.txt +128 -0
- slthcore-0.6.5/slthcore.egg-info/dependency_links.txt +1 -0
- slthcore-0.6.5/slthcore.egg-info/top_level.txt +1 -0
slthcore-0.6.5/PKG-INFO
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: slthcore
|
|
3
|
+
Version: 0.6.5
|
|
4
|
+
Summary: API generator based on yml file
|
|
5
|
+
Home-page: https://github.com/brenokcc
|
|
6
|
+
Author: Breno Silva
|
|
7
|
+
Author-email: brenokcc@yahoo.com.br
|
|
8
|
+
License: BSD License
|
|
9
|
+
Classifier: Environment :: Web Environment
|
|
10
|
+
Classifier: Framework :: Django
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
16
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
slthcore-0.6.5/setup.cfg
ADDED
slthcore-0.6.5/setup.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from setuptools import find_packages, setup
|
|
2
|
+
|
|
3
|
+
install_requires = []
|
|
4
|
+
|
|
5
|
+
setup(
|
|
6
|
+
name='slthcore',
|
|
7
|
+
version='0.6.5',
|
|
8
|
+
packages=find_packages(),
|
|
9
|
+
install_requires=install_requires,
|
|
10
|
+
include_package_data=True,
|
|
11
|
+
license='BSD License',
|
|
12
|
+
description='API generator based on yml file',
|
|
13
|
+
long_description='',
|
|
14
|
+
url='https://github.com/brenokcc',
|
|
15
|
+
author='Breno Silva',
|
|
16
|
+
author_email='brenokcc@yahoo.com.br',
|
|
17
|
+
classifiers=[
|
|
18
|
+
'Environment :: Web Environment',
|
|
19
|
+
'Framework :: Django',
|
|
20
|
+
'Intended Audience :: Developers',
|
|
21
|
+
'License :: OSI Approved :: BSD License',
|
|
22
|
+
'Operating System :: OS Independent',
|
|
23
|
+
'Programming Language :: Python',
|
|
24
|
+
'Topic :: Internet :: WWW/HTTP',
|
|
25
|
+
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
|
26
|
+
],
|
|
27
|
+
)
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import warnings
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from datetime import datetime, date
|
|
5
|
+
from django.db import models
|
|
6
|
+
from .queryset import QuerySet
|
|
7
|
+
from django.db.models import manager
|
|
8
|
+
from .serializer import Serializer, serialize
|
|
9
|
+
from django.db.models.deletion import Collector
|
|
10
|
+
from .factory import FormFactory
|
|
11
|
+
import django.db.models.options as options
|
|
12
|
+
from django.db.models.base import ModelBase
|
|
13
|
+
from django.db.models import Model
|
|
14
|
+
from django.core.exceptions import FieldDoesNotExist
|
|
15
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
16
|
+
from django.utils.autoreload import autoreload_started
|
|
17
|
+
from django.core import serializers
|
|
18
|
+
from django.utils import autoreload
|
|
19
|
+
from .threadlocal import tl
|
|
20
|
+
from django.apps import apps as django_apps
|
|
21
|
+
|
|
22
|
+
from decimal import Decimal
|
|
23
|
+
|
|
24
|
+
warnings.filterwarnings('ignore', module='urllib3')
|
|
25
|
+
|
|
26
|
+
FILENAME = 'application.yml'
|
|
27
|
+
ENDPOINTS = {}
|
|
28
|
+
PROXIED_MODELS = []
|
|
29
|
+
THREADS = []
|
|
30
|
+
|
|
31
|
+
class JSONEncoder(json.JSONEncoder):
|
|
32
|
+
def default(self, obj):
|
|
33
|
+
if isinstance(obj, Decimal):
|
|
34
|
+
return dict(type='decimal', value=float(obj))
|
|
35
|
+
elif isinstance(obj, datetime):
|
|
36
|
+
return dict(type='datetime', value=obj.isoformat())
|
|
37
|
+
elif isinstance(obj, date):
|
|
38
|
+
return dict(type='date', value=obj.isoformat())
|
|
39
|
+
elif isinstance(obj, QuerySet):
|
|
40
|
+
return dict(type='queryset', value=obj.dump())
|
|
41
|
+
elif isinstance(obj, Model):
|
|
42
|
+
return dict(type='object', model=obj._meta.label.lower(), pk=obj.pk)
|
|
43
|
+
return json.JSONEncoder.default(self, obj)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class JSONDecoder(json.JSONDecoder):
|
|
47
|
+
|
|
48
|
+
def __init__(self, *args, **kwargs):
|
|
49
|
+
super().__init__(object_hook=self.object_hook, *args, **kwargs)
|
|
50
|
+
|
|
51
|
+
def object_hook(self, obj):
|
|
52
|
+
type = obj.get('type')
|
|
53
|
+
if type:
|
|
54
|
+
if type == 'decimal':
|
|
55
|
+
return Decimal(obj['value'])
|
|
56
|
+
elif type == 'datetime':
|
|
57
|
+
return datetime.fromisoformat(obj['value'])
|
|
58
|
+
elif type == 'date':
|
|
59
|
+
return date.fromisoformat(obj['value'])
|
|
60
|
+
elif type == 'queryset':
|
|
61
|
+
return QuerySet.load(obj['value'])
|
|
62
|
+
elif type == 'object':
|
|
63
|
+
return django_apps.get_model(obj['model']).objects.get(pk=obj['pk'])
|
|
64
|
+
return obj
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def dumps(data, indent=1, ensure_ascii=False):
|
|
68
|
+
return json.dumps(data, indent=indent, ensure_ascii=ensure_ascii, cls=JSONEncoder)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def loads(data):
|
|
72
|
+
return json.loads(data, cls=JSONDecoder)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class BaseManager(manager.BaseManager):
|
|
76
|
+
def get_queryset(self):
|
|
77
|
+
return super().get_queryset()
|
|
78
|
+
|
|
79
|
+
def all(self):
|
|
80
|
+
return self.get_queryset().all()
|
|
81
|
+
|
|
82
|
+
def __call__(self, model):
|
|
83
|
+
return django_apps.get_model(model)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Manager(BaseManager.from_queryset(QuerySet)):
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
models.QuerySet = QuerySet
|
|
91
|
+
models.Manager = Manager
|
|
92
|
+
setattr(options, 'DEFAULT_NAMES', options.DEFAULT_NAMES + ('icon', 'search_fields'))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ModelMixin(object):
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def get_field(cls, lookup):
|
|
100
|
+
model = cls
|
|
101
|
+
attrs = lookup.split('__')
|
|
102
|
+
while attrs:
|
|
103
|
+
attr_name = attrs.pop(0)
|
|
104
|
+
if attrs: # go deeper
|
|
105
|
+
field = model._meta.get_field(attr_name)
|
|
106
|
+
model = field.related_model
|
|
107
|
+
else:
|
|
108
|
+
try:
|
|
109
|
+
return model._meta.get_field(attr_name)
|
|
110
|
+
except FieldDoesNotExist:
|
|
111
|
+
pass
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
def getroles(self, username_lookup='username'):
|
|
115
|
+
roles = getattr(self, '_roles', None)
|
|
116
|
+
if roles is None:
|
|
117
|
+
obj = self
|
|
118
|
+
for attr_name in username_lookup.split('__'):
|
|
119
|
+
obj = getattr(obj, attr_name)
|
|
120
|
+
roles = django_apps.get_model('api.role').objects.filter(username=obj)
|
|
121
|
+
setattr(self, '_roles', roles)
|
|
122
|
+
return roles
|
|
123
|
+
|
|
124
|
+
def getuser(self, username_lookup):
|
|
125
|
+
obj = self
|
|
126
|
+
for attr_name in username_lookup.split('__'):
|
|
127
|
+
obj = getattr(obj, attr_name)
|
|
128
|
+
return django_apps.get_model('auth.user').objects.get(username=obj)
|
|
129
|
+
|
|
130
|
+
def serializer(self) -> Serializer:
|
|
131
|
+
return Serializer(self)
|
|
132
|
+
|
|
133
|
+
def formfactory(self) -> FormFactory:
|
|
134
|
+
return FormFactory(self)
|
|
135
|
+
|
|
136
|
+
def pre_save(self):
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
def post_save(self):
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
def safe_delete(self, username=None):
|
|
143
|
+
from .models import Deletion
|
|
144
|
+
order = []
|
|
145
|
+
objects = []
|
|
146
|
+
collector = Collector('default')
|
|
147
|
+
qs = type(self).objects.filter(pk=self.pk)
|
|
148
|
+
collector.collect(qs)
|
|
149
|
+
for instances in collector.data.values():
|
|
150
|
+
for instance in instances:
|
|
151
|
+
if instance.__class__.__name__ not in ['Log'] and instance not in objects:
|
|
152
|
+
objects.append(instance)
|
|
153
|
+
if instance.__class__.__name__ not in order:
|
|
154
|
+
order.append(instance.__class__.__name__)
|
|
155
|
+
for qs in collector.fast_deletes:
|
|
156
|
+
for instance in qs:
|
|
157
|
+
if instance.__class__.__name__ not in ['Log'] and instance not in objects:
|
|
158
|
+
objects.append(instance)
|
|
159
|
+
if instance.__class__.__name__ not in order:
|
|
160
|
+
order.append(instance.__class__.__name__)
|
|
161
|
+
backup = dumps(dict(order=order, objects=serializers.serialize("python", objects)))
|
|
162
|
+
instance = '{}.{}:{}'.format(self._meta.app_label, self._meta.model_name, self.pk)
|
|
163
|
+
Deletion.objects.create(username=username, datetime=datetime.now(), instance=instance, backup=backup)
|
|
164
|
+
return self.delete()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def save_decorator(func):
|
|
168
|
+
|
|
169
|
+
def decorate(self, *args, **kwargs):
|
|
170
|
+
diff = {}
|
|
171
|
+
if self.pk:
|
|
172
|
+
action = 'edit'
|
|
173
|
+
obj = type(self).objects.filter(pk=self.pk).first()
|
|
174
|
+
if obj:
|
|
175
|
+
for field in self._meta.fields:
|
|
176
|
+
a = getattr(obj, field.name)
|
|
177
|
+
try:
|
|
178
|
+
b = getattr(self, field.name)
|
|
179
|
+
except ObjectDoesNotExist:
|
|
180
|
+
b = None
|
|
181
|
+
if a != b:
|
|
182
|
+
diff[field.verbose_name] = (serialize(a), serialize(b))
|
|
183
|
+
else:
|
|
184
|
+
action = 'add'
|
|
185
|
+
for field in self._meta.fields:
|
|
186
|
+
try:
|
|
187
|
+
b = getattr(self, field.name)
|
|
188
|
+
except ObjectDoesNotExist:
|
|
189
|
+
b = None
|
|
190
|
+
if b is not None:
|
|
191
|
+
diff[field.verbose_name] = (None, serialize(b))
|
|
192
|
+
func(self, *args, **kwargs)
|
|
193
|
+
if diff:
|
|
194
|
+
model = '{}.{}'.format(self._meta.app_label, self._meta.model_name)
|
|
195
|
+
log = dict(model=model, pk=self.pk, action=action, diff=diff)
|
|
196
|
+
context = getattr(tl, 'context', None)
|
|
197
|
+
if context:
|
|
198
|
+
context['logs'].append(log)
|
|
199
|
+
|
|
200
|
+
return decorate
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def delete_decorator(func):
|
|
204
|
+
def decorate(self, *args, **kwargs):
|
|
205
|
+
diff = {}
|
|
206
|
+
for field in self._meta.fields:
|
|
207
|
+
a = getattr(self, field.name)
|
|
208
|
+
diff[field.verbose_name] = (a, None)
|
|
209
|
+
log = dict(model='{}.{}'.format(
|
|
210
|
+
self._meta.app_label, self._meta.model_name), action='delete', diff=diff
|
|
211
|
+
)
|
|
212
|
+
context = getattr(tl, 'context', None)
|
|
213
|
+
if context:
|
|
214
|
+
context['logs'].append(log)
|
|
215
|
+
func(self, *args, **kwargs)
|
|
216
|
+
|
|
217
|
+
return decorate
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
___new___ = ModelBase.__new__
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def __new__(mcs, name, bases, attrs, **kwargs):
|
|
224
|
+
if attrs['__module__'] != '__fake__':
|
|
225
|
+
# See .db.models.Manager
|
|
226
|
+
if 'objects' in attrs and isinstance(attrs['objects'], QuerySet):
|
|
227
|
+
queryset_class = attrs['objects']
|
|
228
|
+
attrs.update(objects=BaseManager.from_queryset(type(queryset_class))())
|
|
229
|
+
# Defining the objects Manager using .db.models.QuerySet
|
|
230
|
+
if 'objects' not in attrs and not all(['objects' in dir(cls) for cls in bases]):
|
|
231
|
+
attrs.update(objects=BaseManager.from_queryset(QuerySet)())
|
|
232
|
+
|
|
233
|
+
if ModelMixin not in bases:
|
|
234
|
+
bases = bases + (ModelMixin, )
|
|
235
|
+
cls = ___new___(mcs, name, bases, attrs, **kwargs)
|
|
236
|
+
if cls._meta.proxy_for_model:
|
|
237
|
+
PROXIED_MODELS.append(cls._meta.proxy_for_model)
|
|
238
|
+
if name == 'Model':
|
|
239
|
+
cls.save = save_decorator(cls.save)
|
|
240
|
+
cls.delete = delete_decorator(cls.delete)
|
|
241
|
+
return cls
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
ModelBase.__new__ = __new__
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def api_watchdog(sender, **kwargs):
|
|
248
|
+
sender.extra_files.add(Path('application.yml'))
|
|
249
|
+
|
|
250
|
+
autoreload_started.connect(api_watchdog)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def meta(verbose_name=None):
|
|
254
|
+
def decorate(function):
|
|
255
|
+
function.verbose_name = verbose_name
|
|
256
|
+
return function
|
|
257
|
+
return decorate
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
original_trigger_reload = autoreload.trigger_reload
|
|
261
|
+
|
|
262
|
+
def trigger_reload(filename):
|
|
263
|
+
print('Stoping sloth thread....')
|
|
264
|
+
for thread in THREADS:
|
|
265
|
+
thread.stop()
|
|
266
|
+
thread.join()
|
|
267
|
+
print('Thread stopped!')
|
|
268
|
+
original_trigger_reload(filename)
|
|
269
|
+
|
|
270
|
+
autoreload.trigger_reload = trigger_reload
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
|
|
2
|
+
from slth import ENDPOINTS
|
|
3
|
+
from .utils import build_url
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from django.apps import apps
|
|
6
|
+
|
|
7
|
+
APPLICATION_CLASS = None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Style():
|
|
11
|
+
def __init__(self, name, color="black", background="inherite", border="none"):
|
|
12
|
+
self.name = name
|
|
13
|
+
self.update(color=color, background=background, border=border)
|
|
14
|
+
|
|
15
|
+
def update(self, color=None, background=None, border=None):
|
|
16
|
+
if color:
|
|
17
|
+
self.color = color
|
|
18
|
+
if background:
|
|
19
|
+
self.background = background
|
|
20
|
+
if border:
|
|
21
|
+
self.border = border
|
|
22
|
+
|
|
23
|
+
def to_css(self):
|
|
24
|
+
return f"""
|
|
25
|
+
--{self.name}-color: { self.color };
|
|
26
|
+
--{self.name}-border: { self.border };
|
|
27
|
+
--{self.name}-background: { self.background };
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
class ColorSchema:
|
|
31
|
+
def to_css(self):
|
|
32
|
+
css = []
|
|
33
|
+
css.append("<style>")
|
|
34
|
+
css.append(":root{")
|
|
35
|
+
css.append(f"--border-radius: {self.border_radius}px;")
|
|
36
|
+
css.append(self.default.to_css())
|
|
37
|
+
css.append(self.header.to_css())
|
|
38
|
+
css.append(self.footer.to_css())
|
|
39
|
+
css.append(self.fieldset.to_css())
|
|
40
|
+
css.append(self.input.to_css())
|
|
41
|
+
css.append(self.primary.to_css())
|
|
42
|
+
css.append(self.secondary.to_css())
|
|
43
|
+
css.append(self.auxiliary.to_css())
|
|
44
|
+
css.append(self.highlight.to_css())
|
|
45
|
+
css.append(self.info.to_css())
|
|
46
|
+
css.append(self.success.to_css())
|
|
47
|
+
css.append(self.warning.to_css())
|
|
48
|
+
css.append(self.danger.to_css())
|
|
49
|
+
css.append("</style>")
|
|
50
|
+
return "\n".join(css)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Light(ColorSchema):
|
|
54
|
+
def __init__(self):
|
|
55
|
+
self.border_radius = 0
|
|
56
|
+
self.default: Style = Style("default", color="#383838", background="#FFFFFF")
|
|
57
|
+
self.header: Style = Style("header", color="#383838", background="#FFFFFF")
|
|
58
|
+
self.footer: Style = Style("footer", color="#383838", background="#FFFFFF")
|
|
59
|
+
self.fieldset: Style = Style("fieldset", color="#383838", background="#FFFFFF")
|
|
60
|
+
self.input: Style = Style("input", border="solid 1px #d9d9d9", background="#FFFFFF")
|
|
61
|
+
self.primary:Style = Style("primary", color="#1351b4", background="#1351b4")
|
|
62
|
+
self.secondary:Style = Style("secondary", color="#071e41")
|
|
63
|
+
self.auxiliary:Style = Style("auxiliary", color="#2670e8", background="#f8f8f8")
|
|
64
|
+
self.highlight:Style = Style("hightlight", color="#0c326f")
|
|
65
|
+
self.info:Style = Style("info", color="#1351b4", background="#d4e5ff")
|
|
66
|
+
self.success:Style = Style("success", color="#ffffff", background="#1351b4")
|
|
67
|
+
self.warning:Style = Style("warning", color="#fff5c2")
|
|
68
|
+
self.danger:Style = Style("danger", color="#e52207")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Dark(ColorSchema):
|
|
72
|
+
def __init__(self):
|
|
73
|
+
self.border_radius = 3
|
|
74
|
+
self.default: Style = Style("default", color="#c3d0e5", background="#0D1117")
|
|
75
|
+
self.header: Style = Style("header", color="#383838", background="#FFFFFF")
|
|
76
|
+
self.footer: Style = Style("footer", color="#383838", background="#FFFFFF")
|
|
77
|
+
self.fieldset: Style = Style("fieldset", color="#91aad2", background="#262c35")
|
|
78
|
+
self.input: Style = Style("input", border="0", background="#0D1117")
|
|
79
|
+
self.primary:Style = Style("primary", color="#c3d0e5", background="#90C4F9")
|
|
80
|
+
self.secondary:Style = Style("secondary", color="#071e41")
|
|
81
|
+
self.auxiliary:Style = Style("auxiliary", color="#91aad2", background="#262c35")
|
|
82
|
+
self.highlight:Style = Style("hightlight", color="#0c326f")
|
|
83
|
+
self.info:Style = Style("info", color="#c3d0e5", background="#262c35")
|
|
84
|
+
self.success:Style = Style("success", color="#c3d0e5", background="#121f1a", border="1px solid #3b622b")
|
|
85
|
+
self.warning:Style = Style("warning", color="#fff5c2")
|
|
86
|
+
self.danger:Style = Style("danger", color="#e52207")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class Groups(dict):
|
|
90
|
+
def add(self, **kwargs):
|
|
91
|
+
self.update(**kwargs)
|
|
92
|
+
|
|
93
|
+
class Menu(dict):
|
|
94
|
+
def add(self, arg):
|
|
95
|
+
self.update(arg)
|
|
96
|
+
|
|
97
|
+
def process(self, request):
|
|
98
|
+
items = []
|
|
99
|
+
|
|
100
|
+
def get_item(k, v):
|
|
101
|
+
if isinstance(v, dict):
|
|
102
|
+
icon, label = k.split(":") if ":" in k else (None, k)
|
|
103
|
+
subitems = []
|
|
104
|
+
for k1, v1 in v.items():
|
|
105
|
+
subitem = get_item(k1, v1)
|
|
106
|
+
if subitem:
|
|
107
|
+
subitems.append(subitem)
|
|
108
|
+
if subitems:
|
|
109
|
+
return dict(dict(icon=icon, label=label, items=subitems))
|
|
110
|
+
else:
|
|
111
|
+
cls = ENDPOINTS.get(v)
|
|
112
|
+
if cls:
|
|
113
|
+
endpoint = cls().instantiate(request, None)
|
|
114
|
+
if endpoint.check_permission() and endpoint.contribute("menu"):
|
|
115
|
+
icon, label = k.split(":") if ":" in k else (None, k)
|
|
116
|
+
url = build_url(request, cls.get_api_url())
|
|
117
|
+
return dict(dict(label=label, url=url, icon=icon))
|
|
118
|
+
|
|
119
|
+
for k, v in self.items():
|
|
120
|
+
item = get_item(k, v)
|
|
121
|
+
if item:
|
|
122
|
+
items.append(item)
|
|
123
|
+
|
|
124
|
+
return items
|
|
125
|
+
|
|
126
|
+
class Oauth(list):
|
|
127
|
+
def add(self, name, client_id, client_secret, redirect_uri, authorize_url, access_token_url, user_data_url, user_username, user_email=None, user_scope=None, user_create=False, user_logout_url=None):
|
|
128
|
+
super().append(dict(name=name, client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, authorize_url=authorize_url, access_token_url=access_token_url, user_data_url=user_data_url, user_username=user_username, user_email=user_email, user_scope=user_scope, user_create=user_create, user_logout_url=user_logout_url))
|
|
129
|
+
|
|
130
|
+
def serialize(self):
|
|
131
|
+
data = []
|
|
132
|
+
for provider in self:
|
|
133
|
+
redirect_uri = "{}{}".format(settings.SITE_URL, provider['redirect_uri'])
|
|
134
|
+
authorize_url = '{}?response_type=code&client_id={}&redirect_uri={}'.format(
|
|
135
|
+
provider['authorize_url'], provider['client_id'], redirect_uri
|
|
136
|
+
)
|
|
137
|
+
if provider.get('scope'):
|
|
138
|
+
authorize_url = '{}&scope={}'.format(authorize_url, provider.get('scope'))
|
|
139
|
+
data.append(dict(label=f'Entrar com {provider["name"]}', url=authorize_url))
|
|
140
|
+
return data
|
|
141
|
+
|
|
142
|
+
class List(list):
|
|
143
|
+
|
|
144
|
+
def add(self, *names):
|
|
145
|
+
super().extend(names)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class Dashboard():
|
|
149
|
+
def __init__(self):
|
|
150
|
+
self.actions:List = List()
|
|
151
|
+
self.toolbar:List = List()
|
|
152
|
+
self.todo:List = List()
|
|
153
|
+
self.top:List = List()
|
|
154
|
+
self.center:List = List()
|
|
155
|
+
self.boxes:List = List()
|
|
156
|
+
self.search:List = List()
|
|
157
|
+
self.usermenu:List = List()
|
|
158
|
+
self.adder:List = List()
|
|
159
|
+
self.tools:List = List()
|
|
160
|
+
self.settings:List = List()
|
|
161
|
+
self.index = "dashboard"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Theme:
|
|
165
|
+
def __init__(self):
|
|
166
|
+
self.light = Light()
|
|
167
|
+
self.dark = Dark()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class ApplicationMetaclass(type):
|
|
171
|
+
|
|
172
|
+
def __new__(mcs, name, bases, attrs):
|
|
173
|
+
global APPLICATION_CLASS
|
|
174
|
+
cls = super().__new__(mcs, name, bases, attrs)
|
|
175
|
+
APPLICATION_CLASS = cls
|
|
176
|
+
return cls
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class Application(metaclass=ApplicationMetaclass):
|
|
180
|
+
|
|
181
|
+
def __init__(self):
|
|
182
|
+
self.lang = "pt-br"
|
|
183
|
+
self.title = "Sloth"
|
|
184
|
+
self.subtitle = "Take your time!"
|
|
185
|
+
self.icon = "/static/images/logo.png"
|
|
186
|
+
self.logo = "/static/images/logo.png"
|
|
187
|
+
self.brand = None
|
|
188
|
+
self.version = "0.0.1"
|
|
189
|
+
self.oauth:Oauth = Oauth()
|
|
190
|
+
self.groups:Groups = Groups()
|
|
191
|
+
self.menu:Menu = Menu()
|
|
192
|
+
self.dashboard = Dashboard()
|
|
193
|
+
self.theme:Theme = Theme()
|
|
194
|
+
self.sponsors:List = List()
|
|
195
|
+
|
|
196
|
+
def load(self):
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
def serialize(self, request):
|
|
200
|
+
icon = build_url(request, self.icon)
|
|
201
|
+
logo = build_url(request, self.brand or self.logo)
|
|
202
|
+
title = self.title if self.brand is None else None
|
|
203
|
+
if request.user.is_authenticated:
|
|
204
|
+
name = request.user.first_name
|
|
205
|
+
user = request.user.username.split()[0].split("@")[0]
|
|
206
|
+
profile = apps.get_model("slth", "profile").objects.filter(user=request.user).first()
|
|
207
|
+
photo = profile and profile.photo and build_url(request, profile.photo.url) or None
|
|
208
|
+
else:
|
|
209
|
+
name = user = profile = photo = None
|
|
210
|
+
|
|
211
|
+
def contribute(endpoint_list, endpoint_name):
|
|
212
|
+
cls = ENDPOINTS[endpoint_name]
|
|
213
|
+
endpoint = cls().instantiate(request, None)
|
|
214
|
+
if endpoint.check_permission() and endpoint.contribute(entrypoint):
|
|
215
|
+
name = endpoint.get_verbose_name()
|
|
216
|
+
url = build_url(request, cls.get_api_url())
|
|
217
|
+
modal = cls.get_metadata("modal", False)
|
|
218
|
+
icon = cls.get_metadata("icon", None)
|
|
219
|
+
endpoint_list.append(dict(name=name, url=url, modal=modal, icon=icon))
|
|
220
|
+
|
|
221
|
+
endpoints = {"actions": [], "usermenu": [], "adder": [], "settings": [], "tools": [], "toolbar": []}
|
|
222
|
+
for entrypoint in endpoints:
|
|
223
|
+
for endpoint_name in getattr(self.dashboard, entrypoint):
|
|
224
|
+
contribute(endpoints[entrypoint], endpoint_name)
|
|
225
|
+
|
|
226
|
+
top = []
|
|
227
|
+
for endpoint_name in self.dashboard.top:
|
|
228
|
+
contribute(top, endpoint_name)
|
|
229
|
+
|
|
230
|
+
return dict(
|
|
231
|
+
type="application",
|
|
232
|
+
icon=icon,
|
|
233
|
+
navbar=dict(
|
|
234
|
+
type="navbar", title=title, subtitle=self.subtitle, logo=logo, user=user, name=name, **endpoints
|
|
235
|
+
),
|
|
236
|
+
menu=dict(
|
|
237
|
+
type="menu", items=self.menu.process(request), user=user, image=photo
|
|
238
|
+
),
|
|
239
|
+
footer=dict(
|
|
240
|
+
type="footer", version=self.version
|
|
241
|
+
),
|
|
242
|
+
top=top,
|
|
243
|
+
oauth=self.oauth.serialize(),
|
|
244
|
+
sponsors=self.sponsors
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
@staticmethod
|
|
248
|
+
def get_instance():
|
|
249
|
+
instance = APPLICATION_CLASS()
|
|
250
|
+
instance.load()
|
|
251
|
+
return instance
|
|
252
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from django.apps import AppConfig
|
|
6
|
+
from django.utils import autoreload
|
|
7
|
+
from django.apps import apps
|
|
8
|
+
import threading
|
|
9
|
+
from . import THREADS
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Thread(threading.Thread):
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self._stop_event = threading.Event()
|
|
18
|
+
|
|
19
|
+
def run(self):
|
|
20
|
+
while not self._stop_event.is_set():
|
|
21
|
+
# print('.')
|
|
22
|
+
apps.get_model("slth", "email").objects.send()
|
|
23
|
+
apps.get_model("slth", "pushnotification").objects.send()
|
|
24
|
+
apps.get_model("slth", "job").objects.execute()
|
|
25
|
+
for i in range(0, 10):
|
|
26
|
+
if self._stop_event.is_set():
|
|
27
|
+
break
|
|
28
|
+
time.sleep(1)
|
|
29
|
+
|
|
30
|
+
def stop(self):
|
|
31
|
+
print("Stopping e-mail thread...")
|
|
32
|
+
self._stop_event.set()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AppConfig(AppConfig):
|
|
36
|
+
name = 'slth'
|
|
37
|
+
|
|
38
|
+
def ready(self):
|
|
39
|
+
settings.SLOTH = True
|
|
40
|
+
if os.environ.get("RUN_MAIN"):
|
|
41
|
+
print('Starting sloth thread...')
|
|
42
|
+
thread = Thread()
|
|
43
|
+
thread.start()
|
|
44
|
+
THREADS.append(thread)
|