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