# Imports
from django.contrib.humanize.templatetags.humanize import intcomma
from django.db import models
from django.urls import reverse, NoReverseMatch
from django.utils.translation import gettext_lazy as _
import logging
import os
from superdjango.assets.library import JavaScript, StyleSheet
from superdjango.forms import RequestEnabledModelForm
from urllib.parse import quote_plus
from ..constants import FILTER, LOCATION, ORIENTATION, PAGINATION
log = logging.getLogger(__name__)
# Exports
__all__ = (
"Actions",
"Aggregate",
"BaseUtil",
"BlankFooter",
"ChainedLookup",
"Choice",
"ContextMenu",
"DateIncrement",
"Default",
"Dropdown",
"FieldGroup",
"Fieldset",
"Filtering",
"FooterRow",
"FormStep",
"Help",
"HelpText",
"Limit",
"ListTypes",
"OnSelect",
"Ordering",
"Pagination",
"Tab",
"Toggle",
)
# Classes
[docs]class BaseUtil(object):
"""Base for utility classes, the primary purpose of which is to capture any additional kwargs and assign them to the
``attributes`` attribute. These may be
- flattened in templates to assign attributes to the element, or
- used programmatically using the getter.
"""
[docs] def __init__(self, **kwargs):
"""Initialize dynamic attributes."""
self.attributes = kwargs
def __getattr__(self, item):
return self.attributes.get(item)
def __repr__(self):
return "<%s>" % self.__class__.__name__
[docs]class Actions(object):
"""A collection of actions used to output buttons, links, or context menus.
This class may be used instead of a list of action names.
"""
[docs] def __init__(self, *items, enabled=True, label=None, location=LOCATION.DEFAULT):
"""Initialize the instance.
:param items: A list of action (verb) names to be included in the set. These may also be supplied as instances
that extend the :py:class:`BaseAction` class.
:param enabled: Indicates the actions are available to users. This may be toggled programmatically.
:type enabled: bool
:param label: The label for this group of actions. Defaults to ``_("Actions")`` but may be disabled by passing
an empty string.
:type label: str
:param location: The desired location of the actions.
:type location: str
"""
self.enabled = enabled
self.items = list(items)
self.location = location
if label is None:
self.label = _("Actions")
else:
self.label = label
def __iter__(self):
return iter(self.items)
def __repr__(self):
return "<%s %s (%s)>" % (self.__class__.__name__, self.label, len(self.items))
[docs]class Aggregate(object):
"""Specify options for loading aggregate data."""
AVG = "avg"
COUNT = "count"
MAX = "max"
MIN = "min"
STDDEV = "stddev"
SUM = "sum"
VARIANCE = "variance"
[docs] def __init__(self, field, callback=None, criteria=None, formatter=None, label=None):
"""Initialize an aggregate option.
:param field: The name of the field to be aggregated.
:type field: str
:param callback: The callback to use for aggregation. This may be a built-in name (``str``) or a callable. See
notes.
:type callback: callable | str
:param criteria: Optional criteria for filtering aggregate data.
:type criteria: dict
:param formatter: A callable that may be used to format the aggregate result. If should accept a single
parameter, which is the return value of query, and return a ``str``. Note that the value may
be ``None``. If omitted, a default formatter is applied.
:type formatter: callable
:param label: Optional label for the aggregated data.
:type label: str
The built-in callbacks are:
- avg
- count
- max
- min
- stddev
- sum
- variance
When providing a callback, it should exhibit the following signature:
``callback(field, queryset, request, ui, criteria=None)``
"""
self.callback = callback
self.criteria = criteria
self.field = field
self.formatter = formatter or self._default_formatter
self.label = label
self.value = None
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.field)
def __str__(self):
return self.formatter(self.value)
[docs] def load(self, queryset, request, ui):
"""Load the aggregate data. Sets the ``value`` attribute of the Aggregate instance.
:param queryset: The queryset to use for aggregation.
:type queryset: QuerySet
:param request: The current HTTP request instance. This is not used by default.
:param ui: The current model UI instance.
:type ui: ModelUI
:rtype: bool
:returns: ``True`` if a value was acquired. If ``False``, check the logs.
"""
'''
try:
if self.callback in ("average", "avg"):
return self._avg(queryset, request, ui)
elif self.callback == "count":
return self._count(queryset, request, ui)
elif self.callback in ("max", "maximum"):
return self._max(queryset, request, ui)
elif self.callback in ("min", "minimum"):
return self._min(queryset, request, ui)
elif self.callback in ("stddev", "standard_deviation"):
return self._stddev(queryset, request, ui)
elif self.callback in ("total", "sum"):
return self._sum(queryset, request, ui)
elif self.callback in ("var", "variance"):
return self._variance(queryset, request, ui)
elif callable(self.callback):
self.value = self.callback(self.field, queryset, request, ui, criteria=self.criteria)
return self.value is not None
else:
log.warning("Expected a built-in or custom callable for Aggregate callback: %s" % self.field)
return False
except AssertionError as e:
log.warning("%s Aggregate callback: %s" % (str(e), self.field))
return False
'''
# BUG: Utilizing criteria on a paginated queryset results in an AssertionError (Cannot filter a query once a
# slice has been taken). One workaround might be to provide a queryset as an keyword argument to __init__.
if self.callback in ("average", "avg"):
return self._avg(queryset, request, ui)
elif self.callback == "count":
return self._count(queryset, request, ui)
elif self.callback in ("max", "maximum"):
return self._max(queryset, request, ui)
elif self.callback in ("min", "minimum"):
return self._min(queryset, request, ui)
elif self.callback in ("stddev", "standard_deviation"):
return self._stddev(queryset, request, ui)
elif self.callback in ("total", "sum"):
return self._sum(queryset, request, ui)
elif self.callback in ("var", "variance"):
return self._variance(queryset, request, ui)
elif callable(self.callback):
self.value = self.callback(self.field, queryset, request, ui, criteria=self.criteria)
return self.value is not None
else:
log.warning("Expected a built-in or custom callable for Aggregate callback: %s" % self.field)
return False
# noinspection PyUnusedLocal
def _avg(self, queryset, request, ui):
output_key = "%s__avg" % self.field
if self.criteria is not None:
self.value = queryset.filter(**self.criteria).aggregate(models.Avg(self.field))[output_key]
else:
self.value = queryset.all().aggregate(models.Avg(self.field))[output_key]
return self.value is not None
# noinspection PyUnusedLocal
def _count(self, queryset, request, ui):
if self.criteria is not None:
self.value = queryset.filter(**self.criteria).count()
else:
self.value = queryset.all().count()
return self.value is not None
# noinspection PyMethodMayBeStatic
def _default_formatter(self, value):
"""The default formatter.
:param value: The (aggregate) value to be formatted.
:rtype: str
"""
if value is None:
return ""
if "." in str(value):
number, places = str(value).split(".")
if places == "0":
places = "00"
return "%s.%s" % (intcomma(number), places)
return intcomma(value)
# noinspection PyUnusedLocal
def _max(self, queryset, request, ui):
output_key = "%s__max" % self.field
if self.criteria is not None:
self.value = queryset.filter(**self.criteria).aggregate(models.Max(self.field))[output_key]
else:
self.value = queryset.all().aggregate(models.Max(self.field))[output_key]
return self.value is not None
# noinspection PyUnusedLocal
def _min(self, queryset, request, ui):
output_key = "%s__min" % self.field
if self.criteria is not None:
self.value = queryset.filter(**self.criteria).aggregate(models.Min(self.field))[output_key]
else:
self.value = queryset.all().aggregate(models.Min(self.field))[output_key]
return self.value is not None
# noinspection PyUnusedLocal
def _stddev(self, queryset, request, ui):
output_key = "%s__stddev" % self.field
if self.criteria is not None:
self.value = queryset.filter(**self.criteria).aggregate(models.StdDev(self.field))[output_key]
else:
self.value = queryset.all().aggregate(models.StdDev(self.field))[output_key]
return self.value is not None
# noinspection PyUnusedLocal
def _sum(self, queryset, request, ui):
output_key = "%s__sum" % self.field
if self.criteria is not None:
self.value = queryset.filter(**self.criteria).aggregate(models.Sum(self.field))[output_key]
else:
self.value = queryset.all().aggregate(models.Sum(self.field))[output_key]
return self.value is not None
# noinspection PyUnusedLocal
def _variance(self, queryset, request, ui):
output_key = "%s__variance" % self.field
if self.criteria is not None:
self.value = queryset.filter(**self.criteria).aggregate(models.Variance(self.field))[output_key]
else:
self.value = queryset.all().aggregate(models.Variance(self.field))[output_key]
return self.value is not None
[docs]class ChainedLookup(object):
"""Connects the possible values of a reference field to the value of another field."""
[docs] def __init__(self, source_field, target_field, empty_value=None, pattern_name=None):
"""Initialize the chained lookup.
:param source_field: The field name whose value is used to filter the possible values of the target field.
:type source_field: str
:param target_field: The field name whose values depend upon the selected value of the source field.
:type target_field: str
:param empty_value: The value to display when no ``source_field`` value is selected. Defaults to
"Select a <source_field>"
:type empty_value: str
:param pattern_name: The pattern name of the UIAjaxChainedLookupView (or compatible view) that is used for
acquiring the ``target_field`` values. In most cases, automatic resolution does not require
this parameter to be specified.
:type pattern_name: str
"""
self.empty_value = empty_value or _("Select %s" % source_field.replace("_", " ").title())
self.pattern_name = pattern_name
self.source_field = source_field
self.target_field = target_field
[docs] def get_url(self, ui):
"""Get the AJAX URL that provides the lookup.
:param ui: The current model UI instance that is utilizing the lookup.
:type ui: ModelUI
:rtype: str | None
"""
url = None
if self.pattern_name is not None:
pattern_name = self.pattern_name
try:
url = reverse(pattern_name)
except NoReverseMatch:
pass
elif ui.site is not None:
dotted = ui.get_remote_model(self.target_field, dotted=True)
url = ui.site.get_url(dotted, "ajax_chained_lookup")
else:
remote = ui.get_control(self.target_field).remote_field
# noinspection PyProtectedMember
pattern_name = "%s_%s_ajax_chained_lookup" % (
remote.model._meta.app_label,
remote.model._meta.model_name
)
try:
url = reverse(pattern_name)
except NoReverseMatch:
pass
return url
[docs]class Choice(BaseUtil):
"""A user choice."""
[docs] def __init__(self, label, value, abbr=None, description=None, icon=None, is_enabled=True, url=None, **kwargs):
self.abbr = abbr
self.description = description
self.icon = icon
self.is_enabled = is_enabled
self.label = label
self._url = url
self.value = value
super().__init__(**kwargs)
@property
def url(self):
"""Get the encoded URL.
:rtype: str
"""
return quote_plus(self._url)
[docs]class DateIncrement(object):
"""Automatically increment a date field based upon the value entered in another date field.
.. code-block:: python
from superdjango import ui
class ProjectUI(ui.ModelUI):
# ...
controls = {
'end_date': ui.controls.DateControl(increment=ui.DateIncrement(source_field="start_date", days=60)),
}
# ...
JavaScript is produced to perform the increment as soon as the source field is changed.
"""
[docs] def __init__(self, source_field, always=False, days=None, target_field=None):
"""Initialize an increment.
:param source_field: The field whose value is used to set the value of the target field.
:type source_field: str
:param always: Indicates the target field should always be updated, even if it already has a value.
:type always: bool
:param days: The number of days by which the source field value should be incremented. Default: ``30``.
:type days: int
:param target_field: The target field to be updated. This defaults to the field name to which the
``DateIncrement`` is attached.
:type target_field: str
"""
self.always = always
self.days = days or 30
self.source_field = source_field
self.target_field = target_field
def __repr__(self):
return "<%s %s <- %s>" % (self.__class__.__name__, self.source_field, self.target_field)
@property
def source_selector(self):
"""The jQuery selector for the source field.
:rtype: str
"""
return "#id_%s" % self.source_field
@property
def target_selector(self):
"""The jQuery selector for the target field.
:rtype: str
"""
return "#id_%s" % self.target_field
# def get_form_js(self):
# """Get the JavaScript needed to perform the increment.
#
# :rtype: JavaScript
#
# """
# js = JavaScript()
#
# js.append("moment.js", url="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.js")
# js.append("datetime.js", url="superdjango/ui/js/datetime.js")
#
# identifier = "increment %s from %s" % (self.target_field, self.source_field)
# template = os.path.join("superdjango", "ui", "js", "increment_date.js")
# js.from_template(identifier, template, context={'increment': self})
#
# return js
[docs]class Default(object):
"""Programmatically define a default value."""
[docs] def __init__(self, callback=None, takes_request=False, value=None):
"""Initialize the default.
:param callback: A function used to acquire the value.
:param takes_request: Indicates the callback utilizes the current request.
:type takes_request: bool
:param value: The default value.
The callback signature should be ``callback(record=None)``. However, when ``takes_request`` is ``True``, the
callback signature should be: ``callback(request, record=None)``
Additionally, the callback is expected to return the correct Python data type for the intended use of the
default value.
"""
self.callback = callback
self.takes_request = takes_request
self.value = value
def __repr__(self):
if self.callback is not None:
return "<%s callback:%s>" % (self.__class__.__name__, self.callback)
return "<%s value:%s>" % (self.__class__.__name__, self.value)
[docs] def get(self, request, record=None):
"""Get the default value.
:param request: The current HTTP request instance.
:param record: The record (model instance), if any, with which the default is associated.
"""
if self.callback is not None:
if self.takes_request:
return self.callback(request, record=record)
return self.callback(record=record)
return self.value
class Dropdown(object):
def __init__(self, *events, control_field=None):
self.control_field = control_field
self.events = events
def __iter__(self):
return iter(self.events)
def __repr__(self):
return "<%s %s (%s)>" % (self.__class__.__name__, self.control_field, len(self.events))
def get_form_js(self):
"""Get the JavaScript that provides the on select functionality.
:rtype: JavaScript
"""
js = JavaScript()
identifier = "%s dropdown events" % self.control_field
template = os.path.join("superdjango", "ui", "js", "on_select.js")
js.from_template(identifier, template, context={'dropdown': self})
return js
def get_values(self):
a = list()
for e in self.events:
if type(e.value) in (list, tuple):
for v in e.value:
if v not in a:
a.append(v)
elif e.value not in a:
a.append(e.value)
else:
pass
return a
[docs]class FieldGroup(object):
"""A grouping of fields."""
[docs] def __init__(self, *fields, label=None, size=None):
"""Initialize a field group.
:param fields: List of field names to include in the group.
:param label: A label for the group.
:type label: str
:param size: The size of the group's columns.
:type size: int
"""
self.fields = list(fields)
self.label = label
self.size = size
[docs]class Fieldset(BaseUtil):
"""A field set within a form."""
[docs] def __init__(self, *fields, classes=None, description=None, legend=None, **kwargs):
"""Initialize a fieldset.
:param fields: A list of field names to include in the fieldset.
:param classes: The CSS classes to be applied to the fieldset element.
:type classes: str
:param description: The description appears before fields.
:type description: str
:param legend: The fieldset legend.
:type legend: str
"""
self.description = description
self.fields = list(fields)
self.legend = legend
self._fields = list()
if classes is not None:
kwargs['class'] = classes
super().__init__(**kwargs)
def __iter__(self):
return iter(self._fields)
def __len__(self):
"""Required for iteration in templates."""
return len(self._fields)
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.legend)
[docs]class Filtering(object):
"""A utility class for holding filters and filter options."""
[docs] def __init__(self, *fields, default=None, location=LOCATION.DEFAULT, orientation=None, template=None):
"""Initialize filtering options for a list.
:param fields: A list of field names to be included as filters.
:param default: Establish the default filtering of the list. The criteria included here need not be related to
filter fields. Any request made to the list that does not include filtering will use this
criteria instead.
:type default: dict
:param location: The location of the filter on the page.
:type location: str
:param orientation: The orientation of the filter; ``ORIENTATION.HORIZONTAL`` or ``ORIENTATION.VERTICAL``. By
default, the orientation is selected based on the location.
:type orientation: str
:param template: The template to use for the filters. By default, the template is based upon the location and
orientation of the filters.
:type template: str
"""
self.default = default
self.fields = fields
self.filters = None
self.location = location
if orientation is not None:
self.orientation = orientation
elif location in (LOCATION.LEFT, LOCATION.RIGHT):
self.orientation = ORIENTATION.VERTICAL
else:
self.orientation = ORIENTATION.HORIZONTAL
if template is not None:
self.template = template
elif location in (LOCATION.LEFT, LOCATION.RIGHT):
self.template = "model_filter_" + FILTER.LISTGROUP
else:
self.template = "model_filter_" + FILTER.SELECT
[docs]class Help(object):
"""Encapsulate help resources."""
[docs] def __init__(self, articles=None, icon="fas fa-life-ring", include_titles=True, path=None, snippets=None, terms=None, title=None):
self.icon = icon
self.include_titles = include_titles
self.path = path
self.snippets = snippets or list()
self.terms = terms or list()
self.title = title or _("Help")
self._articles = articles or list()
@property
def articles(self):
"""Get the article instances included in the help.
:rtype: list[superdjango.contrib.support.library.Article]
"""
from superdjango.contrib.support.loader import content
a = list()
for slug in self._articles:
article = content.fetch(slug)
if article is not None:
a.append(article)
return a
[docs]class HelpText(object):
"""Manage help text programmatically.
.. code-block:: python
from django.utils.translation import ugettext_lazy as _
from superdjango import ui
from .models import MemberApplication
class MemberApplicationUI(ui.ModelUI):
model = MemberApplication
controls = {
'name': ui.controls.CharControl(
help_text={
ui.USER.NORMAL: ui.HelpText(_("Please enter your full name.")),
ui.USER.ROOT: ui.HelpText(_("The user's full name.")),
}
),
}
"""
[docs] def __init__(self, form_text, detail_text=None, list_text=None):
"""Initialize help text.
:param form_text: The text to display with form elements. Used for all other text when no other options are
provided.
:type form_text: str
:param list_text: The text to use for list display.
:type list_text: str
:param detail_text: The text to use for detail display.
:type detail_text: str
"""
self.detail_text = detail_text or form_text
self.form_text = form_text
self.list_text = list_text or form_text
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.form_text)
def __str__(self):
return str(self.form_text)
@property
def column_text(self):
"""An alias for ``list_text``."""
return self.list_text
@property
def help_text(self):
"""An alias for ``form_text``."""
return self.form_text
[docs]class Limit(object):
"""A utility for capturing options for results per page. Used by :py:class:`Pagination`, but may also be used to
exercise programmatic control over limit and limit display.
"""
WIDGET_BUTTON = "button"
WIDGET_DISABLED = None
WIDGET_LINKS = "links"
WIDGET_PILLS = "pills"
WIDGET_SELECT = "select"
WIDGET_TOOLBAR = "toolbar"
[docs] def __init__(self, increments=None, label=None, location=LOCATION.DEFAULT, value=PAGINATION.LIMIT,
widget=WIDGET_LINKS):
"""Initialize pagination limit configuration.
:param increments: A list of integers the user may select for results per page. Default: ``[10, 25, 50, 100]``
:type increments: list[int]
:param label: The label for the widget. Default: ``_("Results Per Page")``. Different widgets use the label in
different ways.
:type label: str
:param location: The desired location of the widget.
:type location: str
:param value: The initial results per page.
:type value: int
:param widget: The widget to use that allows the user to change the limit. Set to ``Limit.WIDGET_DISABLED``
(``None``) to disallow this feature.
:type widget: str | None
"""
self.increments = increments or [10, 25, 50, 100]
self.label = label or _("Results Per Page")
self.location = location
self.value = value
self.widget = widget
def __eq__(self, other):
return self.value == other
def __int__(self):
return self.value
def __str__(self):
return "%s" % self.value
[docs]class ListTypes(object):
"""Manage list type options."""
[docs] def __init__(self, *items, label=None, location=LOCATION.DEFAULT):
"""Initialize list type options.
:param items: A list of instances that extend :py:class:`superdjango.ui.options.lists.BaseList`.
:param label: The label for the list type switcher. Default: ``_("View As")``.
:type label: str
:param location: The desired location of the list type switcher.
:type location: str
"""
self.items = list(items)
self.label = label or _("View As")
self.location = location
def __contains__(self, item):
for i in self.items:
if i.get_context_name() == item:
return True
return False
def __getitem__(self, item):
if item == "label":
return self.label
if item == "location":
return self.location
for i in self.items:
if i.get_context_name() == item:
return i
return None
def __iter__(self):
return iter(self.items)
def __len__(self):
return len(self.items)
def __repr__(self):
return "<%s %s (%s)>" % (self.__class__.__name__, self.label, len(self.items))
def values(self):
return self.items
class OnSelect(object):
def __init__(self, value, target_field=None, default_value=None, required=None, visible=None):
"""Define what happens when an option is selected from a dropdown menu.
:param value: The selected value.
:param target_field: The field to be updated based on the selected value. See notes below.
:type target_field: dict | list | str | tuple
:param default_value: Set the target field value to this default.
:param required: Indicates the target field is required.
:type required: bool | None
:param visible: Indicates the target field should be made visible.
:type visible: bool | None
When the ``target_field`` is supplied as a `list` or `tuple`, the provided options are applied to all of the
named target fields. This is generally not what you want when providing a ``default_value``.
It is also possible to supply a dictionary of target fields where each target field name is the key and the
values are the options for that field. For example,
TODO: Document usage on OnSelect with dictionary.
Otherwise ``target_field`` is assumed to be a string, and the ``target_fields`` attribute is set to a list of
one element.
"""
self.default_value = default_value
self.required = required
self.target_field = target_field
self.target_values = None
self.value = value
self.visible = visible
if type(target_field) in (list, tuple):
self.target_fields = target_field
elif type(target_field) is dict:
self.target_fields = target_field.keys()
self.target_values = target_field
else:
self.target_fields = [target_field]
def __repr__(self):
return "<%s %s (%s)>" % (self.__class__.__name__, self.value, self.target_field)
[docs]class Ordering(object):
"""Encapsulates ordering options for a list."""
[docs] def __init__(self, direction="asc", fields=None, initial_field=None):
"""Initialize ordering options.
:param direction: The default ordering direction; ``asc`` or ``desc``.
:type direction: str
:param fields: A list of field names that may be used for ordering a list.
:type fields: list[str]
:param initial_field: The name of the field to by which ordering should be initially provided.
:type initial_field: str
"""
self.direction = direction
self.fields = fields or list()
self.initial_field = initial_field
[docs] def get_keyword(self, field):
"""Get the GET keyword and value for the field.
:rtype: str
"""
if field not in self.fields:
return ""
return "%s=%s" % (field, self.direction)
[docs]class Tab(BaseUtil):
"""Create tabbed interface for controls."""
[docs] def __init__(self, *fields, classes=None, description=None, icon=None, identifier=None, label=None, **kwargs):
"""Initialize a tab interface.
:param fields: A list of field names to include in the tab.
:param classes: The CSS classes to be applied to the fieldset element.
:type classes: str
:param description: The description appears before fields.
:type description: str
:param icon: An icon to display with the tab.
:type icon: str
:param identifier: The unique identifier for the tab. If omitted, it is derived from the label.
:type identifier: str
:param label: The label of the tab.
:type label: str
.. tip::
You may supply an class extending :py:class:`InlineModelUI` as the only field. This will cause the inline
formset to display as tabbed content. The ``identifier`` is used to match against the formset's prefix, so
you may need to specify this manually if the formset is not displayed.
"""
self.description = description
self.fields = list(fields)
self.icon = icon
self.identifier = identifier or label.replace(" ", "-").lower()
self.label = label
self._fields = list()
if classes is not None:
kwargs['class'] = classes
super().__init__(**kwargs)
def __iter__(self):
return iter(self._fields)
def __len__(self):
"""Required for iteration in templates."""
return len(self._fields)
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.label)
[docs]class Toggle(object):
"""Toggle the attributes of another field based on the value of a checkbox.
For example, imagine a to-do has a notes field that may be populated when *is complete* is checked:
.. code-block:: python
class TodoUI(ui.ModelUI):
# ...
controls = {
'is_complete': ui.controls.BooleanControl(toggle=ui.Toggle("close_notes", visible=True)),
}
# ...
"""
[docs] def __init__(self, target_field, control_field=None, default_value=None, inverse=False, required=None,
visible=None):
"""Initialize a toggle.
:param target_field: The field that is controlled by the toggle.
:param control_field: The name of the checkbox field that controls the toggle. This is assigned automatically
when using ModelUI.
:type control_field: str
:param default_value: Set a default value on the target field.
:param inverse: For checkboxes that are checked by default, toggle target fields when the box is *unchecked*.
:type inverse: bool
:param required: Indicates the target field should be required when the checkbox is checked.
:type required: bool
:param visible: Indicates the target field should be displayed when the checkbox is checked. This attribute may
also be given as a string; either "disabled" or "readonly". For the difference between these
two, see: https://stackoverflow.com/a/7730719/241720
:type visible: bool | str
"""
self.control_field = control_field
self.default_value = default_value
self.inverse = inverse
self.required = required
self.target_field = target_field
self.visible = visible
# def get_form_js(self):
# """Get the JavaScript that provides the toggle functionality.
#
# :rtype: JavaScript
#
# """
# js = JavaScript()
#
# identifier = "%s toggle" % self.control_field
# template = os.path.join("superdjango", "ui", "js", "toggle.js")
#
# js.from_template(identifier, template, context={'toggle': self})
#
# return js