Use Python 3.12 for python3-django job

With migration from ubuntu jammy to noble, python3.11 is not available
anymore. This makes the job to fail on pre-install step.

So let's use Python 3.12 which is available out of the box on Noble
after switch.

This also bumps pylint version, as older one does not work
anymore with Python 3.12. New pylint brings quite some new
rules with it. Some were disabled, some were fixed within this
patch.

Change-Id: I4ba288966c582910e8a822d4531e29c9c005e48f
This commit is contained in:
Dmitriy Rabotyagov 2024-12-01 16:08:51 +01:00
parent 1b15f51bab
commit 2ec0177edc
41 changed files with 117 additions and 96 deletions

View File

@ -15,12 +15,14 @@ disable=
not-callable, not-callable,
# "W" Warnings for stylistic problems or minor programming issues # "W" Warnings for stylistic problems or minor programming issues
arguments-differ, arguments-differ,
arguments-renamed,
attribute-defined-outside-init, attribute-defined-outside-init,
bad-indentation, bad-indentation,
broad-except, broad-except,
fixme, fixme,
# python3 way: pylint suggests to follow PEP 3102 # python3 way: pylint suggests to follow PEP 3102
keyword-arg-before-vararg, # TODO keyword-arg-before-vararg, # TODO
missing-timeout,
pointless-string-statement, pointless-string-statement,
protected-access, protected-access,
# We should do it carefully considering PEP3134 # We should do it carefully considering PEP3134
@ -37,9 +39,8 @@ disable=
# "C" Coding convention violations # "C" Coding convention violations
abstract-method, abstract-method,
anomalous-backslash-in-string, anomalous-backslash-in-string,
bad-builtin, consider-using-dict-items,
bad-continuation, consider-using-f-string,
deprecated-lambda,
global-statement, global-statement,
# Not a good idea to disable this globally # Not a good idea to disable this globally
# Check one by one and add pylint disabled comment if needed # Check one by one and add pylint disabled comment if needed
@ -52,19 +53,22 @@ disable=
# import order is checked by flake8 (and pylint rule is incompatible with it) # import order is checked by flake8 (and pylint rule is incompatible with it)
wrong-import-order, wrong-import-order,
# "R" Refactor recommendations # "R" Refactor recommendations
consider-using-generator,
duplicate-code, duplicate-code,
inconsistent-return-statements, # TODO inconsistent-return-statements, # TODO
interface-not-implemented,
no-self-use,
too-many-ancestors, too-many-ancestors,
too-many-arguments, too-many-arguments,
too-many-branches, too-many-branches,
too-many-function-args, too-many-function-args,
too-many-instance-attributes, too-many-instance-attributes,
too-many-locals, too-many-locals,
too-many-positional-arguments,
too-many-return-statements, too-many-return-statements,
too-many-statements, too-many-statements,
useless-object-inheritance use-a-generator,
use-dict-literal,
use-yield-from,
useless-object-inheritance,
[Basic] [Basic]
# Variable names can be 1 to 31 characters long, with lowercase and underscores # Variable names can be 1 to 31 characters long, with lowercase and underscores

View File

@ -17,10 +17,10 @@
pre-run: playbooks/horizon-tox-django/pre.yaml pre-run: playbooks/horizon-tox-django/pre.yaml
run: playbooks/horizon-tox-django/run.yaml run: playbooks/horizon-tox-django/run.yaml
vars: vars:
tox_envlist: py311 tox_envlist: py312
# The following should match the base openstack-tox-pyNN job. # The following should match the base openstack-tox-pyNN job.
bindep_profile: test py311 bindep_profile: test py312
python_version: "3.11" python_version: "3.12"
required-projects: required-projects:
- name: openstack/horizon - name: openstack/horizon
@ -42,7 +42,7 @@
# sentinel to make this project template valid even when we support # sentinel to make this project template valid even when we support
# only one version of Django used as the default. Zuul project # only one version of Django used as the default. Zuul project
# template configuration requires at least one job included. # template configuration requires at least one job included.
- openstack-tox-py311 - openstack-tox-py312
# Let's keep at least one job as a template even when we support # Let's keep at least one job as a template even when we support
# only one version of Django covered by the default job. # only one version of Django covered by the default job.
# Just comment it out for such case. # Just comment it out for such case.
@ -50,5 +50,5 @@
gate: gate:
jobs: jobs:
# Default python job in openstack-python3-antelope-jobs(-horizon) # Default python job in openstack-python3-antelope-jobs(-horizon)
- openstack-tox-py311 - openstack-tox-py312
- horizon-tox-python3-django42 - horizon-tox-python3-django42

View File

