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.

Files changed (78) hide show
  1. {slthcore-0.0.4/slthcore.egg-info → slthcore-0.0.5}/PKG-INFO +1 -1
  2. {slthcore-0.0.4 → slthcore-0.0.5}/setup.py +1 -1
  3. {slthcore-0.0.4 → slthcore-0.0.5}/slth/__init__.py +86 -1
  4. slthcore-0.0.5/slth/cmd/configure/__main__.py +88 -0
  5. {slthcore-0.0.4 → slthcore-0.0.5}/slth/components.py +1 -1
  6. slthcore-0.0.5/slth/db/generic.py +98 -0
  7. {slthcore-0.0.4 → slthcore-0.0.5}/slth/db/models.py +11 -0
  8. {slthcore-0.0.4 → slthcore-0.0.5}/slth/endpoints.py +140 -44
  9. {slthcore-0.0.4 → slthcore-0.0.5}/slth/factory.py +1 -1
  10. {slthcore-0.0.4 → slthcore-0.0.5}/slth/forms.py +3 -2
  11. slthcore-0.0.5/slth/migrations/0007_deletion_log.py +49 -0
  12. {slthcore-0.0.4 → slthcore-0.0.5}/slth/models.py +123 -0
  13. {slthcore-0.0.4 → slthcore-0.0.5}/slth/queryset.py +33 -25
  14. {slthcore-0.0.4 → slthcore-0.0.5}/slth/serializer.py +9 -3
  15. {slthcore-0.0.4 → slthcore-0.0.5}/slth/static/js/slth.min.js +54 -16
  16. slthcore-0.0.5/slth/tasks.py +70 -0
  17. slthcore-0.0.5/slth/threadlocal.py +3 -0
  18. {slthcore-0.0.4 → slthcore-0.0.5}/slth/utils.py +7 -1
  19. {slthcore-0.0.4 → slthcore-0.0.5}/slth/views.py +12 -3
  20. {slthcore-0.0.4 → slthcore-0.0.5/slthcore.egg-info}/PKG-INFO +1 -1
  21. {slthcore-0.0.4 → slthcore-0.0.5}/slthcore.egg-info/SOURCES.txt +5 -0
  22. {slthcore-0.0.4 → slthcore-0.0.5}/MANIFEST.in +0 -0
  23. {slthcore-0.0.4 → slthcore-0.0.5}/setup.cfg +0 -0
  24. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/__main__.py +0 -0
  25. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/.DS_Store +0 -0
  26. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/.gitignore +0 -0
  27. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/__init__.py +0 -0
  28. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/asgi.py +0 -0
  29. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/endpoints.py +0 -0
  30. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/models.py +0 -0
  31. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/settings.py +0 -0
  32. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/tests.py +0 -0
  33. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/urls.py +0 -0
  34. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/api/wsgi.py +0 -0
  35. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/application.yml +0 -0
  36. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/entrypoint.sh +0 -0
  37. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/manage.py +0 -0
  38. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/backend/requirements.txt +0 -0
  39. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/base.env +0 -0
  40. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/docker-compose.yml +0 -0
  41. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/frontend/package.json +0 -0
  42. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/frontend/src/main.jsx +0 -0
  43. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/frontend/vite.config.js +0 -0
  44. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/local.env +0 -0
  45. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/run.sh +0 -0
  46. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/selenium/run.sh +0 -0
  47. {slthcore-0.0.4 → slthcore-0.0.5}/slth/cmd/init/boilerplate/test.sh +0 -0
  48. {slthcore-0.0.4 → slthcore-0.0.5}/slth/db/__init__.py +0 -0
  49. {slthcore-0.0.4 → slthcore-0.0.5}/slth/exceptions.py +0 -0
  50. {slthcore-0.0.4 → slthcore-0.0.5}/slth/management/__init__.py +0 -0
  51. {slthcore-0.0.4 → slthcore-0.0.5}/slth/management/commands/__init__.py +0 -0
  52. {slthcore-0.0.4 → slthcore-0.0.5}/slth/management/commands/integration_test.py +0 -0
  53. {slthcore-0.0.4 → slthcore-0.0.5}/slth/management/commands/sync.py +0 -0
  54. {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/0001_initial.py +0 -0
  55. {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/0002_email_role_pushsubscription_error.py +0 -0
  56. {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/0003_rename_photo_profile_alter_profile_options.py +0 -0
  57. {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/0004_alter_profile_photo.py +0 -0
  58. {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/0005_alter_profile_photo.py +0 -0
  59. {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/0006_user.py +0 -0
  60. {slthcore-0.0.4 → slthcore-0.0.5}/slth/migrations/__init__.py +0 -0
  61. {slthcore-0.0.4 → slthcore-0.0.5}/slth/notifications.py +0 -0
  62. {slthcore-0.0.4 → slthcore-0.0.5}/slth/oauth.py +0 -0
  63. {slthcore-0.0.4 → slthcore-0.0.5}/slth/permissions.py +0 -0
  64. {slthcore-0.0.4 → slthcore-0.0.5}/slth/roles.py +0 -0
  65. {slthcore-0.0.4 → slthcore-0.0.5}/slth/selenium/__init__.py +0 -0
  66. {slthcore-0.0.4 → slthcore-0.0.5}/slth/selenium/browser.py +0 -0
  67. {slthcore-0.0.4 → slthcore-0.0.5}/slth/static/.DS_Store +0 -0
  68. {slthcore-0.0.4 → slthcore-0.0.5}/slth/static/css/.DS_Store +0 -0
  69. {slthcore-0.0.4 → slthcore-0.0.5}/slth/static/css/slth.css +0 -0
  70. {slthcore-0.0.4 → slthcore-0.0.5}/slth/static/js/index.min.js +0 -0
  71. {slthcore-0.0.4 → slthcore-0.0.5}/slth/static/js/react.min.js +0 -0
  72. {slthcore-0.0.4 → slthcore-0.0.5}/slth/statistics.py +0 -0
  73. {slthcore-0.0.4 → slthcore-0.0.5}/slth/templates/index.html +0 -0
  74. {slthcore-0.0.4 → slthcore-0.0.5}/slth/templates/service-worker.js +0 -0
  75. {slthcore-0.0.4 → slthcore-0.0.5}/slth/tests.py +0 -0
  76. {slthcore-0.0.4 → slthcore-0.0.5}/slth/urls.py +0 -0
  77. {slthcore-0.0.4 → slthcore-0.0.5}/slthcore.egg-info/dependency_links.txt +0 -0
  78. {slthcore-0.0.4 → slthcore-0.0.5}/slthcore.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: slthcore
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: API generator based on yml file
5
5
  Home-page: https://github.com/brenokcc
6
6
  Author: Breno Silva
@@ -5,7 +5,7 @@ install_requires = []
5
5
 
6
6
  setup(
7
7
  name='slthcore',
8
- version='0.0.4',
8
+ version='0.0.5',
9
9
  packages=find_packages(),
10
10
  install_requires=install_requires,
11
11
  include_package_data=True,
@@ -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')
@@ -289,7 +289,7 @@ class Scheduler(dict):
289
289
  def __init__(
290
290
  self,
291
291
  start_time=7,
292
- end_time=20,
292
+ end_time=23,
293
293
  chucks=2,
294
294
  start_day=None,
295
295
  days=14,
@@ -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
- from .models import Token
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
- return {}
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
- return serialize(self.process())
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, "<int:{}>".format(arg))
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.contextualize(self.request)
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.model().formfactory()
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().delete()
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 = "Icons"
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
- if self.request.user.is_authenticated:
594
- serializer = Serializer(request=self.request)
595
- if APPLICATON["dashboard"]["boxes"]:
596
- boxes = Boxes("Acesso Rápido")
597
- for endpoint in APPLICATON["dashboard"]["boxes"]:
598
- cls = ENDPOINTS[endpoint]
599
- if cls().contextualize(self.request).check_permission():
600
- icon = cls.get_metadata("icon", "check")
601
- label = cls.get_metadata("verbose_name")
602
- url = build_url(self.request, cls.get_api_url())
603
- boxes.append(icon, label, url)
604
- serializer.append("Acesso Rápido", boxes)
605
- if APPLICATON["dashboard"]["top"]:
606
- group = serializer.group("Top")
607
- for endpoint in APPLICATON["dashboard"]["top"]:
608
- cls = ENDPOINTS[endpoint]
609
- if cls.instantiate(
610
- self.request, self.request.user
611
- ).check_permission():
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
- return serializer
623
- else:
624
- self.redirect("/api/login/")
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 endpoint in APPLICATON["dashboard"][entrypoint]:
650
- cls = ENDPOINTS[endpoint]
651
- if cls().instantiate(self.request, self).check_permission():
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
- if cls().instantiate(self.request, self).check_permission():
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