forked from shaba/openuds
Fixed cache bug intruduced on encoders module removal
Improbed type checking for REST (ongoing) Optimized queries (using prefetch) for service pools listing
This commit is contained in:
parent
45ca92b77e
commit
6c54f8e75a
@ -56,6 +56,10 @@ from .handlers import (
|
||||
|
||||
from . import processors
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.core.util.request import ExtendedHttpRequest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__all__ = ['Handler', 'Dispatcher']
|
||||
@ -72,7 +76,7 @@ class Dispatcher(View):
|
||||
|
||||
# pylint: disable=too-many-locals, too-many-return-statements, too-many-branches, too-many-statements
|
||||
@method_decorator(csrf_exempt)
|
||||
def dispatch(self, request: http.HttpRequest, *args, **kwargs):
|
||||
def dispatch(self, request: 'ExtendedHttpRequest', *args, **kwargs):
|
||||
"""
|
||||
Processes the REST request and routes it wherever it needs to be routed
|
||||
"""
|
||||
|
@ -36,7 +36,16 @@ import typing
|
||||
|
||||
from django.db.models import Q, Count
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
from uds.models import ServicePool, OSManager, Service, Image, ServicePoolGroup, Account, User, getSqlDatetime
|
||||
from uds.models import (
|
||||
ServicePool,
|
||||
OSManager,
|
||||
Service,
|
||||
Image,
|
||||
ServicePoolGroup,
|
||||
Account,
|
||||
User,
|
||||
getSqlDatetime,
|
||||
)
|
||||
from uds.models.calendar_action import (
|
||||
CALENDAR_ACTION_INITIAL,
|
||||
CALENDAR_ACTION_MAX,
|
||||
@ -63,7 +72,14 @@ from uds.core.util import permissions
|
||||
from uds.REST.model import ModelHandler
|
||||
from uds.REST import RequestError, ResponseError
|
||||
|
||||
from .user_services import AssignedService, CachedService, Groups, Transports, Publications, Changelog
|
||||
from .user_services import (
|
||||
AssignedService,
|
||||
CachedService,
|
||||
Groups,
|
||||
Transports,
|
||||
Publications,
|
||||
Changelog,
|
||||
)
|
||||
from .op_calendars import AccessCalendars, ActionsCalendars
|
||||
from .services import Services
|
||||
|
||||
@ -75,6 +91,7 @@ class ServicesPools(ModelHandler):
|
||||
"""
|
||||
Handles Services Pools REST requests
|
||||
"""
|
||||
|
||||
model = ServicePool
|
||||
detail = {
|
||||
'services': AssignedService,
|
||||
@ -84,13 +101,30 @@ class ServicesPools(ModelHandler):
|
||||
'publications': Publications,
|
||||
'changelog': Changelog,
|
||||
'access': AccessCalendars,
|
||||
'actions': ActionsCalendars
|
||||
'actions': ActionsCalendars,
|
||||
}
|
||||
|
||||
save_fields = ['name', 'short_name', 'comments', 'tags', 'service_id',
|
||||
'osmanager_id', 'image_id', 'pool_group_id', 'initial_srvs',
|
||||
'cache_l1_srvs', 'cache_l2_srvs', 'max_srvs', 'show_transports', 'visible',
|
||||
'allow_users_remove', 'allow_users_reset', 'ignores_unused', 'account_id', 'calendar_message']
|
||||
save_fields = [
|
||||
'name',
|
||||
'short_name',
|
||||
'comments',
|
||||
'tags',
|
||||
'service_id',
|
||||
'osmanager_id',
|
||||
'image_id',
|
||||
'pool_group_id',
|
||||
'initial_srvs',
|
||||
'cache_l1_srvs',
|
||||
'cache_l2_srvs',
|
||||
'max_srvs',
|
||||
'show_transports',
|
||||
'visible',
|
||||
'allow_users_remove',
|
||||
'allow_users_reset',
|
||||
'ignores_unused',
|
||||
'account_id',
|
||||
'calendar_message',
|
||||
]
|
||||
|
||||
remove_fields = ['osmanager_id', 'service_id']
|
||||
|
||||
@ -120,14 +154,53 @@ class ServicesPools(ModelHandler):
|
||||
|
||||
def getItems(self, *args, **kwargs):
|
||||
# Optimized query, due that there is a lot of info needed for theee
|
||||
d = getSqlDatetime() - datetime.timedelta(seconds=GlobalConfig.RESTRAINT_TIME.getInt())
|
||||
return super().getItems(overview=kwargs.get('overview', True),
|
||||
query=(ServicePool.objects.prefetch_related('service', 'service__provider', 'servicesPoolGroup', 'image', 'tags', 'memberOfMeta__meta_pool', 'account')
|
||||
.annotate(valid_count=Count('userServices', filter=~Q(userServices__state__in=State.INFO_STATES)))
|
||||
.annotate(preparing_count=Count('userServices', filter=Q(userServices__state=State.PREPARING)))
|
||||
.annotate(error_count=Count('userServices', filter=Q(userServices__state=State.ERROR, userServices__state_date__gt=d)))
|
||||
.annotate(usage_count=Count('userServices', filter=Q(userServices__state__in=State.VALID_STATES, userServices__cache_level=0)))
|
||||
)
|
||||
d = getSqlDatetime() - datetime.timedelta(
|
||||
seconds=GlobalConfig.RESTRAINT_TIME.getInt()
|
||||
)
|
||||
return super().getItems(
|
||||
overview=kwargs.get('overview', True),
|
||||
query=(
|
||||
ServicePool.objects.prefetch_related(
|
||||
'service',
|
||||
'service__provider',
|
||||
'servicesPoolGroup',
|
||||
'servicesPoolGroup__image',
|
||||
'osmanager',
|
||||
'image',
|
||||
'tags',
|
||||
'memberOfMeta__meta_pool',
|
||||
'account',
|
||||
)
|
||||
.annotate(
|
||||
valid_count=Count(
|
||||
'userServices',
|
||||
filter=~Q(userServices__state__in=State.INFO_STATES),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
preparing_count=Count(
|
||||
'userServices', filter=Q(userServices__state=State.PREPARING)
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
error_count=Count(
|
||||
'userServices',
|
||||
filter=Q(
|
||||
userServices__state=State.ERROR,
|
||||
userServices__state_date__gt=d,
|
||||
),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
usage_count=Count(
|
||||
'userServices',
|
||||
filter=Q(
|
||||
userServices__state__in=State.VALID_STATES,
|
||||
userServices__cache_level=0,
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
# return super().getItems(overview=kwargs.get('overview', True), prefetch=['service', 'service__provider', 'servicesPoolGroup', 'image', 'tags'])
|
||||
# return super(ServicesPools, self).getItems(*args, **kwargs)
|
||||
@ -161,7 +234,9 @@ class ServicesPools(ModelHandler):
|
||||
'parent_type': item.service.data_type,
|
||||
'comments': item.comments,
|
||||
'state': state,
|
||||
'thumb': item.image.thumb64 if item.image is not None else DEFAULT_THUMB_BASE64,
|
||||
'thumb': item.image.thumb64
|
||||
if item.image is not None
|
||||
else DEFAULT_THUMB_BASE64,
|
||||
'account': item.account.name if item.account is not None else '',
|
||||
'account_id': item.account.uuid if item.account is not None else None,
|
||||
'service_id': item.service.uuid,
|
||||
@ -177,7 +252,10 @@ class ServicesPools(ModelHandler):
|
||||
'allow_users_reset': item.allow_users_reset,
|
||||
'ignores_unused': item.ignores_unused,
|
||||
'fallbackAccess': item.fallbackAccess,
|
||||
'meta_member': [{'id': i.meta_pool.uuid, 'name': i.meta_pool.name} for i in item.memberOfMeta.all()],
|
||||
'meta_member': [
|
||||
{'id': i.meta_pool.uuid, 'name': i.meta_pool.name}
|
||||
for i in item.memberOfMeta.all()
|
||||
],
|
||||
'calendar_message': item.calendar_message,
|
||||
}
|
||||
|
||||
@ -189,8 +267,12 @@ class ServicesPools(ModelHandler):
|
||||
restrained = item.error_count >= GlobalConfig.RESTRAINT_COUNT.getInt() # type: ignore
|
||||
usage_count = item.usage_count # type: ignore
|
||||
else:
|
||||
valid_count = item.userServices.exclude(state__in=State.INFO_STATES).count()
|
||||
preparing_count = item.userServices.filter(state=State.PREPARING).count()
|
||||
valid_count = item.userServices.exclude(
|
||||
state__in=State.INFO_STATES
|
||||
).count()
|
||||
preparing_count = item.userServices.filter(
|
||||
state=State.PREPARING
|
||||
).count()
|
||||
restrained = item.isRestrained()
|
||||
usage_count = -1
|
||||
|
||||
@ -203,9 +285,10 @@ class ServicesPools(ModelHandler):
|
||||
if item.servicesPoolGroup.image is not None:
|
||||
poolGroupThumb = item.servicesPoolGroup.image.thumb64
|
||||
|
||||
|
||||
val['state'] = state
|
||||
val['thumb'] = item.image.thumb64 if item.image is not None else DEFAULT_THUMB_BASE64
|
||||
val['thumb'] = (
|
||||
item.image.thumb64 if item.image is not None else DEFAULT_THUMB_BASE64
|
||||
)
|
||||
val['user_services_count'] = valid_count
|
||||
val['user_services_in_preparation'] = preparing_count
|
||||
val['tags'] = [tag.tag for tag in item.tags.all()]
|
||||
@ -227,51 +310,74 @@ class ServicesPools(ModelHandler):
|
||||
# if OSManager.objects.count() < 1: # No os managers, can't create db
|
||||
# raise ResponseError(ugettext('Create at least one OS Manager before creating a new service pool'))
|
||||
if Service.objects.count() < 1:
|
||||
raise ResponseError(ugettext('Create at least a service before creating a new service pool'))
|
||||
raise ResponseError(
|
||||
ugettext('Create at least a service before creating a new service pool')
|
||||
)
|
||||
|
||||
g = self.addDefaultFields([], ['name', 'short_name', 'comments', 'tags'])
|
||||
|
||||
for f in [{
|
||||
for f in [
|
||||
{
|
||||
'name': 'service_id',
|
||||
'values': [gui.choiceItem('', '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.provider.name + '\\' + v.name) for v in Service.objects.all()]),
|
||||
'values': [gui.choiceItem('', '')]
|
||||
+ gui.sortedChoices(
|
||||
[
|
||||
gui.choiceItem(v.uuid, v.provider.name + '\\' + v.name)
|
||||
for v in Service.objects.all()
|
||||
]
|
||||
),
|
||||
'label': ugettext('Base service'),
|
||||
'tooltip': ugettext('Service used as base of this service pool'),
|
||||
'type': gui.InputField.CHOICE_TYPE,
|
||||
'rdonly': True,
|
||||
'order': 100, # Ensures is At end
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'osmanager_id',
|
||||
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.name) for v in OSManager.objects.all()]),
|
||||
'values': [gui.choiceItem(-1, '')]
|
||||
+ gui.sortedChoices(
|
||||
[gui.choiceItem(v.uuid, v.name) for v in OSManager.objects.all()]
|
||||
),
|
||||
'label': ugettext('OS Manager'),
|
||||
'tooltip': ugettext('OS Manager used as base of this service pool'),
|
||||
'type': gui.InputField.CHOICE_TYPE,
|
||||
'rdonly': True,
|
||||
'order': 101,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'allow_users_remove',
|
||||
'value': False,
|
||||
'label': ugettext('Allow removal by users'),
|
||||
'tooltip': ugettext('If active, the user will be allowed to remove the service "manually". Be careful with this, because the user will have the "power" to delete it\'s own service'),
|
||||
'tooltip': ugettext(
|
||||
'If active, the user will be allowed to remove the service "manually". Be careful with this, because the user will have the "power" to delete it\'s own service'
|
||||
),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
'order': 111,
|
||||
'tab': ugettext('Advanced'),
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'allow_users_reset',
|
||||
'value': False,
|
||||
'label': ugettext('Allow reset by users'),
|
||||
'tooltip': ugettext('If active, the user will be allowed to reset the service'),
|
||||
'tooltip': ugettext(
|
||||
'If active, the user will be allowed to reset the service'
|
||||
),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
'order': 112,
|
||||
'tab': ugettext('Advanced'),
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'ignores_unused',
|
||||
'value': False,
|
||||
'label': ugettext('Ignores unused'),
|
||||
'tooltip': ugettext('If the option is enabled, UDS will not attempt to detect and remove the user services assigned but not in use.'),
|
||||
'tooltip': ugettext(
|
||||
'If the option is enabled, UDS will not attempt to detect and remove the user services assigned but not in use.'
|
||||
),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
'order': 113,
|
||||
'tab': ugettext('Advanced'),
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'visible',
|
||||
'value': True,
|
||||
'label': ugettext('Visible'),
|
||||
@ -279,31 +385,51 @@ class ServicesPools(ModelHandler):
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
'order': 107,
|
||||
'tab': ugettext('Display'),
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'image_id',
|
||||
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in Image.objects.all()]),
|
||||
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)]
|
||||
+ gui.sortedChoices(
|
||||
[
|
||||
gui.choiceImage(v.uuid, v.name, v.thumb64)
|
||||
for v in Image.objects.all()
|
||||
]
|
||||
),
|
||||
'label': ugettext('Associated Image'),
|
||||
'tooltip': ugettext('Image assocciated with this service'),
|
||||
'type': gui.InputField.IMAGECHOICE_TYPE,
|
||||
'order': 120,
|
||||
'tab': ugettext('Display'),
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'pool_group_id',
|
||||
'values': [gui.choiceImage(-1, _('Default'), DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in ServicePoolGroup.objects.all()]),
|
||||
'values': [gui.choiceImage(-1, _('Default'), DEFAULT_THUMB_BASE64)]
|
||||
+ gui.sortedChoices(
|
||||
[
|
||||
gui.choiceImage(v.uuid, v.name, v.thumb64)
|
||||
for v in ServicePoolGroup.objects.all()
|
||||
]
|
||||
),
|
||||
'label': ugettext('Pool group'),
|
||||
'tooltip': ugettext('Pool group for this pool (for pool classify on display)'),
|
||||
'tooltip': ugettext(
|
||||
'Pool group for this pool (for pool classify on display)'
|
||||
),
|
||||
'type': gui.InputField.IMAGECHOICE_TYPE,
|
||||
'order': 121,
|
||||
'tab': ugettext('Display'),
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'calendar_message',
|
||||
'value': '',
|
||||
'label': ugettext('Calendar access denied text'),
|
||||
'tooltip': ugettext('Custom message to be shown to users if access is limited by calendar rules.'),
|
||||
'tooltip': ugettext(
|
||||
'Custom message to be shown to users if access is limited by calendar rules.'
|
||||
),
|
||||
'type': gui.InputField.TEXT_TYPE,
|
||||
'order': 122,
|
||||
'tab': ugettext('Display'),
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'initial_srvs',
|
||||
'value': '0',
|
||||
'minValue': '0',
|
||||
@ -312,55 +438,74 @@ class ServicesPools(ModelHandler):
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 130,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'cache_l1_srvs',
|
||||
'value': '0',
|
||||
'minValue': '0',
|
||||
'label': ugettext('Services to keep in cache'),
|
||||
'tooltip': ugettext('Services kept in cache for improved user service assignation'),
|
||||
'tooltip': ugettext(
|
||||
'Services kept in cache for improved user service assignation'
|
||||
),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 131,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'cache_l2_srvs',
|
||||
'value': '0',
|
||||
'minValue': '0',
|
||||
'label': ugettext('Services to keep in L2 cache'),
|
||||
'tooltip': ugettext('Services kept in cache of level2 for improved service generation'),
|
||||
'tooltip': ugettext(
|
||||
'Services kept in cache of level2 for improved service generation'
|
||||
),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 132,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'max_srvs',
|
||||
'value': '0',
|
||||
'minValue': '1',
|
||||
'label': ugettext('Maximum number of services to provide'),
|
||||
'tooltip': ugettext('Maximum number of service (assigned and L1 cache) that can be created for this service'),
|
||||
'tooltip': ugettext(
|
||||
'Maximum number of service (assigned and L1 cache) that can be created for this service'
|
||||
),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 133,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'show_transports',
|
||||
'value': True,
|
||||
'label': ugettext('Show transports'),
|
||||
'tooltip': ugettext('If active, alternative transports for user will be shown'),
|
||||
'tooltip': ugettext(
|
||||
'If active, alternative transports for user will be shown'
|
||||
),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
'tab': ugettext('Advanced'),
|
||||
'order': 130,
|
||||
}, {
|
||||
},
|
||||
{
|
||||
'name': 'account_id',
|
||||
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.name) for v in Account.objects.all()]),
|
||||
'values': [gui.choiceItem(-1, '')]
|
||||
+ gui.sortedChoices(
|
||||
[gui.choiceItem(v.uuid, v.name) for v in Account.objects.all()]
|
||||
),
|
||||
'label': ugettext('Accounting'),
|
||||
'tooltip': ugettext('Account associated to this service pool'),
|
||||
'type': gui.InputField.CHOICE_TYPE,
|
||||
'tab': ugettext('Advanced'),
|
||||
'order': 131,
|
||||
}]:
|
||||
},
|
||||
]:
|
||||
self.addField(g, f)
|
||||
|
||||
return g
|
||||
|
||||
def beforeSave(self, fields: typing.Dict[str, typing.Any]) -> None: # pylint: disable=too-many-branches,too-many-statements
|
||||
def beforeSave(
|
||||
self, fields: typing.Dict[str, typing.Any]
|
||||
) -> None: # pylint: disable=too-many-branches,too-many-statements
|
||||
# logger.debug(self._params)
|
||||
try:
|
||||
try:
|
||||
@ -379,7 +524,9 @@ class ServicesPools(ModelHandler):
|
||||
self._params['allow_users_reset'] = False
|
||||
|
||||
if serviceType.needsManager is True:
|
||||
osmanager = OSManager.objects.get(uuid=processUuid(fields['osmanager_id']))
|
||||
osmanager = OSManager.objects.get(
|
||||
uuid=processUuid(fields['osmanager_id'])
|
||||
)
|
||||
fields['osmanager_id'] = osmanager.id
|
||||
else:
|
||||
del fields['osmanager_id']
|
||||
@ -390,12 +537,23 @@ class ServicesPools(ModelHandler):
|
||||
fields[k] = v
|
||||
|
||||
if serviceType.maxDeployed != -1:
|
||||
fields['max_srvs'] = min((int(fields['max_srvs']), serviceType.maxDeployed))
|
||||
fields['initial_srvs'] = min(int(fields['initial_srvs']), serviceType.maxDeployed)
|
||||
fields['cache_l1_srvs'] = min(int(fields['cache_l1_srvs']), serviceType.maxDeployed)
|
||||
fields['max_srvs'] = min(
|
||||
(int(fields['max_srvs']), serviceType.maxDeployed)
|
||||
)
|
||||
fields['initial_srvs'] = min(
|
||||
int(fields['initial_srvs']), serviceType.maxDeployed
|
||||
)
|
||||
fields['cache_l1_srvs'] = min(
|
||||
int(fields['cache_l1_srvs']), serviceType.maxDeployed
|
||||
)
|
||||
|
||||
if serviceType.usesCache is False:
|
||||
for k in ('initial_srvs', 'cache_l1_srvs', 'cache_l2_srvs', 'max_srvs'):
|
||||
for k in (
|
||||
'initial_srvs',
|
||||
'cache_l1_srvs',
|
||||
'cache_l2_srvs',
|
||||
'max_srvs',
|
||||
):
|
||||
fields[k] = 0
|
||||
|
||||
if serviceType.usesCache_L2 is False:
|
||||
@ -405,7 +563,13 @@ class ServicesPools(ModelHandler):
|
||||
raise RequestError(ugettext('This service requires an OS Manager'))
|
||||
|
||||
# If max < initial or cache_1 or cache_l2
|
||||
fields['max_srvs'] = max((int(fields['initial_srvs']), int(fields['cache_l1_srvs']), int(fields['max_srvs'])))
|
||||
fields['max_srvs'] = max(
|
||||
(
|
||||
int(fields['initial_srvs']),
|
||||
int(fields['cache_l1_srvs']),
|
||||
int(fields['max_srvs']),
|
||||
)
|
||||
)
|
||||
|
||||
# *** ACCOUNT ***
|
||||
accountId = fields['account_id']
|
||||
@ -414,7 +578,9 @@ class ServicesPools(ModelHandler):
|
||||
|
||||
if accountId != '-1':
|
||||
try:
|
||||
fields['account_id'] = Account.objects.get(uuid=processUuid(accountId)).id
|
||||
fields['account_id'] = Account.objects.get(
|
||||
uuid=processUuid(accountId)
|
||||
).id
|
||||
except Exception:
|
||||
logger.exception('Getting account ID')
|
||||
|
||||
@ -487,7 +653,11 @@ class ServicesPools(ModelHandler):
|
||||
validActions: typing.Tuple[typing.Dict, ...] = ()
|
||||
itemInfo = item.service.getType()
|
||||
if itemInfo.usesCache is True:
|
||||
validActions += (CALENDAR_ACTION_INITIAL, CALENDAR_ACTION_CACHE_L1, CALENDAR_ACTION_MAX)
|
||||
validActions += (
|
||||
CALENDAR_ACTION_INITIAL,
|
||||
CALENDAR_ACTION_CACHE_L1,
|
||||
CALENDAR_ACTION_MAX,
|
||||
)
|
||||
if itemInfo.usesCache_L2 is True:
|
||||
validActions += (CALENDAR_ACTION_CACHE_L2,)
|
||||
|
||||
@ -495,21 +665,33 @@ class ServicesPools(ModelHandler):
|
||||
validActions += (CALENDAR_ACTION_PUBLISH,)
|
||||
|
||||
# Transport & groups actions
|
||||
validActions += (CALENDAR_ACTION_ADD_TRANSPORT, CALENDAR_ACTION_DEL_TRANSPORT, CALENDAR_ACTION_ADD_GROUP, CALENDAR_ACTION_DEL_GROUP)
|
||||
validActions += (
|
||||
CALENDAR_ACTION_ADD_TRANSPORT,
|
||||
CALENDAR_ACTION_DEL_TRANSPORT,
|
||||
CALENDAR_ACTION_ADD_GROUP,
|
||||
CALENDAR_ACTION_DEL_GROUP,
|
||||
)
|
||||
|
||||
# Advanced actions
|
||||
validActions += (CALENDAR_ACTION_IGNORE_UNUSED, CALENDAR_ACTION_REMOVE_USERSERVICES)
|
||||
validActions += (
|
||||
CALENDAR_ACTION_IGNORE_UNUSED,
|
||||
CALENDAR_ACTION_REMOVE_USERSERVICES,
|
||||
)
|
||||
return validActions
|
||||
|
||||
def listAssignables(self, item: ServicePool) -> typing.Any:
|
||||
service = item.service.getInstance()
|
||||
return [gui.choiceItem(i[0], i[1]) for i in service.listAssignables()]
|
||||
|
||||
def createFromAssignable(self, item: ServicePool) ->typing.Any:
|
||||
def createFromAssignable(self, item: ServicePool) -> typing.Any:
|
||||
if 'user_id' not in self._params or 'assignable_id' not in self._params:
|
||||
return self.invalidRequestException('Invalid parameters')
|
||||
|
||||
logger.debug('Creating from assignable: %s', self._params)
|
||||
userServiceManager().createFromAssignable(item, User.objects.get(uuid=processUuid(self._params['user_id'])), self._params['assignable_id'])
|
||||
userServiceManager().createFromAssignable(
|
||||
item,
|
||||
User.objects.get(uuid=processUuid(self._params['user_id'])),
|
||||
self._params['assignable_id'],
|
||||
)
|
||||
|
||||
return True
|
||||
|
@ -45,7 +45,8 @@ from uds.core.ui import gui as uiGui
|
||||
from uds.core.util import log
|
||||
from uds.core.util import permissions
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.models import Tag
|
||||
|
||||
from uds.models import Tag, TaggingMixin, ManagedObjectModel
|
||||
|
||||
from .handlers import (
|
||||
Handler,
|
||||
@ -54,12 +55,11 @@ from .handlers import (
|
||||
RequestError,
|
||||
ResponseError,
|
||||
AccessDenied,
|
||||
NotSupportedError
|
||||
NotSupportedError,
|
||||
)
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.models.managed_object_model import ManagedObjectModel
|
||||
from uds.models import User
|
||||
from uds.core import Module
|
||||
|
||||
@ -80,12 +80,15 @@ class SaveException(HandlerError):
|
||||
Exception thrown if couldn't save
|
||||
"""
|
||||
|
||||
|
||||
class BaseModelHandler(Handler):
|
||||
"""
|
||||
Base Handler for Master & Detail Handlers
|
||||
"""
|
||||
|
||||
def addField(self, gui: typing.List[typing.Any], field: typing.Dict[str, typing.Any]) -> typing.List[typing.Any]:
|
||||
def addField(
|
||||
self, gui: typing.List[typing.Any], field: typing.Dict[str, typing.Any]
|
||||
) -> typing.List[typing.Any]:
|
||||
"""
|
||||
Add a field to a "gui" description.
|
||||
This method checks that every required field element is in there.
|
||||
@ -108,15 +111,17 @@ class BaseModelHandler(Handler):
|
||||
'rdonly': field.get('rdonly', False),
|
||||
'type': field.get('type', uiGui.InputField.TEXT_TYPE),
|
||||
'order': field.get('order', 0),
|
||||
'values': field.get('values', [])
|
||||
}
|
||||
'values': field.get('values', []),
|
||||
},
|
||||
}
|
||||
if 'tab' in field:
|
||||
v['gui']['tab'] = field['tab']
|
||||
gui.append(v)
|
||||
return gui
|
||||
|
||||
def addDefaultFields(self, gui: typing.List[typing.Any], flds: typing.List[str]) -> typing.List[typing.Any]:
|
||||
def addDefaultFields(
|
||||
self, gui: typing.List[typing.Any], flds: typing.List[str]
|
||||
) -> typing.List[typing.Any]:
|
||||
"""
|
||||
Adds default fields (based in a list) to a "gui" description
|
||||
:param gui: Gui list where the "default" fielsds will be added
|
||||
@ -124,66 +129,88 @@ class BaseModelHandler(Handler):
|
||||
'priority' and 'small_name', 'short_name', 'tags'
|
||||
"""
|
||||
if 'tags' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'tags',
|
||||
'label': _('Tags'),
|
||||
'type': 'taglist',
|
||||
'tooltip': _('Tags for this element'),
|
||||
'order': 0 - 105,
|
||||
})
|
||||
self.addField(
|
||||
gui,
|
||||
{
|
||||
'name': 'tags',
|
||||
'label': _('Tags'),
|
||||
'type': 'taglist',
|
||||
'tooltip': _('Tags for this element'),
|
||||
'order': 0 - 105,
|
||||
},
|
||||
)
|
||||
if 'name' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'name',
|
||||
'type': 'text',
|
||||
'required': True,
|
||||
'label': _('Name'),
|
||||
'length': 128,
|
||||
'tooltip': _('Name of this element'),
|
||||
'order': 0 - 100,
|
||||
})
|
||||
self.addField(
|
||||
gui,
|
||||
{
|
||||
'name': 'name',
|
||||
'type': 'text',
|
||||
'required': True,
|
||||
'label': _('Name'),
|
||||
'length': 128,
|
||||
'tooltip': _('Name of this element'),
|
||||
'order': 0 - 100,
|
||||
},
|
||||
)
|
||||
if 'short_name' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'short_name',
|
||||
'type': 'text',
|
||||
'label': _('Short name'),
|
||||
'tooltip': _('Short name for user service visualization'),
|
||||
'required': False,
|
||||
'length': 32,
|
||||
'order': 0 - 95,
|
||||
})
|
||||
self.addField(
|
||||
gui,
|
||||
{
|
||||
'name': 'short_name',
|
||||
'type': 'text',
|
||||
'label': _('Short name'),
|
||||
'tooltip': _('Short name for user service visualization'),
|
||||
'required': False,
|
||||
'length': 32,
|
||||
'order': 0 - 95,
|
||||
},
|
||||
)
|
||||
if 'comments' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'comments',
|
||||
'label': _('Comments'),
|
||||
'tooltip': _('Comments for this element'),
|
||||
'length': 256,
|
||||
'order': 0 - 90,
|
||||
})
|
||||
self.addField(
|
||||
gui,
|
||||
{
|
||||
'name': 'comments',
|
||||
'label': _('Comments'),
|
||||
'tooltip': _('Comments for this element'),
|
||||
'length': 256,
|
||||
'order': 0 - 90,
|
||||
},
|
||||
)
|
||||
if 'priority' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'priority',
|
||||
'type': 'numeric',
|
||||
'label': _('Priority'),
|
||||
'tooltip': _('Selects the priority of this element (lower number means higher priority)'),
|
||||
'required': True,
|
||||
'value': 1,
|
||||
'length': 4,
|
||||
'order': 0 - 85,
|
||||
})
|
||||
self.addField(
|
||||
gui,
|
||||
{
|
||||
'name': 'priority',
|
||||
'type': 'numeric',
|
||||
'label': _('Priority'),
|
||||
'tooltip': _(
|
||||
'Selects the priority of this element (lower number means higher priority)'
|
||||
),
|
||||
'required': True,
|
||||
'value': 1,
|
||||
'length': 4,
|
||||
'order': 0 - 85,
|
||||
},
|
||||
)
|
||||
if 'small_name' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'small_name',
|
||||
'type': 'text',
|
||||
'label': _('Label'),
|
||||
'tooltip': _('Label for this element'),
|
||||
'required': True,
|
||||
'length': 128,
|
||||
'order': 0 - 80,
|
||||
})
|
||||
self.addField(
|
||||
gui,
|
||||
{
|
||||
'name': 'small_name',
|
||||
'type': 'text',
|
||||
'label': _('Label'),
|
||||
'tooltip': _('Label for this element'),
|
||||
'required': True,
|
||||
'length': 128,
|
||||
'order': 0 - 80,
|
||||
},
|
||||
)
|
||||
|
||||
return gui
|
||||
|
||||
def ensureAccess(self, obj: models.Model, permission: int, root: bool = False) -> int:
|
||||
def ensureAccess(
|
||||
self, obj: models.Model, permission: int, root: bool = False
|
||||
) -> int:
|
||||
perm = permissions.getEffectivePermission(self._user, obj, root)
|
||||
if perm < permission:
|
||||
raise self.accessDenied()
|
||||
@ -201,23 +228,25 @@ class BaseModelHandler(Handler):
|
||||
Returns a dictionary describing the type (the name, the icon, description, etc...)
|
||||
"""
|
||||
res = self.typeInfo(type_)
|
||||
res.update({
|
||||
'name': _(type_.name()),
|
||||
'type': type_.type(),
|
||||
'description': _(type_.description()),
|
||||
'icon': type_.icon64().replace('\n', '')
|
||||
})
|
||||
res.update(
|
||||
{
|
||||
'name': _(type_.name()),
|
||||
'type': type_.type(),
|
||||
'description': _(type_.description()),
|
||||
'icon': type_.icon64().replace('\n', ''),
|
||||
}
|
||||
)
|
||||
if hasattr(type_, 'group'):
|
||||
res['group'] = _(type_.group) # Add group info is it is contained
|
||||
return res
|
||||
|
||||
def processTableFields(
|
||||
self,
|
||||
title: str,
|
||||
fields: typing.List[typing.Any],
|
||||
row_style: typing.Dict[str, typing.Any],
|
||||
subtitle: typing.Optional[str] = None
|
||||
) -> typing.Dict[str, typing.Any]:
|
||||
self,
|
||||
title: str,
|
||||
fields: typing.List[typing.Any],
|
||||
row_style: typing.MutableMapping[str, typing.Any],
|
||||
subtitle: typing.Optional[str] = None,
|
||||
) -> typing.MutableMapping[str, typing.Any]:
|
||||
"""
|
||||
Returns a dict containing the table fields description
|
||||
"""
|
||||
@ -225,10 +254,12 @@ class BaseModelHandler(Handler):
|
||||
'title': title,
|
||||
'fields': fields,
|
||||
'row-style': row_style,
|
||||
'subtitle': subtitle or ''
|
||||
'subtitle': subtitle or '',
|
||||
}
|
||||
|
||||
def readFieldsFromParams(self, fldList: typing.List[str]) -> typing.Dict[str, typing.Any]:
|
||||
def readFieldsFromParams(
|
||||
self, fldList: typing.List[str]
|
||||
) -> typing.Dict[str, typing.Any]:
|
||||
"""
|
||||
Reads the indicated fields from the parameters received, and if
|
||||
:param fldList: List of required fields
|
||||
@ -244,26 +275,32 @@ class BaseModelHandler(Handler):
|
||||
|
||||
return args
|
||||
|
||||
def fillIntanceFields(self, item: 'ManagedObjectModel', res: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
|
||||
def fillIntanceFields(
|
||||
self, item: 'models.Model', res: typing.Dict[str, typing.Any]
|
||||
) -> typing.Dict[str, typing.Any]:
|
||||
"""
|
||||
For Managed Objects (db element that contains a serialized object), fills a dictionary with the "field" parameters values.
|
||||
For non managed objects, it does nothing
|
||||
:param item: Item to extract fields
|
||||
:param res: Dictionary to "extend" with instance key-values pairs
|
||||
"""
|
||||
if hasattr(item, 'getInstance'):
|
||||
if isinstance(item, ManagedObjectModel):
|
||||
i = item.getInstance()
|
||||
i.initGui() # Defaults & stuff
|
||||
value: typing.Any
|
||||
for key, value in i.valuesDict().items():
|
||||
if isinstance(value, str):
|
||||
value = {"true": True, "false": False}.get(value, value) # Translate "true" & "false" to True & False (booleans)
|
||||
value = {"true": True, "false": False}.get(
|
||||
value, value
|
||||
) # Translate "true" & "false" to True & False (booleans)
|
||||
logger.debug('%s = %s', key, value)
|
||||
res[key] = value
|
||||
return res
|
||||
|
||||
# Exceptions
|
||||
def invalidRequestException(self, message: typing.Optional[str] = None) -> HandlerError:
|
||||
def invalidRequestException(
|
||||
self, message: typing.Optional[str] = None
|
||||
) -> HandlerError:
|
||||
"""
|
||||
Raises an invalid request error with a default translated string
|
||||
:param message: Custom message to add to exception. If it is None, "Invalid Request" is used
|
||||
@ -271,7 +308,9 @@ class BaseModelHandler(Handler):
|
||||
message = message or _('Invalid Request')
|
||||
return RequestError('{} {}: {}'.format(message, self.__class__, self._args))
|
||||
|
||||
def invalidResponseException(self, message: typing.Optional[str] = None) -> HandlerError:
|
||||
def invalidResponseException(
|
||||
self, message: typing.Optional[str] = None
|
||||
) -> HandlerError:
|
||||
message = 'Invalid response' if message is None else message
|
||||
return ResponseError(message)
|
||||
|
||||
@ -279,9 +318,13 @@ class BaseModelHandler(Handler):
|
||||
"""
|
||||
Raises a NotFound exception with translated "Method not found" string to current locale
|
||||
"""
|
||||
return RequestError(_('Method not found in {}: {}').format(self.__class__, self._args))
|
||||
return RequestError(
|
||||
_('Method not found in {}: {}').format(self.__class__, self._args)
|
||||
)
|
||||
|
||||
def invalidItemException(self, message: typing.Optional[str] = None) -> HandlerError:
|
||||
def invalidItemException(
|
||||
self, message: typing.Optional[str] = None
|
||||
) -> HandlerError:
|
||||
"""
|
||||
Raises a NotFound exception, with location info
|
||||
"""
|
||||
@ -307,7 +350,9 @@ class BaseModelHandler(Handler):
|
||||
"""
|
||||
Invokes a test for an item
|
||||
"""
|
||||
logger.debug('Called base test for %s --> %s', self.__class__.__name__, self._params)
|
||||
logger.debug(
|
||||
'Called base test for %s --> %s', self.__class__.__name__, self._params
|
||||
)
|
||||
raise self.invalidMethodException()
|
||||
|
||||
|
||||
@ -335,6 +380,7 @@ class DetailHandler(BaseModelHandler):
|
||||
|
||||
Also accepts GET methods for "custom" methods
|
||||
"""
|
||||
|
||||
custom_methods: typing.ClassVar[typing.List[str]] = []
|
||||
_parent: typing.Optional['ModelHandler']
|
||||
_path: str
|
||||
@ -344,13 +390,13 @@ class DetailHandler(BaseModelHandler):
|
||||
_user: 'User'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parentHandler: 'ModelHandler',
|
||||
path: str,
|
||||
params: typing.Any,
|
||||
*args: str,
|
||||
**kwargs: typing.Any
|
||||
): # pylint: disable=super-init-not-called
|
||||
self,
|
||||
parentHandler: 'ModelHandler',
|
||||
path: str,
|
||||
params: typing.Any,
|
||||
*args: str,
|
||||
**kwargs: typing.Any
|
||||
): # pylint: disable=super-init-not-called
|
||||
"""
|
||||
Detail Handlers in fact "disabled" handler most initialization, that is no needed because
|
||||
parent modelhandler has already done it (so we must access through parent handler)
|
||||
@ -363,7 +409,9 @@ class DetailHandler(BaseModelHandler):
|
||||
self._kwargs = kwargs
|
||||
self._user = kwargs.get('user', None)
|
||||
|
||||
def __checkCustom(self, check: str, parent: models.Model, arg: typing.Any = None) -> typing.Any:
|
||||
def __checkCustom(
|
||||
self, check: str, parent: models.Model, arg: typing.Any = None
|
||||
) -> typing.Any:
|
||||
"""
|
||||
checks curron methods
|
||||
:param check: Method to check
|
||||
@ -380,7 +428,9 @@ class DetailHandler(BaseModelHandler):
|
||||
|
||||
return None
|
||||
|
||||
def get(self) -> typing.Any: # pylint: disable=too-many-branches,too-many-return-statements
|
||||
def get(
|
||||
self,
|
||||
) -> typing.Any: # pylint: disable=too-many-branches,too-many-return-statements
|
||||
"""
|
||||
Processes GET method for a detail Handler
|
||||
"""
|
||||
@ -409,7 +459,11 @@ class DetailHandler(BaseModelHandler):
|
||||
logger.debug('Types: %s', types_)
|
||||
return types_
|
||||
if self._args[0] == TABLEINFO:
|
||||
return self.processTableFields(self.getTitle(parent), self.getFields(parent), self.getRowStyle(parent))
|
||||
return self.processTableFields(
|
||||
self.getTitle(parent),
|
||||
self.getFields(parent),
|
||||
self.getRowStyle(parent),
|
||||
)
|
||||
|
||||
# try to get id
|
||||
return self.getItems(parent, processUuid(self._args[0]))
|
||||
@ -449,7 +503,9 @@ class DetailHandler(BaseModelHandler):
|
||||
raise self.invalidRequestException()
|
||||
|
||||
logger.debug('Invoking proper saving detail item %s', item)
|
||||
return self.saveItem(parent, item)
|
||||
self.saveItem(parent, item)
|
||||
# Empty response
|
||||
return ''
|
||||
|
||||
def post(self) -> typing.Any:
|
||||
"""
|
||||
@ -494,7 +550,9 @@ class DetailHandler(BaseModelHandler):
|
||||
# if item is None: # Returns ALL detail items
|
||||
# return []
|
||||
# return {} # Returns one item
|
||||
raise NotImplementedError('Must provide an getItems method for {} class'.format(self.__class__))
|
||||
raise NotImplementedError(
|
||||
'Must provide an getItems method for {} class'.format(self.__class__)
|
||||
)
|
||||
|
||||
# Default save
|
||||
def saveItem(self, parent: models.Model, item: typing.Optional[str]) -> None:
|
||||
@ -559,7 +617,9 @@ class DetailHandler(BaseModelHandler):
|
||||
# raise RequestError('Gui not provided for this type of object')
|
||||
return []
|
||||
|
||||
def getTypes(self, parent: models.Model, forType: typing.Optional[str]) -> typing.Iterable[typing.Dict[str, typing.Any]]:
|
||||
def getTypes(
|
||||
self, parent: models.Model, forType: typing.Optional[str]
|
||||
) -> typing.Iterable[typing.Dict[str, typing.Any]]:
|
||||
"""
|
||||
The default is that detail element will not have any types (they are "homogeneous")
|
||||
but we provided this method, that can be overridden, in case one detail needs it
|
||||
@ -594,12 +654,13 @@ class ModelHandler(BaseModelHandler):
|
||||
Note: Instance variables are the variables declared and serialized by modules.
|
||||
The only detail that has types within is "Service", child of "Provider"
|
||||
"""
|
||||
|
||||
# Authentication related
|
||||
authenticated = True
|
||||
needs_staff = True
|
||||
|
||||
# Which model does this manage, must be a django model ofc
|
||||
model: typing.ClassVar[models.Model]
|
||||
model: typing.ClassVar[typing.Type[models.Model]]
|
||||
|
||||
# By default, filter is empty
|
||||
fltr: typing.Optional[str] = None
|
||||
@ -607,9 +668,13 @@ class ModelHandler(BaseModelHandler):
|
||||
# This is an array of tuples of two items, where first is method and second inticates if method needs parent id (normal behavior is it needs it)
|
||||
# For example ('services', True) -- > .../id_parent/services
|
||||
# ('services', False) --> ..../services
|
||||
custom_methods: typing.ClassVar[typing.List[typing.Tuple[str, bool]]] = [] # If this model respond to "custom" methods, we will declare them here
|
||||
custom_methods: typing.ClassVar[
|
||||
typing.List[typing.Tuple[str, bool]]
|
||||
] = [] # If this model respond to "custom" methods, we will declare them here
|
||||
# If this model has details, which ones
|
||||
detail: typing.ClassVar[typing.Optional[typing.Dict[str, typing.Type[DetailHandler]]]] = None # Dictionary containing detail routing
|
||||
detail: typing.ClassVar[
|
||||
typing.Optional[typing.Dict[str, typing.Type[DetailHandler]]]
|
||||
] = None # Dictionary containing detail routing
|
||||
# Put needed fields
|
||||
save_fields: typing.ClassVar[typing.List[str]] = []
|
||||
# Put removable fields before updating
|
||||
@ -704,7 +769,7 @@ class ModelHandler(BaseModelHandler):
|
||||
del self._params['filter'] # Remove parameter
|
||||
logger.debug('Found a filter expression (%s)', self.fltr)
|
||||
|
||||
def doFilter(self, data: typing.List[typing.Dict[str, typing.Any]]) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||
def doFilter(self, data: typing.Any) -> typing.Any:
|
||||
# Right now, filtering only supports a single filter, in a future
|
||||
# we may improve it
|
||||
if self.fltr is None:
|
||||
@ -727,7 +792,7 @@ class ModelHandler(BaseModelHandler):
|
||||
|
||||
r = re.compile(s + fnmatch.translate(pattern) + e, re.RegexFlag.IGNORECASE)
|
||||
|
||||
def fltr_function(item: typing.Dict[str, typing.Any]):
|
||||
def fltr_function(item: typing.MutableMapping[str, typing.Any]):
|
||||
try:
|
||||
if fld not in item or r.match(item[fld]) is None:
|
||||
return False
|
||||
@ -746,8 +811,10 @@ class ModelHandler(BaseModelHandler):
|
||||
|
||||
# Helper to process detail
|
||||
# Details can be managed (writen) by any user that has MANAGEMENT permission over parent
|
||||
def processDetail(self):
|
||||
logger.debug('Processing detail %s for with params %s', self._path, self._params)
|
||||
def processDetail(self) -> typing.Any:
|
||||
logger.debug(
|
||||
'Processing detail %s for with params %s', self._path, self._params
|
||||
)
|
||||
try:
|
||||
item: models.Model = self.model.objects.filter(uuid=self._args[0])[0]
|
||||
# If we do not have access to parent to, at least, read...
|
||||
@ -757,14 +824,28 @@ class ModelHandler(BaseModelHandler):
|
||||
else:
|
||||
requiredPermission = permissions.PERMISSION_READ
|
||||
|
||||
if permissions.checkPermissions(self._user, item, requiredPermission) is False:
|
||||
logger.debug('Permission for user %s does not comply with %s', self._user, requiredPermission)
|
||||
if (
|
||||
permissions.checkPermissions(self._user, item, requiredPermission)
|
||||
is False
|
||||
):
|
||||
logger.debug(
|
||||
'Permission for user %s does not comply with %s',
|
||||
self._user,
|
||||
requiredPermission,
|
||||
)
|
||||
raise self.accessDenied()
|
||||
|
||||
detailCls = self.detail[self._args[1]] # pylint: disable=unsubscriptable-object
|
||||
if not self.detail:
|
||||
raise self.invalidRequestException()
|
||||
|
||||
detailCls = self.detail[
|
||||
self._args[1]
|
||||
] # pylint: disable=unsubscriptable-object
|
||||
args = list(self._args[2:])
|
||||
path = self._path + '/'.join(args[:2])
|
||||
detail = detailCls(self, path, self._params, *args, parent=item, user=self._user)
|
||||
detail = detailCls(
|
||||
self, path, self._params, *args, parent=item, user=self._user
|
||||
)
|
||||
method = getattr(detail, self._operation)
|
||||
|
||||
return method()
|
||||
@ -775,7 +856,9 @@ class ModelHandler(BaseModelHandler):
|
||||
|
||||
raise Exception('Invalid code executed on processDetail')
|
||||
|
||||
def getItems(self, *args, **kwargs) -> typing.Generator[typing.Dict[str, typing.Any], None, None]:
|
||||
def getItems(
|
||||
self, *args, **kwargs
|
||||
) -> typing.Generator[typing.MutableMapping[str, typing.Any], None, None]:
|
||||
if 'overview' in kwargs:
|
||||
overview = kwargs['overview']
|
||||
del kwargs['overview']
|
||||
@ -795,11 +878,20 @@ class ModelHandler(BaseModelHandler):
|
||||
del kwargs['query']
|
||||
else:
|
||||
logger.debug('Args: %s, kwargs: %s', args, kwargs)
|
||||
query = self.model.objects.filter(*args, **kwargs).prefetch_related(*prefetch)
|
||||
query = self.model.objects.filter(*args, **kwargs).prefetch_related(
|
||||
*prefetch
|
||||
)
|
||||
|
||||
for item in query:
|
||||
try:
|
||||
if permissions.checkPermissions(typing.cast('User', self._user), item, permissions.PERMISSION_READ) is False:
|
||||
if (
|
||||
permissions.checkPermissions(
|
||||
typing.cast('User', self._user),
|
||||
item,
|
||||
permissions.PERMISSION_READ,
|
||||
)
|
||||
is False
|
||||
):
|
||||
continue
|
||||
if kwargs.get('overview', True):
|
||||
yield self.item_as_dict_overview(item)
|
||||
@ -819,7 +911,7 @@ class ModelHandler(BaseModelHandler):
|
||||
self.extractFilter()
|
||||
return self.doFilter(self.doGet())
|
||||
|
||||
def doGet(self): # pylint: disable=too-many-statements,too-many-branches,too-many-return-statements
|
||||
def doGet(self) -> typing.Any:
|
||||
logger.debug('method GET for %s, %s', self.__class__.__name__, self._args)
|
||||
nArgs = len(self._args)
|
||||
|
||||
@ -835,7 +927,13 @@ class ModelHandler(BaseModelHandler):
|
||||
operation = getattr(self, self._args[1])
|
||||
item = self.model.objects.get(uuid=self._args[0].lower())
|
||||
except Exception as e:
|
||||
logger.error('Invalid custom method exception %s/%s/%s: %s', self.__class__.__name__, self._args, self._params, e)
|
||||
logger.error(
|
||||
'Invalid custom method exception %s/%s/%s: %s',
|
||||
self.__class__.__name__,
|
||||
self._args,
|
||||
self._params,
|
||||
e,
|
||||
)
|
||||
raise self.invalidMethodException()
|
||||
|
||||
return operation(item)
|
||||
@ -855,9 +953,14 @@ class ModelHandler(BaseModelHandler):
|
||||
if self._args[0] == TYPES:
|
||||
return list(self.getTypes())
|
||||
if self._args[0] == TABLEINFO:
|
||||
return self.processTableFields(self.table_title, self.table_fields, self.table_row_style, self.table_subtitle)
|
||||
return self.processTableFields(
|
||||
self.table_title,
|
||||
self.table_fields,
|
||||
self.table_row_style,
|
||||
self.table_subtitle,
|
||||
)
|
||||
if self._args[0] == GUI:
|
||||
return self.getGui(None)
|
||||
return self.getGui('')
|
||||
|
||||
# get item ID
|
||||
try:
|
||||
@ -890,7 +993,9 @@ class ModelHandler(BaseModelHandler):
|
||||
if nArgs != 2:
|
||||
raise self.invalidRequestException()
|
||||
try:
|
||||
item = self.model.objects.get(uuid=self._args[0].lower()) # DB maybe case sensitive??, anyway, uuids are stored in lowercase
|
||||
item = self.model.objects.get(
|
||||
uuid=self._args[0].lower()
|
||||
) # DB maybe case sensitive??, anyway, uuids are stored in lowercase
|
||||
return self.getLogs(item)
|
||||
except Exception:
|
||||
raise self.invalidItemException()
|
||||
@ -901,8 +1006,7 @@ class ModelHandler(BaseModelHandler):
|
||||
|
||||
raise self.invalidRequestException() # Will not return
|
||||
|
||||
|
||||
def post(self):
|
||||
def post(self) -> typing.Any:
|
||||
"""
|
||||
Processes a POST request
|
||||
"""
|
||||
@ -914,7 +1018,7 @@ class ModelHandler(BaseModelHandler):
|
||||
|
||||
raise self.invalidMethodException() # Will not return
|
||||
|
||||
def put(self): # pylint: disable=too-many-branches, too-many-statements
|
||||
def put(self) -> typing.Any:
|
||||
"""
|
||||
Processes a PUT request
|
||||
"""
|
||||
@ -926,7 +1030,10 @@ class ModelHandler(BaseModelHandler):
|
||||
if len(self._args) > 1: # Detail?
|
||||
return self.processDetail()
|
||||
|
||||
self.ensureAccess(self.model(), permissions.PERMISSION_ALL, root=True) # Must have write permissions to create, modify, etc..
|
||||
# Here, self.model() indicates an "django model object with default params"
|
||||
self.ensureAccess(
|
||||
self.model(), permissions.PERMISSION_ALL, root=True
|
||||
) # Must have write permissions to create, modify, etc..
|
||||
|
||||
try:
|
||||
# Extract fields
|
||||
@ -954,11 +1061,16 @@ class ModelHandler(BaseModelHandler):
|
||||
item.__dict__.update(args) # Update fields from args
|
||||
|
||||
# Now if tags, update them
|
||||
if tags:
|
||||
logger.debug('Updating tags: %s', tags)
|
||||
item.tags.set([Tag.objects.get_or_create(tag=val)[0] for val in tags if val != ''])
|
||||
elif isinstance(tags, list): # Present, but list is empty (will be proccesed on "if" else)
|
||||
item.tags.clear()
|
||||
if isinstance(item, TaggingMixin):
|
||||
if tags:
|
||||
logger.debug('Updating tags: %s', tags)
|
||||
item.tags.set(
|
||||
[Tag.objects.get_or_create(tag=val)[0] for val in tags if val != '']
|
||||
)
|
||||
elif isinstance(
|
||||
tags, list
|
||||
): # Present, but list is empty (will be proccesed on "if" else)
|
||||
item.tags.clear()
|
||||
|
||||
except self.model.DoesNotExist:
|
||||
raise NotFound('Item not found')
|
||||
@ -973,14 +1085,19 @@ class ModelHandler(BaseModelHandler):
|
||||
raise RequestError('incorrect invocation to PUT')
|
||||
|
||||
if not deleteOnError:
|
||||
self.checkSave(item) # Will raise an exception if item can't be saved (only for modify operations..)
|
||||
self.checkSave(
|
||||
item
|
||||
) # Will raise an exception if item can't be saved (only for modify operations..)
|
||||
|
||||
# Store associated object if requested (data_type)
|
||||
try:
|
||||
data_type: typing.Optional[str] = self._params.get('data_type', self._params.get('type'))
|
||||
if data_type:
|
||||
item.data_type = data_type
|
||||
item.data = item.getInstance(self._params).serialize()
|
||||
if isinstance(item, ManagedObjectModel):
|
||||
data_type: typing.Optional[str] = self._params.get(
|
||||
'data_type', self._params.get('type')
|
||||
)
|
||||
if data_type:
|
||||
item.data_type = data_type
|
||||
item.data = item.getInstance(self._params).serialize()
|
||||
|
||||
item.save()
|
||||
|
||||
@ -995,7 +1112,7 @@ class ModelHandler(BaseModelHandler):
|
||||
|
||||
return res
|
||||
|
||||
def delete(self) -> str:
|
||||
def delete(self) -> typing.Any:
|
||||
"""
|
||||
Processes a DELETE request
|
||||
"""
|
||||
@ -1006,7 +1123,9 @@ class ModelHandler(BaseModelHandler):
|
||||
if len(self._args) != 1:
|
||||
raise RequestError('Delete need one and only one argument')
|
||||
|
||||
self.ensureAccess(self.model(), permissions.PERMISSION_ALL, root=True) # Must have write permissions to delete
|
||||
self.ensureAccess(
|
||||
self.model(), permissions.PERMISSION_ALL, root=True
|
||||
) # Must have write permissions to delete
|
||||
|
||||
try:
|
||||
item = self.model.objects.get(uuid=self._args[0].lower())
|
||||
|
@ -55,6 +55,10 @@ from uds.core.auths import Authenticator as AuthenticatorInstance
|
||||
|
||||
from uds.models import User, Authenticator
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.core.util.request import ExtendedHttpRequest
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
authLogger = logging.getLogger('authLog')
|
||||
@ -110,11 +114,11 @@ def webLoginRequired(admin: typing.Union[bool, str] = False) -> typing.Callable[
|
||||
if admin == 'admin', needs admin
|
||||
"""
|
||||
def decorator(view_func: typing.Callable[..., RT]) -> typing.Callable[..., RT]:
|
||||
def _wrapped_view(request: HttpRequest, *args, **kwargs) -> RT:
|
||||
def _wrapped_view(request: 'ExtendedHttpRequest', *args, **kwargs) -> RT:
|
||||
"""
|
||||
Wrapped function for decorator
|
||||
"""
|
||||
if request.user is None:
|
||||
if not request.user:
|
||||
# url = request.build_absolute_uri(GlobalConfig.LOGIN_URL.get())
|
||||
# if GlobalConfig.REDIRECT_TO_HTTPS.getBool() is True:
|
||||
# url = url.replace('http://', 'https://')
|
||||
|
@ -129,7 +129,7 @@ class Cache:
|
||||
now = getSqlDatetime()
|
||||
try:
|
||||
DBCache.objects.create(
|
||||
owner=self._owner, key=key, value=value, created=now, validity=validity
|
||||
owner=self._owner, key=key, value=strValue, created=now, validity=validity
|
||||
) # @UndefinedVariable
|
||||
except Exception:
|
||||
try:
|
||||
|
@ -141,7 +141,7 @@ class CalendarChecker:
|
||||
memCache = caches['memory']
|
||||
|
||||
# First, try to get data from cache if it is valid
|
||||
cacheKey = str(hash(self.calendar.modified)) + str(dtime.date().toordinal()) + self.calendar.uuid + 'checker'
|
||||
cacheKey = str(self.calendar.modified.toordinal()) + str(dtime.date().toordinal()) + self.calendar.uuid + 'checker'
|
||||
# First, check "local memory cache", and if not found, from DB cache
|
||||
cached = memCache.get(cacheKey) or CalendarChecker.cache.get(cacheKey, None)
|
||||
|
||||
|
@ -52,6 +52,9 @@ class ExtendedHttpRequest(HttpRequest):
|
||||
os: DictAsObj
|
||||
user: typing.Optional[User] # type: ignore # HttpRequests users "user" for it own, but we redefine it because base is not used...
|
||||
|
||||
class ExtendedHttpRequestWithUser(ExtendedHttpRequest):
|
||||
user: User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_requests: typing.Dict[int, typing.Tuple[weakref.ref, datetime.datetime]] = {}
|
||||
|
@ -35,7 +35,8 @@ import logging
|
||||
# Utility imports
|
||||
from .util import getSqlDatetime, getSqlDatetimeAsUnix, NEVER, NEVER_UNIX
|
||||
|
||||
# Imports all models so they are available for migrations
|
||||
# Imports all models so they are available for migrations, etc..
|
||||
from .managed_object_model import ManagedObjectModel
|
||||
|
||||
# Permissions
|
||||
from .permissions import Permissions
|
||||
@ -104,7 +105,7 @@ from .account_usage import AccountUsage
|
||||
from .proxy import Proxy
|
||||
|
||||
# Tagging
|
||||
from .tag import Tag
|
||||
from .tag import Tag, TaggingMixin
|
||||
|
||||
# Utility
|
||||
from .dbfile import DBFile
|
||||
|
@ -138,7 +138,7 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore
|
||||
Checks if the access for a service pool is allowed or not (based esclusively on associated calendars)
|
||||
"""
|
||||
if chkDateTime is None:
|
||||
chkDateTime = typing.cast('datetime.datetime', getSqlDatetime())
|
||||
chkDateTime = getSqlDatetime()
|
||||
|
||||
access = self.fallbackAccess
|
||||
# Let's see if we can access by current datetime
|
||||
|
@ -151,7 +151,7 @@ class User(UUIDModel):
|
||||
"""
|
||||
if self.parent:
|
||||
try:
|
||||
usr = User.objects.prefetch_related('groups').get(uuid=self.parent)
|
||||
usr = User.objects.prefetch_related('authenticator', 'groups').get(uuid=self.parent)
|
||||
except Exception: # If parent do not exists
|
||||
usr = self
|
||||
else:
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2012-2020 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -35,7 +35,14 @@ from django.utils.translation import ugettext
|
||||
from django.utils import formats
|
||||
from django.urls import reverse
|
||||
|
||||
from uds.models import ServicePool, Transport, Network, ServicePoolGroup, MetaPool, getSqlDatetime
|
||||
from uds.models import (
|
||||
ServicePool,
|
||||
Transport,
|
||||
Network,
|
||||
ServicePoolGroup,
|
||||
MetaPool,
|
||||
getSqlDatetime,
|
||||
)
|
||||
from uds.core.util.config import GlobalConfig
|
||||
from uds.core.util import html
|
||||
|
||||
@ -43,14 +50,18 @@ from uds.core.managers import userServiceManager
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.core.util.request import ExtendedHttpRequest
|
||||
from uds.core.util.request import ExtendedHttpRequestWithUser
|
||||
from uds.core.util.tools import DictAsObj
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.Any]: # pylint: disable=too-many-locals, too-many-branches, too-many-statements
|
||||
def getServicesData(
|
||||
request: 'ExtendedHttpRequestWithUser',
|
||||
) -> typing.Dict[
|
||||
str, typing.Any
|
||||
]: # pylint: disable=too-many-locals, too-many-branches, too-many-statements
|
||||
"""Obtains the service data dictionary will all available services for this request
|
||||
|
||||
Arguments:
|
||||
@ -65,22 +76,22 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
|
||||
'autorun': autorun
|
||||
|
||||
"""
|
||||
# Session data
|
||||
os: 'DictAsObj' = request.os
|
||||
if not request.user:
|
||||
return {}
|
||||
|
||||
# We look for services for this authenticator groups. User is logged in in just 1 authenticator, so his groups must coincide with those assigned to ds
|
||||
groups = list(request.user.getGroups())
|
||||
availServicePools = list(ServicePool.getDeployedServicesForGroups(groups, request.user)) # Pass in user to get "number_assigned" to optimize
|
||||
availMetaPools = list(MetaPool.getForGroups(groups, request.user)) # Pass in user to get "number_assigned" to optimize
|
||||
availServicePools = list(
|
||||
ServicePool.getDeployedServicesForGroups(groups, request.user)
|
||||
) # Pass in user to get "number_assigned" to optimize
|
||||
availMetaPools = list(
|
||||
MetaPool.getForGroups(groups, request.user)
|
||||
) # Pass in user to get "number_assigned" to optimize
|
||||
now = getSqlDatetime()
|
||||
|
||||
# Information for administrators
|
||||
nets = ''
|
||||
validTrans = ''
|
||||
|
||||
logger.debug('OS: %s', os['OS'])
|
||||
osName = request.os['OS']
|
||||
logger.debug('OS: %s', osName)
|
||||
|
||||
if request.user.isStaff():
|
||||
nets = ','.join([n.name for n in Network.networksFor(request.ip)])
|
||||
@ -99,13 +110,20 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
|
||||
for meta in availMetaPools:
|
||||
# Check that we have access to at least one transport on some of its children
|
||||
hasUsablePools = False
|
||||
in_use = typing.cast(typing.Any, meta).number_in_use > 0 # Override, because we used hear annotations
|
||||
in_use = (
|
||||
typing.cast(typing.Any, meta).number_in_use > 0
|
||||
) # Override, because we used hear annotations
|
||||
for member in meta.members.all():
|
||||
# if pool.isInMaintenance():
|
||||
# continue
|
||||
for t in member.pool.transports.all():
|
||||
typeTrans = t.getType()
|
||||
if t.getType() and t.validForIp(request.ip) and typeTrans.supportsOs(os['OS']) and t.validForOs(os['OS']):
|
||||
if (
|
||||
t.getType()
|
||||
and t.validForIp(request.ip)
|
||||
and typeTrans.supportsOs(osName)
|
||||
and t.validForOs(osName)
|
||||
):
|
||||
hasUsablePools = True
|
||||
break
|
||||
|
||||
@ -120,31 +138,39 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
|
||||
|
||||
# If no usable pools, this is not visible
|
||||
if hasUsablePools:
|
||||
group = meta.servicesPoolGroup.as_dict if meta.servicesPoolGroup else ServicePoolGroup.default().as_dict
|
||||
group = (
|
||||
meta.servicesPoolGroup.as_dict
|
||||
if meta.servicesPoolGroup
|
||||
else ServicePoolGroup.default().as_dict
|
||||
)
|
||||
|
||||
services.append({
|
||||
'id': 'M' + meta.uuid,
|
||||
'name': meta.name,
|
||||
'visual_name': meta.visual_name,
|
||||
'description': meta.comments,
|
||||
'group': group,
|
||||
'transports': [{
|
||||
'id': 'meta',
|
||||
'name': 'meta',
|
||||
'link': html.udsMetaLink(request, 'M' + meta.uuid),
|
||||
'priority': 0
|
||||
}],
|
||||
'imageId': meta.image and meta.image.uuid or 'x',
|
||||
'show_transports': False,
|
||||
'allow_users_remove': False,
|
||||
'allow_users_reset': False,
|
||||
'maintenance': meta.isInMaintenance(),
|
||||
'not_accesible': not meta.isAccessAllowed(now),
|
||||
'in_use': in_use,
|
||||
'to_be_replaced': None,
|
||||
'to_be_replaced_text': '',
|
||||
'custom_calendar_text': meta.calendar_message,
|
||||
})
|
||||
services.append(
|
||||
{
|
||||
'id': 'M' + meta.uuid,
|
||||
'name': meta.name,
|
||||
'visual_name': meta.visual_name,
|
||||
'description': meta.comments,
|
||||
'group': group,
|
||||
'transports': [
|
||||
{
|
||||
'id': 'meta',
|
||||
'name': 'meta',
|
||||
'link': html.udsMetaLink(request, 'M' + meta.uuid),
|
||||
'priority': 0,
|
||||
}
|
||||
],
|
||||
'imageId': meta.image and meta.image.uuid or 'x',
|
||||
'show_transports': False,
|
||||
'allow_users_remove': False,
|
||||
'allow_users_reset': False,
|
||||
'maintenance': meta.isInMaintenance(),
|
||||
'not_accesible': not meta.isAccessAllowed(now),
|
||||
'in_use': in_use,
|
||||
'to_be_replaced': None,
|
||||
'to_be_replaced_text': '',
|
||||
'custom_calendar_text': meta.calendar_message,
|
||||
}
|
||||
)
|
||||
|
||||
# Now generic user service
|
||||
for svr in availServicePools:
|
||||
@ -155,23 +181,24 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
|
||||
use = str(svr.usage(typing.cast(typing.Any, svr).usage_count)) + '%'
|
||||
|
||||
trans = []
|
||||
for t in sorted(svr.transports.all(), key=lambda x: x.priority): # In memory sort, allows reuse prefetched and not too big array
|
||||
for t in sorted(
|
||||
svr.transports.all(), key=lambda x: x.priority
|
||||
): # In memory sort, allows reuse prefetched and not too big array
|
||||
try:
|
||||
typeTrans = t.getType()
|
||||
except Exception:
|
||||
continue
|
||||
if t.validForIp(request.ip) and typeTrans.supportsOs(os['OS']) and t.validForOs(os['OS']):
|
||||
if (
|
||||
t.validForIp(request.ip)
|
||||
and typeTrans.supportsOs(osName)
|
||||
and t.validForOs(osName)
|
||||
):
|
||||
if typeTrans.ownLink:
|
||||
link = reverse('TransportOwnLink', args=('F' + svr.uuid, t.uuid))
|
||||
else:
|
||||
link = html.udsAccessLink(request, 'F' + svr.uuid, t.uuid)
|
||||
trans.append(
|
||||
{
|
||||
'id': t.uuid,
|
||||
'name': t.name,
|
||||
'link': link,
|
||||
'priority': t.priority
|
||||
}
|
||||
{'id': t.uuid, 'name': t.name, 'link': link, 'priority': t.priority}
|
||||
)
|
||||
|
||||
# If empty transports, do not include it on list
|
||||
@ -190,47 +217,69 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
|
||||
# if ads:
|
||||
# in_use = ads.in_use
|
||||
|
||||
group = svr.servicesPoolGroup.as_dict if svr.servicesPoolGroup else ServicePoolGroup.default().as_dict
|
||||
group = (
|
||||
svr.servicesPoolGroup.as_dict
|
||||
if svr.servicesPoolGroup
|
||||
else ServicePoolGroup.default().as_dict
|
||||
)
|
||||
|
||||
# Only add toBeReplaced info in case we allow it. This will generate some "overload" on the services
|
||||
toBeReplaced = svr.toBeReplaced(request.user) if typing.cast(
|
||||
typing.Any, svr).pubs_active > 0 and GlobalConfig.NOTIFY_REMOVAL_BY_PUB.getBool(False) else None
|
||||
toBeReplaced = (
|
||||
svr.toBeReplaced(request.user)
|
||||
if typing.cast(typing.Any, svr).pubs_active > 0
|
||||
and GlobalConfig.NOTIFY_REMOVAL_BY_PUB.getBool(False)
|
||||
else None
|
||||
)
|
||||
# tbr = False
|
||||
if toBeReplaced:
|
||||
toBeReplaced = formats.date_format(toBeReplaced, "SHORT_DATETIME_FORMAT")
|
||||
toBeReplacedTxt = ugettext(
|
||||
'This service is about to be replaced by a new version. Please, close the session before {} and save all your work to avoid loosing it.').format(toBeReplaced)
|
||||
'This service is about to be replaced by a new version. Please, close the session before {} and save all your work to avoid loosing it.'
|
||||
).format(toBeReplaced)
|
||||
else:
|
||||
toBeReplacedTxt = ''
|
||||
|
||||
def datator(x): return x.replace('{use}', use).replace('{total}', str(svr.max_srvs))
|
||||
def datator(x):
|
||||
return x.replace('{use}', use).replace('{total}', str(svr.max_srvs))
|
||||
|
||||
services.append({
|
||||
'id': 'F' + svr.uuid,
|
||||
'name': datator(svr.name),
|
||||
'visual_name': datator(svr.visual_name.replace('{use}', use).replace('{total}', str(svr.max_srvs))),
|
||||
'description': svr.comments,
|
||||
'group': group,
|
||||
'transports': trans,
|
||||
'imageId': imageId,
|
||||
'show_transports': svr.show_transports,
|
||||
'allow_users_remove': svr.allow_users_remove,
|
||||
'allow_users_reset': svr.allow_users_reset,
|
||||
'maintenance': svr.isInMaintenance(),
|
||||
'not_accesible': not svr.isAccessAllowed(now),
|
||||
'in_use': in_use,
|
||||
'to_be_replaced': toBeReplaced,
|
||||
'to_be_replaced_text': toBeReplacedTxt,
|
||||
'custom_calendar_text': svr.calendar_message,
|
||||
})
|
||||
services.append(
|
||||
{
|
||||
'id': 'F' + svr.uuid,
|
||||
'name': datator(svr.name),
|
||||
'visual_name': datator(
|
||||
svr.visual_name.replace('{use}', use).replace(
|
||||
'{total}', str(svr.max_srvs)
|
||||
)
|
||||
),
|
||||
'description': svr.comments,
|
||||
'group': group,
|
||||
'transports': trans,
|
||||
'imageId': imageId,
|
||||
'show_transports': svr.show_transports,
|
||||
'allow_users_remove': svr.allow_users_remove,
|
||||
'allow_users_reset': svr.allow_users_reset,
|
||||
'maintenance': svr.isInMaintenance(),
|
||||
'not_accesible': not svr.isAccessAllowed(now),
|
||||
'in_use': in_use,
|
||||
'to_be_replaced': toBeReplaced,
|
||||
'to_be_replaced_text': toBeReplacedTxt,
|
||||
'custom_calendar_text': svr.calendar_message,
|
||||
}
|
||||
)
|
||||
|
||||
# logger.debug('Services: %s', services)
|
||||
|
||||
# Sort services and remove services with no transports...
|
||||
services = [s for s in sorted(services, key=lambda s: s['name'].upper()) if s['transports']]
|
||||
services = [
|
||||
s for s in sorted(services, key=lambda s: s['name'].upper()) if s['transports']
|
||||
]
|
||||
|
||||
autorun = False
|
||||
if len(services) == 1 and GlobalConfig.AUTORUN_SERVICE.getBool(False) and services[0]['transports']:
|
||||
if (
|
||||
len(services) == 1
|
||||
and GlobalConfig.AUTORUN_SERVICE.getBool(False)
|
||||
and services[0]['transports']
|
||||
):
|
||||
if request.session.get('autorunDone', '0') == '0':
|
||||
request.session['autorunDone'] = '1'
|
||||
autorun = True
|
||||
@ -241,5 +290,5 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
|
||||
'ip': request.ip,
|
||||
'nets': nets,
|
||||
'transports': validTrans,
|
||||
'autorun': autorun
|
||||
'autorun': autorun,
|
||||
}
|
||||
|
@ -51,14 +51,16 @@ from uds.web.util import services
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from django.http import HttpRequest # pylint: disable=ungrouped-imports
|
||||
from uds.core.util.request import ExtendedHttpRequest, ExtendedHttpRequestWithUser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@webLoginRequired(admin=False)
|
||||
def transportOwnLink(request: 'HttpRequest', idService: str, idTransport: str):
|
||||
def transportOwnLink(request: 'ExtendedHttpRequestWithUser', idService: str, idTransport: str):
|
||||
response: typing.MutableMapping[str, typing.Any] = {}
|
||||
|
||||
# For type checkers to "be happy"
|
||||
try:
|
||||
res = userServiceManager().getService(request.user, request.os, request.ip, idService, idTransport)
|
||||
ip, userService, iads, trans, itrans = res # pylint: disable=unused-variable
|
||||
@ -87,7 +89,7 @@ def transportOwnLink(request: 'HttpRequest', idService: str, idTransport: str):
|
||||
|
||||
|
||||
@cache_page(3600, key_prefix='img', cache='memory')
|
||||
def transportIcon(request: 'HttpRequest', idTrans: str) -> HttpResponse:
|
||||
def transportIcon(request: 'ExtendedHttpRequest', idTrans: str) -> HttpResponse:
|
||||
try:
|
||||
transport: Transport = Transport.objects.get(uuid=processUuid(idTrans))
|
||||
return HttpResponse(transport.getInstance().icon(), content_type='image/png')
|
||||
@ -96,7 +98,7 @@ def transportIcon(request: 'HttpRequest', idTrans: str) -> HttpResponse:
|
||||
|
||||
|
||||
@cache_page(3600, key_prefix='img', cache='memory')
|
||||
def serviceImage(request: 'HttpRequest', idImage: str) -> HttpResponse:
|
||||
def serviceImage(request: 'ExtendedHttpRequest', idImage: str) -> HttpResponse:
|
||||
try:
|
||||
icon = Image.objects.get(uuid=processUuid(idImage))
|
||||
return icon.imageResponse()
|
||||
@ -112,7 +114,7 @@ def serviceImage(request: 'HttpRequest', idImage: str) -> HttpResponse:
|
||||
|
||||
@webLoginRequired(admin=False)
|
||||
@never_cache
|
||||
def userServiceEnabler(request: 'HttpRequest', idService: str, idTransport: str) -> HttpResponse:
|
||||
def userServiceEnabler(request: 'ExtendedHttpRequestWithUser', idService: str, idTransport: str) -> HttpResponse:
|
||||
# Maybe we could even protect this even more by limiting referer to own server /? (just a meditation..)
|
||||
logger.debug('idService: %s, idTransport: %s', idService, idTransport)
|
||||
url = ''
|
||||
@ -166,12 +168,12 @@ def userServiceEnabler(request: 'HttpRequest', idService: str, idTransport: str)
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
def closer(request: 'HttpRequest') -> HttpResponse:
|
||||
def closer(request: 'ExtendedHttpRequest') -> HttpResponse:
|
||||
return HttpResponse('<html><body onload="window.close()"></body></html>')
|
||||
|
||||
@webLoginRequired(admin=False)
|
||||
@never_cache
|
||||
def action(request: 'HttpRequest', idService: str, actionString: str) -> HttpResponse:
|
||||
def action(request: 'ExtendedHttpRequestWithUser', idService: str, actionString: str) -> HttpResponse:
|
||||
userService = userServiceManager().locateUserService(request.user, idService, create=False)
|
||||
response: typing.Any = None
|
||||
rebuild: bool = False
|
||||
|
Loading…
Reference in New Issue
Block a user