Switch to a single config file, using a config group per service.
This has two advantages: - Avoids Naming Conflicts (e.g. backend-driver) - Allows common options to be specified once for all services (e.g. pybasedir, logdir) Fixs bug #1096850 Change-Id: I5b02f591ccea6d1a8201b21c3cb8f92bcf6b30fa
This commit is contained in:
parent
f4c0f899d8
commit
bc5fa37b20
@ -23,7 +23,7 @@ from moniker.agent import service as agent_service
|
||||
|
||||
eventlet.monkey_patch()
|
||||
|
||||
utils.read_config('moniker-agent', sys.argv)
|
||||
utils.read_config('moniker', sys.argv)
|
||||
|
||||
logging.setup('moniker')
|
||||
|
||||
|
@ -23,7 +23,7 @@ from moniker.api import service as api_service
|
||||
|
||||
eventlet.monkey_patch()
|
||||
|
||||
utils.read_config('moniker-api', sys.argv)
|
||||
utils.read_config('moniker', sys.argv)
|
||||
|
||||
logging.setup('moniker')
|
||||
|
||||
|
@ -23,7 +23,7 @@ from moniker.central import service as central_service
|
||||
|
||||
eventlet.monkey_patch()
|
||||
|
||||
utils.read_config('moniker-central', sys.argv)
|
||||
utils.read_config('moniker', sys.argv)
|
||||
|
||||
logging.setup('moniker')
|
||||
|
||||
|
@ -15,7 +15,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import sys
|
||||
from moniker import utils
|
||||
from moniker.manage import MonikerShell
|
||||
|
||||
# TODO: Sypport passing --config-file and --config-dir to read_config
|
||||
utils.read_config('moniker', [])
|
||||
|
||||
shell = MonikerShell()
|
||||
sys.exit(shell.run(sys.argv))
|
||||
sys.exit(shell.run(sys.argv[1:]))
|
||||
|
@ -1,39 +0,0 @@
|
||||
[DEFAULT]
|
||||
# Show more verbose log output (sets INFO log level output)
|
||||
verbose = True
|
||||
|
||||
# Show debugging output in logs (sets DEBUG log level output)
|
||||
debug = False
|
||||
|
||||
# Top-level directory for maintaining moniker's state
|
||||
#state_path = /var/lib/moniker
|
||||
|
||||
# Log directory
|
||||
#logdir=/var/log/moniker
|
||||
|
||||
# Driver used for backend communication (e.g. bind9, powerdns)
|
||||
#backend_driver=bind9
|
||||
|
||||
# There has to be a better way to set these defaults
|
||||
allowed_rpc_exception_modules = moniker.exceptions, moniker.openstack.common.exception
|
||||
logging_context_format_string = %(asctime)s %(levelname)s %(name)s [%(request_id)s %(user)s %(tenant)s] %(instance)s %(message)s
|
||||
default_log_levels = amqplib=WARN, sqlalchemy=WARN, boto=WARN, suds=INFO, keystone=INFO, eventlet.wsgi.server=WARN, stevedore=WARN
|
||||
|
||||
# Ability to configure each backend individually
|
||||
#[backend:bind9]
|
||||
#rndc_path = /usr/sbin/rndc
|
||||
#rndc_host = 127.0.0.1
|
||||
#rndc_port = 953
|
||||
#rndc_config_file = /etc/rndc.conf
|
||||
#rndc_key_file = /etc/rndc.key
|
||||
|
||||
# MySQLBind agent options
|
||||
#[backend:mysqlbind9]
|
||||
#database_connection = mysql://user:password@host/schema
|
||||
#rndc_path = /usr/sbin/rndc
|
||||
#rndc_host = 127.0.0.1
|
||||
#rndc_port = 953
|
||||
#rndc_config_file = /etc/rndc.conf
|
||||
#rndc_key_file = /etc/rndc.key
|
||||
#write_database = True
|
||||
#dns_server_type = master
|
@ -1,26 +0,0 @@
|
||||
[DEFAULT]
|
||||
# Show more verbose log output (sets INFO log level output)
|
||||
verbose = True
|
||||
|
||||
# Show debugging output in logs (sets DEBUG log level output)
|
||||
debug = False
|
||||
|
||||
# Top-level directory for maintaining moniker's state
|
||||
#state_path = /var/lib/moniker
|
||||
|
||||
# Log directory
|
||||
#logdir=/var/log/moniker
|
||||
|
||||
# Address to bind the API server
|
||||
api_host = 0.0.0.0
|
||||
|
||||
# Port the bind the API server to
|
||||
api_port = 9001
|
||||
|
||||
# Authentication strategy to use - can be either "noauth" or "keystone"
|
||||
# auth_strategy = noauth
|
||||
|
||||
# There has to be a better way to set these defaults
|
||||
allowed_rpc_exception_modules = moniker.exceptions, moniker.openstack.common.exception
|
||||
logging_context_format_string = %(asctime)s %(levelname)s %(name)s [%(request_id)s %(user)s %(tenant)s] %(instance)s %(message)s
|
||||
default_log_levels = amqplib=WARN, sqlalchemy=WARN, boto=WARN, suds=INFO, keystone=INFO, eventlet.wsgi.server=WARN, stevedore=WARN
|
@ -1,4 +1,5 @@
|
||||
[DEFAULT]
|
||||
## General Configuration
|
||||
# Show more verbose log output (sets INFO log level output)
|
||||
verbose = True
|
||||
|
||||
@ -11,9 +12,6 @@ debug = False
|
||||
# Log directory
|
||||
#logdir=/var/log/moniker
|
||||
|
||||
# Driver used for backend communication (e.g. rpc, bind9, powerdns)
|
||||
#backend_driver=rpc
|
||||
|
||||
# There has to be a better way to set these defaults
|
||||
allowed_rpc_exception_modules = moniker.exceptions, moniker.openstack.common.exception
|
||||
logging_context_format_string = %(asctime)s %(levelname)s %(name)s [%(request_id)s %(user)s %(tenant)s] %(instance)s %(message)s
|
||||
@ -22,11 +20,30 @@ default_log_levels = amqplib=WARN, sqlalchemy=WARN, boto=WARN, suds=INFO, keysto
|
||||
# Driver used for issuing notifications
|
||||
#notification_driver=moniker.openstack.common.notifier.rabbit_notifier
|
||||
|
||||
## Service Configuration
|
||||
[service:central]
|
||||
|
||||
# Driver used for backend communication (e.g. rpc, bind9, powerdns)
|
||||
#backend_driver=rpc
|
||||
|
||||
# List of notification handlers to enable, configuration of these needs to
|
||||
# correspond to a [handler:my_driver] section below or else in the config
|
||||
#enabled_notification_handlers = nova_fixed
|
||||
|
||||
# Sections for *SQL storages
|
||||
[service:api]
|
||||
# Address to bind the API server
|
||||
api_host = 0.0.0.0
|
||||
|
||||
# Port the bind the API server to
|
||||
api_port = 9001
|
||||
# Authentication strategy to use - can be either "noauth" or "keystone"
|
||||
# auth_strategy = noauth
|
||||
|
||||
[service:agent]
|
||||
# Driver used for backend communication (e.g. bind9, powerdns)
|
||||
#backend_driver=bind9
|
||||
|
||||
## Storage Configuration
|
||||
#[storage:sqlalchemy]
|
||||
# Database connection string - to configure options for a given implementation
|
||||
# like sqlalchemy or other see below
|
||||
@ -38,7 +55,26 @@ default_log_levels = amqplib=WARN, sqlalchemy=WARN, boto=WARN, suds=INFO, keysto
|
||||
#max_retries = 10
|
||||
#retry_interval = 10
|
||||
|
||||
## Notification Handler Configuration
|
||||
#[handler:nova_fixed]
|
||||
#domain_id = <random uuid>
|
||||
#notification_topics = monitor
|
||||
#control_exchange = 'nova'
|
||||
|
||||
## Backend Configuration
|
||||
#[backend:bind9]
|
||||
#rndc_path = /usr/sbin/rndc
|
||||
#rndc_host = 127.0.0.1
|
||||
#rndc_port = 953
|
||||
#rndc_config_file = /etc/rndc.conf
|
||||
#rndc_key_file = /etc/rndc.key
|
||||
|
||||
#[backend:mysqlbind9]
|
||||
#database_connection = mysql://user:password@host/schema
|
||||
#rndc_path = /usr/sbin/rndc
|
||||
#rndc_host = 127.0.0.1
|
||||
#rndc_port = 953
|
||||
#rndc_config_file = /etc/rndc.conf
|
||||
#rndc_key_file = /etc/rndc.key
|
||||
#write_database = True
|
||||
#dns_server_type = master
|
@ -0,0 +1,25 @@
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P. All Rights Reserved.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@hp.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from moniker.openstack.common import cfg
|
||||
|
||||
cfg.CONF.register_group(cfg.OptGroup(
|
||||
name='service:agent', title="Configuration for Agent Service"
|
||||
))
|
||||
|
||||
cfg.CONF.register_opts([
|
||||
cfg.StrOpt('backend-driver', default='bind9',
|
||||
help='The backend driver to use'),
|
||||
], group='service:agent')
|
@ -20,15 +20,10 @@ from moniker import backend
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
cfg.CONF.register_opts([
|
||||
cfg.StrOpt('backend-driver', default='bind9',
|
||||
help='The backend driver to use'),
|
||||
])
|
||||
|
||||
|
||||
class Service(rpc_service.Service):
|
||||
def __init__(self, *args, **kwargs):
|
||||
manager = backend.get_backend(cfg.CONF.backend_driver)
|
||||
manager = backend.get_backend(cfg.CONF['service:agent'].backend_driver)
|
||||
|
||||
kwargs.update(
|
||||
host=cfg.CONF.host,
|
||||
|
@ -17,6 +17,9 @@ import flask
|
||||
from moniker.openstack.common import cfg
|
||||
from moniker.openstack.common import jsonutils as json
|
||||
|
||||
cfg.CONF.register_group(cfg.OptGroup(
|
||||
name='service:api', title="Configuration for API Service"
|
||||
))
|
||||
|
||||
cfg.CONF.register_opts([
|
||||
cfg.StrOpt('api_host', default='0.0.0.0',
|
||||
@ -28,7 +31,7 @@ cfg.CONF.register_opts([
|
||||
cfg.StrOpt('auth_strategy', default='noauth',
|
||||
help='The strategy to use for auth. Supports noauth or '
|
||||
'keystone'),
|
||||
])
|
||||
], group='service:api')
|
||||
|
||||
|
||||
# Allows us to serialize datetime's etc
|
||||
|
@ -27,7 +27,7 @@ def pipeline_factory(loader, global_conf, **local_conf):
|
||||
|
||||
Code nabbed from cinder.
|
||||
"""
|
||||
pipeline = local_conf[cfg.CONF.auth_strategy]
|
||||
pipeline = local_conf[cfg.CONF['service:api'].auth_strategy]
|
||||
pipeline = pipeline.split()
|
||||
filters = [loader.get_filter(n) for n in pipeline[:-1]]
|
||||
app = loader.get_app(pipeline[-1])
|
||||
|
@ -27,7 +27,8 @@ LOG = logging.getLogger(__name__)
|
||||
class Service(wsgi.Service):
|
||||
def __init__(self, backlog=128, threads=1000):
|
||||
|
||||
config_paths = utils.find_config(cfg.CONF.api_paste_config)
|
||||
api_paste_config = cfg.CONF['service:api'].api_paste_config
|
||||
config_paths = utils.find_config(api_paste_config)
|
||||
|
||||
if len(config_paths) == 0:
|
||||
msg = 'Unable to determine appropriate api-paste-config file'
|
||||
@ -39,7 +40,7 @@ class Service(wsgi.Service):
|
||||
name='osapi_dns')
|
||||
|
||||
super(Service, self).__init__(application=application,
|
||||
host=cfg.CONF.api_host,
|
||||
port=cfg.CONF.api_port,
|
||||
host=cfg.CONF['service:api'].api_host,
|
||||
port=cfg.CONF['service:api'].api_port,
|
||||
backlog=backlog,
|
||||
threads=threads)
|
||||
|
@ -0,0 +1,29 @@
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P. All Rights Reserved.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@hp.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from moniker.openstack.common import cfg
|
||||
|
||||
cfg.CONF.register_group(cfg.OptGroup(
|
||||
name='service:central', title="Configuration for Central Service"
|
||||
))
|
||||
|
||||
cfg.CONF.register_opts([
|
||||
cfg.StrOpt('backend-driver', default='rpc',
|
||||
help='The backend driver to use'),
|
||||
cfg.StrOpt('storage-driver', default='sqlalchemy',
|
||||
help='The storage driver to use'),
|
||||
cfg.ListOpt('enabled-notification-handlers', default=[],
|
||||
help='Enabled Notification Handlers'),
|
||||
], group='service:central')
|
@ -27,20 +27,12 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
HANDLER_NAMESPACE = 'moniker.notification.handler'
|
||||
|
||||
cfg.CONF.register_opts([
|
||||
cfg.StrOpt('backend-driver', default='rpc',
|
||||
help='The backend driver to use'),
|
||||
cfg.StrOpt('storage-driver', default='sqlalchemy',
|
||||
help='The storage driver to use'),
|
||||
cfg.ListOpt('enabled-notification-handlers', default=[],
|
||||
help='Enabled Notification Handlers'),
|
||||
])
|
||||
|
||||
|
||||
class Service(rpc_service.Service):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
self.backend = backend.get_backend(cfg.CONF.backend_driver)
|
||||
backend_driver = cfg.CONF['service:central'].backend_driver
|
||||
self.backend = backend.get_backend(backend_driver)
|
||||
|
||||
kwargs.update(
|
||||
host=cfg.CONF.host,
|
||||
@ -63,12 +55,14 @@ class Service(rpc_service.Service):
|
||||
|
||||
def _init_extensions(self):
|
||||
""" Loads and prepares all enabled extensions """
|
||||
enabled_notification_handlers = \
|
||||
cfg.CONF['service:central'].enabled_notification_handlers
|
||||
|
||||
self.extensions_manager = NamedExtensionManager(
|
||||
HANDLER_NAMESPACE, names=cfg.CONF.enabled_notification_handlers)
|
||||
HANDLER_NAMESPACE, names=enabled_notification_handlers)
|
||||
|
||||
def _load_extension(ext):
|
||||
handler_cls = ext.plugin
|
||||
handler_cls.register_opts(cfg.CONF)
|
||||
return handler_cls(central_service=self)
|
||||
|
||||
try:
|
||||
|
@ -19,7 +19,6 @@ from migrate.versioning import api as versioning_api
|
||||
from cliff.command import Command
|
||||
from moniker.openstack.common import log as logging
|
||||
from moniker.openstack.common import cfg
|
||||
from moniker import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
REPOSITORY = os.path.abspath(os.path.join(os.path.dirname(__file__), '..',
|
||||
@ -33,8 +32,6 @@ class InitCommand(Command):
|
||||
"Init database"
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
utils.read_config('moniker-central', [])
|
||||
|
||||
url = cfg.CONF['storage:sqlalchemy'].database_connection
|
||||
|
||||
if not os.path.exists(REPOSITORY):
|
||||
@ -53,8 +50,6 @@ class SyncCommand(Command):
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
# TODO: Support specifying version
|
||||
utils.read_config('moniker-central', [])
|
||||
|
||||
url = cfg.CONF['storage:sqlalchemy'].database_connection
|
||||
|
||||
if not os.path.exists(REPOSITORY):
|
||||
|
@ -28,7 +28,7 @@ def get_engine(engine_name):
|
||||
|
||||
|
||||
def get_connection():
|
||||
engine = get_engine(cfg.CONF.storage_driver)
|
||||
engine = get_engine(cfg.CONF['service:central'].storage_driver)
|
||||
return engine.get_connection()
|
||||
|
||||
|
||||
|
@ -20,11 +20,18 @@ from moniker.openstack.common import cfg
|
||||
from moniker.openstack.common import log as logging
|
||||
from moniker.context import MonikerContext
|
||||
from moniker import storage
|
||||
from moniker.agent import service as agent_service
|
||||
from moniker.api import service as api_service
|
||||
from moniker.central import service as central_service
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
cfg.CONF.import_opt('storage_driver', 'moniker.central',
|
||||
group='service:central')
|
||||
cfg.CONF.import_opt('backend_driver', 'moniker.agent',
|
||||
group='service:agent')
|
||||
cfg.CONF.import_opt('auth_strategy', 'moniker.api',
|
||||
group='service:api')
|
||||
cfg.CONF.import_opt('database_connection', 'moniker.storage.impl_sqlalchemy',
|
||||
group='storage:sqlalchemy')
|
||||
|
||||
@ -98,18 +105,33 @@ class TestCase(unittest2.TestCase, AssertMixin):
|
||||
super(TestCase, self).setUp()
|
||||
|
||||
self.mox = mox.Mox()
|
||||
|
||||
self.config(
|
||||
notification_driver=[],
|
||||
rpc_backend='moniker.openstack.common.rpc.impl_fake',
|
||||
)
|
||||
|
||||
self.config(
|
||||
storage_driver='sqlalchemy',
|
||||
backend_driver='fake',
|
||||
notification_driver=[],
|
||||
rpc_backend='moniker.openstack.common.rpc.impl_fake',
|
||||
auth_strategy='noauth'
|
||||
group='service:central'
|
||||
)
|
||||
|
||||
self.config(
|
||||
backend_driver='fake',
|
||||
group='service:agent'
|
||||
)
|
||||
|
||||
self.config(
|
||||
auth_strategy='noauth',
|
||||
group='service:api'
|
||||
)
|
||||
|
||||
self.config(
|
||||
database_connection='sqlite://',
|
||||
group='storage:sqlalchemy'
|
||||
)
|
||||
|
||||
storage.setup_schema()
|
||||
|
||||
self.admin_context = self.get_admin_context()
|
||||
@ -126,6 +148,9 @@ class TestCase(unittest2.TestCase, AssertMixin):
|
||||
for k, v in kwargs.iteritems():
|
||||
cfg.CONF.set_override(k, v, group)
|
||||
|
||||
def get_agent_service(self):
|
||||
return agent_service.Service()
|
||||
|
||||
def get_api_service(self):
|
||||
return api_service.Service()
|
||||
|
||||
|
20
moniker/tests/test_agent/__init__.py
Normal file
20
moniker/tests/test_agent/__init__.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P. All Rights Reserved.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@hp.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from moniker.tests import TestCase
|
||||
|
||||
|
||||
class AgentTestCase(TestCase):
|
||||
__test__ = False
|
29
moniker/tests/test_agent/test_service.py
Normal file
29
moniker/tests/test_agent/test_service.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P. All Rights Reserved.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@hp.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from moniker.tests.test_agent import AgentTestCase
|
||||
|
||||
|
||||
class AgentServiceTest(AgentTestCase):
|
||||
__test__ = True
|
||||
|
||||
def setUp(self):
|
||||
super(AgentServiceTest, self).setUp()
|
||||
self.service = self.get_agent_service()
|
||||
|
||||
def test_start_and_stop(self):
|
||||
# Ensures the start/stop actions don't raise
|
||||
self.service.start()
|
||||
self.service.stop()
|
@ -25,7 +25,7 @@ class BackendTestCase(TestCase):
|
||||
__test__ = False
|
||||
|
||||
def get_backend_driver(self):
|
||||
return backend.get_backend(cfg.CONF.backend_driver)
|
||||
return backend.get_backend(cfg.CONF['service:agent'].backend_driver)
|
||||
|
||||
def test_constructor(self):
|
||||
self.get_backend_driver()
|
||||
|
@ -25,4 +25,4 @@ class Bind9BackendDriverTestCase(BackendTestCase):
|
||||
def setUp(self):
|
||||
super(Bind9BackendDriverTestCase, self).setUp()
|
||||
|
||||
self.config(backend_driver='bind9')
|
||||
self.config(backend_driver='bind9', group='service:agent')
|
||||
|
@ -25,4 +25,4 @@ class FakeBackendDriverTestCase(BackendTestCase):
|
||||
def setUp(self):
|
||||
super(FakeBackendDriverTestCase, self).setUp()
|
||||
|
||||
self.config(backend_driver='fake')
|
||||
self.config(backend_driver='fake', group='service:agent')
|
||||
|
@ -25,4 +25,4 @@ class MySQLBind9BackendDriverTestCase(BackendTestCase):
|
||||
def setUp(self):
|
||||
super(MySQLBind9BackendDriverTestCase, self).setUp()
|
||||
|
||||
self.config(backend_driver='mysqlbind9')
|
||||
self.config(backend_driver='mysqlbind9', group='service:agent')
|
||||
|
@ -28,6 +28,11 @@ class CentralServiceTest(CentralTestCase):
|
||||
super(CentralServiceTest, self).setUp()
|
||||
self.central_service = self.get_central_service()
|
||||
|
||||
def test_start_and_stop(self):
|
||||
# Ensures the start/stop actions don't raise
|
||||
self.central_service.start()
|
||||
self.central_service.stop()
|
||||
|
||||
# Server Tests
|
||||
def test_create_server(self):
|
||||
context = self.get_admin_context()
|
||||
|
Loading…
Reference in New Issue
Block a user