@ -595,6 +595,7 @@ class Dashboard(Registry, HorizonComponent):
panel_group = PanelGroup(self, panels=panel_set) panel_group = PanelGroup(self, panels=panel_set)
# Put our results into their appropriate places # Put our results into their appropriate places
# pylint: disable-next=possibly-used-before-assignment
panels_to_discover.extend(panel_group.panels) panels_to_discover.extend(panel_group.panels)
panel_groups.append((panel_group.slug, panel_group)) panel_groups.append((panel_group.slug, panel_group))
if panel_group.slug == DEFAULT_PANEL_GROUP: if panel_group.slug == DEFAULT_PANEL_GROUP:
@ -615,6 +616,7 @@ class Dashboard(Registry, HorizonComponent):
before_import_registry = copy.copy(self._registry) before_import_registry = copy.copy(self._registry)
import_module('.%s.panel' % panel, package) import_module('.%s.panel' % panel, package)
except Exception: except Exception:
# pylint: disable-next=used-before-assignment
self._registry = before_import_registry self._registry = before_import_registry
if module_has_submodule(mod, panel): if module_has_submodule(mod, panel):
raise raise
@ -866,6 +868,7 @@ class Site(Registry, HorizonComponent):
before_import_registry = copy.copy(self._registry) before_import_registry = copy.copy(self._registry)
import_module('%s.%s' % (package, mod_name)) import_module('%s.%s' % (package, mod_name))
except Exception: except Exception:
# pylint: disable-next=used-before-assignment
self._registry = before_import_registry self._registry = before_import_registry
if module_has_submodule(mod, mod_name): if module_has_submodule(mod, mod_name):
raise raise
@ -897,6 +900,7 @@ class Site(Registry, HorizonComponent):
before_import_registry = copy.copy(self._registry) before_import_registry = copy.copy(self._registry)
import_module('%s.%s' % (app, mod_name)) import_module('%s.%s' % (app, mod_name))
except Exception: except Exception:
# pylint: disable-next=used-before-assignment
self._registry = before_import_registry self._registry = before_import_registry
if module_has_submodule(mod, mod_name): if module_has_submodule(mod, mod_name):
raise raise

View File

@ -628,14 +628,14 @@ class ExternalUploadMeta(forms.DeclarativeFieldsMetaclass):
""" """
@classmethod @classmethod
def __prepare__(cls, name, bases): def __prepare__(mcs, name, bases):
# Required in python 3 to keep the form fields order. # Required in python 3 to keep the form fields order.
# Without this method, the __new__(cls, name, bases, attrs) method # Without this method, the __new__(cls, name, bases, attrs) method
# receives a dict as attrs instead of OrderedDict. # receives a dict as attrs instead of OrderedDict.
# This method will be ignored by Python 2. # This method will be ignored by Python 2.
return collections.OrderedDict() return collections.OrderedDict()
def __new__(cls, name, bases, attrs): def __new__(mcs, name, bases, attrs):
def get_double_name(name): def get_double_name(name):
suffix = '__hidden' suffix = '__hidden'
slen = len(suffix) slen = len(suffix)
@ -660,4 +660,4 @@ class ExternalUploadMeta(forms.DeclarativeFieldsMetaclass):
new_attrs[new_attr_name] = hidden_field new_attrs[new_attr_name] = hidden_field
meth_name = 'clean_' + new_attr_name meth_name = 'clean_' + new_attr_name
new_attrs[meth_name] = make_clean_method(new_attr_name) new_attrs[meth_name] = make_clean_method(new_attr_name)
return super().__new__(cls, name, bases, new_attrs) return super().__new__(mcs, name, bases, new_attrs)

View File

@ -100,5 +100,5 @@ class Command(BaseCommand):
# Ensure to use UTF-8 encoding # Ensure to use UTF-8 encoding
new_po.encoding = 'utf-8' new_po.encoding = 'utf-8'
with open(pofile, 'w+') as f: with open(pofile, 'w+', encoding="utf-8") as f:
f.write(new_po.text) f.write(new_po.text)

View File

@ -69,6 +69,7 @@ class Command(TemplateCommand):
target = options.pop("target", None) target = options.pop("target", None)
if target == "auto": if target == "auto":
# pylint: disable-next=possibly-used-before-assignment
target = os.path.join(os.path.dirname(dashboard_mod.__file__), target = os.path.join(os.path.dirname(dashboard_mod.__file__),
panel_name) panel_name)
if not os.path.exists(target): if not os.path.exists(target):

View File

@ -110,7 +110,7 @@ def _is_path(path):
def _get_processed_messages(messages_path): def _get_processed_messages(messages_path):
msgs = list() msgs = list() # pylint: disable=use-list-literal
if not _is_path(messages_path): if not _is_path(messages_path):
LOG.error('%s is not a valid messages path.', messages_path) LOG.error('%s is not a valid messages path.', messages_path)

View File

@ -48,7 +48,7 @@ class BaseActionMetaClass(type):
parameters for the initializer of the object. The object is then parameters for the initializer of the object. The object is then
initialized clean way. Similar principle is used in DataTableMetaclass. initialized clean way. Similar principle is used in DataTableMetaclass.
""" """
def __new__(cls, name, bases, attrs): def __new__(mcs, name, bases, attrs):
# Options of action are set as class attributes, loading them. # Options of action are set as class attributes, loading them.
options = {} options = {}
if attrs: if attrs:
@ -74,7 +74,7 @@ class BaseActionMetaClass(type):
# instantiating of the specific Action. # instantiating of the specific Action.
attrs['base_options'] = options attrs['base_options'] = options
return type.__new__(cls, name, bases, attrs) return type.__new__(mcs, name, bases, attrs)
def __call__(cls, *args, **kwargs): def __call__(cls, *args, **kwargs):
cls.base_options.update(kwargs) cls.base_options.update(kwargs)

