Fix H405 (multi line docstring) warnings (horizon)
H405: multi line docstring summary not separated with an empty line Partial-Bug: #1696996 Change-Id: I58f71206def1a25f3eda04b9297f1aa7d3caa646
This commit is contained in:
parent
8ecb9c141b
commit
95629a337e
@ -471,14 +471,13 @@ class Dashboard(Registry, HorizonComponent):
|
|||||||
self._panel_groups = None
|
self._panel_groups = None
|
||||||
|
|
||||||
def get_panel(self, panel):
|
def get_panel(self, panel):
|
||||||
"""Returns the specified :class:`~horizon.Panel` instance registered
|
"""Returns the Panel instance registered with this dashboard."""
|
||||||
with this dashboard.
|
|
||||||
"""
|
|
||||||
return self._registered(panel)
|
return self._registered(panel)
|
||||||
|
|
||||||
def get_panels(self):
|
def get_panels(self):
|
||||||
"""Returns the :class:`~horizon.Panel` instances registered with this
|
"""Returns the Panel instances registered with this dashboard in order.
|
||||||
dashboard in order, without any panel groupings.
|
|
||||||
|
Panel grouping information is not included.
|
||||||
"""
|
"""
|
||||||
all_panels = []
|
all_panels = []
|
||||||
panel_groups = self.get_panel_groups()
|
panel_groups = self.get_panel_groups()
|
||||||
@ -487,8 +486,9 @@ class Dashboard(Registry, HorizonComponent):
|
|||||||
return all_panels
|
return all_panels
|
||||||
|
|
||||||
def get_panel_group(self, slug):
|
def get_panel_group(self, slug):
|
||||||
"""Returns the specified :class:~horizon.PanelGroup
|
"""Returns the specified :class:~horizon.PanelGroup.
|
||||||
or None if not registered
|
|
||||||
|
Returns None if not registered.
|
||||||
"""
|
"""
|
||||||
return self._panel_groups.get(slug)
|
return self._panel_groups.get(slug)
|
||||||
|
|
||||||
@ -1006,8 +1006,10 @@ class Site(Registry, HorizonComponent):
|
|||||||
|
|
||||||
|
|
||||||
class HorizonSite(Site):
|
class HorizonSite(Site):
|
||||||
"""A singleton implementation of Site such that all dealings with horizon
|
"""A singleton implementation of Site.
|
||||||
get the same instance no matter what. There can be only one.
|
|
||||||
|
All dealings with horizon get the same instance no matter what.
|
||||||
|
There can be only one.
|
||||||
"""
|
"""
|
||||||
_instance = None
|
_instance = None
|
||||||
|
|
||||||
|
@ -119,8 +119,11 @@ class ResourceBrowser(html.HTMLElement):
|
|||||||
% (attr_name, self.__class__.__name__))
|
% (attr_name, self.__class__.__name__))
|
||||||
|
|
||||||
def set_tables(self, tables):
|
def set_tables(self, tables):
|
||||||
"""Sets the table instances on the browser from a dictionary mapping
|
"""Sets the table instances on the browser.
|
||||||
table names to table instances (as constructed by MultiTableView).
|
|
||||||
|
``tables`` argument specifies tables to be set.
|
||||||
|
It is a dictionary mapping table names to table instances
|
||||||
|
(as constructed by MultiTableView).
|
||||||
"""
|
"""
|
||||||
self.navigation_table = tables[self.navigation_table_class._meta.name]
|
self.navigation_table = tables[self.navigation_table_class._meta.name]
|
||||||
self.content_table = tables[self.content_table_class._meta.name]
|
self.content_table = tables[self.content_table_class._meta.name]
|
||||||
|
@ -19,8 +19,7 @@ from django.contrib.staticfiles.finders import AppDirectoriesFinder
|
|||||||
|
|
||||||
|
|
||||||
class HorizonStaticFinder(AppDirectoriesFinder):
|
class HorizonStaticFinder(AppDirectoriesFinder):
|
||||||
"""A static files finder that also looks into the directory of each panel.
|
"""Static files finder that also looks into the directory of each panel."""
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app_names=None, *args, **kwargs):
|
def __init__(self, app_names=None, *args, **kwargs):
|
||||||
super(HorizonStaticFinder, self).__init__(*args, **kwargs)
|
super(HorizonStaticFinder, self).__init__(*args, **kwargs)
|
||||||
|
@ -45,8 +45,10 @@ class HorizonException(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class Http302(HorizonException):
|
class Http302(HorizonException):
|
||||||
"""Error class which can be raised from within a handler to cause an
|
"""Exception used to redirect at the middleware level.
|
||||||
early bailout and redirect at the middleware level.
|
|
||||||
|
This error class which can be raised from within a handler to cause
|
||||||
|
an early bailout and redirect at the middleware level.
|
||||||
"""
|
"""
|
||||||
status_code = 302
|
status_code = 302
|
||||||
|
|
||||||
@ -56,7 +58,9 @@ class Http302(HorizonException):
|
|||||||
|
|
||||||
|
|
||||||
class NotAuthorized(HorizonException):
|
class NotAuthorized(HorizonException):
|
||||||
"""Raised whenever a user attempts to access a resource which they do not
|
"""User tries to access a resource without sufficient permissions.
|
||||||
|
|
||||||
|
Raised whenever a user attempts to access a resource which they do not
|
||||||
have permission-based access to (such as when failing the
|
have permission-based access to (such as when failing the
|
||||||
:func:`~horizon.decorators.require_perms` decorator).
|
:func:`~horizon.decorators.require_perms` decorator).
|
||||||
|
|
||||||
@ -68,8 +72,7 @@ class NotAuthorized(HorizonException):
|
|||||||
|
|
||||||
|
|
||||||
class NotAuthenticated(HorizonException):
|
class NotAuthenticated(HorizonException):
|
||||||
"""Raised when a user is trying to make requests and they are not logged
|
"""Raised when a user is trying to make requests and they are not logged in.
|
||||||
in.
|
|
||||||
|
|
||||||
The included :class:`~horizon.middleware.HorizonMiddleware` catches
|
The included :class:`~horizon.middleware.HorizonMiddleware` catches
|
||||||
``NotAuthenticated`` and handles it gracefully by displaying an error
|
``NotAuthenticated`` and handles it gracefully by displaying an error
|
||||||
@ -99,8 +102,9 @@ class RecoverableError(HorizonException):
|
|||||||
|
|
||||||
|
|
||||||
class ServiceCatalogException(HorizonException):
|
class ServiceCatalogException(HorizonException):
|
||||||
"""Raised when a requested service is not available in the
|
"""A requested service is not available in the ``ServiceCatalog``.
|
||||||
``ServiceCatalog`` returned by Keystone.
|
|
||||||
|
``ServiceCatalog`` is fetched from Keystone.
|
||||||
"""
|
"""
|
||||||
def __init__(self, service_name):
|
def __init__(self, service_name):
|
||||||
message = 'Invalid service catalog service: %s' % service_name
|
message = 'Invalid service catalog service: %s' % service_name
|
||||||
@ -109,9 +113,7 @@ class ServiceCatalogException(HorizonException):
|
|||||||
|
|
||||||
@six.python_2_unicode_compatible
|
@six.python_2_unicode_compatible
|
||||||
class AlreadyExists(HorizonException):
|
class AlreadyExists(HorizonException):
|
||||||
"""Exception to be raised when trying to create an API resource which
|
"""API resources tried to create already exists."""
|
||||||
already exists.
|
|
||||||
"""
|
|
||||||
def __init__(self, name, resource_type):
|
def __init__(self, name, resource_type):
|
||||||
self.attrs = {"name": name, "resource": resource_type}
|
self.attrs = {"name": name, "resource": resource_type}
|
||||||
self.msg = _('A %(resource)s with the name "%(name)s" already exists.')
|
self.msg = _('A %(resource)s with the name "%(name)s" already exists.')
|
||||||
@ -125,8 +127,10 @@ class AlreadyExists(HorizonException):
|
|||||||
|
|
||||||
@six.python_2_unicode_compatible
|
@six.python_2_unicode_compatible
|
||||||
class GetFileError(HorizonException):
|
class GetFileError(HorizonException):
|
||||||
"""Exception to be raised when the value of get_file did not start with
|
"""Exception to be raised when the value of get_file is not expected.
|
||||||
https:// or http://
|
|
||||||
|
The expected values start with https:// or http://.
|
||||||
|
Otherwise this exception will be raised.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, resource_type):
|
def __init__(self, name, resource_type):
|
||||||
self.attrs = {"name": name, "resource": resource_type}
|
self.attrs = {"name": name, "resource": resource_type}
|
||||||
@ -159,7 +163,9 @@ class WorkflowError(HorizonException):
|
|||||||
|
|
||||||
|
|
||||||
class WorkflowValidationError(HorizonException):
|
class WorkflowValidationError(HorizonException):
|
||||||
"""Exception raised during workflow validation if required data is missing,
|
"""Exception raised during workflow validation.
|
||||||
|
|
||||||
|
It is raised if required data is missing,
|
||||||
or existing data is not valid.
|
or existing data is not valid.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
@ -171,7 +177,9 @@ class MessageFailure(HorizonException):
|
|||||||
|
|
||||||
|
|
||||||
class HandledException(HorizonException):
|
class HandledException(HorizonException):
|
||||||
"""Used internally to track exceptions that have gone through
|
"""Used internally to track exceptions that are already handled.
|
||||||
|
|
||||||
|
It is used to track exceptions that have gone through
|
||||||
:func:`horizon.exceptions.handle` more than once.
|
:func:`horizon.exceptions.handle` more than once.
|
||||||
"""
|
"""
|
||||||
def __init__(self, wrapped):
|
def __init__(self, wrapped):
|
||||||
@ -192,8 +200,10 @@ def error_color(msg):
|
|||||||
|
|
||||||
|
|
||||||
def check_message(keywords, message):
|
def check_message(keywords, message):
|
||||||
"""Checks an exception for given keywords and raises a new ``ActionError``
|
"""Checks an exception for given keywords and raises an error if found.
|
||||||
with the desired message if the keywords are found. This allows selective
|
|
||||||
|
It raises a new ``ActionError`` with the desired message if the
|
||||||
|
keywords are found. This allows selective
|
||||||
control over API error messages.
|
control over API error messages.
|
||||||
"""
|
"""
|
||||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
@ -30,16 +30,15 @@ class SelfHandlingMixin(object):
|
|||||||
|
|
||||||
|
|
||||||
class SelfHandlingForm(SelfHandlingMixin, forms.Form):
|
class SelfHandlingForm(SelfHandlingMixin, forms.Form):
|
||||||
"""A base :class:`Form <django:django.forms.Form>` class which includes
|
"""A base Form class which includes processing logic in its subclasses."""
|
||||||
processing logic in its subclasses.
|
|
||||||
"""
|
|
||||||
required_css_class = 'required'
|
required_css_class = 'required'
|
||||||
|
|
||||||
def api_error(self, message):
|
def api_error(self, message):
|
||||||
"""Adds an error to the form's error dictionary after validation
|
"""Adds an error to the form's error dictionary.
|
||||||
based on problems reported via the API. This is useful when you
|
|
||||||
wish for API errors to appear as errors on the form rather than
|
It can be used after validation based on problems reported via the API.
|
||||||
using the messages framework.
|
This is useful when you wish for API errors to appear as errors on the
|
||||||
|
form rather than using the messages framework.
|
||||||
"""
|
"""
|
||||||
self.add_error(NON_FIELD_ERRORS, message)
|
self.add_error(NON_FIELD_ERRORS, message)
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ IPv6 = 2
|
|||||||
|
|
||||||
class IPField(fields.Field):
|
class IPField(fields.Field):
|
||||||
"""Form field for entering IP/range values, with validation.
|
"""Form field for entering IP/range values, with validation.
|
||||||
|
|
||||||
Supports IPv4/IPv6 in the format:
|
Supports IPv4/IPv6 in the format:
|
||||||
.. xxx.xxx.xxx.xxx
|
.. xxx.xxx.xxx.xxx
|
||||||
.. xxx.xxx.xxx.xxx/zz
|
.. xxx.xxx.xxx.xxx/zz
|
||||||
@ -157,9 +158,10 @@ class MACAddressField(fields.Field):
|
|||||||
|
|
||||||
|
|
||||||
class SelectWidget(widgets.Select):
|
class SelectWidget(widgets.Select):
|
||||||
"""Customizable select widget, that allows to render
|
"""Customizable select widget.
|
||||||
data-xxx attributes from choices. This widget also
|
|
||||||
allows user to specify additional html attributes
|
It allows to render data-xxx attributes from choices.
|
||||||
|
This widget also allows user to specify additional html attributes
|
||||||
for choices.
|
for choices.
|
||||||
|
|
||||||
.. attribute:: data_attrs
|
.. attribute:: data_attrs
|
||||||
@ -301,7 +303,9 @@ class ThemableSelectWidget(SelectWidget):
|
|||||||
|
|
||||||
|
|
||||||
class DynamicSelectWidget(SelectWidget):
|
class DynamicSelectWidget(SelectWidget):
|
||||||
"""A subclass of the ``Select`` widget which renders extra attributes for
|
"""``Select`` widget to handle dynamic changes to the available choices.
|
||||||
|
|
||||||
|
A subclass of the ``Select`` widget which renders extra attributes for
|
||||||
use in callbacks to handle dynamic changes to the available choices.
|
use in callbacks to handle dynamic changes to the available choices.
|
||||||
"""
|
"""
|
||||||
_data_add_url_attr = "data-add-item-url"
|
_data_add_url_attr = "data-add-item-url"
|
||||||
@ -335,8 +339,7 @@ class ThemableChoiceField(fields.ChoiceField):
|
|||||||
|
|
||||||
|
|
||||||
class DynamicChoiceField(fields.ChoiceField):
|
class DynamicChoiceField(fields.ChoiceField):
|
||||||
"""A subclass of ``ChoiceField`` with additional properties that make
|
"""ChoiceField that make dynamically updating its elements easier.
|
||||||
dynamically updating its elements easier.
|
|
||||||
|
|
||||||
Notably, the field declaration takes an extra argument, ``add_item_link``
|
Notably, the field declaration takes an extra argument, ``add_item_link``
|
||||||
which may be a string or callable defining the URL that should be used
|
which may be a string or callable defining the URL that should be used
|
||||||
@ -370,8 +373,9 @@ class ThemableDynamicTypedChoiceField(ThemableDynamicChoiceField,
|
|||||||
|
|
||||||
|
|
||||||
class ThemableCheckboxInput(widgets.CheckboxInput):
|
class ThemableCheckboxInput(widgets.CheckboxInput):
|
||||||
"""A subclass of the ``Checkbox`` widget which renders extra markup to
|
"""Checkbox widget which renders extra markup.
|
||||||
allow a custom checkbox experience.
|
|
||||||
|
It is used to allow a custom checkbox experience.
|
||||||
"""
|
"""
|
||||||
def render(self, name, value, attrs=None):
|
def render(self, name, value, attrs=None):
|
||||||
label_for = attrs.get('id', '')
|
label_for = attrs.get('id', '')
|
||||||
@ -411,10 +415,12 @@ class ThemableCheckboxSelectMultiple(widgets.CheckboxSelectMultiple):
|
|||||||
|
|
||||||
|
|
||||||
class ExternalFileField(fields.FileField):
|
class ExternalFileField(fields.FileField):
|
||||||
"""A special flavor of FileField which is meant to be used in cases when
|
"""Special FileField to upload file to some external location.
|
||||||
instead of uploading file to Django it should be uploaded to some external
|
|
||||||
location, while the form validation is done as usual. Should be paired
|
This is a special flavor of FileField which is meant to be used in cases
|
||||||
with ExternalUploadMeta metaclass embedded into the Form class.
|
when instead of uploading file to Django it should be uploaded to some
|
||||||
|
external location, while the form validation is done as usual. It should be
|
||||||
|
paired with ExternalUploadMeta metaclass embedded into the Form class.
|
||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(ExternalFileField, self).__init__(*args, **kwargs)
|
super(ExternalFileField, self).__init__(*args, **kwargs)
|
||||||
@ -422,9 +428,11 @@ class ExternalFileField(fields.FileField):
|
|||||||
|
|
||||||
|
|
||||||
class ExternalUploadMeta(forms.DeclarativeFieldsMetaclass):
|
class ExternalUploadMeta(forms.DeclarativeFieldsMetaclass):
|
||||||
"""Set this class as the metaclass of a form that contains
|
"""Metaclass to process ExternalFileField fields in a specific way.
|
||||||
ExternalFileField in order to process ExternalFileField fields in a
|
|
||||||
specific way. A hidden CharField twin of FieldField is created which
|
Set this class as the metaclass of a form that contains ExternalFileField
|
||||||
|
in order to process ExternalFileField fields in a specific way.
|
||||||
|
A hidden CharField twin of FieldField is created which
|
||||||
contains just the filename (if any file was selected on browser side) and
|
contains just the filename (if any file was selected on browser side) and
|
||||||
a special `clean` method for FileField is defined which extracts just file
|
a special `clean` method for FileField is defined which extracts just file
|
||||||
name. This allows to avoid actual file upload to Django server, yet
|
name. This allows to avoid actual file upload to Django server, yet
|
||||||
|
@ -27,7 +27,9 @@ ADD_TO_FIELD_HEADER = "HTTP_X_HORIZON_ADD_TO_FIELD"
|
|||||||
|
|
||||||
|
|
||||||
class ModalBackdropMixin(object):
|
class ModalBackdropMixin(object):
|
||||||
"""This mixin class is to be used for together with ModalFormView and
|
"""Mixin class to allow ModalFormView and WorkflowView together.
|
||||||
|
|
||||||
|
This mixin class is to be used for together with ModalFormView and
|
||||||
WorkflowView classes to augment them with modal_backdrop context data.
|
WorkflowView classes to augment them with modal_backdrop context data.
|
||||||
|
|
||||||
.. attribute: modal_backdrop (optional)
|
.. attribute: modal_backdrop (optional)
|
||||||
@ -78,8 +80,10 @@ class ModalFormMixin(ModalBackdropMixin):
|
|||||||
|
|
||||||
|
|
||||||
class ModalFormView(ModalFormMixin, views.HorizonFormView):
|
class ModalFormView(ModalFormMixin, views.HorizonFormView):
|
||||||
"""The main view class from which all views which handle forms in Horizon
|
"""The main view class for all views which handle forms in Horizon.
|
||||||
should inherit. It takes care of all details with processing
|
|
||||||
|
All view which handles forms in Horiozn should inherit this class.
|
||||||
|
It takes care of all details with processing
|
||||||
:class:`~horizon.forms.base.SelfHandlingForm` classes, and modal concerns
|
:class:`~horizon.forms.base.SelfHandlingForm` classes, and modal concerns
|
||||||
when the associated template inherits from
|
when the associated template inherits from
|
||||||
`horizon/common/_modal_form.html`.
|
`horizon/common/_modal_form.html`.
|
||||||
@ -148,16 +152,20 @@ class ModalFormView(ModalFormMixin, views.HorizonFormView):
|
|||||||
return self.cancel_url or self.success_url
|
return self.cancel_url or self.success_url
|
||||||
|
|
||||||
def get_object_id(self, obj):
|
def get_object_id(self, obj):
|
||||||
"""For dynamic insertion of resources created in modals, this method
|
"""Returns the ID of the created object.
|
||||||
returns the id of the created object. Defaults to returning the ``id``
|
|
||||||
attribute.
|
For dynamic insertion of resources created in modals,
|
||||||
|
this method returns the id of the created object.
|
||||||
|
Defaults to returning the ``id`` attribute.
|
||||||
"""
|
"""
|
||||||
return obj.id
|
return obj.id
|
||||||
|
|
||||||
def get_object_display(self, obj):
|
def get_object_display(self, obj):
|
||||||
"""For dynamic insertion of resources created in modals, this method
|
"""Returns the display name of the created object.
|
||||||
returns the display name of the created object. Defaults to returning
|
|
||||||
the ``name`` attribute.
|
For dynamic insertion of resources created in modals,
|
||||||
|
this method returns the display name of the created object.
|
||||||
|
Defaults to returning the ``name`` attribute.
|
||||||
"""
|
"""
|
||||||
return obj.name
|
return obj.name
|
||||||
|
|
||||||
|
@ -111,8 +111,10 @@ class HorizonMiddleware(object):
|
|||||||
timezone.activate(tz)
|
timezone.activate(tz)
|
||||||
|
|
||||||
def process_exception(self, request, exception):
|
def process_exception(self, request, exception):
|
||||||
"""Catches internal Horizon exception classes such as NotAuthorized,
|
"""Catches internal Horizon exception classes.
|
||||||
NotFound and Http302 and handles them gracefully.
|
|
||||||
|
Exception classes such as NotAuthorized, NotFound and Http302
|
||||||
|
are caught and handles them gracefully.
|
||||||
"""
|
"""
|
||||||
if isinstance(exception, (exceptions.NotAuthorized,
|
if isinstance(exception, (exceptions.NotAuthorized,
|
||||||
exceptions.NotAuthenticated)):
|
exceptions.NotAuthenticated)):
|
||||||
@ -155,8 +157,9 @@ class HorizonMiddleware(object):
|
|||||||
dst[header] = src[header]
|
dst[header] = src[header]
|
||||||
|
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
"""Convert HttpResponseRedirect to HttpResponse if request is via ajax
|
"""Convert HttpResponseRedirect to HttpResponse if request is via ajax.
|
||||||
to allow ajax request to redirect url
|
|
||||||
|
This is to allow ajax request to redirect url.
|
||||||
"""
|
"""
|
||||||
if request.is_ajax() and hasattr(request, 'horizon'):
|
if request.is_ajax() and hasattr(request, 'horizon'):
|
||||||
queued_msgs = request.horizon['async_messages']
|
queued_msgs = request.horizon['async_messages']
|
||||||
|
@ -42,8 +42,8 @@ STRING_SEPARATOR = "__"
|
|||||||
|
|
||||||
|
|
||||||
class BaseActionMetaClass(type):
|
class BaseActionMetaClass(type):
|
||||||
"""Metaclass for adding all actions options from inheritance tree
|
"""Metaclass for adding all actions options from inheritance tree to action.
|
||||||
to action.
|
|
||||||
This way actions can inherit from each other but still use
|
This way actions can inherit from each other but still use
|
||||||
the class attributes DSL. Meaning, all attributes of Actions are
|
the class attributes DSL. Meaning, all attributes of Actions are
|
||||||
defined as class attributes, but in the background, it will be used as
|
defined as class attributes, but in the background, it will be used as
|
||||||
@ -99,6 +99,7 @@ class BaseAction(html.HTMLElement):
|
|||||||
|
|
||||||
def data_type_matched(self, datum):
|
def data_type_matched(self, datum):
|
||||||
"""Method to see if the action is allowed for a certain type of data.
|
"""Method to see if the action is allowed for a certain type of data.
|
||||||
|
|
||||||
Only affects mixed data type tables.
|
Only affects mixed data type tables.
|
||||||
"""
|
"""
|
||||||
if datum:
|
if datum:
|
||||||
@ -148,13 +149,15 @@ class BaseAction(html.HTMLElement):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def get_default_classes(self):
|
def get_default_classes(self):
|
||||||
"""Returns a list of the default classes for the action. Defaults to
|
"""Returns a list of the default classes for the action.
|
||||||
``["btn", "btn-default", "btn-sm"]``.
|
|
||||||
|
Defaults to ``["btn", "btn-default", "btn-sm"]``.
|
||||||
"""
|
"""
|
||||||
return getattr(settings, "ACTION_CSS_CLASSES", ACTION_CSS_CLASSES)
|
return getattr(settings, "ACTION_CSS_CLASSES", ACTION_CSS_CLASSES)
|
||||||
|
|
||||||
def get_default_attrs(self):
|
def get_default_attrs(self):
|
||||||
"""Returns a list of the default HTML attributes for the action.
|
"""Returns a list of the default HTML attributes for the action.
|
||||||
|
|
||||||
Defaults to returning an ``id`` attribute with the value
|
Defaults to returning an ``id`` attribute with the value
|
||||||
``{{ table.name }}__action_{{ action.name }}__{{ creation counter }}``.
|
``{{ table.name }}__action_{{ action.name }}__{{ creation counter }}``.
|
||||||
"""
|
"""
|
||||||
@ -527,9 +530,7 @@ class FilterAction(BaseAction):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
def is_api_filter(self, filter_field):
|
def is_api_filter(self, filter_field):
|
||||||
"""Determine if the given filter field should be used as an
|
"""Determine if agiven filter field should be used as an API filter."""
|
||||||
API filter.
|
|
||||||
"""
|
|
||||||
if self.filter_type == 'server':
|
if self.filter_type == 'server':
|
||||||
for choice in self.filter_choices:
|
for choice in self.filter_choices:
|
||||||
if (choice[0] == filter_field and len(choice) > 2 and
|
if (choice[0] == filter_field and len(choice) > 2 and
|
||||||
@ -538,8 +539,9 @@ class FilterAction(BaseAction):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def get_select_options(self):
|
def get_select_options(self):
|
||||||
"""Provide the value, string, and help_text (if applicable)
|
"""Provide the value, string, and help_text for the template to render.
|
||||||
for the template to render.
|
|
||||||
|
help_text is returned if applicable.
|
||||||
"""
|
"""
|
||||||
if self.filter_choices:
|
if self.filter_choices:
|
||||||
return [choice[:4] for choice in self.filter_choices
|
return [choice[:4] for choice in self.filter_choices
|
||||||
@ -579,8 +581,7 @@ class FixedFilterAction(FilterAction):
|
|||||||
return self.categories[filter_string]
|
return self.categories[filter_string]
|
||||||
|
|
||||||
def get_fixed_buttons(self):
|
def get_fixed_buttons(self):
|
||||||
"""Returns a list of dictionaries describing the fixed buttons
|
"""Returns a list of dict describing fixed buttons used for filtering.
|
||||||
to use for filtering.
|
|
||||||
|
|
||||||
Each list item should be a dict with the following keys:
|
Each list item should be a dict with the following keys:
|
||||||
|
|
||||||
@ -605,9 +606,9 @@ class FixedFilterAction(FilterAction):
|
|||||||
|
|
||||||
|
|
||||||
class BatchAction(Action):
|
class BatchAction(Action):
|
||||||
"""A table action which takes batch action on one or more
|
"""A table action which takes batch action on one or more objects.
|
||||||
objects. This action should not require user input on a
|
|
||||||
per-object basis.
|
This action should not require user input on a per-object basis.
|
||||||
|
|
||||||
.. attribute:: name
|
.. attribute:: name
|
||||||
|
|
||||||
@ -723,8 +724,9 @@ class BatchAction(Action):
|
|||||||
return action
|
return action
|
||||||
|
|
||||||
def action(self, request, datum_id):
|
def action(self, request, datum_id):
|
||||||
"""Required. Accepts a single object id and performs the specific
|
"""Accepts a single object id and performs the specific action.
|
||||||
action.
|
|
||||||
|
This method is required.
|
||||||
|
|
||||||
Return values are discarded, errors raised are caught and logged.
|
Return values are discarded, errors raised are caught and logged.
|
||||||
"""
|
"""
|
||||||
|
@ -363,8 +363,9 @@ class Column(html.HTMLElement):
|
|||||||
return '<%s: %s>' % (self.__class__.__name__, self.name)
|
return '<%s: %s>' % (self.__class__.__name__, self.name)
|
||||||
|
|
||||||
def allowed(self, request):
|
def allowed(self, request):
|
||||||
"""Determine whether processing/displaying the column is allowed
|
"""Determine whether processing/displaying the column is allowed.
|
||||||
for the current request.
|
|
||||||
|
It is determined based on the current request.
|
||||||
"""
|
"""
|
||||||
if not self.policy_rules:
|
if not self.policy_rules:
|
||||||
return True
|
return True
|
||||||
@ -376,9 +377,10 @@ class Column(html.HTMLElement):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def get_raw_data(self, datum):
|
def get_raw_data(self, datum):
|
||||||
"""Returns the raw data for this column, before any filters or
|
"""Returns the raw data for this column.
|
||||||
formatting are applied to it. This is useful when doing calculations
|
|
||||||
on data in the table.
|
No filters or formatting are applied to the returned data.
|
||||||
|
This is useful when doing calculations on data in the table.
|
||||||
"""
|
"""
|
||||||
# Callable transformations
|
# Callable transformations
|
||||||
if callable(self.transform):
|
if callable(self.transform):
|
||||||
@ -397,8 +399,7 @@ class Column(html.HTMLElement):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
def get_data(self, datum):
|
def get_data(self, datum):
|
||||||
"""Returns the final display data for this column from the given
|
"""Returns the final display data for this column from the given inputs.
|
||||||
inputs.
|
|
||||||
|
|
||||||
The return value will be either the attribute specified for this column
|
The return value will be either the attribute specified for this column
|
||||||
or the return value of the attr:`~horizon.tables.Column.transform`
|
or the return value of the attr:`~horizon.tables.Column.transform`
|
||||||
@ -471,8 +472,10 @@ class Column(html.HTMLElement):
|
|||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def get_summation(self):
|
def get_summation(self):
|
||||||
"""Returns the summary value for the data in this column if a
|
"""Returns the summary value for the data in this column.
|
||||||
valid summation method is specified for it. Otherwise returns ``None``.
|
|
||||||
|
It returns the summary value if a valid summation method is
|
||||||
|
specified for it. Otherwise returns ``None``.
|
||||||
"""
|
"""
|
||||||
if self.summation not in self.summation_methods:
|
if self.summation not in self.summation_methods:
|
||||||
return None
|
return None
|
||||||
@ -577,11 +580,14 @@ class Row(html.HTMLElement):
|
|||||||
self.cells = []
|
self.cells = []
|
||||||
|
|
||||||
def load_cells(self, datum=None):
|
def load_cells(self, datum=None):
|
||||||
"""Load the row's data (either provided at initialization or as an
|
"""Load the row's data and initialize all the cells in the row.
|
||||||
argument to this function), initialize all the cells contained
|
|
||||||
by this row, and set the appropriate row properties which require
|
It also set the appropriate row properties which require
|
||||||
the row's data to be determined.
|
the row's data to be determined.
|
||||||
|
|
||||||
|
The row's data is provided either at initialization or as an
|
||||||
|
argument to this function.
|
||||||
|
|
||||||
This function is called automatically by
|
This function is called automatically by
|
||||||
:meth:`~horizon.tables.Row.__init__` if the ``datum`` argument is
|
:meth:`~horizon.tables.Row.__init__` if the ``datum`` argument is
|
||||||
provided. However, by not providing the data during initialization
|
provided. However, by not providing the data during initialization
|
||||||
@ -665,14 +671,17 @@ class Row(html.HTMLElement):
|
|||||||
return "%s?%s" % (table_url, params)
|
return "%s?%s" % (table_url, params)
|
||||||
|
|
||||||
def can_be_selected(self, datum):
|
def can_be_selected(self, datum):
|
||||||
"""By default if multiselect enabled return True. You can remove the
|
"""Determines whether the row can be selected.
|
||||||
checkbox after an ajax update here if required.
|
|
||||||
|
By default if multiselect enabled return True.
|
||||||
|
You can remove the checkbox after an ajax update here if required.
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_data(self, request, obj_id):
|
def get_data(self, request, obj_id):
|
||||||
"""Fetches the updated data for the row based on the object id
|
"""Fetches the updated data for the row based on the given object ID.
|
||||||
passed in. Must be implemented by a subclass to allow AJAX updating.
|
|
||||||
|
Must be implemented by a subclass to allow AJAX updating.
|
||||||
"""
|
"""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@ -1322,9 +1331,11 @@ class DataTable(object):
|
|||||||
return str(slugify(self._meta.name))
|
return str(slugify(self._meta.name))
|
||||||
|
|
||||||
def get_filter_string(self):
|
def get_filter_string(self):
|
||||||
"""Get the filter string value. For 'server' type filters this is
|
"""Get the filter string value.
|
||||||
saved in the session so that it gets persisted across table loads.
|
|
||||||
For other filter types this is obtained from the POST dict.
|
For 'server' type filters this is saved in the session so that
|
||||||
|
it gets persisted across table loads. For other filter types
|
||||||
|
this is obtained from the POST dict.
|
||||||
"""
|
"""
|
||||||
filter_action = self._meta._filter_action
|
filter_action = self._meta._filter_action
|
||||||
param_name = filter_action.get_param_name()
|
param_name = filter_action.get_param_name()
|
||||||
@ -1336,8 +1347,9 @@ class DataTable(object):
|
|||||||
return filter_string
|
return filter_string
|
||||||
|
|
||||||
def get_filter_field(self):
|
def get_filter_field(self):
|
||||||
"""Get the filter field value used for 'server' type filters. This
|
"""Get the filter field value used for 'server' type filters.
|
||||||
is the value from the filter action's list of filter choices.
|
|
||||||
|
This is the value from the filter action's list of filter choices.
|
||||||
"""
|
"""
|
||||||
filter_action = self._meta._filter_action
|
filter_action = self._meta._filter_action
|
||||||
param_name = '%s_field' % filter_action.get_param_name()
|
param_name = '%s_field' % filter_action.get_param_name()
|
||||||
@ -1406,15 +1418,19 @@ class DataTable(object):
|
|||||||
return self._no_data_message
|
return self._no_data_message
|
||||||
|
|
||||||
def get_filter_first_message(self):
|
def get_filter_first_message(self):
|
||||||
"""Return the message to be displayed when the user needs to provide
|
"""Return the message to be displayed first in the filter.
|
||||||
first a search criteria before loading any data.
|
|
||||||
|
when the user needs to provide a search criteria first
|
||||||
|
before loading any data.
|
||||||
"""
|
"""
|
||||||
return self._filter_first_message
|
return self._filter_first_message
|
||||||
|
|
||||||
def get_object_by_id(self, lookup):
|
def get_object_by_id(self, lookup):
|
||||||
"""Returns the data object from the table's dataset which matches
|
"""Returns the data object whose ID matches ``loopup`` parameter.
|
||||||
the ``lookup`` parameter specified. An error will be raised if
|
|
||||||
the match is not a single data object.
|
The data object is looked up from the table's dataset and
|
||||||
|
the data which matches the ``lookup`` parameter specified.
|
||||||
|
An error will be raised if the match is not a single data object.
|
||||||
|
|
||||||
We will convert the object id and ``lookup`` to unicode before
|
We will convert the object id and ``lookup`` to unicode before
|
||||||
comparison.
|
comparison.
|
||||||
@ -1445,8 +1461,9 @@ class DataTable(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def has_actions(self):
|
def has_actions(self):
|
||||||
"""Boolean. Indicates whether there are any available actions on this
|
"""Indicates whether there are any available actions on this table.
|
||||||
table.
|
|
||||||
|
Returns a boolean value.
|
||||||
"""
|
"""
|
||||||
if not self.base_actions:
|
if not self.base_actions:
|
||||||
return False
|
return False
|
||||||
@ -1454,8 +1471,9 @@ class DataTable(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def needs_form_wrapper(self):
|
def needs_form_wrapper(self):
|
||||||
"""Boolean. Indicates whether this table should be rendered wrapped in
|
"""Returns if this table should be rendered wrapped in a ``<form>`` tag.
|
||||||
a ``<form>`` tag or not.
|
|
||||||
|
Returns a boolean value.
|
||||||
"""
|
"""
|
||||||
# If needs_form_wrapper is explicitly set, defer to that.
|
# If needs_form_wrapper is explicitly set, defer to that.
|
||||||
if self._needs_form_wrapper is not None:
|
if self._needs_form_wrapper is not None:
|
||||||
@ -1532,8 +1550,10 @@ class DataTable(object):
|
|||||||
return table_actions_template.render(context)
|
return table_actions_template.render(context)
|
||||||
|
|
||||||
def render_row_actions(self, datum, row=False):
|
def render_row_actions(self, datum, row=False):
|
||||||
"""Renders the actions specified in ``Meta.row_actions`` using the
|
"""Renders the actions specified in ``Meta.row_actions``.
|
||||||
current row data. If `row` is True, the actions are rendered in a row
|
|
||||||
|
The actions are rendered using the current row data.
|
||||||
|
If `row` is True, the actions are rendered in a row
|
||||||
of buttons. Otherwise they are rendered in a dropdown box.
|
of buttons. Otherwise they are rendered in a dropdown box.
|
||||||
"""
|
"""
|
||||||
if row:
|
if row:
|
||||||
@ -1550,8 +1570,9 @@ class DataTable(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_action(action_string):
|
def parse_action(action_string):
|
||||||
"""Parses the ``action`` parameter (a string) sent back with the
|
"""Parses the ``action_string`` parameter sent back with the POST data.
|
||||||
POST data. By default this parses a string formatted as
|
|
||||||
|
By default this parses a string formatted as
|
||||||
``{{ table_name }}__{{ action_name }}__{{ row_id }}`` and returns
|
``{{ table_name }}__{{ action_name }}__{{ row_id }}`` and returns
|
||||||
each of the pieces. The ``row_id`` is optional.
|
each of the pieces. The ``row_id`` is optional.
|
||||||
"""
|
"""
|
||||||
@ -1568,10 +1589,10 @@ class DataTable(object):
|
|||||||
return table, action, object_id
|
return table, action, object_id
|
||||||
|
|
||||||
def take_action(self, action_name, obj_id=None, obj_ids=None):
|
def take_action(self, action_name, obj_id=None, obj_ids=None):
|
||||||
"""Locates the appropriate action and routes the object
|
"""Locates the appropriate action and routes the object data to it.
|
||||||
data to it. The action should return an HTTP redirect
|
|
||||||
if successful, or a value which evaluates to ``False``
|
The action should return an HTTP redirect if successful,
|
||||||
if unsuccessful.
|
or a value which evaluates to ``False`` if unsuccessful.
|
||||||
"""
|
"""
|
||||||
# See if we have a list of ids
|
# See if we have a list of ids
|
||||||
obj_ids = obj_ids or self.request.POST.getlist('object_ids')
|
obj_ids = obj_ids or self.request.POST.getlist('object_ids')
|
||||||
@ -1616,8 +1637,10 @@ class DataTable(object):
|
|||||||
return table, action, obj_id
|
return table, action, obj_id
|
||||||
|
|
||||||
def maybe_preempt(self):
|
def maybe_preempt(self):
|
||||||
"""Determine whether the request should be handled by a preemptive
|
"""Determine whether the request should be handled in earlier phase.
|
||||||
action on this table or by an AJAX row update before loading any data.
|
|
||||||
|
It determines the request should be handled by a preemptive action
|
||||||
|
on this table or by an AJAX row update before loading any data.
|
||||||
"""
|
"""
|
||||||
request = self.request
|
request = self.request
|
||||||
table_name, action_name, obj_id = self.check_handler(request)
|
table_name, action_name, obj_id = self.check_handler(request)
|
||||||
@ -1705,8 +1728,7 @@ class DataTable(object):
|
|||||||
return HttpResponse(status=error.status_code)
|
return HttpResponse(status=error.status_code)
|
||||||
|
|
||||||
def inline_update_action(self, request, datum, cell, obj_id, cell_name):
|
def inline_update_action(self, request, datum, cell, obj_id, cell_name):
|
||||||
"""Handling update by POST of the cell.
|
"""Handling update by POST of the cell."""
|
||||||
"""
|
|
||||||
new_cell_value = request.POST.get(
|
new_cell_value = request.POST.get(
|
||||||
cell_name + '__' + obj_id, None)
|
cell_name + '__' + obj_id, None)
|
||||||
if issubclass(cell.column.form_field.__class__,
|
if issubclass(cell.column.form_field.__class__,
|
||||||
@ -1742,7 +1764,9 @@ class DataTable(object):
|
|||||||
content_type="application/json")
|
content_type="application/json")
|
||||||
|
|
||||||
def maybe_handle(self):
|
def maybe_handle(self):
|
||||||
"""Determine whether the request should be handled by any action on
|
"""Handles table actions if needed.
|
||||||
|
|
||||||
|
It determines whether the request should be handled by any action on
|
||||||
this table after data has been loaded.
|
this table after data has been loaded.
|
||||||
"""
|
"""
|
||||||
request = self.request
|
request = self.request
|
||||||
@ -1756,8 +1780,10 @@ class DataTable(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def sanitize_id(self, obj_id):
|
def sanitize_id(self, obj_id):
|
||||||
"""Override to modify an incoming obj_id to match existing
|
"""Override to modify an incoming obj_id to match existing API.
|
||||||
API data types or modify the format.
|
|
||||||
|
It is used to modify an incoming obj_id (used in Horizon)
|
||||||
|
to the data type or format expected by the API.
|
||||||
"""
|
"""
|
||||||
return obj_id
|
return obj_id
|
||||||
|
|
||||||
@ -1787,8 +1813,10 @@ class DataTable(object):
|
|||||||
return getattr(datum, display_key, None)
|
return getattr(datum, display_key, None)
|
||||||
|
|
||||||
def has_prev_data(self):
|
def has_prev_data(self):
|
||||||
"""Returns a boolean value indicating whether there is previous data
|
"""Returns a boolean value indicating whether there is previous data.
|
||||||
available to this table from the source (generally an API).
|
|
||||||
|
Returns True if there is previous data available to this table
|
||||||
|
from the source (generally an API).
|
||||||
|
|
||||||
The method is largely meant for internal use, but if you want to
|
The method is largely meant for internal use, but if you want to
|
||||||
override it to provide custom behavior you can do so at your own risk.
|
override it to provide custom behavior you can do so at your own risk.
|
||||||
@ -1796,8 +1824,10 @@ class DataTable(object):
|
|||||||
return self._meta.has_prev_data
|
return self._meta.has_prev_data
|
||||||
|
|
||||||
def has_more_data(self):
|
def has_more_data(self):
|
||||||
"""Returns a boolean value indicating whether there is more data
|
"""Returns a boolean value indicating whether there is more data.
|
||||||
available to this table from the source (generally an API).
|
|
||||||
|
Returns True if there is more data available to this table
|
||||||
|
from the source (generally an API).
|
||||||
|
|
||||||
The method is largely meant for internal use, but if you want to
|
The method is largely meant for internal use, but if you want to
|
||||||
override it to provide custom behavior you can do so at your own risk.
|
override it to provide custom behavior you can do so at your own risk.
|
||||||
@ -1805,35 +1835,35 @@ class DataTable(object):
|
|||||||
return self._meta.has_more_data
|
return self._meta.has_more_data
|
||||||
|
|
||||||
def get_prev_marker(self):
|
def get_prev_marker(self):
|
||||||
"""Returns the identifier for the first object in the current data set
|
"""Returns the identifier for the first object in the current data set.
|
||||||
for APIs that use marker/limit-based paging.
|
|
||||||
|
The return value will be used as marker/limit-based paging in the API.
|
||||||
"""
|
"""
|
||||||
return http.urlquote_plus(self.get_object_id(self.data[0])) \
|
return http.urlquote_plus(self.get_object_id(self.data[0])) \
|
||||||
if self.data else ''
|
if self.data else ''
|
||||||
|
|
||||||
def get_marker(self):
|
def get_marker(self):
|
||||||
"""Returns the identifier for the last object in the current data set
|
"""Returns the identifier for the last object in the current data set.
|
||||||
for APIs that use marker/limit-based paging.
|
|
||||||
|
The return value will be used as marker/limit-based paging in the API.
|
||||||
"""
|
"""
|
||||||
return http.urlquote_plus(self.get_object_id(self.data[-1])) \
|
return http.urlquote_plus(self.get_object_id(self.data[-1])) \
|
||||||
if self.data else ''
|
if self.data else ''
|
||||||
|
|
||||||
def get_prev_pagination_string(self):
|
def get_prev_pagination_string(self):
|
||||||
"""Returns the query parameter string to paginate this table
|
"""Returns the query parameter string to paginate to the prev page."""
|
||||||
to the previous page.
|
|
||||||
"""
|
|
||||||
return "=".join([self._meta.prev_pagination_param,
|
return "=".join([self._meta.prev_pagination_param,
|
||||||
self.get_prev_marker()])
|
self.get_prev_marker()])
|
||||||
|
|
||||||
def get_pagination_string(self):
|
def get_pagination_string(self):
|
||||||
"""Returns the query parameter string to paginate this table
|
"""Returns the query parameter string to paginate to the next page."""
|
||||||
to the next page.
|
|
||||||
"""
|
|
||||||
return "=".join([self._meta.pagination_param, self.get_marker()])
|
return "=".join([self._meta.pagination_param, self.get_marker()])
|
||||||
|
|
||||||
def calculate_row_status(self, statuses):
|
def calculate_row_status(self, statuses):
|
||||||
"""Returns a boolean value determining the overall row status
|
"""Returns a boolean value determining the overall row status.
|
||||||
based on the dictionary of column name to status mappings passed in.
|
|
||||||
|
It is detremined based on the dictionary of column name
|
||||||
|
to status mappings passed in.
|
||||||
|
|
||||||
By default, it uses the following logic:
|
By default, it uses the following logic:
|
||||||
|
|
||||||
@ -1858,9 +1888,10 @@ class DataTable(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def get_row_status_class(self, status):
|
def get_row_status_class(self, status):
|
||||||
"""Returns a css class name determined by the status value. This class
|
"""Returns a css class name determined by the status value.
|
||||||
name is used to indicate the status of the rows in the table if
|
|
||||||
any ``status_columns`` have been specified.
|
This class name is used to indicate the status of the rows in the table
|
||||||
|
if any ``status_columns`` have been specified.
|
||||||
"""
|
"""
|
||||||
if status is True:
|
if status is True:
|
||||||
return "status_up"
|
return "status_up"
|
||||||
|
@ -158,8 +158,9 @@ class MultiTableMixin(object):
|
|||||||
return filter_info
|
return filter_info
|
||||||
|
|
||||||
def handle_server_filter(self, request, table=None):
|
def handle_server_filter(self, request, table=None):
|
||||||
"""Update the table server filter information in the session and
|
"""Update the table server filter information in the session.
|
||||||
determine if the filter has been changed.
|
|
||||||
|
Returns True if the filter has been changed.
|
||||||
"""
|
"""
|
||||||
if not table:
|
if not table:
|
||||||
table = self.get_table()
|
table = self.get_table()
|
||||||
@ -172,9 +173,10 @@ class MultiTableMixin(object):
|
|||||||
return filter_info['changed']
|
return filter_info['changed']
|
||||||
|
|
||||||
def update_server_filter_action(self, request, table=None):
|
def update_server_filter_action(self, request, table=None):
|
||||||
"""Update the table server side filter action based on the current
|
"""Update the table server side filter action.
|
||||||
filter. The filter info may be stored in the session and this will
|
|
||||||
restore it.
|
It is done based on the current filter. The filter info may be stored
|
||||||
|
in the session and this will restore it.
|
||||||
"""
|
"""
|
||||||
if not table:
|
if not table:
|
||||||
table = self.get_table()
|
table = self.get_table()
|
||||||
@ -187,8 +189,10 @@ class MultiTableMixin(object):
|
|||||||
|
|
||||||
|
|
||||||
class MultiTableView(MultiTableMixin, views.HorizonTemplateView):
|
class MultiTableView(MultiTableMixin, views.HorizonTemplateView):
|
||||||
"""A class-based generic view to handle the display and processing of
|
"""Generic view to handle multiple DataTable classes in a single view.
|
||||||
multiple :class:`~horizon.tables.DataTable` classes in a single view.
|
|
||||||
|
Each DataTable class must be a :class:`~horizon.tables.DataTable` class
|
||||||
|
or its subclass.
|
||||||
|
|
||||||
Three steps are required to use this view: set the ``table_classes``
|
Three steps are required to use this view: set the ``table_classes``
|
||||||
attribute with a tuple of the desired
|
attribute with a tuple of the desired
|
||||||
@ -309,8 +313,7 @@ class DataTableView(MultiTableView):
|
|||||||
|
|
||||||
|
|
||||||
class MixedDataTableView(DataTableView):
|
class MixedDataTableView(DataTableView):
|
||||||
"""A class-based generic view to handle DataTable with mixed data
|
"""A class-based generic view to handle DataTable with mixed data types.
|
||||||
types.
|
|
||||||
|
|
||||||
Basic usage is the same as DataTableView.
|
Basic usage is the same as DataTableView.
|
||||||
|
|
||||||
|
@ -30,8 +30,7 @@ CSS_DISABLED_TAB_CLASSES = ["disabled"]
|
|||||||
|
|
||||||
|
|
||||||
class TabGroup(html.HTMLElement):
|
class TabGroup(html.HTMLElement):
|
||||||
"""A container class which knows how to manage and render
|
"""A container class which knows how to manage and render Tab objects.
|
||||||
:class:`~horizon.tabs.Tab` objects.
|
|
||||||
|
|
||||||
.. attribute:: slug
|
.. attribute:: slug
|
||||||
|
|
||||||
@ -128,21 +127,26 @@ class TabGroup(html.HTMLElement):
|
|||||||
exceptions.handle(self.request)
|
exceptions.handle(self.request)
|
||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
"""Returns the id for this tab group. Defaults to the value of the tab
|
"""Returns the id for this tab group.
|
||||||
|
|
||||||
|
Defaults to the value of the tab
|
||||||
group's :attr:`horizon.tabs.Tab.slug`.
|
group's :attr:`horizon.tabs.Tab.slug`.
|
||||||
"""
|
"""
|
||||||
return self.slug
|
return self.slug
|
||||||
|
|
||||||
def get_default_classes(self):
|
def get_default_classes(self):
|
||||||
"""Returns a list of the default classes for the tab group. Defaults to
|
"""Returns a list of the default classes for the tab group.
|
||||||
``["nav", "nav-tabs", "ajax-tabs"]``.
|
|
||||||
|
Defaults to ``["nav", "nav-tabs", "ajax-tabs"]``.
|
||||||
"""
|
"""
|
||||||
default_classes = super(TabGroup, self).get_default_classes()
|
default_classes = super(TabGroup, self).get_default_classes()
|
||||||
default_classes.extend(CSS_TAB_GROUP_CLASSES)
|
default_classes.extend(CSS_TAB_GROUP_CLASSES)
|
||||||
return default_classes
|
return default_classes
|
||||||
|
|
||||||
def tabs_not_available(self):
|
def tabs_not_available(self):
|
||||||
"""In the event that no tabs are either allowed or enabled, this method
|
"""The fallback handler if no tabs are either allowed or enabled.
|
||||||
|
|
||||||
|
In the event that no tabs are either allowed or enabled, this method
|
||||||
is the fallback handler. By default it's a no-op, but it exists
|
is the fallback handler. By default it's a no-op, but it exists
|
||||||
to make redirecting or raising exceptions possible for subclasses.
|
to make redirecting or raising exceptions possible for subclasses.
|
||||||
"""
|
"""
|
||||||
@ -214,8 +218,7 @@ class TabGroup(html.HTMLElement):
|
|||||||
|
|
||||||
|
|
||||||
class Tab(html.HTMLElement):
|
class Tab(html.HTMLElement):
|
||||||
"""A reusable interface for constructing a tab within a
|
"""A reusable interface for constructing a tab within a TabGroup.
|
||||||
:class:`~horizon.tabs.TabGroup`.
|
|
||||||
|
|
||||||
.. attribute:: name
|
.. attribute:: name
|
||||||
|
|
||||||
@ -301,9 +304,10 @@ class Tab(html.HTMLElement):
|
|||||||
return getattr(self, "_data", None) is not None
|
return getattr(self, "_data", None) is not None
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
"""Renders the tab to HTML using the
|
"""Renders the tab to HTML.
|
||||||
|
|
||||||
:meth:`~horizon.tabs.Tab.get_context_data` method and
|
:meth:`~horizon.tabs.Tab.get_context_data` method and
|
||||||
the :meth:`~horizon.tabs.Tab.get_template_name` method.
|
the :meth:`~horizon.tabs.Tab.get_template_name` method are called.
|
||||||
|
|
||||||
If :attr:`~horizon.tabs.Tab.preload` is ``False`` and ``force_load``
|
If :attr:`~horizon.tabs.Tab.preload` is ``False`` and ``force_load``
|
||||||
is not ``True``, or
|
is not ``True``, or
|
||||||
@ -323,8 +327,9 @@ class Tab(html.HTMLElement):
|
|||||||
return render_to_string(self.get_template_name(self.request), context)
|
return render_to_string(self.get_template_name(self.request), context)
|
||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
"""Returns the id for this tab. Defaults to
|
"""Returns the id for this tab.
|
||||||
``"{{ tab_group.slug }}__{{ tab.slug }}"``.
|
|
||||||
|
Defaults to ``"{{ tab_group.slug }}__{{ tab.slug }}"``.
|
||||||
"""
|
"""
|
||||||
return SEPARATOR.join([self.tab_group.slug, self.slug])
|
return SEPARATOR.join([self.tab_group.slug, self.slug])
|
||||||
|
|
||||||
@ -332,9 +337,10 @@ class Tab(html.HTMLElement):
|
|||||||
return "=".join((self.tab_group.param_name, self.get_id()))
|
return "=".join((self.tab_group.param_name, self.get_id()))
|
||||||
|
|
||||||
def get_default_classes(self):
|
def get_default_classes(self):
|
||||||
"""Returns a list of the default classes for the tab. Defaults to
|
"""Returns a list of the default classes for the tab.
|
||||||
and empty list (``[]``), however additional classes may be added
|
|
||||||
depending on the state of the tab as follows:
|
Defaults to and empty list (``[]``), however additional classes may
|
||||||
|
be added depending on the state of the tab as follows:
|
||||||
|
|
||||||
If the tab is the active tab for the tab group, in which
|
If the tab is the active tab for the tab group, in which
|
||||||
the class ``"active"`` will be added.
|
the class ``"active"`` will be added.
|
||||||
@ -362,14 +368,17 @@ class Tab(html.HTMLElement):
|
|||||||
return self.template_name
|
return self.template_name
|
||||||
|
|
||||||
def get_context_data(self, request, **kwargs):
|
def get_context_data(self, request, **kwargs):
|
||||||
"""This method should return a dictionary of context data used to
|
"""Return a dictionary of context data used to render the tab.
|
||||||
render the tab. Required.
|
|
||||||
|
Required.
|
||||||
"""
|
"""
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def enabled(self, request):
|
def enabled(self, request):
|
||||||
"""Determines whether or not the tab should be accessible
|
"""Determines whether or not the tab should be accessible.
|
||||||
(e.g. be rendered into the HTML on load and respond to a click event).
|
|
||||||
|
For example, the tab should be rendered into the HTML
|
||||||
|
on load and respond to a click event.
|
||||||
|
|
||||||
If a tab returns ``False`` from ``enabled`` it will ignore the value
|
If a tab returns ``False`` from ``enabled`` it will ignore the value
|
||||||
of ``preload`` and only render the HTML of the tab after being clicked.
|
of ``preload`` and only render the HTML of the tab after being clicked.
|
||||||
@ -400,8 +409,7 @@ class Tab(html.HTMLElement):
|
|||||||
|
|
||||||
|
|
||||||
class TableTab(Tab):
|
class TableTab(Tab):
|
||||||
"""A :class:`~horizon.tabs.Tab` class which knows how to deal with
|
"""A Tab class which knows how to deal with DataTable classes inside of it.
|
||||||
:class:`~horizon.tables.DataTable` classes rendered inside of it.
|
|
||||||
|
|
||||||
This distinct class is required due to the complexity involved in handling
|
This distinct class is required due to the complexity involved in handling
|
||||||
both dynamic tab loading, dynamic table updating and table actions all
|
both dynamic tab loading, dynamic table updating and table actions all
|
||||||
@ -432,8 +440,9 @@ class TableTab(Tab):
|
|||||||
self._table_data_loaded = False
|
self._table_data_loaded = False
|
||||||
|
|
||||||
def load_table_data(self):
|
def load_table_data(self):
|
||||||
"""Calls the ``get_{{ table_name }}_data`` methods for each table class
|
"""Calls the ``get_{{ table_name }}_data`` methods for each table class.
|
||||||
and sets the data on the tables.
|
|
||||||
|
When returning, the loaded data is set on the tables.
|
||||||
"""
|
"""
|
||||||
# We only want the data to be loaded once, so we track if we have...
|
# We only want the data to be loaded once, so we track if we have...
|
||||||
if not self._table_data_loaded:
|
if not self._table_data_loaded:
|
||||||
@ -456,8 +465,10 @@ class TableTab(Tab):
|
|||||||
self._table_data_loaded = True
|
self._table_data_loaded = True
|
||||||
|
|
||||||
def get_context_data(self, request, **kwargs):
|
def get_context_data(self, request, **kwargs):
|
||||||
"""Adds a ``{{ table_name }}_table`` item to the context for each table
|
"""Adds a ``{{ table_name }}_table`` item to the context for each table.
|
||||||
in the :attr:`~horizon.tabs.TableTab.table_classes` attribute.
|
|
||||||
|
The target tables are specified by
|
||||||
|
the :attr:`~horizon.tabs.TableTab.table_classes` attribute.
|
||||||
|
|
||||||
If only one table class is provided, a shortcut ``table`` context
|
If only one table class is provided, a shortcut ``table`` context
|
||||||
variable is also added containing the single table.
|
variable is also added containing the single table.
|
||||||
|
@ -19,8 +19,7 @@ from horizon import views
|
|||||||
|
|
||||||
|
|
||||||
class TabView(views.HorizonTemplateView):
|
class TabView(views.HorizonTemplateView):
|
||||||
"""A generic class-based view for displaying a
|
"""A generic view for displaying a :class:`horizon.tabs.TabGroup`.
|
||||||
:class:`horizon.tabs.TabGroup`.
|
|
||||||
|
|
||||||
This view handles selecting specific tabs and deals with AJAX requests
|
This view handles selecting specific tabs and deals with AJAX requests
|
||||||
gracefully.
|
gracefully.
|
||||||
@ -57,8 +56,9 @@ class TabView(views.HorizonTemplateView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def handle_tabbed_response(self, tab_group, context):
|
def handle_tabbed_response(self, tab_group, context):
|
||||||
"""Sends back an AJAX-appropriate response for the tab group if
|
"""Sends back an AJAX-appropriate response for the tab group if needed.
|
||||||
required, otherwise renders the response as normal.
|
|
||||||
|
Otherwise renders the response as normal.
|
||||||
"""
|
"""
|
||||||
if self.request.is_ajax():
|
if self.request.is_ajax():
|
||||||
if tab_group.selected:
|
if tab_group.selected:
|
||||||
@ -79,10 +79,11 @@ class TabbedTableView(tables.MultiTableMixin, TabView):
|
|||||||
self._table_dict = {}
|
self._table_dict = {}
|
||||||
|
|
||||||
def load_tabs(self):
|
def load_tabs(self):
|
||||||
"""Loads the tab group, and compiles the table instances for each
|
"""Loads the tab group.
|
||||||
table attached to any :class:`horizon.tabs.TableTab` instances on
|
|
||||||
the tab group. This step is necessary before processing any
|
It compiles the table instances for each table attached to
|
||||||
tab or table actions.
|
any :class:`horizon.tabs.TableTab` instances on the tab group.
|
||||||
|
This step is necessary before processing any tab or table actions.
|
||||||
"""
|
"""
|
||||||
tab_group = self.get_tabs(self.request, **self.kwargs)
|
tab_group = self.get_tabs(self.request, **self.kwargs)
|
||||||
tabs = tab_group.get_tabs()
|
tabs = tab_group.get_tabs()
|
||||||
@ -99,10 +100,12 @@ class TabbedTableView(tables.MultiTableMixin, TabView):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
def handle_table(self, table_dict):
|
def handle_table(self, table_dict):
|
||||||
"""For the given dict containing a ``DataTable`` and a ``TableTab``
|
"""Loads the table data based on a given table_dict and handles them.
|
||||||
|
|
||||||
|
For the given dict containing a ``DataTable`` and a ``TableTab``
|
||||||
instance, it loads the table data for that tab and calls the
|
instance, it loads the table data for that tab and calls the
|
||||||
table's :meth:`~horizon.tables.DataTable.maybe_handle` method. The
|
table's :meth:`~horizon.tables.DataTable.maybe_handle` method.
|
||||||
return value will be the result of ``maybe_handle``.
|
The return value will be the result of ``maybe_handle``.
|
||||||
"""
|
"""
|
||||||
table = table_dict['table']
|
table = table_dict['table']
|
||||||
tab = table_dict['tab']
|
tab = table_dict['tab']
|
||||||
|
@ -25,12 +25,13 @@ register = template.Library()
|
|||||||
|
|
||||||
@receiver(post_compress)
|
@receiver(post_compress)
|
||||||
def update_angular_template_hash(sender, **kwargs):
|
def update_angular_template_hash(sender, **kwargs):
|
||||||
"""Listen for compress events. If the angular templates
|
"""Listen for compress events.
|
||||||
have been re-compressed, also clear them from the Django
|
|
||||||
cache backend. This is important to allow deployers to
|
If the angular templates have been re-compressed, also clear them
|
||||||
change a template file, re-compress, and not accidentally
|
from the Django cache backend. This is important to allow
|
||||||
serve the old Django cached version of that content to
|
deployers to change a template file, re-compress, and not
|
||||||
clients.
|
accidentally serve the old Django cached version of that content
|
||||||
|
to clients.
|
||||||
"""
|
"""
|
||||||
context = kwargs['context'] # context the compressor is working with
|
context = kwargs['context'] # context the compressor is working with
|
||||||
compressed = context['compressed'] # the compressed content
|
compressed = context['compressed'] # the compressed content
|
||||||
@ -55,9 +56,11 @@ def update_angular_template_hash(sender, **kwargs):
|
|||||||
|
|
||||||
@register.filter(name='angular_escapes')
|
@register.filter(name='angular_escapes')
|
||||||
def angular_escapes(value):
|
def angular_escapes(value):
|
||||||
"""Djangos 'escapejs' is too aggressive and inserts unicode. Provide
|
"""Provide a basic filter to allow angular template content for Angular.
|
||||||
a basic filter to allow angular template content to be used within
|
|
||||||
javascript strings.
|
Djangos 'escapejs' is too aggressive and inserts unicode.
|
||||||
|
It provide a basic filter to allow angular template content to be used
|
||||||
|
within javascript strings.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
value: a string
|
value: a string
|
||||||
@ -75,10 +78,11 @@ def angular_escapes(value):
|
|||||||
|
|
||||||
@register.inclusion_tag('angular/angular_templates.html', takes_context=True)
|
@register.inclusion_tag('angular/angular_templates.html', takes_context=True)
|
||||||
def angular_templates(context):
|
def angular_templates(context):
|
||||||
"""For all static HTML templates, generate a dictionary of template
|
"""Generate a dictionary of template contents for all static HTML templates.
|
||||||
contents. If the template has been overridden by a theme, load the
|
|
||||||
override contents instead of the original HTML file. One use for
|
If the template has been overridden by a theme, load the
|
||||||
this is to pre-populate the angular template cache.
|
override contents instead of the original HTML file.
|
||||||
|
One use for this is to pre-populate the angular template cache.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
context: the context of the current Django template
|
context: the context of the current Django template
|
||||||
|
@ -24,6 +24,7 @@ register = template.Library()
|
|||||||
@register.inclusion_tag('bootstrap/progress_bar.html')
|
@register.inclusion_tag('bootstrap/progress_bar.html')
|
||||||
def bs_progress_bar(*args, **kwargs):
|
def bs_progress_bar(*args, **kwargs):
|
||||||
"""A Standard Bootstrap Progress Bar.
|
"""A Standard Bootstrap Progress Bar.
|
||||||
|
|
||||||
http://getbootstrap.com/components/#progress
|
http://getbootstrap.com/components/#progress
|
||||||
|
|
||||||
param args (Array of Numbers: 0-100): Percent of Progress Bars
|
param args (Array of Numbers: 0-100): Percent of Progress Bars
|
||||||
|
@ -21,6 +21,7 @@ register = template.Library()
|
|||||||
takes_context=True)
|
takes_context=True)
|
||||||
def breadcrumb_nav(context):
|
def breadcrumb_nav(context):
|
||||||
"""A logic heavy function for automagically creating a breadcrumb.
|
"""A logic heavy function for automagically creating a breadcrumb.
|
||||||
|
|
||||||
It uses the dashboard, panel group(if it exists), then current panel.
|
It uses the dashboard, panel group(if it exists), then current panel.
|
||||||
Can also use a "custom_breadcrumb" context item to add extra items.
|
Can also use a "custom_breadcrumb" context item to add extra items.
|
||||||
"""
|
"""
|
||||||
|
@ -43,9 +43,7 @@ class MinifiedNode(Node):
|
|||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def has_permissions(user, component):
|
def has_permissions(user, component):
|
||||||
"""Checks if the given user meets the permissions requirements for
|
"""Checks if the given user meets the permissions requirements."""
|
||||||
the component.
|
|
||||||
"""
|
|
||||||
return user.has_perms(getattr(component, 'permissions', set()))
|
return user.has_perms(getattr(component, 'permissions', set()))
|
||||||
|
|
||||||
|
|
||||||
@ -188,7 +186,9 @@ class JSTemplateNode(template.Node):
|
|||||||
|
|
||||||
@register.tag
|
@register.tag
|
||||||
def jstemplate(parser, token):
|
def jstemplate(parser, token):
|
||||||
"""Replaces ``[[[`` and ``]]]`` with ``{{{`` and ``}}}``,
|
"""Templatetag to handle any of the Mustache-based templates.
|
||||||
|
|
||||||
|
Replaces ``[[[`` and ``]]]`` with ``{{{`` and ``}}}``,
|
||||||
``[[`` and ``]]`` with ``{{`` and ``}}`` and
|
``[[`` and ``]]`` with ``{{`` and ``}}`` and
|
||||||
``[%`` and ``%]`` with ``{%`` and ``%}`` to avoid conflicts
|
``[%`` and ``%]`` with ``{%`` and ``%}`` to avoid conflicts
|
||||||
with Django's template engine when using any of the Mustache-based
|
with Django's template engine when using any of the Mustache-based
|
||||||
|
@ -31,9 +31,7 @@ register = template.Library()
|
|||||||
|
|
||||||
class ParseDateNode(template.Node):
|
class ParseDateNode(template.Node):
|
||||||
def render(self, datestring):
|
def render(self, datestring):
|
||||||
"""Parses a date-like input string into a timezone aware Python
|
"""Parses a date-like string into a timezone aware Python datetime."""
|
||||||
datetime.
|
|
||||||
"""
|
|
||||||
formats = ["%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%d %H:%M:%S.%f",
|
formats = ["%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%d %H:%M:%S.%f",
|
||||||
"%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"]
|
"%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"]
|
||||||
if datestring:
|
if datestring:
|
||||||
|
@ -115,8 +115,7 @@ class RequestFactoryWithMessages(RequestFactory):
|
|||||||
@unittest.skipIf(os.environ.get('SKIP_UNITTESTS', False),
|
@unittest.skipIf(os.environ.get('SKIP_UNITTESTS', False),
|
||||||
"The SKIP_UNITTESTS env variable is set.")
|
"The SKIP_UNITTESTS env variable is set.")
|
||||||
class TestCase(django_test.TestCase):
|
class TestCase(django_test.TestCase):
|
||||||
"""Specialized base test case class for Horizon which gives access to
|
"""Base test case class for Horizon with numerous additional features.
|
||||||
numerous additional features:
|
|
||||||
|
|
||||||
* The ``mox`` mocking framework via ``self.mox``.
|
* The ``mox`` mocking framework via ``self.mox``.
|
||||||
* A ``RequestFactory`` class which supports Django's ``contrib.messages``
|
* A ``RequestFactory`` class which supports Django's ``contrib.messages``
|
||||||
@ -176,15 +175,17 @@ class TestCase(django_test.TestCase):
|
|||||||
self.assertNotRegex(text, unexpected_regexp, msg)
|
self.assertNotRegex(text, unexpected_regexp, msg)
|
||||||
|
|
||||||
def assertNoMessages(self, response=None):
|
def assertNoMessages(self, response=None):
|
||||||
"""Asserts that no messages have been attached by the
|
"""Asserts no messages have been attached by the messages framework.
|
||||||
``contrib.messages`` framework.
|
|
||||||
|
The expected messages framework is ``django.contrib.messages``.
|
||||||
"""
|
"""
|
||||||
self.assertMessageCount(response, success=0, warn=0, info=0, error=0)
|
self.assertMessageCount(response, success=0, warn=0, info=0, error=0)
|
||||||
|
|
||||||
def assertMessageCount(self, response=None, **kwargs):
|
def assertMessageCount(self, response=None, **kwargs):
|
||||||
"""Asserts that the specified number of messages have been attached
|
"""Asserts that the expected number of messages have been attached.
|
||||||
for various message types. Usage would look like
|
|
||||||
``self.assertMessageCount(success=1)``.
|
The expected number of messages can be specified per message type.
|
||||||
|
Usage would look like ``self.assertMessageCount(success=1)``.
|
||||||
"""
|
"""
|
||||||
temp_req = self.client.request(**{'wsgi.input': None})
|
temp_req = self.client.request(**{'wsgi.input': None})
|
||||||
temp_req.COOKIES = self.client.cookies
|
temp_req.COOKIES = self.client.cookies
|
||||||
@ -255,9 +256,9 @@ class SeleniumTestCase(LiveServerTestCase):
|
|||||||
|
|
||||||
|
|
||||||
class JasmineTests(SeleniumTestCase):
|
class JasmineTests(SeleniumTestCase):
|
||||||
"""Helper class which allows you to create a simple Jasmine test running
|
"""Helper class which allows you to create a simple Jasmine test.
|
||||||
through Selenium
|
|
||||||
|
|
||||||
|
Jasmine tests are run through Selenium.
|
||||||
To run a jasmine test suite, create a class which extends JasmineTests in
|
To run a jasmine test suite, create a class which extends JasmineTests in
|
||||||
the :file:`horizon/test/jasmine/jasmine_tests.py` and define two class
|
the :file:`horizon/test/jasmine/jasmine_tests.py` and define two class
|
||||||
attributes
|
attributes
|
||||||
|
@ -21,10 +21,12 @@ from horizon.test import helpers
|
|||||||
|
|
||||||
|
|
||||||
class HackingTestCase(helpers.TestCase):
|
class HackingTestCase(helpers.TestCase):
|
||||||
"""This class tests the hacking checks in horizon.hacking.checks by passing
|
"""This class tests the hacking checks in horizon.hacking.checks.
|
||||||
strings to the check methods like the pep8/flake8 parser would. The parser
|
|
||||||
loops over each line in the file and then passes the parameters to the
|
Each check passes passing strings to the check methods like the
|
||||||
check method. The parameter names in the check method dictate what type of
|
pep8/flake8 parser would. The parser loops over each line in the
|
||||||
|
file and then passes the parameters to the check method.
|
||||||
|
The parameter names in the check method dictate what type of
|
||||||
object is passed to the check method. The parameter types are:
|
object is passed to the check method. The parameter types are:
|
||||||
|
|
||||||
logical_line
|
logical_line
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# -*- encoding: UTF-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
# Copyright 2015, Rackspace, US, Inc.
|
# Copyright 2015, Rackspace, US, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
@ -44,17 +44,14 @@ class ExtractAngularTestCase(test.TestCase):
|
|||||||
messages)
|
messages)
|
||||||
|
|
||||||
def test_attr_value(self):
|
def test_attr_value(self):
|
||||||
"""We should not translate tags that have translate as the value of an
|
"""Should not translate tags with translate as the value of an attr."""
|
||||||
attribute.
|
|
||||||
"""
|
|
||||||
buf = StringIO('<html><div id="translate">hello world!</div></html>')
|
buf = StringIO('<html><div id="translate">hello world!</div></html>')
|
||||||
|
|
||||||
messages = list(extract_angular(buf, [], [], {}))
|
messages = list(extract_angular(buf, [], [], {}))
|
||||||
self.assertEqual([], messages)
|
self.assertEqual([], messages)
|
||||||
|
|
||||||
def test_attr_value_plus_directive(self):
|
def test_attr_value_plus_directive(self):
|
||||||
"""Unless they also have a translate directive.
|
"""Unless they also have a translate directive."""
|
||||||
"""
|
|
||||||
buf = StringIO(
|
buf = StringIO(
|
||||||
'<html><div id="translate" translate>hello world!</div></html>')
|
'<html><div id="translate" translate>hello world!</div></html>')
|
||||||
|
|
||||||
|
@ -118,10 +118,11 @@ class BaseHorizonTests(test.TestCase):
|
|||||||
dash.register(panel)
|
dash.register(panel)
|
||||||
|
|
||||||
def _reload_urls(self):
|
def _reload_urls(self):
|
||||||
"""Clears out the URL caches, reloads the root urls module, and
|
"""Clears out the URL caches, and reloads the root urls module.
|
||||||
re-triggers the autodiscovery mechanism for Horizon. Allows URLs
|
|
||||||
to be re-calculated after registering new dashboards. Useful
|
It re-triggers the autodiscovery mechanism for Horizon.
|
||||||
only for testing and should never be used on a live site.
|
Allows URLs to be re-calculated after registering new dashboards.
|
||||||
|
Useful only for testing and should never be used on a live site.
|
||||||
"""
|
"""
|
||||||
urlresolvers.clear_url_caches()
|
urlresolvers.clear_url_caches()
|
||||||
moves.reload_module(import_module(settings.ROOT_URLCONF))
|
moves.reload_module(import_module(settings.ROOT_URLCONF))
|
||||||
@ -371,8 +372,9 @@ class GetUserHomeTests(BaseHorizonTests):
|
|||||||
|
|
||||||
class CustomPanelTests(BaseHorizonTests):
|
class CustomPanelTests(BaseHorizonTests):
|
||||||
|
|
||||||
"""Test customization of dashboards and panels
|
"""Test customization of dashboards and panels.
|
||||||
using 'customization_module' to HORIZON_CONFIG.
|
|
||||||
|
This tests customization using 'customization_module' to HORIZON_CONFIG.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -407,8 +409,9 @@ class CustomPanelTests(BaseHorizonTests):
|
|||||||
|
|
||||||
class CustomPermissionsTests(BaseHorizonTests):
|
class CustomPermissionsTests(BaseHorizonTests):
|
||||||
|
|
||||||
"""Test customization of permissions on panels
|
"""Test customization of permissions on panels.
|
||||||
using 'customization_module' to HORIZON_CONFIG.
|
|
||||||
|
This tests customization using 'customization_module' to HORIZON_CONFIG.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -71,50 +71,42 @@ class FinderTests(unittest.TestCase):
|
|||||||
# discover_files()
|
# discover_files()
|
||||||
#
|
#
|
||||||
def test_find_all(self):
|
def test_find_all(self):
|
||||||
"""Find all files
|
"""Find all files"""
|
||||||
"""
|
|
||||||
files = fd.discover_files(base_path)
|
files = fd.discover_files(base_path)
|
||||||
self.assertEqual(len(files), 18)
|
self.assertEqual(len(files), 18)
|
||||||
|
|
||||||
def test_find_a(self):
|
def test_find_a(self):
|
||||||
"""Find all files in folder `a`
|
"""Find all files in folder `a`"""
|
||||||
"""
|
|
||||||
files = fd.discover_files(base_path, sub_path='a')
|
files = fd.discover_files(base_path, sub_path='a')
|
||||||
self.assertEqual(len(files), 8)
|
self.assertEqual(len(files), 8)
|
||||||
|
|
||||||
def test_find_b(self):
|
def test_find_b(self):
|
||||||
"""Find all files in folder `b`
|
"""Find all files in folder `b`"""
|
||||||
"""
|
|
||||||
files = fd.discover_files(base_path, sub_path='b')
|
files = fd.discover_files(base_path, sub_path='b')
|
||||||
self.assertEqual(len(files), 10)
|
self.assertEqual(len(files), 10)
|
||||||
|
|
||||||
def test_find_all_js(self):
|
def test_find_all_js(self):
|
||||||
"""Find all files with extension of `.js`
|
"""Find all files with extension of `.js`"""
|
||||||
"""
|
|
||||||
files = fd.discover_files(base_path, ext='.js')
|
files = fd.discover_files(base_path, ext='.js')
|
||||||
self.assertEqual(len(files), 14)
|
self.assertEqual(len(files), 14)
|
||||||
|
|
||||||
def test_find_all_html(self):
|
def test_find_all_html(self):
|
||||||
"""Find all files with extension of `.html`
|
"""Find all files with extension of `.html`"""
|
||||||
"""
|
|
||||||
files = fd.discover_files(base_path, ext='.html')
|
files = fd.discover_files(base_path, ext='.html')
|
||||||
self.assertEqual(len(files), 2)
|
self.assertEqual(len(files), 2)
|
||||||
|
|
||||||
def test_find_all_js_in_a(self):
|
def test_find_all_js_in_a(self):
|
||||||
"""Find all files with extension of `.js` in folder a
|
"""Find all files with extension of `.js` in folder a"""
|
||||||
"""
|
|
||||||
files = fd.discover_files(base_path, sub_path='b', ext='.js')
|
files = fd.discover_files(base_path, sub_path='b', ext='.js')
|
||||||
self.assertEqual(len(files), 7)
|
self.assertEqual(len(files), 7)
|
||||||
|
|
||||||
def test_find_all_html_in_a(self):
|
def test_find_all_html_in_a(self):
|
||||||
"""Find all files with extension of `.html` in folder a
|
"""Find all files with extension of `.html` in folder a"""
|
||||||
"""
|
|
||||||
files = fd.discover_files(base_path, sub_path='b', ext='.html')
|
files = fd.discover_files(base_path, sub_path='b', ext='.html')
|
||||||
self.assertEqual(len(files), 1)
|
self.assertEqual(len(files), 1)
|
||||||
|
|
||||||
def test_find_all_file_trim_base_path(self):
|
def test_find_all_file_trim_base_path(self):
|
||||||
"""Find all files in folder, trim base path
|
"""Find all files in folder, trim base path"""
|
||||||
"""
|
|
||||||
files = fd.discover_files(base_path, sub_path='a', trim_base_path=True)
|
files = fd.discover_files(base_path, sub_path='a', trim_base_path=True)
|
||||||
self.assertTrue(files[0].startswith('a/'))
|
self.assertTrue(files[0].startswith('a/'))
|
||||||
|
|
||||||
@ -125,8 +117,7 @@ class FinderTests(unittest.TestCase):
|
|||||||
# sort_js_files()
|
# sort_js_files()
|
||||||
#
|
#
|
||||||
def test_sort_js_files(self):
|
def test_sort_js_files(self):
|
||||||
"""Sort all JavaScript files
|
"""Sort all JavaScript files"""
|
||||||
"""
|
|
||||||
files = fd.discover_files(base_path, ext='.js')
|
files = fd.discover_files(base_path, ext='.js')
|
||||||
sources, mocks, specs = fd.sort_js_files(files)
|
sources, mocks, specs = fd.sort_js_files(files)
|
||||||
|
|
||||||
@ -156,8 +147,7 @@ class FinderTests(unittest.TestCase):
|
|||||||
# discover_static_files()
|
# discover_static_files()
|
||||||
#
|
#
|
||||||
def test_discover_all_static_files(self):
|
def test_discover_all_static_files(self):
|
||||||
"""Find all static files
|
"""Find all static files"""
|
||||||
"""
|
|
||||||
sources, mocks, specs, templates = fd.discover_static_files(base_path)
|
sources, mocks, specs, templates = fd.discover_static_files(base_path)
|
||||||
self.assertEqual(len(sources), 6)
|
self.assertEqual(len(sources), 6)
|
||||||
self.assertEqual(len(mocks), 2)
|
self.assertEqual(len(mocks), 2)
|
||||||
@ -189,8 +179,7 @@ class FinderTests(unittest.TestCase):
|
|||||||
# populate_horizon_config()
|
# populate_horizon_config()
|
||||||
#
|
#
|
||||||
def test_populate_horizon_config(self):
|
def test_populate_horizon_config(self):
|
||||||
"""Populate horizon config
|
"""Populate horizon config"""
|
||||||
"""
|
|
||||||
horizon_config = {}
|
horizon_config = {}
|
||||||
|
|
||||||
fd.populate_horizon_config(horizon_config, base_path)
|
fd.populate_horizon_config(horizon_config, base_path)
|
||||||
|
@ -12,9 +12,7 @@
|
|||||||
|
|
||||||
|
|
||||||
class ObjDictWrapper(dict):
|
class ObjDictWrapper(dict):
|
||||||
"""ObjDictWrapper is a container that provides both dictionary-like and
|
"""Wrapper that provides both dict- and object-like attribute access."""
|
||||||
object-like attribute access.
|
|
||||||
"""
|
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
if item in self:
|
if item in self:
|
||||||
return self[item]
|
return self[item]
|
||||||
|
@ -109,8 +109,7 @@ class WebElementWrapper(WrapperFindOverride, webelement.WebElement):
|
|||||||
|
|
||||||
|
|
||||||
class WebDriverWrapper(WrapperFindOverride, WebDriver):
|
class WebDriverWrapper(WrapperFindOverride, WebDriver):
|
||||||
"""Wrapper for webdriver to return WebElementWrapper on find_element.
|
"""Wrapper for webdriver to return WebElementWrapper on find_element."""
|
||||||
"""
|
|
||||||
def reload_request(self, locator, index):
|
def reload_request(self, locator, index):
|
||||||
try:
|
try:
|
||||||
# element was found out via find_elements
|
# element was found out via find_elements
|
||||||
|
@ -87,7 +87,9 @@ def offline_context():
|
|||||||
# A piece of middleware that stores the theme cookie value into
|
# A piece of middleware that stores the theme cookie value into
|
||||||
# local thread storage so the template loader can access it
|
# local thread storage so the template loader can access it
|
||||||
class ThemeMiddleware(object):
|
class ThemeMiddleware(object):
|
||||||
"""The Theme Middleware component. The custom template loaders
|
"""The Theme Middleware component.
|
||||||
|
|
||||||
|
The custom template loaders
|
||||||
don't have access to the request object, so we need to store
|
don't have access to the request object, so we need to store
|
||||||
the Cookie's theme value for use later in the Django chain.
|
the Cookie's theme value for use later in the Django chain.
|
||||||
"""
|
"""
|
||||||
@ -111,7 +113,9 @@ class ThemeMiddleware(object):
|
|||||||
|
|
||||||
|
|
||||||
class ThemeTemplateLoader(tLoaderCls):
|
class ThemeTemplateLoader(tLoaderCls):
|
||||||
"""Themes can contain template overrides, so we need to check the
|
"""Theme-aware template loader.
|
||||||
|
|
||||||
|
Themes can contain template overrides, so we need to check the
|
||||||
theme directory first, before loading any of the standard templates.
|
theme directory first, before loading any of the standard templates.
|
||||||
"""
|
"""
|
||||||
is_usable = True
|
is_usable = True
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# -*- encoding: UTF-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
# Copyright 2015, Rackspace, US, Inc.
|
# Copyright 2015, Rackspace, US, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
@ -144,9 +144,11 @@ class AngularGettextHTMLParser(html_parser.HTMLParser):
|
|||||||
|
|
||||||
|
|
||||||
def extract_angular(fileobj, keywords, comment_tags, options):
|
def extract_angular(fileobj, keywords, comment_tags, options):
|
||||||
"""Extract messages from angular template (HTML) files that use the
|
"""Extract messages from angular template (HTML) files.
|
||||||
|
|
||||||
|
It extract messages from angular template (HTML) files that use
|
||||||
angular-gettext translate directive as per
|
angular-gettext translate directive as per
|
||||||
https://angular-gettext.rocketeer.be/ .
|
https://angular-gettext.rocketeer.be/
|
||||||
|
|
||||||
:param fileobj: the file-like object the messages should be extracted
|
:param fileobj: the file-like object the messages should be extracted
|
||||||
from
|
from
|
||||||
|
@ -23,8 +23,7 @@ SPEC_EXT = '.spec.js'
|
|||||||
|
|
||||||
|
|
||||||
def discover_files(base_path, sub_path='', ext='', trim_base_path=False):
|
def discover_files(base_path, sub_path='', ext='', trim_base_path=False):
|
||||||
"""Discovers all files with certain extension in given paths.
|
"""Discovers all files with certain extension in given paths."""
|
||||||
"""
|
|
||||||
file_list = []
|
file_list = []
|
||||||
for root, dirs, files in walk(path.join(base_path, sub_path)):
|
for root, dirs, files in walk(path.join(base_path, sub_path)):
|
||||||
if trim_base_path:
|
if trim_base_path:
|
||||||
@ -36,8 +35,10 @@ def discover_files(base_path, sub_path='', ext='', trim_base_path=False):
|
|||||||
|
|
||||||
|
|
||||||
def sort_js_files(js_files):
|
def sort_js_files(js_files):
|
||||||
"""Sorts JavaScript files in `js_files` into source files, mock files
|
"""Sorts JavaScript files in `js_files`.
|
||||||
and spec files based on file extension.
|
|
||||||
|
It sorts JavaScript files in a given `js_files`
|
||||||
|
into source files, mock files and spec files based on file extension.
|
||||||
|
|
||||||
Output:
|
Output:
|
||||||
|
|
||||||
@ -72,8 +73,10 @@ def sort_js_files(js_files):
|
|||||||
|
|
||||||
|
|
||||||
def discover_static_files(base_path, sub_path=''):
|
def discover_static_files(base_path, sub_path=''):
|
||||||
"""Discovers static files in given paths, returning JavaScript sources,
|
"""Discovers static files in given paths.
|
||||||
mocks, specs and HTML templates, all grouped in lists.
|
|
||||||
|
It returns JavaScript sources, mocks, specs and HTML templates,
|
||||||
|
all grouped in lists.
|
||||||
"""
|
"""
|
||||||
js_files = discover_files(base_path, sub_path=sub_path,
|
js_files = discover_files(base_path, sub_path=sub_path,
|
||||||
ext='.js', trim_base_path=True)
|
ext='.js', trim_base_path=True)
|
||||||
@ -105,8 +108,7 @@ def populate_horizon_config(horizon_config, base_path,
|
|||||||
|
|
||||||
|
|
||||||
def _log(file_list, list_name, in_path):
|
def _log(file_list, list_name, in_path):
|
||||||
"""Logs result at debug level
|
"""Logs result at debug level"""
|
||||||
"""
|
|
||||||
file_names = '\n'.join(file_list)
|
file_names = '\n'.join(file_list)
|
||||||
LOG.debug("\nDiscovered %(size)d %(name)s file(s) in %(path)s:\n"
|
LOG.debug("\nDiscovered %(size)d %(name)s file(s) in %(path)s:\n"
|
||||||
"%(files)s\n",
|
"%(files)s\n",
|
||||||
|
@ -30,9 +30,8 @@ def replace_underscores(string):
|
|||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def parse_isotime(timestr, default=None):
|
def parse_isotime(timestr, default=None):
|
||||||
"""This duplicates oslo timeutils parse_isotime but with a
|
# This duplicates oslo timeutils parse_isotime but with a
|
||||||
@register.filter annotation and a silent fallback on error.
|
# @register.filter annotation and a silent fallback on error.
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
return iso8601.parse_date(timestr)
|
return iso8601.parse_date(timestr)
|
||||||
except (iso8601.ParseError, TypeError):
|
except (iso8601.ParseError, TypeError):
|
||||||
@ -41,8 +40,10 @@ def parse_isotime(timestr, default=None):
|
|||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def timesince_or_never(dt, default=None):
|
def timesince_or_never(dt, default=None):
|
||||||
"""Call the Django ``timesince`` filter, but return the string
|
"""Call the Django ``timesince`` filter or a given default string.
|
||||||
*default* if *dt* is not a valid ``date`` or ``datetime`` object.
|
|
||||||
|
It returns the string *default* if *dt* is not a valid ``date``
|
||||||
|
or ``datetime`` object.
|
||||||
When *default* is None, "Never" is returned.
|
When *default* is None, "Never" is returned.
|
||||||
"""
|
"""
|
||||||
if default is None:
|
if default is None:
|
||||||
|
@ -68,6 +68,7 @@ def logout_with_message(request, msg, redirect=True, status='success'):
|
|||||||
|
|
||||||
def get_config_value(request, key, default, search_in_settings=True):
|
def get_config_value(request, key, default, search_in_settings=True):
|
||||||
"""Retrieves the value of `key` from configuration in the following order:
|
"""Retrieves the value of `key` from configuration in the following order:
|
||||||
|
|
||||||
- from the session; if not found there then
|
- from the session; if not found there then
|
||||||
- from cookies; if not found there then
|
- from cookies; if not found there then
|
||||||
- from the settings file if `search_in_settings` is True,
|
- from the settings file if `search_in_settings` is True,
|
||||||
@ -92,8 +93,7 @@ def get_config_value(request, key, default, search_in_settings=True):
|
|||||||
|
|
||||||
|
|
||||||
def save_config_value(request, response, key, value):
|
def save_config_value(request, response, key, value):
|
||||||
"""Sets value of key `key` to `value` in both session and cookies.
|
"""Sets value of key `key` to `value` in both session and cookies."""
|
||||||
"""
|
|
||||||
request.session[key] = value
|
request.session[key] = value
|
||||||
response.set_cookie(key, value, expires=one_year_from_now())
|
response.set_cookie(key, value, expires=one_year_from_now())
|
||||||
return response
|
return response
|
||||||
@ -126,14 +126,18 @@ def natural_sort(attr):
|
|||||||
|
|
||||||
|
|
||||||
def get_keys(tuple_of_tuples):
|
def get_keys(tuple_of_tuples):
|
||||||
"""Processes a tuple of 2-element tuples and returns a tuple containing
|
"""Returns a tuple containing first component of each tuple.
|
||||||
|
|
||||||
|
It processes a tuple of 2-element tuples and returns a tuple containing
|
||||||
first component of each tuple.
|
first component of each tuple.
|
||||||
"""
|
"""
|
||||||
return tuple([t[0] for t in tuple_of_tuples])
|
return tuple([t[0] for t in tuple_of_tuples])
|
||||||
|
|
||||||
|
|
||||||
def value_for_key(tuple_of_tuples, key):
|
def value_for_key(tuple_of_tuples, key):
|
||||||
"""Processes a tuple of 2-element tuples and returns the value
|
"""Returns a value containing to the given key.
|
||||||
|
|
||||||
|
It processes a tuple of 2-element tuples and returns the value
|
||||||
corresponding to the given key. If no value is found, the key is returned.
|
corresponding to the given key. If no value is found, the key is returned.
|
||||||
"""
|
"""
|
||||||
for t in tuple_of_tuples:
|
for t in tuple_of_tuples:
|
||||||
@ -144,7 +148,9 @@ def value_for_key(tuple_of_tuples, key):
|
|||||||
|
|
||||||
|
|
||||||
def next_key(tuple_of_tuples, key):
|
def next_key(tuple_of_tuples, key):
|
||||||
"""Processes a tuple of 2-element tuples and returns the key which comes
|
"""Returns the key which comes after the given key.
|
||||||
|
|
||||||
|
It processes a tuple of 2-element tuples and returns the key which comes
|
||||||
after the given key.
|
after the given key.
|
||||||
"""
|
"""
|
||||||
for i, t in enumerate(tuple_of_tuples):
|
for i, t in enumerate(tuple_of_tuples):
|
||||||
@ -156,7 +162,9 @@ def next_key(tuple_of_tuples, key):
|
|||||||
|
|
||||||
|
|
||||||
def previous_key(tuple_of_tuples, key):
|
def previous_key(tuple_of_tuples, key):
|
||||||
"""Processes a tuple of 2-element tuples and returns the key which comes
|
"""Returns the key which comes before the give key.
|
||||||
|
|
||||||
|
It Processes a tuple of 2-element tuples and returns the key which comes
|
||||||
before the given key.
|
before the given key.
|
||||||
"""
|
"""
|
||||||
for i, t in enumerate(tuple_of_tuples):
|
for i, t in enumerate(tuple_of_tuples):
|
||||||
@ -168,8 +176,9 @@ def previous_key(tuple_of_tuples, key):
|
|||||||
|
|
||||||
|
|
||||||
def format_value(value):
|
def format_value(value):
|
||||||
"""Returns the given value rounded to one decimal place if it is a
|
"""Returns the given value rounded to one decimal place if deciaml.
|
||||||
decimal, or integer if it is an integer.
|
|
||||||
|
Returns the integer if an integer is given.
|
||||||
"""
|
"""
|
||||||
value = decimal.Decimal(str(value))
|
value = decimal.Decimal(str(value))
|
||||||
if int(value) == value:
|
if int(value) == value:
|
||||||
|
@ -22,21 +22,21 @@ class HTMLElement(object):
|
|||||||
self.classes = getattr(self, "classes", [])
|
self.classes = getattr(self, "classes", [])
|
||||||
|
|
||||||
def get_default_classes(self):
|
def get_default_classes(self):
|
||||||
"""Returns an iterable of default classes which should be combined with
|
"""Returns an iterable of default classes.
|
||||||
any other declared classes.
|
|
||||||
|
They will be combined with any other declared classes.
|
||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_default_attrs(self):
|
def get_default_attrs(self):
|
||||||
"""Returns a dict of default attributes which should be combined with
|
"""Returns a dict of default attributes.
|
||||||
other declared attributes.
|
|
||||||
|
They will be combined with other declared attributes.
|
||||||
"""
|
"""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_final_attrs(self, classes=True):
|
def get_final_attrs(self, classes=True):
|
||||||
"""Returns a dict containing the final attributes of this element
|
"""Returns a dict containing the final attributes to be rendered."""
|
||||||
which will be rendered.
|
|
||||||
"""
|
|
||||||
final_attrs = copy.copy(self.get_default_attrs())
|
final_attrs = copy.copy(self.get_default_attrs())
|
||||||
final_attrs.update(self.attrs)
|
final_attrs.update(self.attrs)
|
||||||
if classes:
|
if classes:
|
||||||
@ -57,14 +57,18 @@ class HTMLElement(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def attr_string(self):
|
def attr_string(self):
|
||||||
"""Returns a flattened string of HTML attributes based on the
|
"""Returns a flattened string of HTML attributes.
|
||||||
|
|
||||||
|
HTML attributes are flattened based on the
|
||||||
``attrs`` dict provided to the class.
|
``attrs`` dict provided to the class.
|
||||||
"""
|
"""
|
||||||
return flatatt(self.get_final_attrs())
|
return flatatt(self.get_final_attrs())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attr_string_nc(self):
|
def attr_string_nc(self):
|
||||||
"""Returns a flattened string of HTML attributes based on the
|
"""Returns a flattened string of HTML attributes.
|
||||||
|
|
||||||
|
HTML attributes are flattened based on the
|
||||||
``attrs`` dict provided to the class.
|
``attrs`` dict provided to the class.
|
||||||
"""
|
"""
|
||||||
return flatatt(self.get_final_attrs(False))
|
return flatatt(self.get_final_attrs(False))
|
||||||
|
@ -33,9 +33,7 @@ ureg = pint.UnitRegistry()
|
|||||||
|
|
||||||
|
|
||||||
def is_supported(unit):
|
def is_supported(unit):
|
||||||
"""Returns a bool indicating whether the unit specified is supported by
|
"""Returns a bool indicating whether the unit specified is supported."""
|
||||||
this module.
|
|
||||||
"""
|
|
||||||
return unit in functions.get_keys(INFORMATION_UNITS) + TIME_UNITS
|
return unit in functions.get_keys(INFORMATION_UNITS) + TIME_UNITS
|
||||||
|
|
||||||
|
|
||||||
@ -56,10 +54,11 @@ def is_larger(unit_1, unit_2):
|
|||||||
|
|
||||||
|
|
||||||
def convert(value, source_unit, target_unit, fmt=False):
|
def convert(value, source_unit, target_unit, fmt=False):
|
||||||
"""Converts value from source_unit to target_unit. Returns a tuple
|
"""Converts value from source_unit to target_unit.
|
||||||
containing the converted value and target_unit. Having fmt set to True
|
|
||||||
causes the value to be formatted to 1 decimal digit if it's a decimal or
|
Returns a tuple containing the converted value and target_unit.
|
||||||
be formatted as integer if it's an integer.
|
Having fmt set to True causes the value to be formatted to 1 decimal digit
|
||||||
|
if it's a decimal or be formatted as integer if it's an integer.
|
||||||
|
|
||||||
E.g:
|
E.g:
|
||||||
|
|
||||||
@ -82,6 +81,7 @@ def convert(value, source_unit, target_unit, fmt=False):
|
|||||||
|
|
||||||
def normalize(value, unit):
|
def normalize(value, unit):
|
||||||
"""Converts the value so that it belongs to some expected range.
|
"""Converts the value so that it belongs to some expected range.
|
||||||
|
|
||||||
Returns the new value and new unit.
|
Returns the new value and new unit.
|
||||||
|
|
||||||
E.g:
|
E.g:
|
||||||
|
@ -44,8 +44,10 @@ class PageTitleMixin(object):
|
|||||||
page_title = ""
|
page_title = ""
|
||||||
|
|
||||||
def render_context_with_title(self, context):
|
def render_context_with_title(self, context):
|
||||||
"""This function takes in a context dict and uses it to render the
|
"""Render a page title and insert it into the context.
|
||||||
page_title variable, it then appends this title to the context using
|
|
||||||
|
This function takes in a context dict and uses it to render the
|
||||||
|
page_title variable. It then appends this title to the context using
|
||||||
the 'page_title' key. If there is already a page_title key defined in
|
the 'page_title' key. If there is already a page_title key defined in
|
||||||
context received then this function will do nothing.
|
context received then this function will do nothing.
|
||||||
"""
|
"""
|
||||||
@ -59,8 +61,10 @@ class PageTitleMixin(object):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def render_to_response(self, context):
|
def render_to_response(self, context):
|
||||||
"""This is an override of the default render_to_response function that
|
"""render_to_response() with a page title.
|
||||||
exists in the django generic views, this is here to inject the
|
|
||||||
|
This is an override of the default render_to_response function that
|
||||||
|
exists in the django generic views. This is here to inject the
|
||||||
page title into the context before the main template is rendered.
|
page title into the context before the main template is rendered.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -105,7 +109,9 @@ class APIView(HorizonTemplateView):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def get_data(self, request, context, *args, **kwargs):
|
def get_data(self, request, context, *args, **kwargs):
|
||||||
"""This method should handle any necessary API calls, update the
|
"""Load necessary API data into the context.
|
||||||
|
|
||||||
|
This method should handle any necessary API calls, update the
|
||||||
context object, and return the context object at the end.
|
context object, and return the context object at the end.
|
||||||
"""
|
"""
|
||||||
return context
|
return context
|
||||||
|
@ -79,11 +79,11 @@ class ActionMetaclass(forms.forms.DeclarativeFieldsMetaclass):
|
|||||||
@six.python_2_unicode_compatible
|
@six.python_2_unicode_compatible
|
||||||
@six.add_metaclass(ActionMetaclass)
|
@six.add_metaclass(ActionMetaclass)
|
||||||
class Action(forms.Form):
|
class Action(forms.Form):
|
||||||
"""An ``Action`` represents an atomic logical interaction you can have with
|
"""An ``Action`` represents an atomic logical interaction with the system.
|
||||||
the system. This is easier to understand with a conceptual example: in the
|
|
||||||
context of a "launch instance" workflow, actions would include "naming
|
This is easier to understand with a conceptual example: in the context of
|
||||||
the instance", "selecting an image", and ultimately "launching the
|
a "launch instance" workflow, actions would include "naming the instance",
|
||||||
instance".
|
"selecting an image", and ultimately "launching the instance".
|
||||||
|
|
||||||
Because ``Actions`` are always interactive, they always provide form
|
Because ``Actions`` are always interactive, they always provide form
|
||||||
controls, and thus inherit from Django's ``Form`` class. However, they
|
controls, and thus inherit from Django's ``Form`` class. However, they
|
||||||
@ -192,9 +192,10 @@ class Action(forms.Form):
|
|||||||
self.errors[NON_FIELD_ERRORS] = self.error_class([message])
|
self.errors[NON_FIELD_ERRORS] = self.error_class([message])
|
||||||
|
|
||||||
def handle(self, request, context):
|
def handle(self, request, context):
|
||||||
"""Handles any requisite processing for this action. The method should
|
"""Handles any requisite processing for this action.
|
||||||
return either ``None`` or a dictionary of data to be passed to
|
|
||||||
:meth:`~horizon.workflows.Step.contribute`.
|
The method should return either ``None`` or a dictionary of data
|
||||||
|
to be passed to :meth:`~horizon.workflows.Step.contribute`.
|
||||||
|
|
||||||
Returns ``None`` by default, effectively making it a no-op.
|
Returns ``None`` by default, effectively making it a no-op.
|
||||||
"""
|
"""
|
||||||
@ -216,8 +217,9 @@ class MembershipAction(Action):
|
|||||||
|
|
||||||
@six.python_2_unicode_compatible
|
@six.python_2_unicode_compatible
|
||||||
class Step(object):
|
class Step(object):
|
||||||
"""A step is a wrapper around an action which defines its context in a
|
"""A wrapper around an action which defines its context in a workflow.
|
||||||
workflow. It knows about details such as:
|
|
||||||
|
It knows about details such as:
|
||||||
|
|
||||||
* The workflow's context data (data passed from step to step).
|
* The workflow's context data (data passed from step to step).
|
||||||
|
|
||||||
@ -399,8 +401,9 @@ class Step(object):
|
|||||||
return self._action
|
return self._action
|
||||||
|
|
||||||
def prepare_action_context(self, request, context):
|
def prepare_action_context(self, request, context):
|
||||||
"""Allows for customization of how the workflow context is passed to
|
"""Hook to customize how the workflow context is passed to the action.
|
||||||
the action; this is the reverse of what "contribute" does to make the
|
|
||||||
|
This is the reverse of what "contribute" does to make the
|
||||||
action outputs sane for the workflow. Changes to the context are not
|
action outputs sane for the workflow. Changes to the context are not
|
||||||
saved globally here. They are localized to the action.
|
saved globally here. They are localized to the action.
|
||||||
|
|
||||||
@ -430,8 +433,9 @@ class Step(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def contribute(self, data, context):
|
def contribute(self, data, context):
|
||||||
"""Adds the data listed in ``contributes`` to the workflow's shared
|
"""Adds the data listed in ``contributes`` to the workflow's context.
|
||||||
context. By default, the context is simply updated with all the data
|
|
||||||
|
By default, the context is simply updated with all the data
|
||||||
returned by the action.
|
returned by the action.
|
||||||
|
|
||||||
Note that even if the value of one of the ``contributes`` keys is
|
Note that even if the value of one of the ``contributes`` keys is
|
||||||
@ -524,9 +528,10 @@ class UpdateMembersStep(Step):
|
|||||||
@six.python_2_unicode_compatible
|
@six.python_2_unicode_compatible
|
||||||
@six.add_metaclass(WorkflowMetaclass)
|
@six.add_metaclass(WorkflowMetaclass)
|
||||||
class Workflow(html.HTMLElement):
|
class Workflow(html.HTMLElement):
|
||||||
"""A Workflow is a collection of Steps. Its interface is very
|
"""A Workflow is a collection of Steps.
|
||||||
straightforward, but it is responsible for handling some very
|
|
||||||
important tasks such as:
|
Its interface is very straightforward, but it is responsible for handling
|
||||||
|
some very important tasks such as:
|
||||||
|
|
||||||
* Handling the injection, removal, and ordering of arbitrary steps.
|
* Handling the injection, removal, and ordering of arbitrary steps.
|
||||||
|
|
||||||
@ -777,8 +782,7 @@ class Workflow(html.HTMLElement):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def unregister(cls, step_class):
|
def unregister(cls, step_class):
|
||||||
"""Unregisters a :class:`~horizon.workflows.Step` from the workflow.
|
"""Unregisters a :class:`~horizon.workflows.Step` from the workflow."""
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
cls._cls_registry.remove(step_class)
|
cls._cls_registry.remove(step_class)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -786,14 +790,17 @@ class Workflow(html.HTMLElement):
|
|||||||
return cls._unregister(step_class)
|
return cls._unregister(step_class)
|
||||||
|
|
||||||
def validate(self, context):
|
def validate(self, context):
|
||||||
"""Hook for custom context data validation. Should return a boolean
|
"""Hook for custom context data validation.
|
||||||
value or raise :class:`~horizon.exceptions.WorkflowValidationError`.
|
|
||||||
|
Should return a booleanvalue or
|
||||||
|
raise :class:`~horizon.exceptions.WorkflowValidationError`.
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
"""Verified that all required data is present in the context and
|
"""Verifies that all required data is present in the context.
|
||||||
calls the ``validate`` method to allow for finer-grained checks
|
|
||||||
|
It also calls the ``validate`` method to allow for finer-grained checks
|
||||||
on the context data.
|
on the context data.
|
||||||
"""
|
"""
|
||||||
missing = self.depends_on - set(self.context.keys())
|
missing = self.depends_on - set(self.context.keys())
|
||||||
@ -814,10 +821,12 @@ class Workflow(html.HTMLElement):
|
|||||||
return self.validate(self.context)
|
return self.validate(self.context)
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
"""Finalizes a workflow by running through all the actions in order
|
"""Finalizes a workflow by running through all the actions.
|
||||||
and calling their ``handle`` methods. Returns ``True`` on full success,
|
|
||||||
or ``False`` for a partial success, e.g. there were non-critical
|
It runs all the actions in order and calling their ``handle`` methods.
|
||||||
errors. (If it failed completely the function wouldn't return.)
|
Returns ``True`` on full success, or ``False`` for a partial success,
|
||||||
|
e.g. there were non-critical errors.
|
||||||
|
(If it failed completely the function wouldn't return.)
|
||||||
"""
|
"""
|
||||||
partial = False
|
partial = False
|
||||||
for step in self.steps:
|
for step in self.steps:
|
||||||
@ -837,16 +846,18 @@ class Workflow(html.HTMLElement):
|
|||||||
return not partial
|
return not partial
|
||||||
|
|
||||||
def handle(self, request, context):
|
def handle(self, request, context):
|
||||||
"""Handles any final processing for this workflow. Should return a
|
"""Handles any final processing for this workflow.
|
||||||
boolean value indicating success.
|
|
||||||
|
Should return a boolean value indicating success.
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Returns a URL to redirect the user to upon completion. By default it
|
"""Returns a URL to redirect the user to upon completion.
|
||||||
will attempt to parse a ``success_url`` attribute on the workflow,
|
|
||||||
which can take the form of a reversible URL pattern name, or a
|
By default it will attempt to parse a ``success_url`` attribute on the
|
||||||
standard HTTP URL.
|
workflow, which can take the form of a reversible URL pattern name,
|
||||||
|
or a standard HTTP URL.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return urlresolvers.reverse(self.success_url)
|
return urlresolvers.reverse(self.success_url)
|
||||||
@ -854,8 +865,10 @@ class Workflow(html.HTMLElement):
|
|||||||
return self.success_url
|
return self.success_url
|
||||||
|
|
||||||
def format_status_message(self, message):
|
def format_status_message(self, message):
|
||||||
"""Hook to allow customization of the message returned to the user
|
"""Hook to allow customization of the message returned to the user.
|
||||||
upon successful or unsuccessful completion of the workflow.
|
|
||||||
|
This is called upon both successful or unsuccessful completion of
|
||||||
|
the workflow.
|
||||||
|
|
||||||
By default it simply inserts the workflow's name into the message
|
By default it simply inserts the workflow's name into the message
|
||||||
string.
|
string.
|
||||||
@ -894,10 +907,13 @@ class Workflow(html.HTMLElement):
|
|||||||
return self.request.get_full_path().partition('?')[0]
|
return self.request.get_full_path().partition('?')[0]
|
||||||
|
|
||||||
def add_error_to_step(self, message, slug):
|
def add_error_to_step(self, message, slug):
|
||||||
"""Adds an error to the workflow's Step with the
|
"""Adds an error message to the workflow's Step.
|
||||||
specified slug based on API issues. This is useful
|
|
||||||
when you wish for API errors to appear as errors on
|
This is useful when you wish for API errors to appear as errors
|
||||||
the form rather than using the messages framework.
|
on the form rather than using the messages framework.
|
||||||
|
|
||||||
|
The workflow's Step is specified by its slug.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
step = self.get_step(slug)
|
step = self.get_step(slug)
|
||||||
if step:
|
if step:
|
||||||
|
@ -29,8 +29,7 @@ from horizon import messages
|
|||||||
|
|
||||||
|
|
||||||
class WorkflowView(hz_views.ModalBackdropMixin, generic.TemplateView):
|
class WorkflowView(hz_views.ModalBackdropMixin, generic.TemplateView):
|
||||||
"""A generic class-based view which handles the intricacies of workflow
|
"""A generic view which handles the intricacies of workflow processing.
|
||||||
processing with minimal user configuration.
|
|
||||||
|
|
||||||
.. attribute:: workflow_class
|
.. attribute:: workflow_class
|
||||||
|
|
||||||
@ -67,8 +66,10 @@ class WorkflowView(hz_views.ModalBackdropMixin, generic.TemplateView):
|
|||||||
"on %s." % self.__class__.__name__)
|
"on %s." % self.__class__.__name__)
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
"""Returns initial data for the workflow. Defaults to using the GET
|
"""Returns initial data for the workflow.
|
||||||
parameters to allow pre-seeding of the workflow context values.
|
|
||||||
|
Defaults to using the GET parameters
|
||||||
|
to allow pre-seeding of the workflow context values.
|
||||||
"""
|
"""
|
||||||
return copy.copy(self.request.GET)
|
return copy.copy(self.request.GET)
|
||||||
|
|
||||||
@ -102,8 +103,10 @@ class WorkflowView(hz_views.ModalBackdropMixin, generic.TemplateView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def get_layout(self):
|
def get_layout(self):
|
||||||
"""returns classes for the workflow element in template based on
|
"""Returns classes for the workflow element in template.
|
||||||
the workflow characteristics
|
|
||||||
|
The returned classes are determied based on
|
||||||
|
the workflow characteristics.
|
||||||
"""
|
"""
|
||||||
if self.request.is_ajax():
|
if self.request.is_ajax():
|
||||||
layout = ['modal', ]
|
layout = ['modal', ]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user