utg-base 1.5.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.5.1/PKG-INFO +24 -0
- utg_base-1.5.1/README.md +1 -0
- utg_base-1.5.1/pyproject.toml +30 -0
- utg_base-1.5.1/src/utg_base/__init__.py +0 -0
- utg_base-1.5.1/src/utg_base/api/__init__.py +0 -0
- utg_base-1.5.1/src/utg_base/api/base.py +73 -0
- utg_base-1.5.1/src/utg_base/api/pagination.py +33 -0
- utg_base-1.5.1/src/utg_base/api/permissions.py +11 -0
- utg_base-1.5.1/src/utg_base/api/routers.py +7 -0
- utg_base-1.5.1/src/utg_base/api/serializers.py +39 -0
- utg_base-1.5.1/src/utg_base/api/views.py +13 -0
- utg_base-1.5.1/src/utg_base/authentications/__init__.py +2 -0
- utg_base-1.5.1/src/utg_base/authentications/microservice_authentication.py +29 -0
- utg_base-1.5.1/src/utg_base/authentications/models.py +92 -0
- utg_base-1.5.1/src/utg_base/celery/__init__.py +0 -0
- utg_base-1.5.1/src/utg_base/celery/apps.py +6 -0
- utg_base-1.5.1/src/utg_base/celery/filters/__init__.py +1 -0
- utg_base-1.5.1/src/utg_base/celery/filters/task_result.py +5 -0
- utg_base-1.5.1/src/utg_base/celery/management/__init__.py +0 -0
- utg_base-1.5.1/src/utg_base/celery/management/commands/__init__.py +0 -0
- utg_base-1.5.1/src/utg_base/celery/management/commands/migrate_tasks.py +45 -0
- utg_base-1.5.1/src/utg_base/celery/serializers/__init__.py +2 -0
- utg_base-1.5.1/src/utg_base/celery/serializers/periodic_task.py +38 -0
- utg_base-1.5.1/src/utg_base/celery/serializers/task_result.py +8 -0
- utg_base-1.5.1/src/utg_base/celery/urls.py +14 -0
- utg_base-1.5.1/src/utg_base/celery/views/__init__.py +2 -0
- utg_base-1.5.1/src/utg_base/celery/views/periodic_task.py +56 -0
- utg_base-1.5.1/src/utg_base/celery/views/task_result.py +18 -0
- utg_base-1.5.1/src/utg_base/constants/__init__.py +1 -0
- utg_base-1.5.1/src/utg_base/constants/available_languages.py +5 -0
- utg_base-1.5.1/src/utg_base/env.py +27 -0
- utg_base-1.5.1/src/utg_base/logging.py +125 -0
- utg_base-1.5.1/src/utg_base/middleware/__init__.py +0 -0
- utg_base-1.5.1/src/utg_base/middleware/locale.py +13 -0
- utg_base-1.5.1/src/utg_base/models/__init__.py +2 -0
- utg_base-1.5.1/src/utg_base/models/jwt_user.py +11 -0
- utg_base-1.5.1/src/utg_base/models/timestamp.py +9 -0
- utg_base-1.5.1/src/utg_base/references_api/__init__.py +0 -0
- utg_base-1.5.1/src/utg_base/references_api/admin.py +3 -0
- utg_base-1.5.1/src/utg_base/references_api/apps.py +6 -0
- utg_base-1.5.1/src/utg_base/references_api/migrations/__init__.py +0 -0
- utg_base-1.5.1/src/utg_base/references_api/models.py +3 -0
- utg_base-1.5.1/src/utg_base/references_api/urls.py +13 -0
- utg_base-1.5.1/src/utg_base/references_api/utils.py +181 -0
- utg_base-1.5.1/src/utg_base/services/__init__.py +1 -0
- utg_base-1.5.1/src/utg_base/services/base_api.py +128 -0
- utg_base-1.5.1/src/utg_base/utils/__init__.py +2 -0
- utg_base-1.5.1/src/utg_base/utils/data.py +106 -0
- utg_base-1.5.1/src/utg_base/utils/date.py +126 -0
- utg_base-1.5.1/src/utg_base/utils/dict_util.py +20 -0
- utg_base-1.5.1/src/utg_base/utils/response_processors.py +31 -0
- utg_base-1.5.1/src/utg_base/utils/sql.py +25 -0
- utg_base-1.5.1/src/utg_base/utils/translation.py +18 -0
utg_base-1.5.1/PKG-INFO
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: utg-base
|
|
3
|
+
Version: 1.5.1
|
|
4
|
+
Summary: UTG Base Package
|
|
5
|
+
Author: Rovshen
|
|
6
|
+
Author-email: rovshenashirov1619@gmail.com
|
|
7
|
+
Requires-Python: >=3.14,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Requires-Dist: celery (>=5.5.3,<6.0.0)
|
|
10
|
+
Requires-Dist: croniter (>=2.0.3,<3.0.0)
|
|
11
|
+
Requires-Dist: django (>=5.2.7,<6.0.0)
|
|
12
|
+
Requires-Dist: django-celery-beat (>=2.8.1,<3.0.0)
|
|
13
|
+
Requires-Dist: django-celery-results (>=2.6.0,<3.0.0)
|
|
14
|
+
Requires-Dist: django-filter (>=23.5,<24.0)
|
|
15
|
+
Requires-Dist: djangorestframework (>=3.16.1,<4.0.0)
|
|
16
|
+
Requires-Dist: djangorestframework-simplejwt[crypto] (>=5.5.1,<6.0.0)
|
|
17
|
+
Requires-Dist: dotenv (>=0.9.9,<0.10.0)
|
|
18
|
+
Requires-Dist: drf-spectacular[sidecar] (>=0.29.0,<0.30.0)
|
|
19
|
+
Requires-Dist: hvac (>=2.4.0,<3.0.0)
|
|
20
|
+
Requires-Dist: inflect (>=7.2.1,<8.0.0)
|
|
21
|
+
Requires-Dist: openpyxl (>=3.1.2,<4.0.0)
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# UzTransGas Base
|
utg_base-1.5.1/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# UzTransGas Base
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "utg-base"
|
|
3
|
+
version = "1.5.1"
|
|
4
|
+
description = "UTG Base Package"
|
|
5
|
+
authors = ["Rovshen <rovshenashirov1619@gmail.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
|
|
8
|
+
[tool.poetry.dependencies]
|
|
9
|
+
python = "^3.14"
|
|
10
|
+
djangorestframework = "^3.16.1"
|
|
11
|
+
djangorestframework-simplejwt = {extras = ["crypto"], version = "^5.5.1"}
|
|
12
|
+
django = "^5.2.7"
|
|
13
|
+
django-filter = "^23.5"
|
|
14
|
+
croniter = "^2.0.3"
|
|
15
|
+
drf-spectacular = {extras = ["sidecar"], version = "^0.29.0"}
|
|
16
|
+
openpyxl = "^3.1.2"
|
|
17
|
+
inflect = "^7.2.1"
|
|
18
|
+
celery = "^5.5.3"
|
|
19
|
+
django-celery-beat = "^2.8.1"
|
|
20
|
+
django-celery-results = "^2.6.0"
|
|
21
|
+
hvac = "^2.4.0"
|
|
22
|
+
dotenv = "^0.9.9"
|
|
23
|
+
|
|
24
|
+
[[tool.poetry.source]]
|
|
25
|
+
name = "PyPI"
|
|
26
|
+
priority = "primary"
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["poetry-core"]
|
|
30
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
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 utg_base.constants import AVAILABLE_LANGUAGES
|
|
7
|
+
from utg_base.models import JWTUser
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseRequest(Request):
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def user(self) -> JWTUser:
|
|
14
|
+
return super(BaseRequest, self).user()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseAPIView(APIView):
|
|
18
|
+
user_id_assign_fields_on_create = []
|
|
19
|
+
user_id_assign_fields_on_update = []
|
|
20
|
+
user_id_assign_fields_on_delete = []
|
|
21
|
+
deleted_at_field = None
|
|
22
|
+
|
|
23
|
+
def __init__(self, **kwargs):
|
|
24
|
+
# Check user_id_assign_fields_on_create
|
|
25
|
+
assert isinstance(self.user_id_assign_fields_on_create, list), (
|
|
26
|
+
'Expected iterable user_id_assign_fields_on_create field'
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Check user_id_assign_fields_on_update
|
|
30
|
+
assert isinstance(self.user_id_assign_fields_on_update, list), (
|
|
31
|
+
'Expected iterable user_id_assign_fields_on_update field'
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Check user_id_assign_fields_on_delete
|
|
35
|
+
assert isinstance(self.user_id_assign_fields_on_delete, list), (
|
|
36
|
+
'Expected iterable user_id_assign_fields_on_delete field'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Check deleted_at_field
|
|
40
|
+
assert isinstance(self.deleted_at_field, (str, type(None))), (
|
|
41
|
+
'Expected str | None deleted_at_field field'
|
|
42
|
+
)
|
|
43
|
+
super().__init__(**kwargs)
|
|
44
|
+
self.request: BaseRequest | None = None
|
|
45
|
+
|
|
46
|
+
def perform_create(self, serializer):
|
|
47
|
+
kws = {}
|
|
48
|
+
for attr in getattr(self, 'user_id_assign_fields_on_create', []):
|
|
49
|
+
kws[attr] = self.request.user.id
|
|
50
|
+
serializer.save(**kws)
|
|
51
|
+
|
|
52
|
+
def perform_update(self, serializer):
|
|
53
|
+
kws = {}
|
|
54
|
+
for attr in getattr(self, 'user_id_assign_fields_on_update', []):
|
|
55
|
+
kws[attr] = self.request.user.id
|
|
56
|
+
serializer.save(**kws)
|
|
57
|
+
|
|
58
|
+
def perform_destroy(self, instance):
|
|
59
|
+
for attr in getattr(self, 'user_id_assign_fields_on_delete', []):
|
|
60
|
+
setattr(instance, attr, self.request.user.id)
|
|
61
|
+
if self.deleted_at_field and hasattr(instance, self.deleted_at_field):
|
|
62
|
+
setattr(instance, self.deleted_at_field, timezone.now())
|
|
63
|
+
instance.save()
|
|
64
|
+
|
|
65
|
+
def update_lang(self):
|
|
66
|
+
activate(self.get_language())
|
|
67
|
+
|
|
68
|
+
def get_language(self):
|
|
69
|
+
if lang_from_header := self.request.headers.get('accept-language'):
|
|
70
|
+
if lang_from_header and lang_from_header not in AVAILABLE_LANGUAGES:
|
|
71
|
+
lang_from_header = 'ru'
|
|
72
|
+
|
|
73
|
+
return lang_from_header or get_language()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from rest_framework.pagination import PageNumberPagination
|
|
2
|
+
from rest_framework.response import Response
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Pagination(PageNumberPagination):
|
|
6
|
+
page_size_query_param = 'page_size'
|
|
7
|
+
max_page_size = 200
|
|
8
|
+
|
|
9
|
+
def get_paginated_response(self, data):
|
|
10
|
+
return Response({
|
|
11
|
+
'page': self.page.number,
|
|
12
|
+
'pageSize': self.page.paginator.per_page,
|
|
13
|
+
'count': self.page.end_index() - self.page.start_index() + 1,
|
|
14
|
+
'total': self.page.paginator.count,
|
|
15
|
+
'pagesCount': self.page.paginator.num_pages,
|
|
16
|
+
'data': data,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LargeResultsSetPagination(Pagination):
|
|
21
|
+
page_size = 50
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class StandardResultsSetPagination(Pagination):
|
|
25
|
+
page_size = 20
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SmallResultsSetPagination(Pagination):
|
|
29
|
+
page_size = 10
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MiniResultsSetPagination(Pagination):
|
|
33
|
+
page_size = 5
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from rest_framework.permissions import BasePermission
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class IsSuperUser(BasePermission):
|
|
5
|
+
"""
|
|
6
|
+
Custom permission to allow access only to superusers.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def has_permission(self, request, view):
|
|
10
|
+
# Check if the requesting user is a superuser
|
|
11
|
+
return request.user and request.user.is_superuser
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from rest_framework import serializers
|
|
2
|
+
from rest_framework.exceptions import ValidationError
|
|
3
|
+
|
|
4
|
+
from utg_base.utils.date import to_udate, to_udatetime
|
|
5
|
+
from utg_base.utils.translation import translate as _
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UDateField(serializers.DateField):
|
|
9
|
+
def to_internal_value(self, value):
|
|
10
|
+
return to_udate(super().to_internal_value(value))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UDateTimeField(serializers.DateTimeField):
|
|
14
|
+
def to_internal_value(self, value):
|
|
15
|
+
return to_udatetime(super().to_internal_value(value))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DateSerializer(serializers.Serializer):
|
|
19
|
+
dt = UDateField()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DatePeriodSerializer(DateSerializer):
|
|
23
|
+
period = serializers.ChoiceField(choices=['daily', 'monthly', 'yearly'], default='daily')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DateTimePeriodSerializer(DateSerializer):
|
|
27
|
+
dt = UDateTimeField(required=False)
|
|
28
|
+
period = serializers.ChoiceField(choices=['hourly', 'today', 'daily', 'monthly', 'yearly'], default='daily')
|
|
29
|
+
|
|
30
|
+
def validate(self, attrs):
|
|
31
|
+
period = attrs.get('period', 'daily')
|
|
32
|
+
dt = attrs.get('dt')
|
|
33
|
+
|
|
34
|
+
if period != 'today' and dt is None:
|
|
35
|
+
raise ValidationError({
|
|
36
|
+
'dt': _('This field is required when period is not "today".')
|
|
37
|
+
})
|
|
38
|
+
return attrs
|
|
39
|
+
|
|
@@ -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,29 @@
|
|
|
1
|
+
from ipaddress import ip_address, ip_network
|
|
2
|
+
|
|
3
|
+
from rest_framework import authentication
|
|
4
|
+
|
|
5
|
+
from . import MicroserviceUser
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MicroserviceUserAuthentication(authentication.BaseAuthentication):
|
|
9
|
+
# List of local IP address ranges
|
|
10
|
+
local_ip_ranges = [
|
|
11
|
+
'127.0.0.0/8', # Loopback addresses
|
|
12
|
+
'10.0.0.0/8', # Private network addresses
|
|
13
|
+
'172.16.0.0/12', # Private network addresses
|
|
14
|
+
'192.168.0.0/16', # Private network addresses
|
|
15
|
+
# Add more local ranges if needed
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
def authenticate(self, request):
|
|
19
|
+
client_ip = request.META.get('REMOTE_ADDR', None)
|
|
20
|
+
server_ip = request.META.get('SERVER_ADDR', None)
|
|
21
|
+
|
|
22
|
+
if any(ip_address(client_ip) in ip_network(ip_range, strict=False) for ip_range in self.local_ip_ranges):
|
|
23
|
+
return self.get_user(), None
|
|
24
|
+
|
|
25
|
+
if client_ip == server_ip:
|
|
26
|
+
return self.get_user(), None
|
|
27
|
+
|
|
28
|
+
def get_user(self) -> MicroserviceUser:
|
|
29
|
+
return MicroserviceUser()
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from typing import List, Optional, Union
|
|
2
|
+
|
|
3
|
+
from django.contrib.auth import models as auth_models
|
|
4
|
+
from django.db.models.manager import EmptyManager
|
|
5
|
+
from django.utils.functional import cached_property
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MicroserviceUser:
|
|
9
|
+
is_active = True
|
|
10
|
+
|
|
11
|
+
_groups = EmptyManager(auth_models.Group)
|
|
12
|
+
_user_permissions = EmptyManager(auth_models.Permission)
|
|
13
|
+
|
|
14
|
+
def __str__(self) -> str:
|
|
15
|
+
return f"{self.id}"
|
|
16
|
+
|
|
17
|
+
@cached_property
|
|
18
|
+
def id(self) -> Union[int, str]:
|
|
19
|
+
return -1
|
|
20
|
+
|
|
21
|
+
@cached_property
|
|
22
|
+
def pk(self) -> Union[int, str]:
|
|
23
|
+
return self.id
|
|
24
|
+
|
|
25
|
+
@cached_property
|
|
26
|
+
def username(self) -> str:
|
|
27
|
+
return 'microservice'
|
|
28
|
+
|
|
29
|
+
@cached_property
|
|
30
|
+
def is_staff(self) -> bool:
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
@cached_property
|
|
34
|
+
def is_superuser(self) -> bool:
|
|
35
|
+
return True
|
|
36
|
+
|
|
37
|
+
def __eq__(self, other: object) -> bool:
|
|
38
|
+
if not isinstance(other, MicroserviceUser):
|
|
39
|
+
return NotImplemented
|
|
40
|
+
return self.id == other.id
|
|
41
|
+
|
|
42
|
+
def __ne__(self, other: object) -> bool:
|
|
43
|
+
return not self.__eq__(other)
|
|
44
|
+
|
|
45
|
+
def __hash__(self) -> int:
|
|
46
|
+
return hash(self.id)
|
|
47
|
+
|
|
48
|
+
def save(self) -> None:
|
|
49
|
+
raise NotImplementedError("Microservice users have no DB representation")
|
|
50
|
+
|
|
51
|
+
def delete(self) -> None:
|
|
52
|
+
raise NotImplementedError("Microservice users have no DB representation")
|
|
53
|
+
|
|
54
|
+
def set_password(self, raw_password: str) -> None:
|
|
55
|
+
raise NotImplementedError("Microservice users have no DB representation")
|
|
56
|
+
|
|
57
|
+
def check_password(self, raw_password: str) -> None:
|
|
58
|
+
raise NotImplementedError("Microservice users have no DB representation")
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def groups(self) -> auth_models.Group:
|
|
62
|
+
return self._groups
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def user_permissions(self) -> auth_models.Permission:
|
|
66
|
+
return self._user_permissions
|
|
67
|
+
|
|
68
|
+
def get_group_permissions(self, obj: Optional[object] = None) -> set:
|
|
69
|
+
return set()
|
|
70
|
+
|
|
71
|
+
def get_all_permissions(self, obj: Optional[object] = None) -> set:
|
|
72
|
+
return set()
|
|
73
|
+
|
|
74
|
+
def has_perm(self, perm: str, obj: Optional[object] = None) -> bool:
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
def has_perms(self, perm_list: List[str], obj: Optional[object] = None) -> bool:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
def has_module_perms(self, module: str) -> bool:
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def is_anonymous(self) -> bool:
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def is_authenticated(self) -> bool:
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
def get_username(self) -> str:
|
|
92
|
+
return self.username
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .task_result import TaskResultFilterSet
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from django.core.management.base import BaseCommand
|
|
5
|
+
from django_celery_beat.models import CrontabSchedule, PeriodicTask
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Command(BaseCommand):
|
|
9
|
+
help = "Migrate tasks"
|
|
10
|
+
|
|
11
|
+
def add_arguments(self, parser):
|
|
12
|
+
parser.add_argument('--app', type=str, help="An optional argument", required=True)
|
|
13
|
+
|
|
14
|
+
def handle(self, *args, **options):
|
|
15
|
+
try:
|
|
16
|
+
tasks = importlib.import_module(f"{options['app']}.tasks")
|
|
17
|
+
tasks = tasks.tasks
|
|
18
|
+
for task in tasks:
|
|
19
|
+
celery_task, _ = PeriodicTask.objects.update_or_create(
|
|
20
|
+
name=task['name'],
|
|
21
|
+
defaults={
|
|
22
|
+
'crontab': self.get_crontab_schedule(task['crontab']),
|
|
23
|
+
'task': task['task'].name,
|
|
24
|
+
'args': json.dumps(task.get('args') or []),
|
|
25
|
+
'kwargs': json.dumps(task.get('kwargs') or {}),
|
|
26
|
+
'enabled': task.get('enabled', True),
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
self.stdout.write(
|
|
30
|
+
self.style.SUCCESS('Successfully migrated tasks')
|
|
31
|
+
)
|
|
32
|
+
except ModuleNotFoundError:
|
|
33
|
+
self.stdout.write(self.style.ERROR("Tasks module not found"))
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def get_crontab_schedule(crontab='* * * * *'):
|
|
37
|
+
minute, hour, day_of_month, month_of_year, day_of_week = crontab.split(' ')
|
|
38
|
+
cron, _ = CrontabSchedule.objects.get_or_create(
|
|
39
|
+
minute=minute,
|
|
40
|
+
hour=hour,
|
|
41
|
+
day_of_month=day_of_month,
|
|
42
|
+
month_of_year=month_of_year,
|
|
43
|
+
day_of_week=day_of_week,
|
|
44
|
+
)
|
|
45
|
+
return cron
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from django_celery_beat.models import PeriodicTask, CrontabSchedule
|
|
2
|
+
from django_celery_results.models import TaskResult
|
|
3
|
+
from rest_framework import serializers
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from utg_base.celery.serializers import TaskResultSerializer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CrontabScheduleSerializer(serializers.ModelSerializer):
|
|
10
|
+
timezone = serializers.SerializerMethodField()
|
|
11
|
+
|
|
12
|
+
class Meta:
|
|
13
|
+
model = CrontabSchedule
|
|
14
|
+
fields = '__all__'
|
|
15
|
+
|
|
16
|
+
def get_timezone(self, obj):
|
|
17
|
+
return str(obj.timezone) if obj.timezone else None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PeriodicTaskSerializer(serializers.ModelSerializer):
|
|
21
|
+
crontab = CrontabScheduleSerializer(read_only=True)
|
|
22
|
+
task_result = serializers.SerializerMethodField(read_only=True)
|
|
23
|
+
|
|
24
|
+
class Meta:
|
|
25
|
+
model = PeriodicTask
|
|
26
|
+
fields = '__all__'
|
|
27
|
+
read_only_fields = ['name', 'task', 'args', 'kwargs', 'queue', 'exchange', 'routing_key', 'headers', 'priority',
|
|
28
|
+
'expires', 'expire_seconds', 'one_off', 'start_time', 'interval', 'solar', 'clocked']
|
|
29
|
+
|
|
30
|
+
def get_task_result(self, obj):
|
|
31
|
+
latest_task_result = TaskResult.objects.filter(
|
|
32
|
+
periodic_task_name=obj.name
|
|
33
|
+
).order_by('-date_created').first()
|
|
34
|
+
|
|
35
|
+
if not latest_task_result:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
return TaskResultSerializer(latest_task_result).data
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from django.urls import path
|
|
2
|
+
|
|
3
|
+
from utg_base.api.routers import OptionalSlashRouter
|
|
4
|
+
from utg_base.celery.views import TaskResultViewSet, PeriodicTaskViewSet, PeriodicTaskRunNowView
|
|
5
|
+
|
|
6
|
+
router = OptionalSlashRouter()
|
|
7
|
+
router.register('task-results', TaskResultViewSet, basename='task-results')
|
|
8
|
+
router.register('periodic-tasks', PeriodicTaskViewSet, basename='periodic-tasks')
|
|
9
|
+
|
|
10
|
+
urlpatterns = [
|
|
11
|
+
path('periodic-tasks/<pk>/run-now/', PeriodicTaskRunNowView.as_view(), name='periodic-tasks-run-now'),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
urlpatterns += router.urls
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from celery import current_app
|
|
4
|
+
from django_celery_beat.models import PeriodicTask
|
|
5
|
+
from drf_spectacular.utils import extend_schema
|
|
6
|
+
from rest_framework import viewsets, filters
|
|
7
|
+
from rest_framework.exceptions import NotFound, ValidationError
|
|
8
|
+
from rest_framework.permissions import IsAuthenticated
|
|
9
|
+
from rest_framework.response import Response
|
|
10
|
+
from rest_framework.views import APIView
|
|
11
|
+
|
|
12
|
+
from utg_base.api.permissions import IsSuperUser
|
|
13
|
+
from utg_base.celery.serializers import PeriodicTaskSerializer
|
|
14
|
+
from utg_base.utils.translation import translate as _
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@extend_schema(tags=['admin/periodic-tasks'])
|
|
18
|
+
class PeriodicTaskViewSet(viewsets.ModelViewSet):
|
|
19
|
+
http_method_names = ['get', 'patch']
|
|
20
|
+
queryset = PeriodicTask.objects.all()
|
|
21
|
+
serializer_class = PeriodicTaskSerializer
|
|
22
|
+
permission_classes = [IsSuperUser]
|
|
23
|
+
filter_backends = [filters.SearchFilter]
|
|
24
|
+
search_fields = ['name']
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@extend_schema(tags=['admin/periodic-tasks'])
|
|
28
|
+
class PeriodicTaskRunNowView(APIView):
|
|
29
|
+
permission_classes = [IsAuthenticated]
|
|
30
|
+
|
|
31
|
+
def patch(self, request, pk):
|
|
32
|
+
try:
|
|
33
|
+
periodic_task = PeriodicTask.objects.get(pk=pk)
|
|
34
|
+
except PeriodicTask.DoesNotExist:
|
|
35
|
+
raise NotFound(detail=_("Periodic task not found"))
|
|
36
|
+
|
|
37
|
+
if not periodic_task.enabled:
|
|
38
|
+
raise ValidationError(detail=_("Task is disabled"))
|
|
39
|
+
|
|
40
|
+
args = json.loads(periodic_task.args) if periodic_task.args else []
|
|
41
|
+
kwargs = json.loads(periodic_task.kwargs) if periodic_task.kwargs else {}
|
|
42
|
+
|
|
43
|
+
task = current_app.send_task(
|
|
44
|
+
periodic_task.task,
|
|
45
|
+
args=args,
|
|
46
|
+
kwargs=kwargs,
|
|
47
|
+
countdown=0,
|
|
48
|
+
headers = {'periodic_task_name': periodic_task.name}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return Response({
|
|
52
|
+
"detail": _("Task successfully triggered"),
|
|
53
|
+
"task_id": task.id,
|
|
54
|
+
"task_name": periodic_task.name,
|
|
55
|
+
"celery_task": periodic_task.task,
|
|
56
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import django_filters
|
|
2
|
+
from django_celery_results.models import TaskResult
|
|
3
|
+
from drf_spectacular.utils import extend_schema
|
|
4
|
+
from rest_framework import viewsets, filters
|
|
5
|
+
|
|
6
|
+
from utg_base.celery.filters import TaskResultFilterSet
|
|
7
|
+
from utg_base.api.permissions import IsSuperUser
|
|
8
|
+
from utg_base.celery.serializers import TaskResultSerializer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@extend_schema(tags=['admin/task-results'])
|
|
12
|
+
class TaskResultViewSet(viewsets.ReadOnlyModelViewSet):
|
|
13
|
+
queryset = TaskResult.objects.all()
|
|
14
|
+
serializer_class = TaskResultSerializer
|
|
15
|
+
permission_classes = [IsSuperUser]
|
|
16
|
+
filterset_class = TaskResultFilterSet
|
|
17
|
+
filter_backends = [filters.SearchFilter, django_filters.rest_framework.DjangoFilterBackend]
|
|
18
|
+
search_fields = ['name']
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .available_languages import AVAILABLE_LANGUAGES
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import dotenv
|
|
4
|
+
import hvac
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
|
|
7
|
+
dotenv.load_dotenv(settings.BASE_DIR / '.env')
|
|
8
|
+
|
|
9
|
+
client = hvac.Client(
|
|
10
|
+
url=os.environ.get("VAULT_URL"),
|
|
11
|
+
token=os.environ.get("VAULT_TOKEN"),
|
|
12
|
+
verify=False
|
|
13
|
+
)
|
|
14
|
+
if not client.is_authenticated():
|
|
15
|
+
raise Exception("Vault authentication failed")
|
|
16
|
+
|
|
17
|
+
envs = client.secrets.kv.read_secret_version(
|
|
18
|
+
path=os.environ.get("VAULT_PATH"),
|
|
19
|
+
mount_point="utg-scada"
|
|
20
|
+
)['data']['data']
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def env(key, default=None):
|
|
24
|
+
if key in os.environ:
|
|
25
|
+
return os.environ.get(key, default)
|
|
26
|
+
else:
|
|
27
|
+
return envs.get(key, default)
|