View File

@ -1175,7 +1175,7 @@ class DataTableOptions(object):
class DataTableMetaclass(type): class DataTableMetaclass(type):
"""Metaclass to add options to DataTable class and collect columns.""" """Metaclass to add options to DataTable class and collect columns."""
def __new__(cls, name, bases, attrs): def __new__(mcs, name, bases, attrs):
# Process options from Meta # Process options from Meta
class_name = name class_name = name
dt_attrs = {} dt_attrs = {}
@ -1247,7 +1247,7 @@ class DataTableMetaclass(type):
opts._filter_action = actions_dict[opts._filter_action.name] opts._filter_action = actions_dict[opts._filter_action.name]
# Create our new class! # Create our new class!
return type.__new__(cls, name, bases, dt_attrs) return type.__new__(mcs, name, bases, dt_attrs)
class DataTable(object, metaclass=DataTableMetaclass): class DataTable(object, metaclass=DataTableMetaclass):

View File

@ -48,7 +48,7 @@ def read_from_file(key_file='.secret_key'):
raise FilePermissionError( raise FilePermissionError(
"Insecure permissions on key file %s, should be 0600." % "Insecure permissions on key file %s, should be 0600." %
os.path.abspath(key_file)) os.path.abspath(key_file))
with open(key_file, 'r') as f: with open(key_file, 'r', encoding="utf-8") as f:
key = f.readline() key = f.readline()
return key return key
@ -76,7 +76,7 @@ def generate_or_read_from_file(key_file='.secret_key', key_length=64):
if not os.path.exists(key_file): if not os.path.exists(key_file):
key = generate_key(key_length) key = generate_key(key_length)
old_umask = os.umask(0o177) # Use '0600' file permissions old_umask = os.umask(0o177) # Use '0600' file permissions
with open(key_file, 'w') as f: with open(key_file, 'w', encoding="utf-8") as f:
f.write(key) f.write(key)
os.umask(old_umask) os.umask(old_umask)
else: else:

View File

@ -54,28 +54,30 @@ class WorkflowContext(dict):
return self.__setitem__(key, None) return self.__setitem__(key, None)
def set(self, key, val): def set(self, key, val):
# pylint: disable-next=unnecessary-dunder-call
return self.__setitem__(key, val) return self.__setitem__(key, val)
def unset(self, key): def unset(self, key):
# pylint: disable-next=unnecessary-dunder-call
return self.__delitem__(key) return self.__delitem__(key)
class ActionMetaclass(forms.forms.DeclarativeFieldsMetaclass): class ActionMetaclass(forms.forms.DeclarativeFieldsMetaclass):
def __new__(cls, name, bases, attrs): def __new__(mcs, name, bases, attrs):
# Pop Meta for later processing # Pop Meta for later processing
opts = attrs.pop("Meta", None) opts = attrs.pop("Meta", None)
# Create our new class # Create our new class
cls_ = super().__new__(cls, name, bases, attrs) mcs_ = super().__new__(mcs, name, bases, attrs)
# Process options from Meta # Process options from Meta
cls_.name = getattr(opts, "name", name) mcs_.name = getattr(opts, "name", name)
cls_.slug = getattr(opts, "slug", slugify(name)) mcs_.slug = getattr(opts, "slug", slugify(name))
cls_.permissions = getattr(opts, "permissions", ()) mcs_.permissions = getattr(opts, "permissions", ())
cls_.policy_rules = getattr(opts, "policy_rules", ()) mcs_.policy_rules = getattr(opts, "policy_rules", ())
cls_.progress_message = getattr(opts, "progress_message", mcs_.progress_message = getattr(opts, "progress_message",
_("Processing...")) _("Processing..."))
cls_.help_text = getattr(opts, "help_text", "") mcs_.help_text = getattr(opts, "help_text", "")
cls_.help_text_template = getattr(opts, "help_text_template", None) mcs_.help_text_template = getattr(opts, "help_text_template", None)
return cls_ return mcs_
class Action(forms.Form, metaclass=ActionMetaclass): class Action(forms.Form, metaclass=ActionMetaclass):
@ -478,10 +480,10 @@ class Step(object):
class WorkflowMetaclass(type): class WorkflowMetaclass(type):
def __new__(cls, name, bases, attrs): def __new__(mcs, name, bases, attrs):
super().__new__(cls, name, bases, attrs) super().__new__(mcs, name, bases, attrs)
attrs["_cls_registry"] = [] attrs["_cls_registry"] = []
return type.__new__(cls, name, bases, attrs) return type.__new__(mcs, name, bases, attrs)
class UpdateMembersStep(Step): class UpdateMembersStep(Step):

View File

