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:
Adolfo Gómez García 2020-11-13 11:23:22 +01:00
parent 45ca92b77e
commit 6c54f8e75a
12 changed files with 647 additions and 283 deletions

View File

@ -56,6 +56,10 @@ from .handlers import (
from . import processors 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__) logger = logging.getLogger(__name__)
__all__ = ['Handler', 'Dispatcher'] __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 # pylint: disable=too-many-locals, too-many-return-statements, too-many-branches, too-many-statements
@method_decorator(csrf_exempt) @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 Processes the REST request and routes it wherever it needs to be routed
""" """

View File

@ -36,7 +36,16 @@ import typing
from django.db.models import Q, Count from django.db.models import Q, Count
from django.utils.translation import ugettext, ugettext_lazy as _ 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 ( from uds.models.calendar_action import (
CALENDAR_ACTION_INITIAL, CALENDAR_ACTION_INITIAL,
CALENDAR_ACTION_MAX, CALENDAR_ACTION_MAX,
@ -63,7 +72,14 @@ from uds.core.util import permissions
from uds.REST.model import ModelHandler from uds.REST.model import ModelHandler
from uds.REST import RequestError, ResponseError 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 .op_calendars import AccessCalendars, ActionsCalendars
from .services import Services from .services import Services
@ -75,6 +91,7 @@ class ServicesPools(ModelHandler):
""" """
Handles Services Pools REST requests Handles Services Pools REST requests
""" """
model = ServicePool model = ServicePool
detail = { detail = {
'services': AssignedService, 'services': AssignedService,
@ -84,13 +101,30 @@ class ServicesPools(ModelHandler):
'publications': Publications, 'publications': Publications,
'changelog': Changelog, 'changelog': Changelog,
'access': AccessCalendars, 'access': AccessCalendars,
'actions': ActionsCalendars 'actions': ActionsCalendars,
} }
save_fields = ['name', 'short_name', 'comments', 'tags', 'service_id', save_fields = [
'osmanager_id', 'image_id', 'pool_group_id', 'initial_srvs', 'name',
'cache_l1_srvs', 'cache_l2_srvs', 'max_srvs', 'show_transports', 'visible', 'short_name',
'allow_users_remove', 'allow_users_reset', 'ignores_unused', 'account_id', 'calendar_message'] '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'] remove_fields = ['osmanager_id', 'service_id']
@ -120,14 +154,53 @@ class ServicesPools(ModelHandler):
def getItems(self, *args, **kwargs): def getItems(self, *args, **kwargs):
# Optimized query, due that there is a lot of info needed for theee # Optimized query, due that there is a lot of info needed for theee
d = getSqlDatetime() - datetime.timedelta(seconds=GlobalConfig.RESTRAINT_TIME.getInt()) d = getSqlDatetime() - datetime.timedelta(
return super().getItems(overview=kwargs.get('overview', True), seconds=GlobalConfig.RESTRAINT_TIME.getInt()
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))) return super().getItems(
.annotate(preparing_count=Count('userServices', filter=Q(userServices__state=State.PREPARING))) overview=kwargs.get('overview', True),
.annotate(error_count=Count('userServices', filter=Q(userServices__state=State.ERROR, userServices__state_date__gt=d))) query=(
.annotate(usage_count=Count('userServices', filter=Q(userServices__state__in=State.VALID_STATES, userServices__cache_level=0))) 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().getItems(overview=kwargs.get('overview', True), prefetch=['service', 'service__provider', 'servicesPoolGroup', 'image', 'tags'])
# return super(ServicesPools, self).getItems(*args, **kwargs) # return super(ServicesPools, self).getItems(*args, **kwargs)
@ -161,7 +234,9 @@ class ServicesPools(ModelHandler):
'parent_type': item.service.data_type, 'parent_type': item.service.data_type,
'comments': item.comments, 'comments': item.comments,
'state': state, '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': item.account.name if item.account is not None else '',
'account_id': item.account.uuid if item.account is not None else None, 'account_id': item.account.uuid if item.account is not None else None,
'service_id': item.service.uuid, 'service_id': item.service.uuid,
@ -177,7 +252,10 @@ class ServicesPools(ModelHandler):
'allow_users_reset': item.allow_users_reset, 'allow_users_reset': item.allow_users_reset,
'ignores_unused': item.ignores_unused, 'ignores_unused': item.ignores_unused,
'fallbackAccess': item.fallbackAccess, '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, 'calendar_message': item.calendar_message,
} }
@ -189,8 +267,12 @@ class ServicesPools(ModelHandler):
restrained = item.error_count >= GlobalConfig.RESTRAINT_COUNT.getInt() # type: ignore restrained = item.error_count >= GlobalConfig.RESTRAINT_COUNT.getInt() # type: ignore
usage_count = item.usage_count # type: ignore usage_count = item.usage_count # type: ignore
else: else:
valid_count = item.userServices.exclude(state__in=State.INFO_STATES).count() valid_count = item.userServices.exclude(
preparing_count = item.userServices.filter(state=State.PREPARING).count() state__in=State.INFO_STATES
).count()
preparing_count = item.userServices.filter(
state=State.PREPARING
).count()
restrained = item.isRestrained() restrained = item.isRestrained()
usage_count = -1 usage_count = -1
@ -203,9 +285,10 @@ class ServicesPools(ModelHandler):
if item.servicesPoolGroup.image is not None: if item.servicesPoolGroup.image is not None:
poolGroupThumb = item.servicesPoolGroup.image.thumb64 poolGroupThumb = item.servicesPoolGroup.image.thumb64
val['state'] = state 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_count'] = valid_count
val['user_services_in_preparation'] = preparing_count val['user_services_in_preparation'] = preparing_count
val['tags'] = [tag.tag for tag in item.tags.all()] 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 # 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')) # raise ResponseError(ugettext('Create at least one OS Manager before creating a new service pool'))
if Service.objects.count() < 1: 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']) g = self.addDefaultFields([], ['name', 'short_name', 'comments', 'tags'])
for f in [{ for f in [
{
'name': 'service_id', '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'), 'label': ugettext('Base service'),
'tooltip': ugettext('Service used as base of this service pool'), 'tooltip': ugettext('Service used as base of this service pool'),
'type': gui.InputField.CHOICE_TYPE, 'type': gui.InputField.CHOICE_TYPE,
'rdonly': True, 'rdonly': True,
'order': 100, # Ensures is At end 'order': 100, # Ensures is At end
}, { },
{
'name': 'osmanager_id', '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'), 'label': ugettext('OS Manager'),
'tooltip': ugettext('OS Manager used as base of this service pool'), 'tooltip': ugettext('OS Manager used as base of this service pool'),
'type': gui.InputField.CHOICE_TYPE, 'type': gui.InputField.CHOICE_TYPE,
'rdonly': True, 'rdonly': True,
'order': 101, 'order': 101,
}, { },
{
'name': 'allow_users_remove', 'name': 'allow_users_remove',
'value': False, 'value': False,
'label': ugettext('Allow removal by users'), '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, 'type': gui.InputField.CHECKBOX_TYPE,
'order': 111, 'order': 111,
'tab': ugettext('Advanced'), 'tab': ugettext('Advanced'),
}, { },
{
'name': 'allow_users_reset', 'name': 'allow_users_reset',
'value': False, 'value': False,
'label': ugettext('Allow reset by users'), '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, 'type': gui.InputField.CHECKBOX_TYPE,
'order': 112, 'order': 112,
'tab': ugettext('Advanced'), 'tab': ugettext('Advanced'),
}, { },
{
'name': 'ignores_unused', 'name': 'ignores_unused',
'value': False, 'value': False,
'label': ugettext('Ignores unused'), '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, 'type': gui.InputField.CHECKBOX_TYPE,
'order': 113, 'order': 113,
'tab': ugettext('Advanced'), 'tab': ugettext('Advanced'),
}, { },
{
'name': 'visible', 'name': 'visible',
'value': True, 'value': True,
'label': ugettext('Visible'), 'label': ugettext('Visible'),
@ -279,31 +385,51 @@ class ServicesPools(ModelHandler):
'type': gui.InputField.CHECKBOX_TYPE, 'type': gui.InputField.CHECKBOX_TYPE,
'order': 107, 'order': 107,
'tab': ugettext('Display'), 'tab': ugettext('Display'),
}, { },
{
'name': 'image_id', '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'), 'label': ugettext('Associated Image'),
'tooltip': ugettext('Image assocciated with this service'), 'tooltip': ugettext('Image assocciated with this service'),
'type': gui.InputField.IMAGECHOICE_TYPE, 'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 120, 'order': 120,
'tab': ugettext('Display'), 'tab': ugettext('Display'),
}, { },
{
'name': 'pool_group_id', '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'), '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, 'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 121, 'order': 121,
'tab': ugettext('Display'), 'tab': ugettext('Display'),
}, { },
{
'name': 'calendar_message', 'name': 'calendar_message',
'value': '', 'value': '',
'label': ugettext('Calendar access denied text'), '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, 'type': gui.InputField.TEXT_TYPE,
'order': 122, 'order': 122,
'tab': ugettext('Display'), 'tab': ugettext('Display'),
}, { },
{
'name': 'initial_srvs', 'name': 'initial_srvs',
'value': '0', 'value': '0',
'minValue': '0', 'minValue': '0',
@ -312,55 +438,74 @@ class ServicesPools(ModelHandler):
'type': gui.InputField.NUMERIC_TYPE, 'type': gui.InputField.NUMERIC_TYPE,
'order': 130, 'order': 130,
'tab': ugettext('Availability'), 'tab': ugettext('Availability'),
}, { },
{
'name': 'cache_l1_srvs', 'name': 'cache_l1_srvs',
'value': '0', 'value': '0',
'minValue': '0', 'minValue': '0',
'label': ugettext('Services to keep in cache'), '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, 'type': gui.InputField.NUMERIC_TYPE,
'order': 131, 'order': 131,
'tab': ugettext('Availability'), 'tab': ugettext('Availability'),
}, { },
{
'name': 'cache_l2_srvs', 'name': 'cache_l2_srvs',
'value': '0', 'value': '0',
'minValue': '0', 'minValue': '0',
'label': ugettext('Services to keep in L2 cache'), '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, 'type': gui.InputField.NUMERIC_TYPE,
'order': 132, 'order': 132,
'tab': ugettext('Availability'), 'tab': ugettext('Availability'),
}, { },
{
'name': 'max_srvs', 'name': 'max_srvs',
'value': '0', 'value': '0',
'minValue': '1', 'minValue': '1',
'label': ugettext('Maximum number of services to provide'), '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, 'type': gui.InputField.NUMERIC_TYPE,
'order': 133, 'order': 133,
'tab': ugettext('Availability'), 'tab': ugettext('Availability'),
}, { },
{
'name': 'show_transports', 'name': 'show_transports',
'value': True, 'value': True,
'label': ugettext('Show transports'), '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, 'type': gui.InputField.CHECKBOX_TYPE,
'tab': ugettext('Advanced'), 'tab': ugettext('Advanced'),
'order': 130, 'order': 130,
}, { },
{
'name': 'account_id', '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'), 'label': ugettext('Accounting'),
'tooltip': ugettext('Account associated to this service pool'), 'tooltip': ugettext('Account associated to this service pool'),
'type': gui.InputField.CHOICE_TYPE, 'type': gui.InputField.CHOICE_TYPE,
'tab': ugettext('Advanced'), 'tab': ugettext('Advanced'),
'order': 131, 'order': 131,
}]: },
]:
self.addField(g, f) self.addField(g, f)
return g 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) # logger.debug(self._params)
try: try:
try: try:
@ -379,7 +524,9 @@ class ServicesPools(ModelHandler):
self._params['allow_users_reset'] = False self._params['allow_users_reset'] = False
if serviceType.needsManager is True: 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 fields['osmanager_id'] = osmanager.id
else: else:
del fields['osmanager_id'] del fields['osmanager_id']
@ -390,12 +537,23 @@ class ServicesPools(ModelHandler):
fields[k] = v fields[k] = v
if serviceType.maxDeployed != -1: if serviceType.maxDeployed != -1:
fields['max_srvs'] = min((int(fields['max_srvs']), serviceType.maxDeployed)) fields['max_srvs'] = min(
fields['initial_srvs'] = min(int(fields['initial_srvs']), serviceType.maxDeployed) (int(fields['max_srvs']), serviceType.maxDeployed)
fields['cache_l1_srvs'] = min(int(fields['cache_l1_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: 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 fields[k] = 0
if serviceType.usesCache_L2 is False: if serviceType.usesCache_L2 is False:
@ -405,7 +563,13 @@ class ServicesPools(ModelHandler):
raise RequestError(ugettext('This service requires an OS Manager')) raise RequestError(ugettext('This service requires an OS Manager'))
# If max < initial or cache_1 or cache_l2 # 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 *** # *** ACCOUNT ***
accountId = fields['account_id'] accountId = fields['account_id']
@ -414,7 +578,9 @@ class ServicesPools(ModelHandler):
if accountId != '-1': if accountId != '-1':
try: try:
fields['account_id'] = Account.objects.get(uuid=processUuid(accountId)).id fields['account_id'] = Account.objects.get(
uuid=processUuid(accountId)
).id
except Exception: except Exception:
logger.exception('Getting account ID') logger.exception('Getting account ID')
@ -487,7 +653,11 @@ class ServicesPools(ModelHandler):
validActions: typing.Tuple[typing.Dict, ...] = () validActions: typing.Tuple[typing.Dict, ...] = ()
itemInfo = item.service.getType() itemInfo = item.service.getType()
if itemInfo.usesCache is True: 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: if itemInfo.usesCache_L2 is True:
validActions += (CALENDAR_ACTION_CACHE_L2,) validActions += (CALENDAR_ACTION_CACHE_L2,)
@ -495,21 +665,33 @@ class ServicesPools(ModelHandler):
validActions += (CALENDAR_ACTION_PUBLISH,) validActions += (CALENDAR_ACTION_PUBLISH,)
# Transport & groups actions # 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 # Advanced actions
validActions += (CALENDAR_ACTION_IGNORE_UNUSED, CALENDAR_ACTION_REMOVE_USERSERVICES) validActions += (
CALENDAR_ACTION_IGNORE_UNUSED,
CALENDAR_ACTION_REMOVE_USERSERVICES,
)
return validActions return validActions
def listAssignables(self, item: ServicePool) -> typing.Any: def listAssignables(self, item: ServicePool) -> typing.Any:
service = item.service.getInstance() service = item.service.getInstance()
return [gui.choiceItem(i[0], i[1]) for i in service.listAssignables()] 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: if 'user_id' not in self._params or 'assignable_id' not in self._params:
return self.invalidRequestException('Invalid parameters') return self.invalidRequestException('Invalid parameters')
logger.debug('Creating from assignable: %s', self._params) 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 return True

View File

@ -45,7 +45,8 @@ from uds.core.ui import gui as uiGui
from uds.core.util import log from uds.core.util import log
from uds.core.util import permissions from uds.core.util import permissions
from uds.core.util.model import processUuid from uds.core.util.model import processUuid
from uds.models import Tag
from uds.models import Tag, TaggingMixin, ManagedObjectModel
from .handlers import ( from .handlers import (
Handler, Handler,
@ -54,12 +55,11 @@ from .handlers import (
RequestError, RequestError,
ResponseError, ResponseError,
AccessDenied, AccessDenied,
NotSupportedError NotSupportedError,
) )
# Not imported at runtime, just for type checking # Not imported at runtime, just for type checking
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from uds.models.managed_object_model import ManagedObjectModel
from uds.models import User from uds.models import User
from uds.core import Module from uds.core import Module
@ -80,12 +80,15 @@ class SaveException(HandlerError):
Exception thrown if couldn't save Exception thrown if couldn't save
""" """
class BaseModelHandler(Handler): class BaseModelHandler(Handler):
""" """
Base Handler for Master & Detail Handlers 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. Add a field to a "gui" description.
This method checks that every required field element is in there. This method checks that every required field element is in there.
@ -108,15 +111,17 @@ class BaseModelHandler(Handler):
'rdonly': field.get('rdonly', False), 'rdonly': field.get('rdonly', False),
'type': field.get('type', uiGui.InputField.TEXT_TYPE), 'type': field.get('type', uiGui.InputField.TEXT_TYPE),
'order': field.get('order', 0), 'order': field.get('order', 0),
'values': field.get('values', []) 'values': field.get('values', []),
} },
} }
if 'tab' in field: if 'tab' in field:
v['gui']['tab'] = field['tab'] v['gui']['tab'] = field['tab']
gui.append(v) gui.append(v)
return gui 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 Adds default fields (based in a list) to a "gui" description
:param gui: Gui list where the "default" fielsds will be added :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' 'priority' and 'small_name', 'short_name', 'tags'
""" """
if 'tags' in flds: if 'tags' in flds:
self.addField(gui, { self.addField(
'name': 'tags', gui,
'label': _('Tags'), {
'type': 'taglist', 'name': 'tags',
'tooltip': _('Tags for this element'), 'label': _('Tags'),
'order': 0 - 105, 'type': 'taglist',
}) 'tooltip': _('Tags for this element'),
'order': 0 - 105,
},
)
if 'name' in flds: if 'name' in flds:
self.addField(gui, { self.addField(
'name': 'name', gui,
'type': 'text', {
'required': True, 'name': 'name',
'label': _('Name'), 'type': 'text',
'length': 128, 'required': True,
'tooltip': _('Name of this element'), 'label': _('Name'),
'order': 0 - 100, 'length': 128,
}) 'tooltip': _('Name of this element'),
'order': 0 - 100,
},
)
if 'short_name' in flds: if 'short_name' in flds:
self.addField(gui, { self.addField(
'name': 'short_name', gui,
'type': 'text', {
'label': _('Short name'), 'name': 'short_name',
'tooltip': _('Short name for user service visualization'), 'type': 'text',
'required': False, 'label': _('Short name'),
'length': 32, 'tooltip': _('Short name for user service visualization'),
'order': 0 - 95, 'required': False,
}) 'length': 32,
'order': 0 - 95,
},
)
if 'comments' in flds: if 'comments' in flds:
self.addField(gui, { self.addField(
'name': 'comments', gui,
'label': _('Comments'), {
'tooltip': _('Comments for this element'), 'name': 'comments',
'length': 256, 'label': _('Comments'),
'order': 0 - 90, 'tooltip': _('Comments for this element'),
}) 'length': 256,
'order': 0 - 90,
},
)
if 'priority' in flds: if 'priority' in flds:
self.addField(gui, { self.addField(
'name': 'priority', gui,
'type': 'numeric', {
'label': _('Priority'), 'name': 'priority',
'tooltip': _('Selects the priority of this element (lower number means higher priority)'), 'type': 'numeric',
'required': True, 'label': _('Priority'),
'value': 1, 'tooltip': _(
'length': 4, 'Selects the priority of this element (lower number means higher priority)'
'order': 0 - 85, ),
}) 'required': True,
'value': 1,
'length': 4,
'order': 0 - 85,
},
)
if 'small_name' in flds: if 'small_name' in flds:
self.addField(gui, { self.addField(
'name': 'small_name', gui,
'type': 'text', {
'label': _('Label'), 'name': 'small_name',
'tooltip': _('Label for this element'), 'type': 'text',
'required': True, 'label': _('Label'),
'length': 128, 'tooltip': _('Label for this element'),
'order': 0 - 80, 'required': True,
}) 'length': 128,
'order': 0 - 80,
},
)
return gui 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) perm = permissions.getEffectivePermission(self._user, obj, root)
if perm < permission: if perm < permission:
raise self.accessDenied() raise self.accessDenied()
@ -201,23 +228,25 @@ class BaseModelHandler(Handler):
Returns a dictionary describing the type (the name, the icon, description, etc...) Returns a dictionary describing the type (the name, the icon, description, etc...)
""" """
res = self.typeInfo(type_) res = self.typeInfo(type_)
res.update({ res.update(
'name': _(type_.name()), {
'type': type_.type(), 'name': _(type_.name()),
'description': _(type_.description()), 'type': type_.type(),
'icon': type_.icon64().replace('\n', '') 'description': _(type_.description()),
}) 'icon': type_.icon64().replace('\n', ''),
}
)
if hasattr(type_, 'group'): if hasattr(type_, 'group'):
res['group'] = _(type_.group) # Add group info is it is contained res['group'] = _(type_.group) # Add group info is it is contained
return res return res
def processTableFields( def processTableFields(
self, self,
title: str, title: str,
fields: typing.List[typing.Any], fields: typing.List[typing.Any],
row_style: typing.Dict[str, typing.Any], row_style: typing.MutableMapping[str, typing.Any],
subtitle: typing.Optional[str] = None subtitle: typing.Optional[str] = None,
) -> typing.Dict[str, typing.Any]: ) -> typing.MutableMapping[str, typing.Any]:
""" """
Returns a dict containing the table fields description Returns a dict containing the table fields description
""" """
@ -225,10 +254,12 @@ class BaseModelHandler(Handler):
'title': title, 'title': title,
'fields': fields, 'fields': fields,
'row-style': row_style, '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 Reads the indicated fields from the parameters received, and if
:param fldList: List of required fields :param fldList: List of required fields
@ -244,26 +275,32 @@ class BaseModelHandler(Handler):
return args 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 Managed Objects (db element that contains a serialized object), fills a dictionary with the "field" parameters values.
For non managed objects, it does nothing For non managed objects, it does nothing
:param item: Item to extract fields :param item: Item to extract fields
:param res: Dictionary to "extend" with instance key-values pairs :param res: Dictionary to "extend" with instance key-values pairs
""" """
if hasattr(item, 'getInstance'): if isinstance(item, ManagedObjectModel):
i = item.getInstance() i = item.getInstance()
i.initGui() # Defaults & stuff i.initGui() # Defaults & stuff
value: typing.Any value: typing.Any
for key, value in i.valuesDict().items(): for key, value in i.valuesDict().items():
if isinstance(value, str): 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) logger.debug('%s = %s', key, value)
res[key] = value res[key] = value
return res return res
# Exceptions # 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 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 :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') message = message or _('Invalid Request')
return RequestError('{} {}: {}'.format(message, self.__class__, self._args)) 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 message = 'Invalid response' if message is None else message
return ResponseError(message) return ResponseError(message)
@ -279,9 +318,13 @@ class BaseModelHandler(Handler):
""" """
Raises a NotFound exception with translated "Method not found" string to current locale 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 Raises a NotFound exception, with location info
""" """
@ -307,7 +350,9 @@ class BaseModelHandler(Handler):
""" """
Invokes a test for an item 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() raise self.invalidMethodException()
@ -335,6 +380,7 @@ class DetailHandler(BaseModelHandler):
Also accepts GET methods for "custom" methods Also accepts GET methods for "custom" methods
""" """
custom_methods: typing.ClassVar[typing.List[str]] = [] custom_methods: typing.ClassVar[typing.List[str]] = []
_parent: typing.Optional['ModelHandler'] _parent: typing.Optional['ModelHandler']
_path: str _path: str
@ -344,13 +390,13 @@ class DetailHandler(BaseModelHandler):
_user: 'User' _user: 'User'
def __init__( def __init__(
self, self,
parentHandler: 'ModelHandler', parentHandler: 'ModelHandler',
path: str, path: str,
params: typing.Any, params: typing.Any,
*args: str, *args: str,
**kwargs: typing.Any **kwargs: typing.Any
): # pylint: disable=super-init-not-called ): # pylint: disable=super-init-not-called
""" """
Detail Handlers in fact "disabled" handler most initialization, that is no needed because 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) parent modelhandler has already done it (so we must access through parent handler)
@ -363,7 +409,9 @@ class DetailHandler(BaseModelHandler):
self._kwargs = kwargs self._kwargs = kwargs
self._user = kwargs.get('user', None) 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 checks curron methods
:param check: Method to check :param check: Method to check
@ -380,7 +428,9 @@ class DetailHandler(BaseModelHandler):
return None 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 Processes GET method for a detail Handler
""" """
@ -409,7 +459,11 @@ class DetailHandler(BaseModelHandler):
logger.debug('Types: %s', types_) logger.debug('Types: %s', types_)
return types_ return types_
if self._args[0] == TABLEINFO: 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 # try to get id
return self.getItems(parent, processUuid(self._args[0])) return self.getItems(parent, processUuid(self._args[0]))
@ -449,7 +503,9 @@ class DetailHandler(BaseModelHandler):
raise self.invalidRequestException() raise self.invalidRequestException()
logger.debug('Invoking proper saving detail item %s', item) 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: def post(self) -> typing.Any:
""" """
@ -494,7 +550,9 @@ class DetailHandler(BaseModelHandler):
# if item is None: # Returns ALL detail items # if item is None: # Returns ALL detail items
# return [] # return []
# return {} # Returns one item # 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 # Default save
def saveItem(self, parent: models.Model, item: typing.Optional[str]) -> None: 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') # raise RequestError('Gui not provided for this type of object')
return [] 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") 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 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. Note: Instance variables are the variables declared and serialized by modules.
The only detail that has types within is "Service", child of "Provider" The only detail that has types within is "Service", child of "Provider"
""" """
# Authentication related # Authentication related
authenticated = True authenticated = True
needs_staff = True needs_staff = True
# Which model does this manage, must be a django model ofc # 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 # By default, filter is empty
fltr: typing.Optional[str] = None 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) # 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 # For example ('services', True) -- > .../id_parent/services
# ('services', False) --> ..../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 # 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 # Put needed fields
save_fields: typing.ClassVar[typing.List[str]] = [] save_fields: typing.ClassVar[typing.List[str]] = []
# Put removable fields before updating # Put removable fields before updating
@ -704,7 +769,7 @@ class ModelHandler(BaseModelHandler):
del self._params['filter'] # Remove parameter del self._params['filter'] # Remove parameter
logger.debug('Found a filter expression (%s)', self.fltr) 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 # Right now, filtering only supports a single filter, in a future
# we may improve it # we may improve it
if self.fltr is None: if self.fltr is None:
@ -727,7 +792,7 @@ class ModelHandler(BaseModelHandler):
r = re.compile(s + fnmatch.translate(pattern) + e, re.RegexFlag.IGNORECASE) 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: try:
if fld not in item or r.match(item[fld]) is None: if fld not in item or r.match(item[fld]) is None:
return False return False
@ -746,8 +811,10 @@ class ModelHandler(BaseModelHandler):
# Helper to process detail # Helper to process detail
# Details can be managed (writen) by any user that has MANAGEMENT permission over parent # Details can be managed (writen) by any user that has MANAGEMENT permission over parent
def processDetail(self): def processDetail(self) -> typing.Any:
logger.debug('Processing detail %s for with params %s', self._path, self._params) logger.debug(
'Processing detail %s for with params %s', self._path, self._params
)
try: try:
item: models.Model = self.model.objects.filter(uuid=self._args[0])[0] item: models.Model = self.model.objects.filter(uuid=self._args[0])[0]
# If we do not have access to parent to, at least, read... # If we do not have access to parent to, at least, read...
@ -757,14 +824,28 @@ class ModelHandler(BaseModelHandler):
else: else:
requiredPermission = permissions.PERMISSION_READ requiredPermission = permissions.PERMISSION_READ
if permissions.checkPermissions(self._user, item, requiredPermission) is False: if (
logger.debug('Permission for user %s does not comply with %s', self._user, requiredPermission) 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() 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:]) args = list(self._args[2:])
path = self._path + '/'.join(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) method = getattr(detail, self._operation)
return method() return method()
@ -775,7 +856,9 @@ class ModelHandler(BaseModelHandler):
raise Exception('Invalid code executed on processDetail') 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: if 'overview' in kwargs:
overview = kwargs['overview'] overview = kwargs['overview']
del kwargs['overview'] del kwargs['overview']
@ -795,11 +878,20 @@ class ModelHandler(BaseModelHandler):
del kwargs['query'] del kwargs['query']
else: else:
logger.debug('Args: %s, kwargs: %s', args, kwargs) 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: for item in query:
try: 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 continue
if kwargs.get('overview', True): if kwargs.get('overview', True):
yield self.item_as_dict_overview(item) yield self.item_as_dict_overview(item)
@ -819,7 +911,7 @@ class ModelHandler(BaseModelHandler):
self.extractFilter() self.extractFilter()
return self.doFilter(self.doGet()) 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) logger.debug('method GET for %s, %s', self.__class__.__name__, self._args)
nArgs = len(self._args) nArgs = len(self._args)
@ -835,7 +927,13 @@ class ModelHandler(BaseModelHandler):
operation = getattr(self, self._args[1]) operation = getattr(self, self._args[1])
item = self.model.objects.get(uuid=self._args[0].lower()) item = self.model.objects.get(uuid=self._args[0].lower())
except Exception as e: 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() raise self.invalidMethodException()
return operation(item) return operation(item)
@ -855,9 +953,14 @@ class ModelHandler(BaseModelHandler):
if self._args[0] == TYPES: if self._args[0] == TYPES:
return list(self.getTypes()) return list(self.getTypes())
if self._args[0] == TABLEINFO: 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: if self._args[0] == GUI:
return self.getGui(None) return self.getGui('')
# get item ID # get item ID
try: try:
@ -890,7 +993,9 @@ class ModelHandler(BaseModelHandler):
if nArgs != 2: if nArgs != 2:
raise self.invalidRequestException() raise self.invalidRequestException()
try: 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) return self.getLogs(item)
except Exception: except Exception:
raise self.invalidItemException() raise self.invalidItemException()
@ -901,8 +1006,7 @@ class ModelHandler(BaseModelHandler):
raise self.invalidRequestException() # Will not return raise self.invalidRequestException() # Will not return
def post(self) -> typing.Any:
def post(self):
""" """
Processes a POST request Processes a POST request
""" """
@ -914,7 +1018,7 @@ class ModelHandler(BaseModelHandler):
raise self.invalidMethodException() # Will not return 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 Processes a PUT request
""" """
@ -926,7 +1030,10 @@ class ModelHandler(BaseModelHandler):
if len(self._args) > 1: # Detail? if len(self._args) > 1: # Detail?
return self.processDetail() 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: try:
# Extract fields # Extract fields
@ -954,11 +1061,16 @@ class ModelHandler(BaseModelHandler):
item.__dict__.update(args) # Update fields from args item.__dict__.update(args) # Update fields from args
# Now if tags, update them # Now if tags, update them
if tags: if isinstance(item, TaggingMixin):
logger.debug('Updating tags: %s', tags) if tags:
item.tags.set([Tag.objects.get_or_create(tag=val)[0] for val in tags if val != '']) logger.debug('Updating tags: %s', tags)
elif isinstance(tags, list): # Present, but list is empty (will be proccesed on "if" else) item.tags.set(
item.tags.clear() [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: except self.model.DoesNotExist:
raise NotFound('Item not found') raise NotFound('Item not found')
@ -973,14 +1085,19 @@ class ModelHandler(BaseModelHandler):
raise RequestError('incorrect invocation to PUT') raise RequestError('incorrect invocation to PUT')
if not deleteOnError: 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) # Store associated object if requested (data_type)
try: try:
data_type: typing.Optional[str] = self._params.get('data_type', self._params.get('type')) if isinstance(item, ManagedObjectModel):
if data_type: data_type: typing.Optional[str] = self._params.get(
item.data_type = data_type 'data_type', self._params.get('type')
item.data = item.getInstance(self._params).serialize() )
if data_type:
item.data_type = data_type
item.data = item.getInstance(self._params).serialize()
item.save() item.save()
@ -995,7 +1112,7 @@ class ModelHandler(BaseModelHandler):
return res return res
def delete(self) -> str: def delete(self) -> typing.Any:
""" """
Processes a DELETE request Processes a DELETE request
""" """
@ -1006,7 +1123,9 @@ class ModelHandler(BaseModelHandler):
if len(self._args) != 1: if len(self._args) != 1:
raise RequestError('Delete need one and only one argument') 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: try:
item = self.model.objects.get(uuid=self._args[0].lower()) item = self.model.objects.get(uuid=self._args[0].lower())

