utg-base 1.0.1__tar.gz → 1.1.1__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.
- {utg_base-1.0.1 → utg_base-1.1.1}/PKG-INFO +1 -1
- {utg_base-1.0.1 → utg_base-1.1.1}/pyproject.toml +1 -1
- utg_base-1.1.1/src/utg_base/api/base.py +75 -0
- utg_base-1.1.1/src/utg_base/api/views.py +13 -0
- utg_base-1.1.1/src/utg_base/models/__init__.py +1 -0
- utg_base-1.1.1/src/utg_base/models/jwt_user.py +11 -0
- utg_base-1.1.1/src/utg_base/references_api/__init__.py +0 -0
- utg_base-1.1.1/src/utg_base/references_api/admin.py +3 -0
- utg_base-1.1.1/src/utg_base/references_api/apps.py +6 -0
- utg_base-1.1.1/src/utg_base/references_api/migrations/__init__.py +0 -0
- utg_base-1.1.1/src/utg_base/references_api/models.py +3 -0
- utg_base-1.1.1/src/utg_base/references_api/urls.py +13 -0
- utg_base-1.1.1/src/utg_base/references_api/utils.py +181 -0
- {utg_base-1.0.1 → utg_base-1.1.1}/README.md +0 -0
- {utg_base-1.0.1 → utg_base-1.1.1}/src/utg_base/__init__.py +0 -0
- {utg_base-1.0.1 → utg_base-1.1.1}/src/utg_base/api/__init__.py +0 -0
- {utg_base-1.0.1 → utg_base-1.1.1}/src/utg_base/api/pagination.py +0 -0
- {utg_base-1.0.1 → utg_base-1.1.1}/src/utg_base/api/routers.py +0 -0
- {utg_base-1.0.1 → utg_base-1.1.1}/src/utg_base/authentications/__init__.py +0 -0
- {utg_base-1.0.1 → utg_base-1.1.1}/src/utg_base/authentications/microservice_authentication.py +0 -0
- {utg_base-1.0.1 → utg_base-1.1.1}/src/utg_base/authentications/models.py +0 -0
- {utg_base-1.0.1 → utg_base-1.1.1}/src/utg_base/logging.py +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from django.utils import timezone
|
|
2
|
+
from django.utils.translation import activate, get_language
|
|
3
|
+
from rest_framework.request import Request
|
|
4
|
+
from rest_framework.views import APIView
|
|
5
|
+
|
|
6
|
+
from ..models import JWTUser
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
AVAILABLE_LANGUAGES = ['uz', 'ru', 'crl']
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseRequest(Request):
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def user(self) -> JWTUser:
|
|
16
|
+
return super(BaseRequest, self).user()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseAPIView(APIView):
|
|
20
|
+
user_id_assign_fields_on_create = []
|
|
21
|
+
user_id_assign_fields_on_update = []
|
|
22
|
+
user_id_assign_fields_on_delete = []
|
|
23
|
+
deleted_at_field = None
|
|
24
|
+
|
|
25
|
+
def __init__(self, **kwargs):
|
|
26
|
+
# Check user_id_assign_fields_on_create
|
|
27
|
+
assert isinstance(self.user_id_assign_fields_on_create, list), (
|
|
28
|
+
'Expected iterable user_id_assign_fields_on_create field'
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Check user_id_assign_fields_on_update
|
|
32
|
+
assert isinstance(self.user_id_assign_fields_on_update, list), (
|
|
33
|
+
'Expected iterable user_id_assign_fields_on_update field'
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Check user_id_assign_fields_on_delete
|
|
37
|
+
assert isinstance(self.user_id_assign_fields_on_delete, list), (
|
|
38
|
+
'Expected iterable user_id_assign_fields_on_delete field'
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Check deleted_at_field
|
|
42
|
+
assert isinstance(self.deleted_at_field, (str, type(None))), (
|
|
43
|
+
'Expected str | None deleted_at_field field'
|
|
44
|
+
)
|
|
45
|
+
super().__init__(**kwargs)
|
|
46
|
+
self.request: BaseRequest | None = None
|
|
47
|
+
|
|
48
|
+
def perform_create(self, serializer):
|
|
49
|
+
kws = {}
|
|
50
|
+
for attr in getattr(self, 'user_id_assign_fields_on_create', []):
|
|
51
|
+
kws[attr] = self.request.user.id
|
|
52
|
+
serializer.save(**kws)
|
|
53
|
+
|
|
54
|
+
def perform_update(self, serializer):
|
|
55
|
+
kws = {}
|
|
56
|
+
for attr in getattr(self, 'user_id_assign_fields_on_update', []):
|
|
57
|
+
kws[attr] = self.request.user.id
|
|
58
|
+
serializer.save(**kws)
|
|
59
|
+
|
|
60
|
+
def perform_destroy(self, instance):
|
|
61
|
+
for attr in getattr(self, 'user_id_assign_fields_on_delete', []):
|
|
62
|
+
setattr(instance, attr, self.request.user.id)
|
|
63
|
+
if self.deleted_at_field and hasattr(instance, self.deleted_at_field):
|
|
64
|
+
setattr(instance, self.deleted_at_field, timezone.now())
|
|
65
|
+
instance.save()
|
|
66
|
+
|
|
67
|
+
def update_lang(self):
|
|
68
|
+
activate(self.get_language())
|
|
69
|
+
|
|
70
|
+
def get_language(self):
|
|
71
|
+
if lang_from_header := self.request.headers.get('accept-language'):
|
|
72
|
+
if lang_from_header and lang_from_header not in AVAILABLE_LANGUAGES:
|
|
73
|
+
lang_from_header = 'ru'
|
|
74
|
+
|
|
75
|
+
return lang_from_header or get_language()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
from django.db.models import Manager
|
|
3
|
+
from rest_framework import generics
|
|
4
|
+
|
|
5
|
+
from .base import BaseAPIView
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TranslatedListView(generics.ListAPIView, BaseAPIView):
|
|
9
|
+
manager: Manager
|
|
10
|
+
|
|
11
|
+
def get_queryset(self):
|
|
12
|
+
self.update_lang()
|
|
13
|
+
return self.manager.all()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .jwt_user import JWTUser
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from rest_framework_simplejwt.models import TokenUser
|
|
2
|
+
from rest_framework_simplejwt.tokens import Token
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class JWTUser(TokenUser):
|
|
6
|
+
|
|
7
|
+
def __init__(self, token: "Token"):
|
|
8
|
+
super().__init__(token)
|
|
9
|
+
self.phone = self.token.get("phone")
|
|
10
|
+
self._groups = self.token.get("groups")
|
|
11
|
+
self.should_cache_requests = self.token.get("should_cache_requests")
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .utils import get_model_classes, create_view_set, get_basename, get_url_prefix
|
|
2
|
+
from ..api.routers import OptionalSlashRouter
|
|
3
|
+
|
|
4
|
+
router = OptionalSlashRouter()
|
|
5
|
+
|
|
6
|
+
for model_class in get_model_classes():
|
|
7
|
+
router.register(
|
|
8
|
+
prefix=get_url_prefix(model_class),
|
|
9
|
+
viewset=create_view_set(model_class),
|
|
10
|
+
basename=get_basename(model_class)
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
urlpatterns = router.urls
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from urllib.parse import urlparse
|
|
3
|
+
|
|
4
|
+
import inflect
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from django.db.models import Model, CharField
|
|
7
|
+
from drf_spectacular.utils import extend_schema, extend_schema_serializer
|
|
8
|
+
from rest_framework import filters
|
|
9
|
+
from rest_framework import viewsets, serializers
|
|
10
|
+
|
|
11
|
+
from ..api.views import TranslatedListView
|
|
12
|
+
|
|
13
|
+
serializer_classes = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def find_model(model_name: str):
|
|
17
|
+
for model_class in get_model_classes():
|
|
18
|
+
if get_model_class_name(model_class).lower() == model_name.lower():
|
|
19
|
+
return model_class
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_api_meta_property(model: Model, property_name: str):
|
|
23
|
+
if hasattr(model, 'ApiMeta'):
|
|
24
|
+
if hasattr(model.ApiMeta, property_name):
|
|
25
|
+
return getattr(model.ApiMeta, property_name)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_model_class_name(model: Model):
|
|
29
|
+
return model._meta.object_name
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_model_fields_list(model: Model):
|
|
33
|
+
return model._meta.get_fields()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_view_set_name(model: Model):
|
|
37
|
+
return get_model_class_name(model) + 'ViewSet'
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_basename(model: Model):
|
|
41
|
+
return 'admin-' + get_plural_form(get_model_class_name(model).lower())
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def camel_to_snake(name: str):
|
|
45
|
+
return ''.join(map(lambda c: '-' + c.lower() if c.isupper() else c, name)).removeprefix('-')
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_url_prefix(model: Model):
|
|
49
|
+
return 'admin/' + get_plural_form(camel_to_snake(get_model_class_name(model))).replace(' ', '-')
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_plural_form(word: str):
|
|
53
|
+
p = inflect.engine()
|
|
54
|
+
return p.plural(word)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_serializer_name(model: Model):
|
|
58
|
+
return get_model_class_name(model) + 'Serializer'
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_ordering(model: Model):
|
|
62
|
+
if model._meta.ordering:
|
|
63
|
+
return model._meta.ordering
|
|
64
|
+
if hasattr(model, 'created_at'):
|
|
65
|
+
return ('-created_at',)
|
|
66
|
+
if hasattr(model, 'id'):
|
|
67
|
+
return ('id',)
|
|
68
|
+
return ()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_model_classes():
|
|
72
|
+
model_classes = []
|
|
73
|
+
for model_import_path in settings.REFERENCE_API_MODELS:
|
|
74
|
+
module_name, class_name = model_import_path.rsplit('.', 1)
|
|
75
|
+
module = importlib.import_module(module_name)
|
|
76
|
+
model_classes.append(getattr(module, class_name))
|
|
77
|
+
|
|
78
|
+
return model_classes
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def create_view_set(model: Model):
|
|
82
|
+
fields_list = get_model_fields_list(model)
|
|
83
|
+
|
|
84
|
+
@extend_schema(tags=[get_url_prefix(model)])
|
|
85
|
+
class ViewSet(viewsets.ModelViewSet, TranslatedListView):
|
|
86
|
+
http_method_names = get_api_meta_property(model, 'http_method_names') or ['get', 'patch']
|
|
87
|
+
manager = model.objects.order_by(*get_ordering(model))
|
|
88
|
+
filter_backends = [filters.SearchFilter]
|
|
89
|
+
search_fields = (get_api_meta_property(model, 'search_fields') or
|
|
90
|
+
[field.name for field in fields_list if isinstance(field, CharField)])
|
|
91
|
+
|
|
92
|
+
def get_serializer_class(self):
|
|
93
|
+
if self.action == 'create':
|
|
94
|
+
return create_serializer_for_create(model)
|
|
95
|
+
|
|
96
|
+
if self.action == 'partial_update':
|
|
97
|
+
return create_serializer_for_partial_update(model)
|
|
98
|
+
|
|
99
|
+
return create_serializer(model)
|
|
100
|
+
|
|
101
|
+
return ViewSet
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def create_serializer_for_create(model: Model):
|
|
105
|
+
@extend_schema_serializer(component_name='Admin' + get_serializer_name(model))
|
|
106
|
+
class Serializer(serializers.ModelSerializer):
|
|
107
|
+
class Meta:
|
|
108
|
+
fields = '__all__'
|
|
109
|
+
|
|
110
|
+
Serializer.Meta.model = model
|
|
111
|
+
return Serializer
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def create_serializer_for_partial_update(model: Model):
|
|
115
|
+
fields_list = get_model_fields_list(model)
|
|
116
|
+
|
|
117
|
+
@extend_schema_serializer(component_name='Admin' + get_serializer_name(model))
|
|
118
|
+
class Serializer(serializers.ModelSerializer):
|
|
119
|
+
class Meta:
|
|
120
|
+
fields = '__all__'
|
|
121
|
+
read_only_fields = list(
|
|
122
|
+
{
|
|
123
|
+
'created_at',
|
|
124
|
+
'updated_at',
|
|
125
|
+
'deleted_at',
|
|
126
|
+
'created_by',
|
|
127
|
+
'updated_by',
|
|
128
|
+
'deleted_by',
|
|
129
|
+
'code'
|
|
130
|
+
} & set([field.name for field in fields_list])
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
Serializer.Meta.model = model
|
|
134
|
+
return Serializer
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def create_serializer(model: Model, depth=0):
|
|
138
|
+
if get_model_class_name(model) in serializer_classes:
|
|
139
|
+
return serializer_classes[get_model_class_name(model)]
|
|
140
|
+
|
|
141
|
+
fields_list = get_model_fields_list(model)
|
|
142
|
+
|
|
143
|
+
class ImageNameField(serializers.ImageField):
|
|
144
|
+
def to_representation(self, value):
|
|
145
|
+
if not value:
|
|
146
|
+
return None
|
|
147
|
+
parsed_url = urlparse(value.url)
|
|
148
|
+
return f'{parsed_url.path}?{parsed_url.query}'
|
|
149
|
+
|
|
150
|
+
@extend_schema_serializer(component_name='Admin' + get_serializer_name(model))
|
|
151
|
+
class Serializer(serializers.ModelSerializer):
|
|
152
|
+
def __init__(self, *args, **kwargs):
|
|
153
|
+
super().__init__(*args, **kwargs)
|
|
154
|
+
nonlocal model, fields_list
|
|
155
|
+
for field in fields_list:
|
|
156
|
+
if field.__class__.__name__.endswith('ImageField') or field.__class__.__name__.endswith('FileField'):
|
|
157
|
+
self.fields[field.name] = ImageNameField()
|
|
158
|
+
|
|
159
|
+
if field.__class__.__name__.endswith('ForeignKey'):
|
|
160
|
+
model_name = field.name
|
|
161
|
+
_model = find_model(model_name)
|
|
162
|
+
if _model is not None and depth == 0:
|
|
163
|
+
self.fields[model_name] = create_serializer(_model, depth + 1)()
|
|
164
|
+
|
|
165
|
+
class Meta:
|
|
166
|
+
fields = '__all__'
|
|
167
|
+
read_only_fields = list(
|
|
168
|
+
{
|
|
169
|
+
'created_at',
|
|
170
|
+
'updated_at',
|
|
171
|
+
'deleted_at',
|
|
172
|
+
'created_by',
|
|
173
|
+
'updated_by',
|
|
174
|
+
'deleted_by',
|
|
175
|
+
'code'
|
|
176
|
+
} & set([field.name for field in fields_list])
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
Serializer.Meta.model = model
|
|
180
|
+
serializer_classes[get_model_class_name(model)] = Serializer
|
|
181
|
+
return Serializer
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{utg_base-1.0.1 → utg_base-1.1.1}/src/utg_base/authentications/microservice_authentication.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|