@ -90,7 +90,7 @@ def _load_default_rules(service, enforcer):
return return
try: try:
with open(policy_file) as f: with open(policy_file, encoding="utf-8") as f:
policies = yaml.safe_load(f) policies = yaml.safe_load(f)
except IOError as e: except IOError as e:
LOG.error('Failed to open the policy file for %(service)s %(path)s: ' LOG.error('Failed to open the policy file for %(service)s %(path)s: '

View File

@ -280,6 +280,7 @@ class QuotaSet(collections.abc.Sequence):
return match.pop() if match else Quota(key, default) return match.pop() if match else Quota(key, default)
def add(self, other): def add(self, other):
# pylint: disable-next=unnecessary-dunder-call
return self.__add__(other) return self.__add__(other)

View File

@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json import json
import json.encoder as encoder from json import encoder
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _

View File

@ -76,8 +76,7 @@ def get_trace(trace_id):
# finishes before the dependent requests do so, to we need to # finishes before the dependent requests do so, to we need to
# normalize the duration of all requests by the finishing time of # normalize the duration of all requests by the finishing time of
# the one which took longest # the one which took longest
if child_finished > finished: finished = max(finished, child_finished)
finished = child_finished
return _data, finished return _data, finished
engine = _get_engine() engine = _get_engine()

View File

@ -137,9 +137,8 @@ class SubnetsTab(project_tabs_subnets_tab):
subnet_id = subnet_usage.get("subnet_id") subnet_id = subnet_usage.get("subnet_id")
subnet_used_ips = subnet_usage.get("used_ips") subnet_used_ips = subnet_usage.get("used_ips")
subnet_total_ips = subnet_usage.get("total_ips") subnet_total_ips = subnet_usage.get("total_ips")
subnet_free_ips = subnet_total_ips - subnet_used_ips subnet_free_ips = max(subnet_total_ips - subnet_used_ips, 0)
if subnet_free_ips < 0:
subnet_free_ips = 0
for item in subnets_dict: for item in subnets_dict:
id = item.get("id") id = item.get("id")
if id == subnet_id: if id == subnet_id:

View File

@ -12,7 +12,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import horizon.views as views from horizon import views
class IndexView(views.HorizonTemplateView): class IndexView(views.HorizonTemplateView):

View File

@ -42,6 +42,7 @@ class QosSpecMixin(object):
qos_spec_id = context['qos_spec_id'] qos_spec_id = context['qos_spec_id']
try: try:
# pylint: disable-next=possibly-used-before-assignment
qos_list = api.cinder.qos_spec_get(self.request, qos_spec_id) qos_list = api.cinder.qos_spec_get(self.request, qos_spec_id)
context['qos_spec_name'] = qos_list.name context['qos_spec_name'] = qos_list.name
except Exception: except Exception:

View File

@ -100,6 +100,7 @@ def download_ec2_bundle(request):
# Create our file bundle # Create our file bundle
template = 'project/api_access/ec2rc.sh.template' template = 'project/api_access/ec2rc.sh.template'
try: try:
# pylint: disable-next=consider-using-with
temp_zip = tempfile.NamedTemporaryFile(delete=True) temp_zip = tempfile.NamedTemporaryFile(delete=True)
with closing(zipfile.ZipFile(temp_zip.name, mode='w')) as archive: with closing(zipfile.ZipFile(temp_zip.name, mode='w')) as archive:
archive.writestr('ec2rc.sh', render_to_string(template, context)) archive.writestr('ec2rc.sh', render_to_string(template, context))

View File

@ -95,16 +95,16 @@ class DeleteRule(tables.DeleteAction):
@staticmethod @staticmethod
def action_present(count): def action_present(count):
return ngettext_lazy( return ngettext_lazy(
u"Delete Rule", "Delete Rule",
u"Delete Rules", "Delete Rules",
count count
) )
@staticmethod @staticmethod
def action_past(count): def action_past(count):
return ngettext_lazy( return ngettext_lazy(
u"Deleted Rule", "Deleted Rule",
u"Deleted Rules", "Deleted Rules",
count count
) )

View File

@ -121,8 +121,8 @@ class ReleaseIPsPortForwarding(ReleaseIPs):
@staticmethod @staticmethod
def action_past(count): def action_past(count):
return ngettext_lazy( return ngettext_lazy(
u"Successfully redirected", "Successfully redirected",
u"Successfully redirected", "Successfully redirected",
count count
) )

View File

@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# pylint: disable=no-name-in-module,import-error # pylint: disable=no-name-in-module,import-error,deprecated-module
from distutils.command import install from distutils.command import install

View File

@ -52,7 +52,9 @@ def _format_default_policy(default):
def _write_yaml_file(policies, output_file): def _write_yaml_file(policies, output_file):
stream = open(output_file, 'w') if output_file else sys.stdout # pylint: disable-next=consider-using-with
stream = open(output_file, 'w',
encoding="utf-8") if output_file else sys.stdout
yaml.dump(policies, stream=stream) yaml.dump(policies, stream=stream)
if output_file: if output_file:
stream.close() stream.close()

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# pylint: disable=import-error # pylint: disable=import-error,deprecated-module
from distutils.dist import Distribution from distutils.dist import Distribution
import os import os
from subprocess import call from subprocess import call

View File

@ -26,7 +26,7 @@ from django import template
# rendering it unreadable. # rendering it unreadable.
warnings.simplefilter('ignore') warnings.simplefilter('ignore')
cmd_name = __name__.split('.')[-1] cmd_name = __name__.rsplit('.', maxsplit=1)[-1]
CURDIR = os.path.realpath(os.path.dirname(__file__)) CURDIR = os.path.realpath(os.path.dirname(__file__))
PROJECT_PATH = os.path.realpath(os.path.join(CURDIR, '../..')) PROJECT_PATH = os.path.realpath(os.path.join(CURDIR, '../..'))
@ -301,11 +301,12 @@ location you desire, e.g.::
# Generate the WSGI. # Generate the WSGI.
if options.get('wsgi'): if options.get('wsgi'):
with open( with open(
os.path.join(CURDIR, 'horizon.wsgi.template'), 'r' os.path.join(CURDIR, 'horizon.wsgi.template'), 'r',
encoding="utf-8"
) as fp: ) as fp:
wsgi_template = template.Template(fp.read()) wsgi_template = template.Template(fp.read())
if not os.path.exists(context['WSGI_FILE']) or force: if not os.path.exists(context['WSGI_FILE']) or force:
with open(context['WSGI_FILE'], 'w') as fp: with open(context['WSGI_FILE'], 'w', encoding="utf-8") as fp:
fp.write(wsgi_template.render(context)) fp.write(wsgi_template.render(context))
print('Generated "%s"' % context['WSGI_FILE']) print('Generated "%s"' % context['WSGI_FILE'])
else: else:
@ -319,7 +320,8 @@ location you desire, e.g.::
context['WSGI_FILE'] = context['DEFAULT_WSGI_FILE'] context['WSGI_FILE'] = context['DEFAULT_WSGI_FILE']
with open( with open(
os.path.join(CURDIR, 'apache_vhost.conf.template'), 'r' os.path.join(CURDIR, 'apache_vhost.conf.template'), 'r',
encoding="utf-8"
) as fp: ) as fp:
wsgi_template = template.Template(fp.read()) wsgi_template = template.Template(fp.read())
sys.stdout.write(wsgi_template.render(context)) sys.stdout.write(wsgi_template.render(context))

View File

@ -114,10 +114,13 @@ class Command(BaseCommand):
""" """
with DirContext(self.local_settings_dir) as dircontext: with DirContext(self.local_settings_dir) as dircontext:
if not os.path.exists(self.local_settings_diff) or force: if not os.path.exists(self.local_settings_diff,
with open(self.local_settings_example, 'r') as fp: encoding="utf-8") or force:
with open(self.local_settings_example, 'r',
encoding="utf-8") as fp:
example_lines = fp.readlines() example_lines = fp.readlines()
with open(self.local_settings_file, 'r') as fp: with open(self.local_settings_file, 'r',
encoding="utf-8") as fp:
local_settings_lines = fp.readlines() local_settings_lines = fp.readlines()
local_settings_example_mtime = time.strftime( local_settings_example_mtime = time.strftime(
self.time_fmt, self.time_fmt,
@ -134,7 +137,8 @@ class Command(BaseCommand):
dircontext.curdir, dircontext.curdir,
self.local_settings_diff) self.local_settings_diff)
) )
with open(self.local_settings_diff, 'w') as fp: with open(self.local_settings_diff, 'w',
encoding="utf-8") as fp:
for line in difflib.unified_diff( for line in difflib.unified_diff(
example_lines, local_settings_lines, example_lines, local_settings_lines,
fromfile=self.local_settings_example, fromfile=self.local_settings_example,

View File

@ -18,8 +18,8 @@
import os import os
from subprocess import call from subprocess import call
import babel.messages.catalog as catalog from babel.messages import catalog
import babel.messages.pofile as babel_pofile from babel.messages import pofile as babel_pofile
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils import translation from django.utils import translation
@ -110,7 +110,7 @@ class Command(BaseCommand):
continue continue
# Pseudo translation logic # Pseudo translation logic
with open(potfile, 'r') as f: with open(potfile, 'r', encoding="utf-8") as f:
pot_cat = babel_pofile.read_po(f, ignore_obsolete=True) pot_cat = babel_pofile.read_po(f, ignore_obsolete=True)
new_cat = catalog.Catalog(locale=locale, new_cat = catalog.Catalog(locale=locale,

View File

@ -272,7 +272,9 @@ if os.path.exists(LOCAL_SETTINGS_DIR_PATH):
for filename in sorted(filenames): for filename in sorted(filenames):
if filename.endswith(".py"): if filename.endswith(".py"):
try: try:
with open(os.path.join(dirpath, filename)) as f: with open(
os.path.join(dirpath, filename), encoding="utf-8"
) as f:
# pylint: disable=exec-used # pylint: disable=exec-used
exec(f.read()) exec(f.read())
except Exception: except Exception:

View File

@ -127,7 +127,7 @@ def record_video(request, report_dir, xdisplay):
'-video_size', f'{width}x{height}', '-video_size', f'{width}x{height}',
'-framerate', str(frame_rate), '-framerate', str(frame_rate),
'-f', 'x11grab', '-f', 'x11grab',
'-i', f':{display}', '-i', f':{display}', # noqa: E231
filepath, filepath,
] ]
fnull = open(os.devnull, 'w') fnull = open(os.devnull, 'w')

View File

@ -224,7 +224,7 @@ def new_instance_demo(complete_default_test_network, request, instance_name,
yield instance yield instance
if count > 1: if count > 1:
for instance in range(0, count): for instance in range(0, count):
openstack_demo.delete_server(f"{instance_name}-{instance+1}") openstack_demo.delete_server(f"{instance_name}-{instance + 1}")
else: else:
openstack_demo.delete_server(instance_name) openstack_demo.delete_server(instance_name)

View File

@ -438,7 +438,7 @@ def test_edit_image_description_admin(login, driver, image_names,
".//button[@class='btn btn-primary finish']").click() ".//button[@class='btn btn-primary finish']").click()
messages = widgets.get_and_dismiss_messages(driver) messages = widgets.get_and_dismiss_messages(driver)
assert f"Success: Image {image_name} " \ assert f"Success: Image {image_name} " \
f"was successfully updated." in messages f"was successfully updated." in messages
image_id = new_image_admin.id image_id = new_image_admin.id
assert (openstack_admin.compute.get(f"/images/{image_id}").json( assert (openstack_admin.compute.get(f"/images/{image_id}").json(
)['image']['metadata']['description'] == new_description) )['image']['metadata']['description'] == new_description)
@ -471,7 +471,7 @@ def test_update_image_metadata_admin(login, driver,
image_form.find_element_by_css_selector( image_form.find_element_by_css_selector(
"button.btn span[class='fa fa-plus']").click() "button.btn span[class='fa fa-plus']").click()
image_form.find_element_by_xpath( image_form.find_element_by_xpath(
f"//span[@title='{name}']/parent::div/input").send_keys(value) f"//span[@title='{name}']/parent::div/input").send_keys(value) # noqa: E231,E501
image_form.find_element_by_xpath( image_form.find_element_by_xpath(
"//button[@ng-click='modal.save()']").click() "//button[@ng-click='modal.save()']").click()
messages = widgets.get_and_dismiss_messages(driver) messages = widgets.get_and_dismiss_messages(driver)
@ -548,7 +548,7 @@ def test_create_volume_from_image_admin(login, driver, volume_name,
name_field.send_keys(volume_name) name_field.send_keys(volume_name)
create_vol_btn = WebDriverWait(driver, config.selenium.page_timeout).until( create_vol_btn = WebDriverWait(driver, config.selenium.page_timeout).until(
EC.element_to_be_clickable((By.XPATH, f"//button[@class='btn " EC.element_to_be_clickable((By.XPATH, f"//button[@class='btn "
f"btn-primary finish']"))) f"btn-primary finish']")))
create_vol_btn.click() create_vol_btn.click()
messages = widgets.get_and_dismiss_messages(driver) messages = widgets.get_and_dismiss_messages(driver)
assert f"Info: Creating volume {volume_name}" in messages assert f"Info: Creating volume {volume_name}" in messages

View File

@ -42,7 +42,7 @@ def new_instance_admin(complete_default_test_network, request, instance_name,
yield instance yield instance
if count > 1: if count > 1:
for instance in range(0, count): for instance in range(0, count):
openstack_admin.delete_server(f"{instance_name}-{instance+1}") openstack_admin.delete_server(f"{instance_name}-{instance + 1}")
else: else:
openstack_admin.delete_server(instance_name) openstack_admin.delete_server(instance_name)

View File

@ -77,7 +77,7 @@ def ensure_checkbox(required_state, element):
current_state = element.is_selected() current_state = element.is_selected()
if required_state != current_state: if required_state != current_state:
element.find_element_by_xpath( element.find_element_by_xpath(
f".//following-sibling::label").click() f".//following-sibling::label").click() # noqa: E231
def test_create_network_without_subnet_demo(login, openstack_demo, driver, def test_create_network_without_subnet_demo(login, openstack_demo, driver,

View File

@ -120,8 +120,8 @@ def test_add_member_to_project(login, driver, project_name, openstack_admin,
rows[0].find_element_by_css_selector(".data-table-action").click() rows[0].find_element_by_css_selector(".data-table-action").click()
project_form = driver.find_element_by_css_selector("form .modal-content") project_form = driver.find_element_by_css_selector("form .modal-content")
project_form.find_element_by_xpath( project_form.find_element_by_xpath(
f".//*[text()='{admin_name}']//ancestor::li" f".//*[text()='{admin_name}']//ancestor::li" # noqa: E231
f"/following-sibling::li/a[@href='#add_remove']").click() f"/following-sibling::li/a[@href='#add_remove']").click() # noqa: E231
project_form.find_element_by_css_selector( project_form.find_element_by_css_selector(
".btn-primary[value='Save']").click() ".btn-primary[value='Save']").click()
messages = widgets.get_and_dismiss_messages(driver) messages = widgets.get_and_dismiss_messages(driver)
@ -151,8 +151,8 @@ def test_add_role_to_project_member(login, driver, openstack_admin, config,
rows[0].find_element_by_css_selector(".data-table-action").click() rows[0].find_element_by_css_selector(".data-table-action").click()
project_form = driver.find_element_by_css_selector("form .modal-content") project_form = driver.find_element_by_css_selector("form .modal-content")
select_roles_dropdown = project_form.find_element_by_xpath( select_roles_dropdown = project_form.find_element_by_xpath(
f".//*[text()='{admin_name}']//ancestor::li" f".//*[text()='{admin_name}']//ancestor::li" # noqa: E231
f"/following-sibling::li[@class='dropdown role_options']") f"/following-sibling::li[@class='dropdown role_options']") # noqa: E231
widgets.select_from_dropdown(select_roles_dropdown, admin_role_name) widgets.select_from_dropdown(select_roles_dropdown, admin_role_name)
project_form.find_element_by_css_selector( project_form.find_element_by_css_selector(
".btn-primary[value='Save']").click() ".btn-primary[value='Save']").click()
@ -188,8 +188,8 @@ def test_add_group_to_project(login, driver, openstack_admin,
widgets.select_from_dropdown(actions_column, "Modify Groups") widgets.select_from_dropdown(actions_column, "Modify Groups")
project_form = driver.find_element_by_css_selector("form .modal-content") project_form = driver.find_element_by_css_selector("form .modal-content")
project_form.find_element_by_xpath( project_form.find_element_by_xpath(
f".//*[text()='{group_name}']//ancestor::li" f".//*[text()='{group_name}']//ancestor::li" # noqa: E231
f"/following-sibling::li/a[@href='#add_remove']").click() f"/following-sibling::li/a[@href='#add_remove']").click() # noqa: E231
project_form.find_element_by_css_selector( project_form.find_element_by_css_selector(
".btn-primary[value='Save']").click() ".btn-primary[value='Save']").click()
messages = widgets.get_and_dismiss_messages(driver) messages = widgets.get_and_dismiss_messages(driver)
@ -220,8 +220,8 @@ def test_add_role_to_project_group(login, driver, openstack_admin, config,
widgets.select_from_dropdown(actions_column, "Modify Groups") widgets.select_from_dropdown(actions_column, "Modify Groups")
project_form = driver.find_element_by_css_selector("form .modal-content") project_form = driver.find_element_by_css_selector("form .modal-content")
select_roles_dropdown = project_form.find_element_by_xpath( select_roles_dropdown = project_form.find_element_by_xpath(
f".//*[text()='{group_name}']//ancestor::li" f".//*[text()='{group_name}']//ancestor::li" # noqa: E231
f"/following-sibling::li[@class='dropdown role_options']") f"/following-sibling::li[@class='dropdown role_options']") # noqa: E231
widgets.select_from_dropdown(select_roles_dropdown, admin_role_name) widgets.select_from_dropdown(select_roles_dropdown, admin_role_name)
project_form.find_element_by_css_selector( project_form.find_element_by_css_selector(
".btn-primary[value='Save']").click() ".btn-primary[value='Save']").click()

View File

@ -157,7 +157,7 @@ def test_router_add_interface_demo(login, driver, router_name, openstack_demo,
".modal-dialog form") ".modal-dialog form")
widgets.select_from_dropdown( widgets.select_from_dropdown(
add_interface_form, f"{new_network_demo.name}: {new_subnet_demo.cidr} " add_interface_form, f"{new_network_demo.name}: {new_subnet_demo.cidr} "
f"({new_subnet_demo.name})") f"({new_subnet_demo.name})")
add_interface_form.find_element_by_id( add_interface_form.find_element_by_id(
"id_ip_address").send_keys(fixed_ip_test) "id_ip_address").send_keys(fixed_ip_test)
add_interface_form.find_element_by_css_selector(".btn-primary").click() add_interface_form.find_element_by_css_selector(".btn-primary").click()

View File

@ -145,7 +145,7 @@ def test_browse_left_panel(live_server, driver, user, dashboard_data,
WebDriverWait(driver, config.selenium.implicit_wait).until( WebDriverWait(driver, config.selenium.implicit_wait).until(
EC.element_to_be_clickable( EC.element_to_be_clickable(
(By.CSS_SELECTOR, f"a[data-target='#sidebar-accordion" (By.CSS_SELECTOR, f"a[data-target='#sidebar-accordion"
f"-{main_panel}-{sec_panel}']"))).click() f"-{main_panel}-{sec_panel}']"))).click()
sidebar = driver.find_element_by_id( sidebar = driver.find_element_by_id(
f"sidebar-accordion-{main_panel}-{sec_panel}") f"sidebar-accordion-{main_panel}-{sec_panel}")
else: else:

View File

@ -53,24 +53,24 @@ def test_vcpu_pcpu_data_display(live_server, driver, user, dashboard_data):
driver.get(live_server.url + '/admin/hypervisors') driver.get(live_server.url + '/admin/hypervisors')
assert (driver.find_element_by_xpath( assert (driver.find_element_by_xpath(
f"//*[normalize-space()='VCPU Usage']/" f"//*[normalize-space()='VCPU Usage']/"
f"ancestor::div[contains(@class,'d3_quota_bar')]" f"ancestor::div[contains(@class,'d3_quota_bar')]" # noqa: E231
f"/div[contains(@class,'h6')]/" f"/div[contains(@class,'h6')]/" # noqa: E231
f"span[1]").text == str(p['vcpus_used'])) f"span[1]").text == str(p['vcpus_used']))
assert (driver.find_element_by_xpath( assert (driver.find_element_by_xpath(
f"//*[normalize-space()='VCPU Usage']/" f"//*[normalize-space()='VCPU Usage']/"
f"ancestor::div[contains(@class,'d3_quota_bar')]" f"ancestor::div[contains(@class,'d3_quota_bar')]" # noqa: E231
f"/div[contains(@class,'h6')]/" f"/div[contains(@class,'h6')]/" # noqa: E231
f"span[2]").text == str(p['vcpus_capacity'])) f"span[2]").text == str(p['vcpus_capacity']))
assert (driver.find_element_by_xpath( assert (driver.find_element_by_xpath(
f"//*[normalize-space()='PCPU Usage']/" f"//*[normalize-space()='PCPU Usage']/"
f"ancestor::div[contains(@class,'d3_quota_bar')]" f"ancestor::div[contains(@class,'d3_quota_bar')]" # noqa: E231
f"/div[contains(@class,'h6')]/" f"/div[contains(@class,'h6')]/" # noqa: E231
f"span[1]").text == str(p['pcpus_used'])) f"span[1]").text == str(p['pcpus_used']))
assert (driver.find_element_by_xpath( assert (driver.find_element_by_xpath(
f"//*[normalize-space()='PCPU Usage']/" f"//*[normalize-space()='PCPU Usage']/"
f"ancestor::div[contains(@class,'d3_quota_bar')]" f"ancestor::div[contains(@class,'d3_quota_bar')]" # noqa: E231
f"/div[contains(@class,'h6')]/" f"/div[contains(@class,'h6')]/" # noqa: E231
f"span[2]").text == str(p['pcpus_capacity'])) f"span[2]").text == str(p['pcpus_capacity']))
driver.find_element_by_link_text("Resource Provider").click() driver.find_element_by_link_text("Resource Provider").click()

View File

@ -55,7 +55,7 @@ def select_from_dropdown(element, label):
def select_from_specific_dropdown_in_form(driver, dropdown_id, label): def select_from_specific_dropdown_in_form(driver, dropdown_id, label):
dropdown = driver.find_element_by_xpath( dropdown = driver.find_element_by_xpath(
f".//*[@for='{dropdown_id}']/following-sibling::div") f".//*[@for='{dropdown_id}']/following-sibling::div") # noqa: E231
dropdown.click() dropdown.click()
dropdown_options = dropdown.find_element_by_css_selector( dropdown_options = dropdown.find_element_by_css_selector(
"ul.dropdown-menu") "ul.dropdown-menu")
@ -128,12 +128,12 @@ def select_from_transfer_table(element, label):
try: try:
element.find_element_by_xpath( element.find_element_by_xpath(
f".//*[text()='{label}']//ancestor::tr/td//*" f".//*[text()='{label}']//ancestor::tr/td//*" # noqa: E231
f"[@class='btn btn-default fa fa-arrow-up']").click() f"[@class='btn btn-default fa fa-arrow-up']").click()
except exceptions.NoSuchElementException: except exceptions.NoSuchElementException:
try: try:
element.find_element_by_xpath( element.find_element_by_xpath(
f".//*[text()='{label}']//ancestor::tr/td//*" f".//*[text()='{label}']//ancestor::tr/td//*" # noqa: E231
f"[@class='btn btn-default fa fa-arrow-down']") f"[@class='btn btn-default fa fa-arrow-down']")
except exceptions.NoSuchElementException: except exceptions.NoSuchElementException:
raise raise

View File

@ -163,9 +163,7 @@ class QuotaUsage(dict):
def update_available(self, name): def update_available(self, name):
"""Updates the "available" metric for the given quota.""" """Updates the "available" metric for the given quota."""
quota = self.usages.get(name, {}).get('quota', float('inf')) quota = self.usages.get(name, {}).get('quota', float('inf'))
available = quota - self.usages[name]['used'] available = max(quota - self.usages[name]['used'], 0)
if available < 0:
available = 0
self.usages[name]['available'] = available self.usages[name]['available'] = available

View File

@ -312,6 +312,7 @@ def get_xstatic_dirs(XSTATIC_MODULES, HORIZON_CONFIG):
file = 'horizon/lib/' + module.NAME + '/' + file file = 'horizon/lib/' + module.NAME + '/' + file
HORIZON_CONFIG['xstatic_lib_files'].append(file) HORIZON_CONFIG['xstatic_lib_files'].append(file)
except TypeError: except TypeError:
# pylint: disable-next=broad-exception-raised
raise Exception( raise Exception(
'%s: Nothing to include because files to include are not ' '%s: Nothing to include because files to include are not '
'defined (i.e., None) in BASE_XSTATIC_MODULES list and ' 'defined (i.e., None) in BASE_XSTATIC_MODULES list and '

View File

@ -37,7 +37,7 @@ commands = {posargs}
deps = deps =
{[testenv]deps} {[testenv]deps}
flake8-import-order==0.12 # LGPLv3 flake8-import-order==0.12 # LGPLv3
pylint==2.6.0 # GPLv2 pylint==3.3.1 # GPLv2
bandit[baseline]>=1.7.7 # Apache-2.0 bandit[baseline]>=1.7.7 # Apache-2.0
setenv = setenv =
DJANGO_SETTINGS_MODULE=openstack_dashboard.test.settings DJANGO_SETTINGS_MODULE=openstack_dashboard.test.settings