View File

@ -55,6 +55,10 @@ from uds.core.auths import Authenticator as AuthenticatorInstance
from uds.models import User, Authenticator 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__) logger = logging.getLogger(__name__)
authLogger = logging.getLogger('authLog') authLogger = logging.getLogger('authLog')
@ -110,11 +114,11 @@ def webLoginRequired(admin: typing.Union[bool, str] = False) -> typing.Callable[
if admin == 'admin', needs admin if admin == 'admin', needs admin
""" """
def decorator(view_func: typing.Callable[..., RT]) -> typing.Callable[..., RT]: 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 Wrapped function for decorator
""" """
if request.user is None: if not request.user:
# url = request.build_absolute_uri(GlobalConfig.LOGIN_URL.get()) # url = request.build_absolute_uri(GlobalConfig.LOGIN_URL.get())
# if GlobalConfig.REDIRECT_TO_HTTPS.getBool() is True: # if GlobalConfig.REDIRECT_TO_HTTPS.getBool() is True:
# url = url.replace('http://', 'https://') # url = url.replace('http://', 'https://')

View File

@ -129,7 +129,7 @@ class Cache:
now = getSqlDatetime() now = getSqlDatetime()
try: try:
DBCache.objects.create( 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 ) # @UndefinedVariable
except Exception: except Exception:
try: try:

View File

@ -141,7 +141,7 @@ class CalendarChecker:
memCache = caches['memory'] memCache = caches['memory']
# First, try to get data from cache if it is valid # 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 # First, check "local memory cache", and if not found, from DB cache
cached = memCache.get(cacheKey) or CalendarChecker.cache.get(cacheKey, None) cached = memCache.get(cacheKey) or CalendarChecker.cache.get(cacheKey, None)

View File

@ -52,6 +52,9 @@ class ExtendedHttpRequest(HttpRequest):
os: DictAsObj os: DictAsObj
user: typing.Optional[User] # type: ignore # HttpRequests users "user" for it own, but we redefine it because base is not used... 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__) logger = logging.getLogger(__name__)
_requests: typing.Dict[int, typing.Tuple[weakref.ref, datetime.datetime]] = {} _requests: typing.Dict[int, typing.Tuple[weakref.ref, datetime.datetime]] = {}

View File

@ -35,7 +35,8 @@ import logging
# Utility imports # Utility imports
from .util import getSqlDatetime, getSqlDatetimeAsUnix, NEVER, NEVER_UNIX 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 # Permissions
from .permissions import Permissions from .permissions import Permissions
@ -104,7 +105,7 @@ from .account_usage import AccountUsage
from .proxy import Proxy from .proxy import Proxy
# Tagging # Tagging
from .tag import Tag from .tag import Tag, TaggingMixin
# Utility # Utility
from .dbfile import DBFile from .dbfile import DBFile

View File

@ -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) Checks if the access for a service pool is allowed or not (based esclusively on associated calendars)
""" """
if chkDateTime is None: if chkDateTime is None:
chkDateTime = typing.cast('datetime.datetime', getSqlDatetime()) chkDateTime = getSqlDatetime()
access = self.fallbackAccess access = self.fallbackAccess
# Let's see if we can access by current datetime # Let's see if we can access by current datetime

