environment: coding style (#29240)
This commit is contained in:
parent
ca5d0b18c3
commit
9bf094388a
|
@ -1,10 +1,25 @@
|
|||
# hobo - portal to configure and deploy applications
|
||||
# Copyright (C) 2015-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.validators import validate_email
|
||||
|
||||
|
||||
from .models import (Authentic, Wcs, Passerelle, Variable, Combo, Fargo, Welco,
|
||||
|
@ -12,7 +27,8 @@ from .models import (Authentic, Wcs, Passerelle, Variable, Combo, Fargo, Welco,
|
|||
from .utils import get_setting_variable
|
||||
|
||||
EXCLUDED_FIELDS = ('last_operational_check_timestamp',
|
||||
'last_operational_success_timestamp', 'secret_key', 'secondary')
|
||||
'last_operational_success_timestamp', 'secret_key',
|
||||
'secondary')
|
||||
|
||||
|
||||
class BaseForm(forms.ModelForm):
|
||||
|
@ -29,8 +45,8 @@ class BaseForm(forms.ModelForm):
|
|||
# choice that was selected as an additional, disabled, <select> widget.
|
||||
if self.instance.id and len(choices) > 1:
|
||||
self.fields['template_name_readonly'] = forms.fields.CharField(
|
||||
label=_('Template'), required=False,
|
||||
initial=self.instance.template_name)
|
||||
label=_('Template'), required=False,
|
||||
initial=self.instance.template_name)
|
||||
self.fields['template_name_readonly'].widget = forms.Select(choices=choices)
|
||||
self.fields['template_name_readonly'].widget.attrs['disabled'] = 'disabled'
|
||||
|
||||
|
@ -116,11 +132,13 @@ class MandayeJSForm(BaseForm):
|
|||
model = MandayeJS
|
||||
exclude = EXCLUDED_FIELDS
|
||||
|
||||
|
||||
class ChronoForm(BaseForm):
|
||||
class Meta:
|
||||
model = Chrono
|
||||
exclude = EXCLUDED_FIELDS
|
||||
|
||||
|
||||
class CorboForm(BaseForm):
|
||||
class Meta:
|
||||
model = Corbo
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
# hobo - portal to configure and deploy applications
|
||||
# Copyright (C) 2015-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
import datetime
|
||||
import json
|
||||
|
@ -13,6 +29,7 @@ from django.utils.encoding import force_text
|
|||
from django.utils.six.moves.urllib.parse import urlparse
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import six
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import URLValidator
|
||||
|
||||
|
@ -23,16 +40,16 @@ from .utils import Zone, get_installed_services
|
|||
from .mandayejs_app_settings import APP_SETTINGS_CLASSES, DEFAULT_APP_SETTINGS
|
||||
|
||||
SECRET_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
|
||||
|
||||
FLOAT_RE = re.compile(r'^\s*[0-9]+\.[0-9]+\s*')
|
||||
|
||||
|
||||
class Variable(models.Model):
|
||||
name = models.CharField(max_length=100, verbose_name=_('name'))
|
||||
label = models.CharField(max_length=100, blank=True, verbose_name=_('label'))
|
||||
value = models.TextField(verbose_name=_('value'),
|
||||
blank=True,
|
||||
help_text=_('start with [ or { for a JSON document'))
|
||||
value = models.TextField(
|
||||
verbose_name=_('value'),
|
||||
blank=True,
|
||||
help_text=_('start with [ or { for a JSON document'))
|
||||
auto = models.BooleanField(default=False)
|
||||
service_type = models.ForeignKey(ContentType, null=True)
|
||||
service_pk = models.PositiveIntegerField(null=True)
|
||||
|
@ -81,16 +98,18 @@ class ServiceBase(models.Model):
|
|||
last_operational_success_timestamp = models.DateTimeField(null=True)
|
||||
last_update_timestamp = models.DateTimeField(auto_now=True, null=True)
|
||||
|
||||
variables = GenericRelation(Variable,
|
||||
content_type_field='service_type', object_id_field='service_pk')
|
||||
variables = GenericRelation(
|
||||
Variable,
|
||||
content_type_field='service_type',
|
||||
object_id_field='service_pk')
|
||||
|
||||
@classmethod
|
||||
def is_enabled(cls):
|
||||
return True
|
||||
|
||||
def is_operational(self):
|
||||
return (self.last_operational_success_timestamp is not None and
|
||||
self.last_operational_success_timestamp == self.last_operational_check_timestamp)
|
||||
return (self.last_operational_success_timestamp is not None
|
||||
and self.last_operational_success_timestamp == self.last_operational_check_timestamp)
|
||||
|
||||
def check_operational(self):
|
||||
once_now = now()
|
||||
|
@ -100,7 +119,7 @@ class ServiceBase(models.Model):
|
|||
response = requests.get(zone.href, timeout=10, allow_redirects=False)
|
||||
response.raise_for_status()
|
||||
self.last_operational_success_timestamp = once_now
|
||||
except requests.RequestException as e:
|
||||
except requests.RequestException:
|
||||
pass
|
||||
self.save(update_fields=('last_operational_check_timestamp', 'last_operational_success_timestamp'))
|
||||
|
||||
|
@ -120,7 +139,7 @@ class ServiceBase(models.Model):
|
|||
|
||||
def as_dict(self):
|
||||
as_dict = dict([(x, y) for (x, y) in self.__dict__.items()
|
||||
if type(y) in (int, str, unicode)])
|
||||
if isinstance(y, six.integer_types + six.string_types)])
|
||||
as_dict['base_url'] = self.get_base_url_path()
|
||||
as_dict['service-id'] = self.Extra.service_id
|
||||
as_dict['service-label'] = force_text(self.Extra.service_label)
|
||||
|
@ -225,8 +244,8 @@ class ServiceBase(models.Model):
|
|||
|
||||
class Authentic(ServiceBase):
|
||||
use_as_idp_for_self = models.BooleanField(
|
||||
verbose_name=_('Use as IdP'),
|
||||
default=False)
|
||||
verbose_name=_('Use as IdP'),
|
||||
default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Authentic Identity Provider')
|
||||
|
@ -240,9 +259,9 @@ class Authentic(ServiceBase):
|
|||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(_('User Management'), 'users', self.get_base_url_path() + 'manage/users/'),
|
||||
Zone(_('Role Management'), 'roles', self.get_base_url_path() + 'manage/roles/'),
|
||||
]
|
||||
Zone(_('User Management'), 'users', self.get_base_url_path() + 'manage/users/'),
|
||||
Zone(_('Role Management'), 'roles', self.get_base_url_path() + 'manage/roles/'),
|
||||
]
|
||||
|
||||
def get_saml_idp_metadata_url(self):
|
||||
return self.get_base_url_path() + 'idp/saml2/metadata'
|
||||
|
@ -267,8 +286,8 @@ class Wcs(ServiceBase):
|
|||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'webforms', self.get_base_url_path() + 'admin/'),
|
||||
]
|
||||
Zone(self.title, 'webforms', self.get_base_url_path() + 'admin/'),
|
||||
]
|
||||
|
||||
def get_saml_sp_metadata_url(self):
|
||||
return self.get_base_url_path() + 'saml/metadata'
|
||||
|
@ -287,8 +306,8 @@ class Passerelle(ServiceBase):
|
|||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'webservices', self.get_base_url_path() + 'manage/')
|
||||
]
|
||||
Zone(self.title, 'webservices', self.get_base_url_path() + 'manage/')
|
||||
]
|
||||
|
||||
def get_saml_sp_metadata_url(self):
|
||||
return self.get_base_url_path() + 'accounts/mellon/metadata/'
|
||||
|
@ -310,8 +329,8 @@ class Combo(ServiceBase):
|
|||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'portal', self.get_base_url_path() + 'manage/')
|
||||
]
|
||||
Zone(self.title, 'portal', self.get_base_url_path() + 'manage/')
|
||||
]
|
||||
|
||||
def get_saml_sp_metadata_url(self):
|
||||
return self.get_base_url_path() + 'accounts/mellon/metadata/'
|
||||
|
@ -333,8 +352,8 @@ class Fargo(ServiceBase):
|
|||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'document-box', self.get_base_url_path() + 'admin/')
|
||||
]
|
||||
Zone(self.title, 'document-box', self.get_base_url_path() + 'admin/')
|
||||
]
|
||||
|
||||
def get_saml_sp_metadata_url(self):
|
||||
return self.get_base_url_path() + 'accounts/mellon/metadata/'
|
||||
|
@ -352,8 +371,8 @@ class Welco(ServiceBase):
|
|||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'multichannel-guichet', self.get_base_url_path() + 'admin/')
|
||||
]
|
||||
Zone(self.title, 'multichannel-guichet', self.get_base_url_path() + 'admin/')
|
||||
]
|
||||
|
||||
def get_saml_sp_metadata_url(self):
|
||||
return self.get_base_url_path() + 'accounts/mellon/metadata/'
|
||||
|
@ -363,10 +382,12 @@ class Welco(ServiceBase):
|
|||
|
||||
|
||||
class MandayeJS(ServiceBase):
|
||||
site_app = models.CharField(_('Site Application'), max_length=128,
|
||||
choices = APP_SETTINGS_CLASSES,
|
||||
default = DEFAULT_APP_SETTINGS
|
||||
)
|
||||
site_app = models.CharField(
|
||||
_('Site Application'),
|
||||
max_length=128,
|
||||
choices=APP_SETTINGS_CLASSES,
|
||||
default=DEFAULT_APP_SETTINGS)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Authentication Reverse Proxy')
|
||||
ordering = ['title']
|
||||
|
@ -382,11 +403,11 @@ class MandayeJS(ServiceBase):
|
|||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'mandayejs', self.get_base_url_path() + '_mandaye/admin/')
|
||||
]
|
||||
Zone(self.title, 'mandayejs', self.get_base_url_path() + '_mandaye/admin/')
|
||||
]
|
||||
|
||||
def get_saml_sp_metadata_url(self):
|
||||
return self.get_base_url_path()+ '_mandaye/accounts/mellon/metadata/'
|
||||
return self.get_base_url_path() + '_mandaye/accounts/mellon/metadata/'
|
||||
|
||||
|
||||
class Chrono(ServiceBase):
|
||||
|
@ -402,8 +423,8 @@ class Chrono(ServiceBase):
|
|||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'calendar', self.get_base_url_path() + 'manage/')
|
||||
]
|
||||
Zone(self.title, 'calendar', self.get_base_url_path() + 'manage/')
|
||||
]
|
||||
|
||||
def get_saml_sp_metadata_url(self):
|
||||
return self.get_base_url_path() + 'accounts/mellon/metadata/'
|
||||
|
@ -425,8 +446,8 @@ class Hobo(ServiceBase):
|
|||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'hobo', self.get_base_url_path())
|
||||
]
|
||||
Zone(self.title, 'hobo', self.get_base_url_path())
|
||||
]
|
||||
|
||||
def get_saml_sp_metadata_url(self):
|
||||
return self.get_base_url_path() + 'accounts/mellon/metadata/'
|
||||
|
@ -452,11 +473,11 @@ class Corbo(ServiceBase):
|
|||
|
||||
def get_admin_zones(self):
|
||||
return [
|
||||
Zone(self.title, 'corbo', self.get_base_url_path() + 'admin/')
|
||||
]
|
||||
Zone(self.title, 'corbo', self.get_base_url_path() + 'admin/')
|
||||
]
|
||||
|
||||
def get_saml_sp_metadata_url(self):
|
||||
return self.get_base_url_path()+ 'accounts/mellon/metadata/'
|
||||
return self.get_base_url_path() + 'accounts/mellon/metadata/'
|
||||
|
||||
def get_backoffice_menu_url(self):
|
||||
return self.get_base_url_path() + 'manage/menu.json'
|
||||
|
|
|
@ -1,22 +1,38 @@
|
|||
# hobo - portal to configure and deploy applications
|
||||
# Copyright (C) 2015-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import *
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', HomeView.as_view(), name='environment-home'),
|
||||
url(r'^variables$', VariablesView.as_view(), name='environment-variables'),
|
||||
url(r'^new-variable$', VariableCreateView.as_view(), name='new-variable',),
|
||||
url(r'^update-variable/(?P<pk>\w+)$', VariableUpdateView.as_view(),
|
||||
url(r'^$', views.HomeView.as_view(), name='environment-home'),
|
||||
url(r'^variables$', views.VariablesView.as_view(), name='environment-variables'),
|
||||
url(r'^new-variable$', views.VariableCreateView.as_view(), name='new-variable',),
|
||||
url(r'^update-variable/(?P<pk>\w+)$', views.VariableUpdateView.as_view(),
|
||||
name='update-variable'),
|
||||
url(r'^delete-variable/(?P<pk>\w+)$', VariableDeleteView.as_view(),
|
||||
url(r'^delete-variable/(?P<pk>\w+)$', views.VariableDeleteView.as_view(),
|
||||
name='delete-variable'),
|
||||
url(r'^check_operational/(?P<service>\w+)/(?P<slug>[\w-]+)$',
|
||||
operational_check_view, name='operational-check'),
|
||||
url(r'^new-(?P<service>\w+)$', ServiceCreateView.as_view(), name='create-service'),
|
||||
url(r'^save-(?P<service>\w+)/(?P<slug>[\w-]+)$', ServiceUpdateView.as_view(), name='save-service'),
|
||||
url(r'^delete-(?P<service>\w+)/(?P<slug>[\w-]+)$', ServiceDeleteView.as_view(), name='delete-service'),
|
||||
views.operational_check_view, name='operational-check'),
|
||||
url(r'^new-(?P<service>\w+)$', views.ServiceCreateView.as_view(), name='create-service'),
|
||||
url(r'^save-(?P<service>\w+)/(?P<slug>[\w-]+)$', views.ServiceUpdateView.as_view(), name='save-service'),
|
||||
url(r'^delete-(?P<service>\w+)/(?P<slug>[\w-]+)$', views.ServiceDeleteView.as_view(), name='delete-service'),
|
||||
|
||||
url(r'^new-variable-(?P<service>\w+)/(?P<slug>[\w-]+)$',
|
||||
VariableCreateView.as_view(), name='new-variable-service',),
|
||||
url(r'^debug.json$', debug_json, name='debug-json'),
|
||||
views.VariableCreateView.as_view(), name='new-variable-service',),
|
||||
url(r'^debug.json$', views.debug_json, name='debug-json'),
|
||||
]
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
# hobo - portal to configure and deploy applications
|
||||
# Copyright (C) 2015-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import connection
|
||||
|
@ -13,11 +29,13 @@ def get_installed_services():
|
|||
installed_services.extend(available_service.objects.all())
|
||||
return installed_services
|
||||
|
||||
|
||||
def get_operational_services():
|
||||
return [x for x in get_installed_services() if x.is_operational()]
|
||||
|
||||
|
||||
def get_installed_services_dict():
|
||||
from .models import AVAILABLE_SERVICES, Variable
|
||||
from .models import Variable
|
||||
hobo_service = []
|
||||
build_absolute_uri = None
|
||||
if hasattr(connection, 'get_tenant') and hasattr(connection.get_tenant(), 'build_absolute_uri'):
|
||||
|
@ -38,9 +56,8 @@ def get_installed_services_dict():
|
|||
}]
|
||||
return {
|
||||
'services': hobo_service + [x.as_dict() for x in get_installed_services()],
|
||||
'variables': dict(((v.name, v.json)
|
||||
for v in Variable.objects.filter(service_pk__isnull=True))),
|
||||
}
|
||||
'variables': {v.name: v.json for v in Variable.objects.filter(service_pk__isnull=True)}
|
||||
}
|
||||
|
||||
|
||||
class Zone:
|
||||
|
@ -56,8 +73,12 @@ class Zone:
|
|||
|
||||
def get_setting_variable(name):
|
||||
from .models import Variable
|
||||
variable, created = Variable.objects.get_or_create(name=name,
|
||||
defaults={'auto': True, 'value': settings.VARIABLE_SETTINGS_DEFAULTS.get(name) or ''})
|
||||
variable, created = Variable.objects.get_or_create(
|
||||
name=name,
|
||||
defaults={
|
||||
'auto': True,
|
||||
'value': settings.VARIABLE_SETTINGS_DEFAULTS.get(name) or ''
|
||||
})
|
||||
return variable
|
||||
|
||||
def create_base_url(hostname, service):
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
# hobo - portal to configure and deploy applications
|
||||
# Copyright (C) 2015-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import json
|
||||
import string
|
||||
|
||||
|
@ -25,7 +41,8 @@ class HomeView(TemplateView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super(HomeView, self).get_context_data(**kwargs)
|
||||
context['available_services'] = [
|
||||
AvailableService(x) for x in AVAILABLE_SERVICES if x.is_enabled()]
|
||||
AvailableService(x) for x in AVAILABLE_SERVICES if x.is_enabled()
|
||||
]
|
||||
context['installed_services'] = [x for x in utils.get_installed_services() if not x.secondary]
|
||||
return context
|
||||
|
||||
|
@ -35,8 +52,7 @@ class VariablesView(TemplateView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(VariablesView, self).get_context_data(**kwargs)
|
||||
context['variables'] = Variable.objects.filter(auto=False,
|
||||
service_pk__isnull=True).order_by('label')
|
||||
context['variables'] = Variable.objects.filter(auto=False, service_pk__isnull=True).order_by('label')
|
||||
return context
|
||||
|
||||
|
||||
|
@ -67,8 +83,8 @@ class VariableCreateView(CreateView):
|
|||
}
|
||||
try:
|
||||
self.object = Variable.objects.get(
|
||||
name=form.instance.name,
|
||||
**service_kwargs)
|
||||
name=form.instance.name,
|
||||
**service_kwargs)
|
||||
except Variable.DoesNotExist:
|
||||
self.object = form.save()
|
||||
else:
|
||||
|
@ -113,7 +129,9 @@ class ServiceCreateView(CreateView):
|
|||
|
||||
def get_initial(self):
|
||||
initial = super(ServiceCreateView, self).get_initial()
|
||||
initial['base_url'] = utils.create_base_url(self.request.build_absolute_uri(), self.model.Extra.service_default_slug)
|
||||
initial['base_url'] = utils.create_base_url(
|
||||
self.request.build_absolute_uri(),
|
||||
self.model.Extra.service_default_slug)
|
||||
initial['slug'] = self.model.Extra.service_default_slug
|
||||
return initial
|
||||
|
||||
|
@ -166,6 +184,7 @@ class ServiceUpdateView(UpdateView):
|
|||
return form_class
|
||||
return None
|
||||
|
||||
|
||||
class ServiceDeleteView(DeleteView):
|
||||
success_url = reverse_lazy('environment-home')
|
||||
template_name = 'environment/generic_confirm_delete.html'
|
||||
|
@ -179,6 +198,7 @@ class ServiceDeleteView(DeleteView):
|
|||
return service.objects.get(slug=service_slug)
|
||||
return None
|
||||
|
||||
|
||||
def operational_check_view(request, service, slug, **kwargs):
|
||||
|
||||
for klass in AVAILABLE_SERVICES:
|
||||
|
|
Loading…
Reference in New Issue