slthcore 0.0.4__tar.gz → 0.0.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.
Potentially problematic release.
This version of slthcore might be problematic. Click here for more details.
- {slthcore-0.0.4/slthcore.egg-info → slthcore-0.0.5}/PKG-INFO +1 -1
- {slthcore-0.0.4 → slthcore-0.0.5}/setup.py +1 -1
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/__init__.py +86 -1
- slthcore-0.0.5/slth/cmd/configure/__main__.py +88 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/components.py +1 -1
- slthcore-0.0.5/slth/db/generic.py +98 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/db/models.py +11 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/endpoints.py +140 -44
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/factory.py +1 -1
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/forms.py +3 -2
- slthcore-0.0.5/slth/migrations/0007_deletion_log.py +49 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/models.py +123 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/queryset.py +33 -25
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/serializer.py +9 -3
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/static/js/slth.min.js +54 -16
- slthcore-0.0.5/slth/tasks.py +70 -0
- slthcore-0.0.5/slth/threadlocal.py +3 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/utils.py +7 -1
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/views.py +12 -3
- {slthcore-0.0.4 → slthcore-0.0.5/slthcore.egg-info}/PKG-INFO +1 -1
- {slthcore-0.0.4 → slthcore-0.0.5}/slthcore.egg-info/SOURCES.txt +5 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/MANIFEST.in +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/setup.cfg +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/__main__.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/.DS_Store +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/.gitignore +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/__init__.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/asgi.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/endpoints.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/models.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/settings.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/tests.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/urls.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/wsgi.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/application.yml +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/entrypoint.sh +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/manage.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/requirements.txt +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/base.env +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/docker-compose.yml +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/frontend/package.json +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/frontend/src/main.jsx +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/frontend/vite.config.js +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/local.env +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/run.sh +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/selenium/run.sh +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/test.sh +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/db/__init__.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/exceptions.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/management/__init__.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/management/commands/__init__.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/management/commands/integration_test.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/management/commands/sync.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/0001_initial.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/0002_email_role_pushsubscription_error.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/0003_rename_photo_profile_alter_profile_options.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/0004_alter_profile_photo.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/0005_alter_profile_photo.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/0006_user.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/__init__.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/notifications.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/oauth.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/permissions.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/roles.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/selenium/__init__.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/selenium/browser.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/static/.DS_Store +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/static/css/.DS_Store +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/static/css/slth.css +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/static/js/index.min.js +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/static/js/react.min.js +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/statistics.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/templates/index.html +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/templates/service-worker.js +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/tests.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slth/urls.py +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slthcore.egg-info/dependency_links.txt +0 -0
- {slthcore-0.0.4 → slthcore-0.0.5}/slthcore.egg-info/top_level.txt +0 -0
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import os
|
|
3
3
|
import yaml
|
|
4
|
+
import json
|
|
4
5
|
import warnings
|
|
5
6
|
from pathlib import Path
|
|
7
|
+
from datetime import datetime
|
|
6
8
|
from django.apps import apps
|
|
7
9
|
from django.db import models
|
|
8
10
|
from .queryset import QuerySet
|
|
9
11
|
from django.db.models import manager
|
|
10
|
-
from .serializer import Serializer
|
|
12
|
+
from .serializer import Serializer, serialize
|
|
13
|
+
from django.db.models.deletion import Collector
|
|
11
14
|
from .factory import FormFactory
|
|
12
15
|
import django.db.models.options as options
|
|
13
16
|
from django.db.models.base import ModelBase
|
|
14
17
|
from django.core.exceptions import FieldDoesNotExist
|
|
15
18
|
from django.utils.autoreload import autoreload_started
|
|
19
|
+
from django.core import serializers
|
|
20
|
+
from .threadlocal import tl
|
|
16
21
|
|
|
17
22
|
warnings.filterwarnings('ignore', module='urllib3')
|
|
18
23
|
|
|
@@ -89,6 +94,83 @@ class ModelMixin(object):
|
|
|
89
94
|
|
|
90
95
|
def formfactory(self) -> FormFactory:
|
|
91
96
|
return FormFactory(self)
|
|
97
|
+
|
|
98
|
+
def pre_save(self):
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
def post_save(self):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
def safe_delete(self, username=None):
|
|
105
|
+
from .models import Deletion
|
|
106
|
+
order = []
|
|
107
|
+
objects = []
|
|
108
|
+
collector = Collector('default')
|
|
109
|
+
qs = type(self).objects.filter(pk=self.pk)
|
|
110
|
+
collector.collect(qs)
|
|
111
|
+
for instances in collector.data.values():
|
|
112
|
+
for instance in instances:
|
|
113
|
+
if instance.__class__.__name__ not in ['Log'] and instance not in objects:
|
|
114
|
+
objects.append(instance)
|
|
115
|
+
if instance.__class__.__name__ not in order:
|
|
116
|
+
order.append(instance.__class__.__name__)
|
|
117
|
+
for qs in collector.fast_deletes:
|
|
118
|
+
for instance in qs:
|
|
119
|
+
if instance.__class__.__name__ not in ['Log'] and instance not in objects:
|
|
120
|
+
objects.append(instance)
|
|
121
|
+
if instance.__class__.__name__ not in order:
|
|
122
|
+
order.append(instance.__class__.__name__)
|
|
123
|
+
backup = json.dumps(dict(order=order, objects=serializers.serialize("python", objects)))
|
|
124
|
+
instance = '{}.{}:{}'.format(self._meta.app_label, self._meta.model_name, self.pk)
|
|
125
|
+
Deletion.objects.create(username=username, datetime=datetime.now(), instance=instance, backup=backup)
|
|
126
|
+
return self.delete()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def save_decorator(func):
|
|
130
|
+
|
|
131
|
+
def decorate(self, *args, **kwargs):
|
|
132
|
+
diff = {}
|
|
133
|
+
if self.pk:
|
|
134
|
+
action = 'edit'
|
|
135
|
+
obj = type(self).objects.filter(pk=self.pk).first()
|
|
136
|
+
if obj:
|
|
137
|
+
for field in self._meta.fields:
|
|
138
|
+
a = getattr(obj, field.name)
|
|
139
|
+
b = getattr(self, field.name)
|
|
140
|
+
if a != b:
|
|
141
|
+
diff[field.verbose_name] = (serialize(a), serialize(b))
|
|
142
|
+
else:
|
|
143
|
+
action = 'add'
|
|
144
|
+
for field in self._meta.fields:
|
|
145
|
+
b = getattr(self, field.name)
|
|
146
|
+
if b is not None:
|
|
147
|
+
diff[field.verbose_name] = (None, serialize(b))
|
|
148
|
+
func(self, *args, **kwargs)
|
|
149
|
+
if diff:
|
|
150
|
+
model = '{}.{}'.format(self._meta.app_label, self._meta.model_name)
|
|
151
|
+
log = dict(model=model, pk=self.pk, action=action, diff=diff)
|
|
152
|
+
context = getattr(tl, 'context', None)
|
|
153
|
+
if context:
|
|
154
|
+
context['logs'].append(log)
|
|
155
|
+
|
|
156
|
+
return decorate
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def delete_decorator(func):
|
|
160
|
+
def decorate(self, *args, **kwargs):
|
|
161
|
+
diff = {}
|
|
162
|
+
for field in self._meta.fields:
|
|
163
|
+
a = getattr(self, field.name)
|
|
164
|
+
diff[field.verbose_name] = (a, None)
|
|
165
|
+
log = dict(model='{}.{}'.format(
|
|
166
|
+
self._meta.app_label, self._meta.model_name), action='delete', diff=diff
|
|
167
|
+
)
|
|
168
|
+
context = getattr(tl, 'context', None)
|
|
169
|
+
if context:
|
|
170
|
+
context['logs'].append(log)
|
|
171
|
+
func(self, *args, **kwargs)
|
|
172
|
+
|
|
173
|
+
return decorate
|
|
92
174
|
|
|
93
175
|
|
|
94
176
|
___new___ = ModelBase.__new__
|
|
@@ -109,6 +191,9 @@ def __new__(mcs, name, bases, attrs, **kwargs):
|
|
|
109
191
|
cls = ___new___(mcs, name, bases, attrs, **kwargs)
|
|
110
192
|
if cls._meta.proxy_for_model:
|
|
111
193
|
PROXIED_MODELS.append(cls._meta.proxy_for_model)
|
|
194
|
+
if name == 'Model':
|
|
195
|
+
cls.save = save_decorator(cls.save)
|
|
196
|
+
cls.delete = delete_decorator(cls.delete)
|
|
112
197
|
return cls
|
|
113
198
|
|
|
114
199
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import pathlib
|
|
4
|
+
from slth.utils import parse_string_template
|
|
5
|
+
from subprocess import Popen, PIPE
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
USAGE = 'USAGE: python -m slth.cmd.configure [nginx|systemctl]'
|
|
9
|
+
|
|
10
|
+
NGINX = '''server {
|
|
11
|
+
listen 80;
|
|
12
|
+
server_name {{ server_name }};
|
|
13
|
+
{% if ssl %}
|
|
14
|
+
listen 443 ssl;
|
|
15
|
+
ssl_certificate /etc/letsencrypt/live/{{ server_name }}/fullchain.pem;
|
|
16
|
+
ssl_certificate_key /etc/letsencrypt/live/{{ server_name }}/privkey.pem;
|
|
17
|
+
if ($scheme = http) { return 301 https://$server_name$request_uri; }
|
|
18
|
+
{% endif %}
|
|
19
|
+
location / {
|
|
20
|
+
proxy_pass http://127.0.0.1:{{ port }};
|
|
21
|
+
}
|
|
22
|
+
}'''
|
|
23
|
+
|
|
24
|
+
SERVICE_SCRIPT = '''#!/bin/bash
|
|
25
|
+
source .venv/bin/activate
|
|
26
|
+
|
|
27
|
+
export POSTGRES_HOST=localhost
|
|
28
|
+
export POSTGRES_DB={{ name }}
|
|
29
|
+
|
|
30
|
+
python manage.py sync
|
|
31
|
+
gunicorn api.wsgi -b 0.0.0.0:8000 -w 3 --log-level info --reload --timeout 3600
|
|
32
|
+
'''
|
|
33
|
+
|
|
34
|
+
SERVICE_CONTENT = '''[Unit]
|
|
35
|
+
Description={{ server_name }}
|
|
36
|
+
|
|
37
|
+
[Service]
|
|
38
|
+
User=www-data
|
|
39
|
+
WorkingDirectory={{ dirname }}
|
|
40
|
+
ExecStart=bash service.sh
|
|
41
|
+
Restart=always
|
|
42
|
+
RestartSec=3
|
|
43
|
+
|
|
44
|
+
[Install]
|
|
45
|
+
WantedBy=multi-user.target
|
|
46
|
+
'''
|
|
47
|
+
|
|
48
|
+
def execute(command):
|
|
49
|
+
process = Popen(command.split(), stdout=PIPE, stderr=PIPE)
|
|
50
|
+
stdout, stderr = process.communicate()
|
|
51
|
+
#print(stdout, stderr)
|
|
52
|
+
|
|
53
|
+
def print_content(file_path, content):
|
|
54
|
+
print()
|
|
55
|
+
print(file_path)
|
|
56
|
+
print('--------------------------')
|
|
57
|
+
print(content)
|
|
58
|
+
print('--------------------------')
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
dirname = pathlib.Path().resolve()
|
|
62
|
+
server_name = input('DOMAIN: ')
|
|
63
|
+
name = server_name.split('.')[0]
|
|
64
|
+
|
|
65
|
+
execute('createdb -U postgres {};'.format(name))
|
|
66
|
+
|
|
67
|
+
ssl = input('SSL [y|N]: ').lower() == 'y'
|
|
68
|
+
port = input('PORT [8000]: ') or '8000'
|
|
69
|
+
nginx_file_path = f'/etc/nginx/conf.d/{server_name}.conf'
|
|
70
|
+
nginx_file_content = parse_string_template(NGINX, server_name=server_name, ssl=ssl, port=port)
|
|
71
|
+
print_content(nginx_file_path, nginx_file_content)
|
|
72
|
+
|
|
73
|
+
service_script_path = os.path.join(dirname, 'service.sh')
|
|
74
|
+
service_script_content = parse_string_template(SERVICE_SCRIPT, name=name)
|
|
75
|
+
service_file_content = parse_string_template(SERVICE_CONTENT, server_name=server_name, dirname=dirname)
|
|
76
|
+
service_file_path = f'/etc/systemd/system/{server_name}.service'
|
|
77
|
+
print_content(service_script_path, service_script_content)
|
|
78
|
+
print_content(service_file_path, service_file_content)
|
|
79
|
+
save = input('SAVE [y|N]: ').lower() == 'y'
|
|
80
|
+
if save:
|
|
81
|
+
with open(nginx_file_path, 'w') as file:
|
|
82
|
+
file.write(nginx_file_content)
|
|
83
|
+
with open(service_script_path, 'w') as file:
|
|
84
|
+
file.write(service_script_content)
|
|
85
|
+
with open(service_file_path, 'w') as file:
|
|
86
|
+
file.write(service_file_content)
|
|
87
|
+
|
|
88
|
+
execute('systemctl daemon-reload')
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import datetime
|
|
3
|
+
import json
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
|
|
6
|
+
from django.apps import apps
|
|
7
|
+
from django.db.models import *
|
|
8
|
+
from django.db.models.query_utils import DeferredAttribute
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GenericModelWrapper(object):
|
|
12
|
+
def __init__(self, obj):
|
|
13
|
+
self._wrapped_obj = obj
|
|
14
|
+
|
|
15
|
+
def __getattr__(self, attr):
|
|
16
|
+
if attr == 'prepare_database_save':
|
|
17
|
+
raise AttributeError()
|
|
18
|
+
return getattr(self._wrapped_obj, attr)
|
|
19
|
+
|
|
20
|
+
def __setattr__(self, attr, value):
|
|
21
|
+
if attr == '_wrapped_obj':
|
|
22
|
+
super().__setattr__(attr, value)
|
|
23
|
+
elif self._wrapped_obj is not None:
|
|
24
|
+
self._wrapped_obj.__setattr__(attr, value._wrapped_obj)
|
|
25
|
+
|
|
26
|
+
def __str__(self):
|
|
27
|
+
return self._wrapped_obj.__str__()
|
|
28
|
+
|
|
29
|
+
def __repr__(self):
|
|
30
|
+
return self._wrapped_obj.__repr__()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class GenericValue(object):
|
|
34
|
+
def __init__(self, value):
|
|
35
|
+
self.value = value
|
|
36
|
+
|
|
37
|
+
def get_value(self):
|
|
38
|
+
if isinstance(self.value, str) and '::' in self.value:
|
|
39
|
+
value_type, value = self.value.split('::')
|
|
40
|
+
if '.' in value_type:
|
|
41
|
+
self.value = apps.get_model(value_type).objects.get(pk=value)
|
|
42
|
+
elif value_type == 'str':
|
|
43
|
+
self.value = value
|
|
44
|
+
elif value_type == 'int':
|
|
45
|
+
self.value = int(value)
|
|
46
|
+
elif value_type == 'Decimal':
|
|
47
|
+
self.value = Decimal(value)
|
|
48
|
+
elif value_type in ('date', 'datetime'):
|
|
49
|
+
self.value = datetime.datetime.strptime(value[0:10], '%Y-%m-%d')
|
|
50
|
+
elif value_type == 'float':
|
|
51
|
+
self.value = float(value)
|
|
52
|
+
elif value_type == 'bool':
|
|
53
|
+
self.value = value == 'True'
|
|
54
|
+
elif value_type == 'list':
|
|
55
|
+
self.value = json.loads(value)
|
|
56
|
+
return self.value
|
|
57
|
+
|
|
58
|
+
def dumps(self):
|
|
59
|
+
value = self.value
|
|
60
|
+
if value is not None:
|
|
61
|
+
if isinstance(value, Model):
|
|
62
|
+
value = GenericModelWrapper(value)
|
|
63
|
+
if isinstance(value, GenericModelWrapper):
|
|
64
|
+
return '{}.{}::{}'.format(
|
|
65
|
+
value._meta.app_label, value._meta.model_name, value.pk
|
|
66
|
+
)
|
|
67
|
+
if hasattr(value, 'model'):
|
|
68
|
+
value = list(value.values_list('pk', flat=True))
|
|
69
|
+
if isinstance(value, list):
|
|
70
|
+
value = json.dumps(value)
|
|
71
|
+
return '{}::{}'.format(type(value).__name__, value)
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class GenericFieldDescriptor(DeferredAttribute):
|
|
76
|
+
def __get__(self, instance, cls=None):
|
|
77
|
+
obj = super().__get__(instance, cls=cls)
|
|
78
|
+
if isinstance(obj.value, Model):
|
|
79
|
+
return GenericModelWrapper(obj.value)
|
|
80
|
+
return obj.get_value()
|
|
81
|
+
|
|
82
|
+
def __set__(self, instance, value):
|
|
83
|
+
instance.__dict__[self.field.attname] = GenericValue(value)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class GenericField(CharField):
|
|
87
|
+
descriptor_class = GenericFieldDescriptor
|
|
88
|
+
|
|
89
|
+
def __init__(self, *args, max_length=255, null=True, **kwargs):
|
|
90
|
+
super().__init__(*args, max_length=max_length, null=null, **kwargs)
|
|
91
|
+
|
|
92
|
+
def get_prep_value(self, value):
|
|
93
|
+
if value is not None:
|
|
94
|
+
if isinstance(value, GenericValue):
|
|
95
|
+
value = value.dumps()
|
|
96
|
+
else:
|
|
97
|
+
value = GenericValue(value).dumps()
|
|
98
|
+
return value
|
|
@@ -2,8 +2,11 @@ from uuid import uuid1
|
|
|
2
2
|
from django.db.models import Model as DjangoModel
|
|
3
3
|
from django.db.models import *
|
|
4
4
|
from django.utils.translation import gettext_lazy as _
|
|
5
|
+
from . import generic
|
|
5
6
|
from .. import ModelMixin
|
|
6
7
|
|
|
8
|
+
GenericField = generic.GenericField
|
|
9
|
+
|
|
7
10
|
class CharField(CharField):
|
|
8
11
|
def __init__(self, *args, **kwargs):
|
|
9
12
|
self.mask = kwargs.pop('mask', None)
|
|
@@ -138,3 +141,11 @@ class ImageField(ImageField):
|
|
|
138
141
|
class Model(DjangoModel, ModelMixin):
|
|
139
142
|
class Meta:
|
|
140
143
|
abstract = True
|
|
144
|
+
|
|
145
|
+
class Filter:
|
|
146
|
+
|
|
147
|
+
def get_label(self):
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
def choices(self, queryset):
|
|
151
|
+
return queryset
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import types
|
|
3
3
|
import inspect
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
from .models import Token, Role, Log, Deletion
|
|
5
6
|
from django.apps import apps
|
|
6
7
|
from typing import TypeVar, Generic
|
|
7
8
|
from django.core.cache import cache
|
|
@@ -13,6 +14,8 @@ from django.views.decorators.csrf import csrf_exempt
|
|
|
13
14
|
from .factory import FormFactory
|
|
14
15
|
from django.core.exceptions import ValidationError
|
|
15
16
|
from slth import forms
|
|
17
|
+
from django.db.models import Model
|
|
18
|
+
from datetime import datetime
|
|
16
19
|
from django.contrib.auth import authenticate
|
|
17
20
|
from .forms import ModelForm, Form
|
|
18
21
|
from .serializer import serialize, Serializer
|
|
@@ -27,10 +30,12 @@ from .components import (
|
|
|
27
30
|
)
|
|
28
31
|
from .exceptions import JsonResponseException
|
|
29
32
|
from .utils import build_url, append_url
|
|
30
|
-
from .models import PushSubscription, Profile, User
|
|
33
|
+
from .models import PushSubscription, Profile, User, Log
|
|
31
34
|
from slth.queryset import QuerySet
|
|
32
35
|
from slth import APPLICATON, ENDPOINTS
|
|
33
36
|
from . import oauth
|
|
37
|
+
from .tasks import Task, TaskRunner
|
|
38
|
+
from .threadlocal import tl
|
|
34
39
|
|
|
35
40
|
|
|
36
41
|
T = TypeVar("T")
|
|
@@ -114,13 +119,20 @@ class Endpoint(metaclass=EnpointMetaclass):
|
|
|
114
119
|
return apps.get_model(model).objects
|
|
115
120
|
|
|
116
121
|
def get(self):
|
|
117
|
-
|
|
122
|
+
fields = []
|
|
123
|
+
for name in dir(self):
|
|
124
|
+
if isinstance(getattr(self, name), forms.Field):
|
|
125
|
+
fields.append(name)
|
|
126
|
+
return self.formfactory().fields(*fields) if fields else {}
|
|
118
127
|
|
|
119
128
|
def post(self):
|
|
120
129
|
return Response(message="Ação realizada com sucesso")
|
|
121
130
|
|
|
122
131
|
def check_permission(self):
|
|
123
132
|
return self.request.user.is_superuser
|
|
133
|
+
|
|
134
|
+
def contribute(self, entrypoint):
|
|
135
|
+
return True
|
|
124
136
|
|
|
125
137
|
def check_role(self, *names, superuser=True):
|
|
126
138
|
if self.request.user.is_superuser and superuser:
|
|
@@ -169,7 +181,11 @@ class Endpoint(metaclass=EnpointMetaclass):
|
|
|
169
181
|
return data
|
|
170
182
|
|
|
171
183
|
def serialize(self):
|
|
172
|
-
|
|
184
|
+
output = self.process()
|
|
185
|
+
if isinstance(output, Task):
|
|
186
|
+
TaskRunner(output).start()
|
|
187
|
+
output = Response(f'Tarefa {output.key} iniciada.', task=output.key)
|
|
188
|
+
return serialize(output)
|
|
173
189
|
|
|
174
190
|
def to_response(self):
|
|
175
191
|
return ApiResponse(self.serialize(), safe=False)
|
|
@@ -225,7 +241,7 @@ class Endpoint(metaclass=EnpointMetaclass):
|
|
|
225
241
|
args = inspect.getfullargspec(cls.__init__).args[1:]
|
|
226
242
|
pattern = "{}/".format(cls.get_api_name())
|
|
227
243
|
for arg in args:
|
|
228
|
-
pattern = "{}{}/".format(pattern, "<
|
|
244
|
+
pattern = "{}{}/".format(pattern, "<str:{}>".format(arg))
|
|
229
245
|
return pattern
|
|
230
246
|
|
|
231
247
|
@classmethod
|
|
@@ -269,7 +285,29 @@ class Endpoint(metaclass=EnpointMetaclass):
|
|
|
269
285
|
|
|
270
286
|
def get_verbose_name(self):
|
|
271
287
|
return self.get_metadata("verbose_name")
|
|
288
|
+
|
|
289
|
+
def get_instance(self):
|
|
290
|
+
return None
|
|
291
|
+
|
|
292
|
+
def start_audit_trail(self):
|
|
293
|
+
instance = self.get_instance()
|
|
294
|
+
pk = instance.pk if isinstance(instance, Model) else None
|
|
295
|
+
tl.context = dict(
|
|
296
|
+
endpoint = self.get_api_name(), model=None, pk=pk,
|
|
297
|
+
datetime=datetime.now().strftime('%d/%m/%Y %H:%M:%S'),
|
|
298
|
+
user=self.request.user.username if self.request.user.is_authenticated else None,
|
|
299
|
+
url=self.request.get_full_path(), logs=[]
|
|
300
|
+
)
|
|
272
301
|
|
|
302
|
+
def finish_audit_trail(self):
|
|
303
|
+
if hasattr(tl, 'context') and tl.context['logs']:
|
|
304
|
+
pk = tl.context['pk']
|
|
305
|
+
instance = self.get_instance()
|
|
306
|
+
print(tl.context)
|
|
307
|
+
if pk or isinstance(instance, Model):
|
|
308
|
+
model = '{}.{}'.format(instance._meta.app_label, instance._meta.model_name)
|
|
309
|
+
tl.context.update(model=model, pk=pk or instance.pk)
|
|
310
|
+
Log.objects.create(data=tl.context)
|
|
273
311
|
|
|
274
312
|
class PublicEndpoint(Endpoint):
|
|
275
313
|
def check_permission(self):
|
|
@@ -308,7 +346,7 @@ class AdminEndpoint(Generic[T], ModelEndpoint):
|
|
|
308
346
|
|
|
309
347
|
class ListEndpoint(Generic[T], ModelEndpoint):
|
|
310
348
|
def get(self) -> QuerySet:
|
|
311
|
-
return self.model.objects
|
|
349
|
+
return self.model.objects#.contextualize(self.request)
|
|
312
350
|
|
|
313
351
|
|
|
314
352
|
class AddEndpoint(Generic[T], ModelEndpoint):
|
|
@@ -317,10 +355,13 @@ class AddEndpoint(Generic[T], ModelEndpoint):
|
|
|
317
355
|
self.instance = self.model()
|
|
318
356
|
|
|
319
357
|
def get(self) -> FormFactory:
|
|
320
|
-
return self.
|
|
358
|
+
return self.formfactory()
|
|
321
359
|
|
|
322
360
|
def get_instance(self):
|
|
323
361
|
return self.instance
|
|
362
|
+
|
|
363
|
+
def formfactory(self):
|
|
364
|
+
return self.instance.formfactory()
|
|
324
365
|
|
|
325
366
|
|
|
326
367
|
class ModelInstanceEndpoint(ModelEndpoint):
|
|
@@ -365,7 +406,7 @@ class DeleteEndpoint(Generic[T], ModelInstanceEndpoint):
|
|
|
365
406
|
return self.formfactory(self.get_instance()).fields()
|
|
366
407
|
|
|
367
408
|
def post(self):
|
|
368
|
-
self.get_instance().
|
|
409
|
+
self.get_instance().safe_delete(self.request.user.username)
|
|
369
410
|
return super().post()
|
|
370
411
|
|
|
371
412
|
|
|
@@ -467,7 +508,7 @@ class Login(PublicEndpoint):
|
|
|
467
508
|
token = Token.objects.create(user=user)
|
|
468
509
|
return Response(
|
|
469
510
|
message="Bem-vindo!",
|
|
470
|
-
redirect="/api/dashboard/",
|
|
511
|
+
redirect=self.request.GET.get("next", "/api/dashboard/"),
|
|
471
512
|
store=dict(token=token.key, application=None),
|
|
472
513
|
)
|
|
473
514
|
else:
|
|
@@ -493,13 +534,58 @@ class Logout(Endpoint):
|
|
|
493
534
|
class Icons(PublicEndpoint):
|
|
494
535
|
class Meta:
|
|
495
536
|
modal = True
|
|
496
|
-
verbose_name = "
|
|
537
|
+
verbose_name = "Ícones"
|
|
497
538
|
|
|
498
539
|
def get(self):
|
|
499
540
|
return IconSet()
|
|
500
541
|
|
|
501
542
|
def check_permission(self):
|
|
502
543
|
return settings.DEBUG
|
|
544
|
+
|
|
545
|
+
def contribute(self, entrypoint):
|
|
546
|
+
return self.request.user.is_authenticated
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
class Roles(ListEndpoint[Role]):
|
|
550
|
+
class Meta:
|
|
551
|
+
modal = False
|
|
552
|
+
verbose_name = 'Papéis dos Usuários'
|
|
553
|
+
|
|
554
|
+
def get(self):
|
|
555
|
+
return super().get().all()
|
|
556
|
+
|
|
557
|
+
class Logs(ListEndpoint[Log]):
|
|
558
|
+
class Meta:
|
|
559
|
+
modal = False
|
|
560
|
+
verbose_name = 'Histórico de Alterações'
|
|
561
|
+
|
|
562
|
+
def get(self):
|
|
563
|
+
return super().get().all()
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
class Deletions(ListEndpoint[Deletion]):
|
|
567
|
+
class Meta:
|
|
568
|
+
modal = False
|
|
569
|
+
verbose_name = 'Exclusões'
|
|
570
|
+
|
|
571
|
+
def get(self):
|
|
572
|
+
return super().get().all()
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
class RestoreDeletion(InstanceEndpoint[Deletion]):
|
|
576
|
+
class Meta:
|
|
577
|
+
verbose_name = 'Restaurar'
|
|
578
|
+
|
|
579
|
+
def get(self):
|
|
580
|
+
return (super().formfactory().fields())
|
|
581
|
+
|
|
582
|
+
def post(self):
|
|
583
|
+
self.instance.restore()
|
|
584
|
+
return super().post()
|
|
585
|
+
|
|
586
|
+
def check_permission(self):
|
|
587
|
+
return super().check_permission() and not self.instance.restored
|
|
588
|
+
|
|
503
589
|
|
|
504
590
|
|
|
505
591
|
class Search(Endpoint):
|
|
@@ -590,38 +676,35 @@ class Dashboard(Endpoint):
|
|
|
590
676
|
verbose_name = ""
|
|
591
677
|
|
|
592
678
|
def get(self):
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
group.endpoint(
|
|
613
|
-
cls.get_metadata("verbose_name"), cls, wrap=False
|
|
614
|
-
)
|
|
615
|
-
group.parent()
|
|
616
|
-
if APPLICATON["dashboard"]["center"]:
|
|
617
|
-
for endpoint in APPLICATON["dashboard"]["center"]:
|
|
618
|
-
cls = ENDPOINTS[endpoint]
|
|
619
|
-
serializer.endpoint(
|
|
679
|
+
serializer = Serializer(request=self.request)
|
|
680
|
+
if APPLICATON["dashboard"]["boxes"]:
|
|
681
|
+
boxes = Boxes("Acesso Rápido")
|
|
682
|
+
for endpoint in APPLICATON["dashboard"]["boxes"]:
|
|
683
|
+
cls = ENDPOINTS[endpoint]
|
|
684
|
+
if cls().contextualize(self.request).check_permission():
|
|
685
|
+
icon = cls.get_metadata("icon", "check")
|
|
686
|
+
label = cls.get_metadata("verbose_name")
|
|
687
|
+
url = build_url(self.request, cls.get_api_url())
|
|
688
|
+
boxes.append(icon, label, url)
|
|
689
|
+
serializer.append("Acesso Rápido", boxes)
|
|
690
|
+
if APPLICATON["dashboard"]["top"]:
|
|
691
|
+
group = serializer.group("Top")
|
|
692
|
+
for endpoint in APPLICATON["dashboard"]["top"]:
|
|
693
|
+
cls = ENDPOINTS[endpoint]
|
|
694
|
+
if cls.instantiate(
|
|
695
|
+
self.request, self.request.user
|
|
696
|
+
).check_permission():
|
|
697
|
+
group.endpoint(
|
|
620
698
|
cls.get_metadata("verbose_name"), cls, wrap=False
|
|
621
699
|
)
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
700
|
+
group.parent()
|
|
701
|
+
if APPLICATON["dashboard"]["center"]:
|
|
702
|
+
for endpoint in APPLICATON["dashboard"]["center"]:
|
|
703
|
+
cls = ENDPOINTS[endpoint]
|
|
704
|
+
serializer.endpoint(
|
|
705
|
+
cls.get_metadata("verbose_name"), cls, wrap=False
|
|
706
|
+
)
|
|
707
|
+
return serializer
|
|
625
708
|
|
|
626
709
|
def check_permission(self):
|
|
627
710
|
return self.request.user.is_authenticated
|
|
@@ -646,14 +729,16 @@ class Application(PublicEndpoint):
|
|
|
646
729
|
)
|
|
647
730
|
for entrypoint in ["actions", "usermenu", "adder", "settings", "tools", "toolbar"]:
|
|
648
731
|
if APPLICATON["dashboard"][entrypoint]:
|
|
649
|
-
for
|
|
650
|
-
cls = ENDPOINTS[
|
|
651
|
-
|
|
732
|
+
for endpoint_name in APPLICATON["dashboard"][entrypoint]:
|
|
733
|
+
cls = ENDPOINTS[endpoint_name]
|
|
734
|
+
endpoint = cls().instantiate(self.request, self)
|
|
735
|
+
if endpoint.check_permission() and endpoint.contribute(entrypoint):
|
|
652
736
|
label = cls.get_metadata("verbose_name")
|
|
653
737
|
url = build_url(self.request, cls.get_api_url())
|
|
654
738
|
modal = cls.get_metadata("modal", False)
|
|
655
739
|
icon = cls.get_metadata("icon", None)
|
|
656
740
|
navbar.add_action(entrypoint, label, url, modal, icon=icon)
|
|
741
|
+
|
|
657
742
|
if APPLICATON["menu"]:
|
|
658
743
|
items = []
|
|
659
744
|
|
|
@@ -670,7 +755,8 @@ class Application(PublicEndpoint):
|
|
|
670
755
|
else:
|
|
671
756
|
cls = ENDPOINTS.get(v)
|
|
672
757
|
if cls:
|
|
673
|
-
|
|
758
|
+
endpoint = cls().instantiate(self.request, self)
|
|
759
|
+
if endpoint.check_permission() and endpoint.contribute("menu"):
|
|
674
760
|
icon, label = k.split(":") if ":" in k else (None, k)
|
|
675
761
|
url = build_url(self.request, cls.get_api_url())
|
|
676
762
|
return dict(dict(label=label, url=url, icon=icon))
|
|
@@ -806,3 +892,13 @@ class EditProfile(Endpoint):
|
|
|
806
892
|
class About(PublicEndpoint):
|
|
807
893
|
def get(self):
|
|
808
894
|
return dict(version=APPLICATON["version"])
|
|
895
|
+
|
|
896
|
+
class TaskProgress(PublicEndpoint):
|
|
897
|
+
|
|
898
|
+
def __init__(self, key):
|
|
899
|
+
self.key = key
|
|
900
|
+
super().__init__()
|
|
901
|
+
|
|
902
|
+
def get(self):
|
|
903
|
+
return cache.get(self.key)
|
|
904
|
+
|
|
@@ -102,7 +102,7 @@ class FormFactory:
|
|
|
102
102
|
class Meta:
|
|
103
103
|
model = type(self._instance)
|
|
104
104
|
fields = () if self._empty else (fieldlist if self._fieldlist else '__all__')
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
form = Form(instance=self._instance, endpoint=endpoint, initial=self._initial)
|
|
107
107
|
form._key = endpoint.get_api_name()
|
|
108
108
|
form._title = self._title
|