View File

@ -151,7 +151,7 @@ class User(UUIDModel):
""" """
if self.parent: if self.parent:
try: 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 except Exception: # If parent do not exists
usr = self usr = self
else: else:

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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.utils import formats
from django.urls import reverse 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.config import GlobalConfig
from uds.core.util import html from uds.core.util import html
@ -43,14 +50,18 @@ from uds.core.managers import userServiceManager
# Not imported at runtime, just for type checking # Not imported at runtime, just for type checking
if typing.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 from uds.core.util.tools import DictAsObj
logger = logging.getLogger(__name__) 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 """Obtains the service data dictionary will all available services for this request
Arguments: Arguments:
@ -65,22 +76,22 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
'autorun': autorun '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 # 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()) groups = list(request.user.getGroups())
availServicePools = list(ServicePool.getDeployedServicesForGroups(groups, request.user)) # Pass in user to get "number_assigned" to optimize availServicePools = list(
availMetaPools = list(MetaPool.getForGroups(groups, request.user)) # Pass in user to get "number_assigned" to optimize 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() now = getSqlDatetime()
# Information for administrators # Information for administrators
nets = '' nets = ''
validTrans = '' validTrans = ''
logger.debug('OS: %s', os['OS']) osName = request.os['OS']
logger.debug('OS: %s', osName)
if request.user.isStaff(): if request.user.isStaff():
nets = ','.join([n.name for n in Network.networksFor(request.ip)]) 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: for meta in availMetaPools:
# Check that we have access to at least one transport on some of its children # Check that we have access to at least one transport on some of its children
hasUsablePools = False 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(): for member in meta.members.all():
# if pool.isInMaintenance(): # if pool.isInMaintenance():
# continue # continue
for t in member.pool.transports.all(): for t in member.pool.transports.all():
typeTrans = t.getType() 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 hasUsablePools = True
break break
@ -120,31 +138,39 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
# If no usable pools, this is not visible # If no usable pools, this is not visible
if hasUsablePools: 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({ services.append(
'id': 'M' + meta.uuid, {
'name': meta.name, 'id': 'M' + meta.uuid,
'visual_name': meta.visual_name, 'name': meta.name,
'description': meta.comments, 'visual_name': meta.visual_name,
'group': group, 'description': meta.comments,
'transports': [{ 'group': group,
'id': 'meta', 'transports': [
'name': 'meta', {
'link': html.udsMetaLink(request, 'M' + meta.uuid), 'id': 'meta',
'priority': 0 'name': 'meta',
}], 'link': html.udsMetaLink(request, 'M' + meta.uuid),
'imageId': meta.image and meta.image.uuid or 'x', 'priority': 0,
'show_transports': False, }
'allow_users_remove': False, ],
'allow_users_reset': False, 'imageId': meta.image and meta.image.uuid or 'x',
'maintenance': meta.isInMaintenance(), 'show_transports': False,
'not_accesible': not meta.isAccessAllowed(now), 'allow_users_remove': False,
'in_use': in_use, 'allow_users_reset': False,
'to_be_replaced': None, 'maintenance': meta.isInMaintenance(),
'to_be_replaced_text': '', 'not_accesible': not meta.isAccessAllowed(now),
'custom_calendar_text': meta.calendar_message, 'in_use': in_use,
}) 'to_be_replaced': None,
'to_be_replaced_text': '',
'custom_calendar_text': meta.calendar_message,
}
)
# Now generic user service # Now generic user service
for svr in availServicePools: 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)) + '%' use = str(svr.usage(typing.cast(typing.Any, svr).usage_count)) + '%'
trans = [] 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: try:
typeTrans = t.getType() typeTrans = t.getType()
except Exception: except Exception:
continue 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: if typeTrans.ownLink:
link = reverse('TransportOwnLink', args=('F' + svr.uuid, t.uuid)) link = reverse('TransportOwnLink', args=('F' + svr.uuid, t.uuid))
else: else:
link = html.udsAccessLink(request, 'F' + svr.uuid, t.uuid) link = html.udsAccessLink(request, 'F' + svr.uuid, t.uuid)
trans.append( 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 # If empty transports, do not include it on list
@ -190,47 +217,69 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
# if ads: # if ads:
# in_use = ads.in_use # 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 # 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( toBeReplaced = (
typing.Any, svr).pubs_active > 0 and GlobalConfig.NOTIFY_REMOVAL_BY_PUB.getBool(False) else None 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 # tbr = False
if toBeReplaced: if toBeReplaced:
toBeReplaced = formats.date_format(toBeReplaced, "SHORT_DATETIME_FORMAT") toBeReplaced = formats.date_format(toBeReplaced, "SHORT_DATETIME_FORMAT")
toBeReplacedTxt = ugettext( 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: else:
toBeReplacedTxt = '' 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({ services.append(
'id': 'F' + svr.uuid, {
'name': datator(svr.name), 'id': 'F' + svr.uuid,
'visual_name': datator(svr.visual_name.replace('{use}', use).replace('{total}', str(svr.max_srvs))), 'name': datator(svr.name),
'description': svr.comments, 'visual_name': datator(
'group': group, svr.visual_name.replace('{use}', use).replace(
'transports': trans, '{total}', str(svr.max_srvs)
'imageId': imageId, )
'show_transports': svr.show_transports, ),
'allow_users_remove': svr.allow_users_remove, 'description': svr.comments,
'allow_users_reset': svr.allow_users_reset, 'group': group,
'maintenance': svr.isInMaintenance(), 'transports': trans,
'not_accesible': not svr.isAccessAllowed(now), 'imageId': imageId,
'in_use': in_use, 'show_transports': svr.show_transports,
'to_be_replaced': toBeReplaced, 'allow_users_remove': svr.allow_users_remove,
'to_be_replaced_text': toBeReplacedTxt, 'allow_users_reset': svr.allow_users_reset,
'custom_calendar_text': svr.calendar_message, '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) # logger.debug('Services: %s', services)
# Sort services and remove services with no transports... # 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 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': if request.session.get('autorunDone', '0') == '0':
request.session['autorunDone'] = '1' request.session['autorunDone'] = '1'
autorun = True autorun = True
@ -241,5 +290,5 @@ def getServicesData(request: 'ExtendedHttpRequest') -> typing.Dict[str, typing.A
'ip': request.ip, 'ip': request.ip,
'nets': nets, 'nets': nets,
'transports': validTrans, 'transports': validTrans,
'autorun': autorun 'autorun': autorun,
} }

View File

@ -51,14 +51,16 @@ from uds.web.util import services
# Not imported at runtime, just for type checking # Not imported at runtime, just for type checking
if typing.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__) logger = logging.getLogger(__name__)
@webLoginRequired(admin=False) @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] = {} response: typing.MutableMapping[str, typing.Any] = {}
# For type checkers to "be happy"
try: try:
res = userServiceManager().getService(request.user, request.os, request.ip, idService, idTransport) res = userServiceManager().getService(request.user, request.os, request.ip, idService, idTransport)
ip, userService, iads, trans, itrans = res # pylint: disable=unused-variable 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') @cache_page(3600, key_prefix='img', cache='memory')
def transportIcon(request: 'HttpRequest', idTrans: str) -> HttpResponse: def transportIcon(request: 'ExtendedHttpRequest', idTrans: str) -> HttpResponse:
try: try:
transport: Transport = Transport.objects.get(uuid=processUuid(idTrans)) transport: Transport = Transport.objects.get(uuid=processUuid(idTrans))
return HttpResponse(transport.getInstance().icon(), content_type='image/png') 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') @cache_page(3600, key_prefix='img', cache='memory')
def serviceImage(request: 'HttpRequest', idImage: str) -> HttpResponse: def serviceImage(request: 'ExtendedHttpRequest', idImage: str) -> HttpResponse:
try: try:
icon = Image.objects.get(uuid=processUuid(idImage)) icon = Image.objects.get(uuid=processUuid(idImage))
return icon.imageResponse() return icon.imageResponse()
@ -112,7 +114,7 @@ def serviceImage(request: 'HttpRequest', idImage: str) -> HttpResponse:
@webLoginRequired(admin=False) @webLoginRequired(admin=False)
@never_cache @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..) # 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) logger.debug('idService: %s, idTransport: %s', idService, idTransport)
url = '' url = ''
@ -166,12 +168,12 @@ def userServiceEnabler(request: 'HttpRequest', idService: str, idTransport: str)
content_type='application/json' content_type='application/json'
) )
def closer(request: 'HttpRequest') -> HttpResponse: def closer(request: 'ExtendedHttpRequest') -> HttpResponse:
return HttpResponse('<html><body onload="window.close()"></body></html>') return HttpResponse('<html><body onload="window.close()"></body></html>')
@webLoginRequired(admin=False) @webLoginRequired(admin=False)
@never_cache @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) userService = userServiceManager().locateUserService(request.user, idService, create=False)
response: typing.Any = None response: typing.Any = None
rebuild: bool = False rebuild: bool = False