diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 365f7777..46aa0284 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,19 +5,8 @@ repos: - id: black language_version: python3.11 - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.272 hooks: - - id: pyupgrade - args: [--py38-plus] - - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - - - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - + - id: ruff + args: [--fix, --exit-non-zero-on-fix] \ No newline at end of file diff --git a/django_tables2/columns/base.py b/django_tables2/columns/base.py index f5edf77f..51a59a8b 100644 --- a/django_tables2/columns/base.py +++ b/django_tables2/columns/base.py @@ -33,8 +33,9 @@ def column_for_field(self, field, **kwargs): """ Return a column object suitable for model field. - Returns: - `.Column` object or `None` + Returns + ------- + `..Column` object or `None` """ if field is None: return self.columns[0](**kwargs) @@ -70,25 +71,23 @@ class LinkTransform: def __init__(self, url=None, accessor=None, attrs=None, reverse_args=None): """ - arguments: - url (callable): If supplied, the result of this callable will be used as ``href`` attribute. - accessor (Accessor): if supplied, the accessor will be used to decide on which object - ``get_absolute_url()`` is called. - attrs (dict): Customize attributes for the ```` tag. - Values of the dict can be either static text or a - callable. The callable can optionally declare any subset - of the following keyword arguments: value, record, column, - bound_column, bound_row, table. These arguments will then - be passed automatically. - reverse_args (dict, tuple): Arguments to ``django.urls.reverse()``. If dict, the arguments - are assumed to be keyword arguments to ``reverse()``, if tuple, a ``(viewname, args)`` - or ``(viewname, kwargs)`` + Arguments: + --------- + url (callable): If supplied, the result of this callable will be used as ``href`` attribute. + accessor (Accessor): if supplied, the accessor will be used to decide on which object + ``get_absolute_url()`` is called. + attrs (dict): Customize attributes for the ```` tag. + Values of the dict can be either static text or a callable. The callable can optionally declare any subset + of the following keyword arguments: value, record, column, bound_column, bound_row, table. + These arguments will then be passed automatically. + reverse_args (dict, tuple): Arguments to ``django.urls.reverse()``. If dict, the arguments are assumed to be + keyword arguments to ``reverse()``, if tuple, a ``(viewname, args)`` or ``(viewname, kwargs)``. """ self.url = url self.attrs = attrs self.accessor = accessor - if isinstance(reverse_args, (list, tuple)): + if isinstance(reverse_args, list | tuple): viewname, args = reverse_args reverse_args = {"viewname": viewname} reverse_args["kwargs" if isinstance(args, dict) else "args"] = args @@ -120,9 +119,7 @@ def compose_url(self, **kwargs): return context.get_absolute_url() def call_reverse(self, record): - """ - Prepares the arguments to reverse() for this record and calls reverse() - """ + """Prepare the arguments to reverse() for this record and calls reverse().""" def resolve_if_accessor(val): return val.resolve(record) if isinstance(val, Accessor) else val @@ -166,60 +163,49 @@ class Column: within it) are rendered. Arguments: - attrs (dict): HTML attributes for elements that make up the column. - This API is extended by subclasses to allow arbitrary HTML - attributes to be added to the output. - - By default `.Column` supports: - - - ``th`` -- ``table/thead/tr/th`` elements - - ``td`` -- ``table/tbody/tr/td`` elements - - ``cell`` -- fallback if ``th`` or ``td`` is not defined - - ``a`` -- To control the attributes for the ``a`` tag if the cell - is wrapped in a link. - accessor (str or `~.Accessor`): An accessor that describes how to - extract values for this column from the :term:`table data`. - default (str or callable): The default value for the column. This can be - a value or a callable object [1]_. If an object in the data provides - `None` for a column, the default will be used instead. - - The default value may affect ordering, depending on the type of data - the table is using. The only case where ordering is not affected is - when a `.QuerySet` is used as the table data (since sorting is - performed by the database). - empty_values (iterable): list of values considered as a missing value, - for which the column will render the default value. Defaults to - `(None, '')` - exclude_from_export (bool): If `True`, this column will not be added to - the data iterator returned from as_values(). - footer (str, callable): Defines the footer of this column. If a callable - is passed, it can take optional keyword arguments `column`, - `bound_column` and `table`. - order_by (str, tuple or `.Accessor`): Allows one or more accessors to be - used for ordering rather than *accessor*. - orderable (bool): If `False`, this column will not be allowed to - influence row ordering/sorting. - verbose_name (str): A human readable version of the column name. - visible (bool): If `True`, this column will be rendered. - Columns with `visible=False` will not be rendered, but will be included - in ``.Table.as_values()`` and thus also in :ref:`export`. - localize: If the cells in this column will be localized by the - `localize` filter: - - - If `True`, force localization - - If `False`, values are not localized - - If `None` (default), localization depends on the ``USE_L10N`` setting. - linkify (bool, str, callable, dict, tuple): Controls if cell content will be wrapped in an - ``a`` tag. The different ways to define the ``href`` attribute: - - - If `True`, the ``record.get_absolute_url()`` or the related model's - `get_absolute_url()` is used. - - If a callable is passed, the returned value is used, if it's not ``None``. - The callable can optionally accept any argument valid for :ref:`table.render_foo`-methods, - for example `record` or `value`. - - If a `dict` is passed, it's passed on to ``~django.urls.reverse``. - - If a `tuple` is passed, it must be either a (viewname, args) or (viewname, kwargs) - tuple, which is also passed to ``~django.urls.reverse``. + --------- + attrs (dict): HTML attributes for elements that make up the column. + This API is extended by subclasses to allow arbitrary HTML attributes to be added to the output. + + By default `.Column` supports: + + - ``th`` -- ``table/thead/tr/th`` elements + - ``td`` -- ``table/tbody/tr/td`` elements + - ``cell`` -- fallback if ``th`` or ``td`` is not defined + - ``a`` -- To control the attributes for the ``a`` tag if the cell + is wrapped in a link. + accessor (str or `~.Accessor`): An accessor that describes how to extract values for this column from the + :term:`table data`. + default (str or callable): The default value for the column. + This can be a value or a callable object [1]_. If an object in the data provides `None` for a column, the + default will be used instead. The default value may affect ordering, depending on the type of data the table is + using. The only case where ordering is not affected is when a `.QuerySet` is used as the table data (since + sorting is performed by the database). + empty_values (iterable): list of values considered as a missing value, for which the column will render the default + value. Defaults to `(None, '')` + exclude_from_export (bool): If `True`, the column will not be added to the data iterator returned from as_values(). + footer (str, callable): Defines the footer of this column. If a callable is passed, it can take optional keyword + arguments `column`, `bound_column` and `table`. + order_by (str, tuple or `.Accessor`): Allows one or more accessors to be used for ordering rather than *accessor*. + orderable (bool): If `False`, this column will not be allowed to influence row ordering/sorting. + verbose_name (str): A human readable version of the column name. + visible (bool): If `True`, this column will be rendered. Columns with `visible=False` will not be rendered, but + will be included in ``.Table.as_values()`` and thus also in :ref:`export`. + localize: If the cells in this column will be localized by the `localize` filter: + - If `True`, force localization + - If `False`, values are not localized + - If `None` (default), localization depends on the ``USE_L10N`` setting. + linkify (bool, str, callable, dict, tuple): Controls if cell content will be wrapped in an ``a`` tag. The different + ways to define the ``href`` attribute: + + - If `True`, the ``record.get_absolute_url()`` or the related model's + `get_absolute_url()` is used. + - If a callable is passed, the returned value is used, if it's not ``None``. + The callable can optionally accept any argument valid for :ref:`table.render_foo`-methods, + for example `record` or `value`. + - If a `dict` is passed, it's passed on to ``~django.urls.reverse``. + - If a `tuple` is passed, it must be either a (viewname, args) or (viewname, kwargs) + tuple, which is also passed to ``~django.urls.reverse``. Examples, assuming this model:: @@ -301,7 +287,7 @@ def __init__( link_kwargs = None if callable(linkify) or hasattr(self, "get_url"): link_kwargs = dict(url=linkify if callable(linkify) else self.get_url) - elif isinstance(linkify, (dict, tuple)): + elif isinstance(linkify, dict | tuple): link_kwargs = dict(reverse_args=linkify) elif linkify is True: link_kwargs = dict(accessor=self.accessor) @@ -357,13 +343,11 @@ def render(self, value): """ Return the content for a specific cell. - This method can be overridden by :ref:`table.render_FOO` methods on the - table or by subclassing `.Column`. + This method can be overridden by :ref:`table.render_FOO` methods on the table or by subclassing `.Column`. - If the value for this cell is in `.empty_values`, this method is - skipped and an appropriate default value is rendered instead. - Subclasses should set `.empty_values` to ``()`` if they want to handle - all values in `.render`. + If the value for this cell is in `.empty_values`, this method is skipped and an appropriate default value is + rendered instead. + Subclasses should set `.empty_values` to ``()`` if they want to handle all values in `.render`. """ return value @@ -372,12 +356,11 @@ def value(self, **kwargs): Return the content for a specific cell for exports. Similar to `.render` but without any html content. - This can be used to get the data in the formatted as it is presented but in a - form that could be added to a csv file. + This can be used to get the data in the formatted as it is presented but in a form that could be added to a csv + file. - The default implementation just calls the `render` function but any - subclasses where `render` returns html content should override this - method. + The default implementation just calls the `render` function but any subclasses where `render` returns html + content should override this method. See `LinkColumn` for an example. """ @@ -389,12 +372,12 @@ def order(self, queryset, is_descending): """ Order the QuerySet of the table. - This method can be overridden by :ref:`table.order_FOO` methods on the - table or by subclassing `.Column`; but only overrides if second element - in return tuple is True. + This method can be overridden by :ref:`table.order_FOO` methods on the table or by subclassing `.Column`; but + only overrides if second element in return tuple is True. - returns: - Tuple (QuerySet, boolean) + Returns + ------- + Tuple (QuerySet, boolean) """ return (queryset, False) @@ -404,15 +387,17 @@ def from_field(cls, field, **kwargs): Return a specialized column for the model field or `None`. Arguments: - field (Model Field instance): the field that needs a suitable column - Returns: - `.Column` object or `None` + --------- + field (Model Field instance): the field that needs a suitable column. + **kwargs: any - If the column is not specialized for the given model field, it should - return `None`. This gives other columns the opportunity to do better. + Returns: + ------- + `.Column` object or `None` - If the column is specialized, it should return an instance of itself - that is configured appropriately for the field. + If the column is not specialized for the given model field, it should return `None`. This gives other columns + the opportunity to do better. + If the column is specialized, it should return an properly configured instance of itself for the field. """ # Since this method is inherited by every subclass, only provide a # column if this class was asked directly. @@ -429,7 +414,8 @@ class BoundColumn: In practice, this means that a `.BoundColumn` knows the *"variable name"* given to the `.Column` when it was declared on the `.Table`. - arguments: + Arguments: + --------- table (`~.Table`): The table in which this column exists column (`~.Column`): The type of column name (str): The variable name of the column used when defining the @@ -453,7 +439,7 @@ def __str__(self): @property def accessor(self): - """Returns the string used to access data for this column out of the data source.""" + """Return the string used to access data for this column out of the data source.""" return self.column.accessor or Accessor(self.name) @property @@ -466,7 +452,6 @@ def attrs(self): templates easier. ``tf`` is not actually a HTML tag, but this key name will be used for attributes for column's footer, if the column has one. """ - # prepare kwargs for computed_values() kwargs = {"table": self._table, "bound_column": self} # BoundRow.items() sets current_record and current_value when iterating over @@ -504,25 +489,19 @@ def attrs(self): return attrs def _get_cell_class(self, attrs): - """ - Return a set of the classes from the class key in ``attrs``. - """ + """Return a set of the classes from the class key in ``attrs``.""" classes = attrs.get("class", None) classes = set() if classes is None else set(classes.split(" ")) return self._table.get_column_class_names(classes, self) def get_td_class(self, td_attrs): - """ - Returns the HTML class attribute for a data cell in this column - """ + """Return the HTML class attribute for a data cell in this column.""" classes = sorted(self._get_cell_class(td_attrs)) return None if len(classes) == 0 else " ".join(classes) def get_th_class(self, th_attrs): - """ - Returns the HTML class attribute for a header cell in this column - """ + """Return the HTML class attribute for a header cell in this column.""" classes = self._get_cell_class(th_attrs) # add classes for ordering @@ -540,7 +519,7 @@ def get_th_class(self, th_attrs): @property def default(self): - """Returns the default value for this column.""" + """Return the default value for this column.""" value = self.column.default if value is None: value = self._table.default @@ -699,7 +678,7 @@ def visible(self): @property def localize(self): - """Return `True`, `False` or `None` as described in ``Column.localize``""" + """Return `True`, `False` or `None` as described in ``Column.localize``.""" return self.column.localize @@ -721,6 +700,7 @@ class BoundColumns: `.Table.columns` property. Arguments: + --------- table (`.Table`): the table containing the columns """ @@ -743,10 +723,7 @@ def names(self): return list(self.iternames()) def iterall(self): - """ - Return an iterator that exposes all `.BoundColumn` objects, - regardless of visibility or sortability. - """ + """Return an iterator that exposes all `.BoundColumn` objects, regardless of visibility or sortability.""" return (column for name, column in self.iteritems()) def all(self): @@ -756,11 +733,9 @@ def iteritems(self): """ Return an iterator of ``(name, column)`` pairs (where ``column`` is a `BoundColumn`). - This method is the mechanism for retrieving columns that takes into - consideration all of the ordering and filtering modifiers that a table - supports (e.g. `~Table.Meta.exclude` and `~Table.Meta.sequence`). + This method is the mechanism for retrieving columns that takes into consideration all of the ordering and + filtering modifiers that a table supports (e.g. `~Table.Meta.exclude` and `~Table.Meta.sequence`). """ - for name in self._table.sequence: if name not in self._table.exclude: yield (name, self.columns[name]) @@ -770,43 +745,28 @@ def items(self): def iterorderable(self): """ - Same as `BoundColumns.all` but only returns orderable columns. + Return an ordered variant of `BoundColumns.all`. - This is useful in templates, where iterating over the full - set and checking ``{% if column.ordarable %}`` can be problematic in - conjunction with e.g. ``{{ forloop.last }}`` (the last column might not - be the actual last that is rendered). + This is useful in templates, where iterating over the full set and checking ``{% if column.ordarable %}`` can + be problematic in conjunction with e.g. ``{{ forloop.last }}`` (the last column might not be the actual last + that is rendered). """ return (x for x in self.iterall() if x.orderable) def itervisible(self): - """ - Same as `.iterorderable` but only returns visible `.BoundColumn` objects. - - This is geared towards table rendering. - """ + """Ruturn all visible `.BoundColumn` objects for table rendering.""" return (x for x in self.iterall() if x.visible) def hide(self, name): - """ - Hide a column. - - Arguments: - name(str): name of the column - """ + """Hide column with ``name``.""" self.columns[name].column.visible = False def show(self, name): - """ - Show a column otherwise hidden. - - Arguments: - name(str): name of the column - """ + """Show column with ``name`` if it was otherwise hidden.""" self.columns[name].column.visible = True def __iter__(self): - """Convenience API, alias of `.itervisible`.""" + """Iterate over visible columns, same as `.itervisible()`.""" return self.itervisible() def __contains__(self, item): diff --git a/django_tables2/columns/booleancolumn.py b/django_tables2/columns/booleancolumn.py index d3bcbb28..a2ea8848 100644 --- a/django_tables2/columns/booleancolumn.py +++ b/django_tables2/columns/booleancolumn.py @@ -11,6 +11,7 @@ class BooleanColumn(Column): A column suitable for rendering boolean data. Arguments: + --------- null (bool): is `None` different from `False`? yesno (str): comma separated values string or 2-tuple to display for True/False values. @@ -52,9 +53,7 @@ def render(self, value, record, bound_column): return format_html("{}", AttributeDict(attrs).as_html(), escape(text)) def value(self, record, value, bound_column): - """ - Returns the content for a specific cell similarly to `.render` however without any html content. - """ + """Return the content for a specific cell similarly to `.render` however without any html content.""" value = self._get_bool_value(record, value, bound_column) return str(value) diff --git a/django_tables2/columns/checkboxcolumn.py b/django_tables2/columns/checkboxcolumn.py index caecb7f9..0e3b383b 100644 --- a/django_tables2/columns/checkboxcolumn.py +++ b/django_tables2/columns/checkboxcolumn.py @@ -25,6 +25,7 @@ class CheckBoxColumn(Column): - ``orderable`` defaults to `False`. Arguments: + --------- attrs (dict): In addition to *attrs* keys supported by `~.Column`, the following are available: @@ -71,9 +72,7 @@ def render(self, value, bound_column, record): return mark_safe(f"") def is_checked(self, value, record): - """ - Determine if the checkbox should be checked - """ + """Determine if the checkbox should be checked.""" if self.checked is None: return False if self.checked is True: diff --git a/django_tables2/columns/datecolumn.py b/django_tables2/columns/datecolumn.py index ef500115..03286d58 100644 --- a/django_tables2/columns/datecolumn.py +++ b/django_tables2/columns/datecolumn.py @@ -10,6 +10,7 @@ class DateColumn(TemplateColumn): A column that renders dates in the local timezone. Arguments: + --------- format (str): format string in same format as Django's ``date`` template filter (optional) short (bool): if `format` is not specified, use Django's diff --git a/django_tables2/columns/datetimecolumn.py b/django_tables2/columns/datetimecolumn.py index 097735e5..2fc5130f 100644 --- a/django_tables2/columns/datetimecolumn.py +++ b/django_tables2/columns/datetimecolumn.py @@ -10,6 +10,7 @@ class DateTimeColumn(TemplateColumn): A column that renders `datetime` instances in the local timezone. Arguments: + --------- format (str): format string for datetime (optional). Note that *format* uses Django's `date` template tag syntax. short (bool): if `format` is not specified, use Django's diff --git a/django_tables2/columns/emailcolumn.py b/django_tables2/columns/emailcolumn.py index 16561517..ff3486c8 100644 --- a/django_tables2/columns/emailcolumn.py +++ b/django_tables2/columns/emailcolumn.py @@ -10,6 +10,7 @@ class EmailColumn(BaseLinkColumn): Render email addresses to `mailto:`-links. Arguments: + --------- attrs (dict): HTML attributes that are added to the rendered ``...`` tag. text: Either static text, or a callable. If set, this will be used to diff --git a/django_tables2/columns/filecolumn.py b/django_tables2/columns/filecolumn.py index 930f6ad3..4a0fb9f0 100644 --- a/django_tables2/columns/filecolumn.py +++ b/django_tables2/columns/filecolumn.py @@ -26,6 +26,7 @@ class FileColumn(BaseLinkColumn): `.Column.attrs` keys ``a`` and ``span`` can be used to add additional attributes. Arguments: + --------- verify_exists (bool): attempt to determine if the file exists If *verify_exists*, the HTML class ``exists`` or ``missing`` is added to the element to indicate the integrity of the storage. diff --git a/django_tables2/columns/jsoncolumn.py b/django_tables2/columns/jsoncolumn.py index c1a0f520..77c7e8ba 100644 --- a/django_tables2/columns/jsoncolumn.py +++ b/django_tables2/columns/jsoncolumn.py @@ -35,6 +35,7 @@ class JSONColumn(BaseLinkColumn): used manually without it. Arguments: + --------- json_dumps_kwargs: kwargs passed to `json.dumps`, defaults to `{'indent': 2}` attrs (dict): In addition to *attrs* keys supported by `~.Column`, the following are available: @@ -60,5 +61,5 @@ def render(self, record, value): @classmethod def from_field(cls, field, **kwargs): if POSTGRES_AVAILABLE: - if isinstance(field, (JSONField, HStoreField)): + if isinstance(field, JSONField | HStoreField): return cls(**kwargs) diff --git a/django_tables2/columns/linkcolumn.py b/django_tables2/columns/linkcolumn.py index adf58762..3643358c 100644 --- a/django_tables2/columns/linkcolumn.py +++ b/django_tables2/columns/linkcolumn.py @@ -6,13 +6,11 @@ class BaseLinkColumn(Column): The base for other columns that render links. Arguments: - text (str or callable): If set, this value will be used to render the - text inside link instead of value. The callable gets the record - being rendered as argument. - attrs (dict): In addition to ``attrs`` keys supported by `~.Column`, the - following are available: - - - `a` -- ```` in ```` elements. + --------- + text (str or callable): If set, this value will be used to render the text inside link instead of value. + The callable gets the record being rendered as argument. + attrs (dict): In addition to ``attrs`` keys supported by `~.Column`, the following are available: + - `a` -- ```` in ```` elements. """ def __init__(self, text=None, *args, **kwargs): @@ -25,10 +23,7 @@ def text_value(self, record, value): return self.text(record) if callable(self.text) else self.text def value(self, record, value): - """ - Returns the content for a specific cell similarly to `.render` however - without any html content. - """ + """Return the content for a specific cell similarly to `.render` however without any html content.""" return self.text_value(record, value) def render(self, record, value): @@ -56,6 +51,7 @@ class LinkColumn(BaseLinkColumn): rendered ```` tag. Arguments: + --------- viewname (str or None): See `~django.urls.reverse`, or use `None` to use the model's `get_absolute_url` urlconf (str): See `~django.urls.reverse`. @@ -74,7 +70,7 @@ class LinkColumn(BaseLinkColumn): `~django.urls.reverse` is called. Example: - + ------- .. code-block:: python # models.py diff --git a/django_tables2/columns/manytomanycolumn.py b/django_tables2/columns/manytomanycolumn.py index 30c9f0e0..6704d544 100644 --- a/django_tables2/columns/manytomanycolumn.py +++ b/django_tables2/columns/manytomanycolumn.py @@ -8,11 +8,12 @@ @library.register class ManyToManyColumn(Column): """ - Display the list of objects from a `ManyRelatedManager` + Display the list of objects from a `ManyRelatedManager`. Ordering is disabled for this column. Arguments: + --------- transform: callable to transform each item to text, it gets an item as argument and must return a string-like representation of the item. By default, it calls `~django.utils.force_str` on each item. @@ -62,7 +63,7 @@ def __init__( link_kwargs = None if callable(linkify_item): link_kwargs = dict(url=linkify_item) - elif isinstance(linkify_item, (dict, tuple)): + elif isinstance(linkify_item, dict | tuple): link_kwargs = dict(reverse_args=linkify_item) elif linkify_item is True: link_kwargs = dict() @@ -71,9 +72,7 @@ def __init__( self.linkify_item = LinkTransform(attrs=self.attrs.get("a", {}), **link_kwargs) def transform(self, obj): - """ - Transform is applied to each item of the list of objects from the ManyToMany relation. - """ + """Transform is applied to each item of the list of objects from the ManyToMany relation.""" return force_str(obj) def filter(self, qs): diff --git a/django_tables2/columns/templatecolumn.py b/django_tables2/columns/templatecolumn.py index ddddc24a..00931003 100644 --- a/django_tables2/columns/templatecolumn.py +++ b/django_tables2/columns/templatecolumn.py @@ -12,9 +12,10 @@ class TemplateColumn(Column): the cell value. Arguments: - template_code (str): template code to render - template_name (str): name of the template to render - extra_context (dict): optional extra template context + --------- + template_code (str): template code to render + template_name (str): name of the template to render + extra_context (dict): optional extra template context A `~django.template.Template` object is created from the *template_code* or *template_name* and rendered with a context containing: @@ -26,7 +27,7 @@ class TemplateColumn(Column): - any context variables passed using the `extra_context` argument to `TemplateColumn`. Example: - + ------- .. code-block:: python class ExampleTable(tables.Table): @@ -69,9 +70,10 @@ def render(self, record, table, value, bound_column, **kwargs): def value(self, **kwargs): """ - The value returned from a call to `value()` on a `TemplateColumn` is - the rendered template with `django.utils.html.strip_tags` applied. - Leading and trailing whitespace is stripped. + Value of this column or exports. + + The value returned from a call to `value()` on a `TemplateColumn` is the rendered template with + `django.utils.html.strip_tags` applied. Leading and trailing whitespace is stripped. """ html = super().value(**kwargs) return strip_tags(html).strip() if isinstance(html, str) else html diff --git a/django_tables2/columns/timecolumn.py b/django_tables2/columns/timecolumn.py index b4564fc2..f2a317e4 100644 --- a/django_tables2/columns/timecolumn.py +++ b/django_tables2/columns/timecolumn.py @@ -10,6 +10,7 @@ class TimeColumn(TemplateColumn): A column that renders times in the local timezone. Arguments: + --------- format (str): format string in same format as Django's ``time`` template filter (optional). short (bool): if *format* is not specified, use Django's ``TIME_FORMAT`` setting. """ diff --git a/django_tables2/columns/urlcolumn.py b/django_tables2/columns/urlcolumn.py index 69a49777..41bc728e 100644 --- a/django_tables2/columns/urlcolumn.py +++ b/django_tables2/columns/urlcolumn.py @@ -10,6 +10,7 @@ class URLColumn(BaseLinkColumn): Renders URL values as hyperlinks. Arguments: + --------- text (str or callable): Either static text, or a callable. If set, this will be used to render the text inside link instead of value (default) attrs (dict): Additional attributes for the ```` tag diff --git a/django_tables2/config.py b/django_tables2/config.py index 097149fc..1326be83 100644 --- a/django_tables2/config.py +++ b/django_tables2/config.py @@ -8,6 +8,7 @@ class RequestConfig: A single RequestConfig can be used for multiple tables in one view. Arguments: + --------- paginate (dict or bool): Indicates whether to paginate, and if so, what default values to use. If the value evaluates to `False`, pagination will be disabled. A `dict` can be used to specify default values for @@ -35,7 +36,8 @@ def configure(self, table): Configure a table using information from the request. Arguments: - table (`~.Table`): table to be configured + --------- + table (`~.Table`): table to be configured """ table.request = self.request diff --git a/django_tables2/data.py b/django_tables2/data.py index c4900c63..ebb77b40 100644 --- a/django_tables2/data.py +++ b/django_tables2/data.py @@ -6,17 +6,13 @@ class TableData: - """ - Base class for table data containers. - """ + """Base class for table data containers.""" def __init__(self, data): self.data = data def __getitem__(self, key): - """ - Slicing returns a new `.TableData` instance, indexing returns a single record. - """ + """Slicing returns a new `.TableData` instance, indexing returns a single record.""" return self.data[key] def __iter__(self): @@ -29,10 +25,9 @@ def __iter__(self): def set_table(self, table): """ - `Table.__init__` calls this method to inject an instance of itself into the - `TableData` instance. - Good place to do additional checks if Table and TableData instance will work - together properly. + Set the table attribute, for use by `Table.__init__`. + + Good place to do additional checks if Table and TableData instance will work together properly. """ self.table = table @@ -63,31 +58,28 @@ def from_data(data): return TableListData(list(data)) raise ValueError( - "data must be QuerySet-like (have count() and order_by()) or support" - f" list(data) -- {type(data).__name__} has neither" + "data must be QuerySet-like (have count() and order_by()) or support list(data) -- " + f"{type(data).__name__} has neither" ) class TableListData(TableData): """ - Table data container for a list of dicts, for example:: + Table data container for a list of dicts. + - [ - {'name': 'John', 'age': 20}, - {'name': 'Brian', 'age': 25} - ] + Example:: + + [{"name": "John", "age": 20}, {"name": "Brian", "age": 25}] .. note:: - Other structures might have worked in the past, but are not explicitly - supported or tested. + Other structures might have worked in the past, but are not explicitly supported or tested. """ @staticmethod def validate(data): - """ - Validates `data` for use in this container - """ + """Validate `data` for use in this container.""" return hasattr(data, "__iter__") or ( hasattr(data, "__len__") and hasattr(data, "__getitem__") ) @@ -105,13 +97,12 @@ def verbose_name_plural(self): def order_by(self, aliases): """ - Order the data based on order by aliases (prefixed column names) in the - table. + Order the data based on order by aliases (prefixed column names) in the table. Arguments: - aliases (`~.utils.OrderByTuple`): optionally prefixed names of - columns ('-' indicates descending order) in order of - significance with regard to data ordering. + --------- + aliases (`~.utils.OrderByTuple`): optionally prefixed names of columns ('-' indicates descending order) in + order of significance with regard to data ordering. """ accessors = [] for alias in aliases: @@ -129,15 +120,11 @@ def order_by(self, aliases): class TableQuerysetData(TableData): - """ - Table data container for a queryset. - """ + """Table data container for a queryset.""" @staticmethod def validate(data): - """ - Validates `data` for use in this container - """ + """Validate `data` for use in this container.""" return ( hasattr(data, "count") and callable(data.count) @@ -146,7 +133,7 @@ def validate(data): ) def __len__(self): - """Cached data length""" + """Return cached data length.""" if not hasattr(self, "_length") or self._length is None: if hasattr(self.table, "paginator"): # for paginated tables, use QuerySet.count() as we are interested in total number of records. @@ -176,7 +163,6 @@ def ordering(self): This works by inspecting the actual underlying data. As such it's only supported for querysets. """ - aliases = {} for bound_column in self.table.columns: aliases[bound_column.order_by_alias] = bound_column.order_by @@ -191,9 +177,9 @@ def order_by(self, aliases): table. Arguments: - aliases (`~.utils.OrderByTuple`): optionally prefixed names of - columns ('-' indicates descending order) in order of - significance with regard to data ordering. + --------- + aliases (`~.utils.OrderByTuple`): optionally prefixed names of columns ('-' indicates descending order) in + order of significance with regard to data ordering. """ modified_any = False accessors = [] @@ -226,7 +212,7 @@ def order_by(self, aliases): @cached_property def verbose_name(self): """ - The full (singular) name for the data. + Return the full singular name for the data. Model's `~django.db.Model.Meta.verbose_name` is honored. """ @@ -235,7 +221,7 @@ def verbose_name(self): @cached_property def verbose_name_plural(self): """ - The full (plural) name for the data. + Return the full plural name for the data. Model's `~django.db.Model.Meta.verbose_name` is honored. """ diff --git a/django_tables2/export/export.py b/django_tables2/export/export.py index 49bda338..fdf0277c 100644 --- a/django_tables2/export/export.py +++ b/django_tables2/export/export.py @@ -14,6 +14,7 @@ class TableExport: Export data from a table to the file type specified. Arguments: + --------- export_format (str): one of `csv, json, latex, ods, tsv, xls, xlsx, yaml` table (`~.Table`): instance of the table to export the data from @@ -72,30 +73,24 @@ def default_dataset_title(): @classmethod def is_valid_format(self, export_format): - """ - Returns true if `export_format` is one of the supported export formats - """ + """Return true if `export_format` is one of the supported export formats.""" return export_format is not None and export_format in TableExport.FORMATS.keys() def content_type(self): - """ - Returns the content type for the current export format - """ + """Return the content type for the current export format.""" return self.FORMATS[self.format] def export(self): - """ - Returns the string/bytes for the current export format - """ + """Return the string/bytes for the current export format.""" return self.dataset.export(self.format) def response(self, filename=None): """ - Builds and returns a `HttpResponse` containing the exported data + Build and return a `HttpResponse` containing the exported data. Arguments: - filename (str): if not `None`, the filename is attached to the - `Content-Disposition` header of the response. + --------- + filename (str): if not `None`, the filename is attached to the `Content-Disposition` header of the response. """ response = HttpResponse(content_type=self.content_type()) if filename is not None: diff --git a/django_tables2/export/views.py b/django_tables2/export/views.py index bb434f2e..92a3097e 100644 --- a/django_tables2/export/views.py +++ b/django_tables2/export/views.py @@ -7,7 +7,8 @@ class ExportMixin: `ExportMixin` looks for some attributes on the class to change it's behavior: - Attributes: + Attributes + ---------- export_class (TableExport): Allows using a custom implementation of `TableExport`. export_name (str): is the name of file that will be exported, without extension. export_trigger_param (str): is the name of the GET attribute used to trigger diff --git a/django_tables2/rows.py b/django_tables2/rows.py index de5d0c1c..da4727e3 100644 --- a/django_tables2/rows.py +++ b/django_tables2/rows.py @@ -7,9 +7,7 @@ class CellAccessor: - """ - Allows accessing cell contents on a row object (see `BoundRow`) - """ + """Allows accessing cell contents on a row object (see `BoundRow`).""" def __init__(self, row): self.row = row @@ -73,6 +71,7 @@ class BoundRow: 1 Arguments: + --------- table: The `.Table` in which this row exists. record: a single record from the :term:`table data` that is used to populate the row. A record could be a `~django.db.Model` object, a @@ -99,6 +98,7 @@ def get_even_odd_css_class(self): Return css class, alternating for odd and even records. Return: + ------ string: `even` for even records, `odd` otherwise. """ return "odd" if self.row_counter % 2 else "even" @@ -174,9 +174,10 @@ def _get_and_render_with(self, bound_column, render_func, default): def _optional_cell_arguments(self, bound_column, value): """ - Defines the arguments that will optionally be passed while calling the - cell's rendering or value getter if that function has one of these as a - keyword argument. + Return optional arguments for render functions. + + Defines the arguments that will optionally be passed while calling the cell's rendering or value getter if that + function has one of these as keyword arguments. """ return { "value": value, @@ -188,10 +189,7 @@ def _optional_cell_arguments(self, bound_column, value): } def get_cell(self, name): - """ - Returns the final rendered html for a cell in the row, given the name - of a column. - """ + """Return the final rendered html for a cell in the row, given the name of a column.""" bound_column = self.table.columns[name] return self._get_and_render_with( @@ -199,40 +197,31 @@ def get_cell(self, name): ) def _call_render(self, bound_column, value=None): - """ - Call the column's render method with appropriate kwargs - """ + """Call the column's render method with appropriate kwargs.""" render_kwargs = self._optional_cell_arguments(bound_column, value) content = call_with_appropriate(bound_column.render, render_kwargs) return bound_column.link(content, **render_kwargs) if bound_column.link else content def get_cell_value(self, name): - """ - Returns the final rendered value (excluding any html) for a cell in the - row, given the name of a column. - """ + """Return the final rendered value (excluding any html) for a cell in the row, given the name of a column.""" return self._get_and_render_with( self.table.columns[name], render_func=self._call_value, default=None ) def _call_value(self, bound_column, value=None): - """ - Call the column's value method with appropriate kwargs - """ + """Call the column's value method with appropriate kwargs.""" return call_with_appropriate( bound_column.value, self._optional_cell_arguments(bound_column, value) ) def __contains__(self, item): - """ - Check by both row object and column name. - """ + """Check by both row object and column name.""" return item in (self.table.columns if isinstance(item, str) else self) def items(self): """ - Returns iterator yielding ``(bound_column, cell)`` pairs. + Return an iterator yielding ``(bound_column, cell)`` pairs. *cell* is ``row[name]`` -- the rendered unicode value that should be ``rendered within ````. @@ -247,9 +236,7 @@ def items(self): class BoundPinnedRow(BoundRow): - """ - Represents a *pinned* row in a table. - """ + """Represents a *pinned* row in a table.""" @property def attrs(self): @@ -258,7 +245,8 @@ def attrs(self): Add CSS classes `pinned-row` and `odd` or `even` to `class` attribute. Return: - AttributeDict: Attributes for pinned rows. + ------ + AttributeDict: Attributes for pinned rows. """ row_attrs = computed_values(self._table.pinned_row_attrs, kwargs={"record": self._record}) css_class = " ".join( @@ -273,12 +261,14 @@ class BoundRows: Container for spawning `.BoundRow` objects. Arguments: + --------- data: iterable of records table: the `~.Table` in which the rows exist pinned_data: dictionary with iterable of records for top and/or bottom pinned rows. Example: + ------- >>> pinned_data = { ... 'top': iterable, # or None value ... 'bottom': iterable, # or None value @@ -297,10 +287,12 @@ def generator_pinned_row(self, data): Top and bottom pinned rows generator. Arguments: - data: Iterable data for all records for top or bottom pinned rows. + --------- + data: Iterable data for all records for top or bottom pinned rows. Yields: - BoundPinnedRow: Top or bottom `BoundPinnedRow` object for single pinned record. + ------ + BoundPinnedRow: Top or bottom `BoundPinnedRow` object for single pinned record. """ if data is not None: if hasattr(data, "__iter__") is False: diff --git a/django_tables2/tables.py b/django_tables2/tables.py index f7fcded4..7fbaba97 100644 --- a/django_tables2/tables.py +++ b/django_tables2/tables.py @@ -111,6 +111,7 @@ class TableOptions: variables in this class. Arguments: + --------- options (`.Table.Meta`): options for a table from `.Table.Meta` """ @@ -154,9 +155,7 @@ def __init__(self, options, class_name): self.unlocalize = getattr(options, "unlocalize", ()) def _check_types(self, options, class_name): - """ - Check class Meta attributes to prevent common mistakes. - """ + """Check class Meta attributes to prevent common mistakes.""" if options is None: return @@ -188,6 +187,7 @@ class Table(metaclass=DeclarativeColumnsMetaclass): A representation of a table. Arguments: + --------- data (QuerySet, list of dicts): The data to display. This is a required variable, a `TypeError` will be raised if it's not passed. @@ -375,15 +375,18 @@ def get_top_pinned_data(self): Iterable type like: QuerySet, list of dicts, list of objects. Having a non-zero number of pinned rows will not result in an empty result set message being rendered, - even if there are no regular data rows + even if there are no regular data rows. Returns: + ------- `None` (default) no pinned rows at the top, iterable, data for pinned rows at the top. Note: + ---- To show pinned row this method should be overridden. Example: + ------- >>> class TableWithTopPinnedRows(Table): ... def get_top_pinned_data(self): ... return [{ @@ -396,18 +399,20 @@ def get_top_pinned_data(self): def get_bottom_pinned_data(self): """ Return data for bottom pinned rows containing data for each row. - Iterable type like: QuerySet, list of dicts, list of objects. - Having a non-zero number of pinned rows - will not result in an empty result set message being rendered, - even if there are no regular data rows + + Iterable type like: QuerySet, list of dicts, list of objects. Having a non-zero number of pinned rows will not + result in an empty result set message being rendered, even if there are no regular data rows. Returns: - `None` (default) no pinned rows at the bottom, iterable, data for pinned rows at the bottom. + ------- + `None` (default) no pinned rows at the bottom, iterable, data for pinned rows at the bottom. Note: - To show pinned row this method should be overridden. + ---- + To show pinned row this method should be overridden. Example: + ------- >>> class TableWithBottomPinnedRows(Table): ... def get_bottom_pinned_data(self): ... return [{ @@ -419,14 +424,14 @@ def get_bottom_pinned_data(self): def before_render(self, request): """ - A way to hook into the moment just before rendering the template. + Call before rendering the table. Can be used to hide a column. Arguments: - request: contains the `WGSIRequest` instance, containing a `user` attribute if - `.django.contrib.auth.middleware.AuthenticationMiddleware` is added to - your `MIDDLEWARE_CLASSES`. + --------- + request: contains the `WGSIRequest` instance, containing a `user` attribute if + `.django.contrib.auth.middleware.AuthenticationMiddleware` is added to your `MIDDLEWARE_CLASSES`. Example:: @@ -440,12 +445,10 @@ def before_render(self, request): else: self.columns.show('country') """ - return + pass def as_html(self, request): - """ - Render the table to an HTML table, adding `request` to the context. - """ + """Render the table to an HTML table, adding `request` to the context.""" # reset counter for new rendering self._counter = count() template = get_template(self.template_name) @@ -457,11 +460,11 @@ def as_html(self, request): def as_values(self, exclude_columns=None): """ - Return a row iterator of the data which would be shown in the table where - the first row is the table headers. + Return a row iterator of the data which would be shown in the table where the first row is the table headers. - arguments: - exclude_columns (iterable): columns to exclude in the data iterator. + Arguments: + --------- + exclude_columns (iterable): columns to exclude in the data iterator. This can be used to output the table data as CSV, excel, for example using the `~.export.ExportMixin`. @@ -501,10 +504,7 @@ def value_name(self, value): ] def has_footer(self): - """ - Returns True if any of the columns define a ``_footer`` attribute or a - ``render_footer()`` method - """ + """Return True if any of the columns define a ``_footer`` attribute or a ``render_footer()`` method.""" return self.show_footer and any(column.has_footer() for column in self.columns) @property @@ -525,7 +525,8 @@ def order_by(self, value): Order the rows of the table based on columns. Arguments: - value: iterable or comma separated string of order by aliases. + --------- + value: iterable or comma separated string of order by aliases. """ # collapse empty values to () order_by = () if not value else value @@ -561,23 +562,22 @@ def page_field(self, value): def paginate(self, paginator_class=Paginator, per_page=None, page=1, *args, **kwargs): """ - Paginates the table using a paginator and creates a ``page`` property - containing information for the current page. + Paginate the table with paginator_class and add property ``page`` containing information for the current page. Arguments: - paginator_class (`~django.core.paginator.Paginator`): A paginator class to - paginate the results. + --------- + paginator_class (`~django.core.paginator.Paginator`): A paginator class to paginate the results. + per_page (int): Number of records to display on each page. + page (int): Page to display. + *args: Positional argument passed to the paginator + **kwargs: Keyword argument passed to the paginator - per_page (int): Number of records to display on each page. - page (int): Page to display. Extra arguments are passed to the paginator. - Pagination exceptions (`~django.core.paginator.EmptyPage` and - `~django.core.paginator.PageNotAnInteger`) may be raised from this - method and should be handled by the caller. + Pagination exceptions (`~django.core.paginator.EmptyPage` and `~django.core.paginator.PageNotAnInteger`) may be + raised from this method and should be handled by the caller. """ - per_page = per_page or self._meta.per_page self.paginator = paginator_class(self.rows, per_page, *args, **kwargs) self.page = self.paginator.page(page) @@ -649,34 +649,30 @@ def template_name(self, value): @property def paginated_rows(self): - """ - Return the rows for the current page if the table is paginated, else all rows. - """ + """Return the rows for the current page if the table is paginated, else all rows.""" if hasattr(self, "page"): return self.page.object_list return self.rows def get_column_class_names(self, classes_set, bound_column): """ - Returns a set of HTML class names for cells (both ``td`` and ``th``) of a - **bound column** in this table. - By default this returns the column class names defined in the table's - attributes. - This method can be overridden to change the default behavior, for - example to simply `return classes_set`. + Return a set of HTML class names for cells (both ``td`` and ``th``) of a **bound column** in this table. + + By default this returns the column class names defined in the table's attributes. + This method can be overridden to change the default behavior, for example to simply `return classes_set`. Arguments: - classes_set(set of string): a set of class names to be added - to the cell, retrieved from the column's attributes. In the case - of a header cell (th), this also includes ordering classes. - To set the classes for a column, see `.Column`. - To configure ordering classes, see :ref:`ordering-class-name` + --------- + classes_set(set of string): a set of class names to be added to the cell, retrieved from the column's + attributes. In the case of a header cell (th), this also includes ordering classes. To set the classes + for a column, see `.Column`. To configure ordering classes, see :ref:`ordering-class-name` - bound_column(`.BoundColumn`): the bound column the class names are - determined for. Useful for accessing `bound_column.name`. + bound_column(`.BoundColumn`): the bound column the class names are determined for. Useful for accessing + `bound_column.name`. Returns: - A set of class names to be added to cells of this column + ------- + A set of class names to be added to cells of this column If you want to add the column names to the list of classes for a column, override this method in your custom table:: @@ -695,18 +691,21 @@ def get_column_class_names(self, classes_set, bound_column): def table_factory(model, table=Table, fields=None, exclude=None, localize=None): """ - Return Table class for given `model`, equivalent to defining a custom table class:: + Return Table class for given `model`, equivalent to defining a custom table class. + Example: + ------- class MyTable(tables.Table): class Meta: model = model Arguments: - model (`~django.db.models.Model`): Model associated with the new table - table (`.Table`): Base Table class used to create the new one - fields (list of str): Fields displayed in tables - exclude (list of str): Fields exclude in tables - localize (list of str): Fields to localize + --------- + model (`~django.db.models.Model`): Model associated with the new table + table (`.Table`): Base Table class used to create the new one + fields (list of str): Fields displayed in tables + exclude (list of str): Fields exclude in tables + localize (list of str): Fields to localize """ attrs = {"model": model} if fields is not None: diff --git a/django_tables2/templatetags/django_tables2.py b/django_tables2/templatetags/django_tables2.py index cfcf5276..76d5ce09 100644 --- a/django_tables2/templatetags/django_tables2.py +++ b/django_tables2/templatetags/django_tables2.py @@ -26,9 +26,9 @@ def token_kwargs(bits, parser): """ - Based on Django's `~django.template.defaulttags.token_kwargs`, but with a - few changes: + Based on Django's `~django.template.defaulttags.token_kwargs`, but with a few changes. + Changes: - No legacy mode. - Both keys and values are compiled as a filter """ @@ -81,11 +81,12 @@ def render(self, context): @register.tag def querystring(parser, token): """ - Creates a URL (containing only the query string [including "?"]) derived - from the current URL's query string, by updating it with the provided - keyword arguments. + Compile a query string based on current query string overridden with supplied values. - Example (imagine URL is ``/abc/?gender=male&name=Brad``):: + Creates a URL (containing only the query string [including "?"]) derived from the current URL's query string, + by updating it with the provided keyword arguments. + + Example (with URL: ``/abc/?gender=male&name=Brad``):: # {% querystring "name"="abc" "age"=15 %} ?name=abc&gender=male&age=15 @@ -119,9 +120,12 @@ def querystring(parser, token): class RenderTableNode(Node): """ - parameters: + Node to render a table. + + Parameters + ---------- table (~.Table): the table to render - template (str or list): Name[s] of template to render + template (str or list): Name[s] of template to render. """ def __init__(self, table, template_name=None): @@ -213,8 +217,7 @@ class Meta: @register.simple_tag(takes_context=True) def export_url(context, export_format, export_trigger_param=None): """ - Returns an export URL for the given file `export_format`, preserving current - query string parameters. + Return an export URL for the given file `export_format`, preserving current query string parameters. Example for a page requested with querystring ``?q=blue``:: @@ -224,7 +227,6 @@ def export_url(context, export_format, export_trigger_param=None): ?q=blue&_export=csv """ - if export_trigger_param is None and "view" in context: export_trigger_param = getattr(context["view"], "export_trigger_param", None) @@ -243,11 +245,11 @@ def table_page_range(page, paginator): - containing one or two '...' to skip ranges between first/last and current. Example: + ------- {% for p in table.page|table_page_range:table.paginator %} {{ p }} {% endfor %} """ - page_range = getattr(settings, "DJANGO_TABLES2_PAGE_RANGE", 10) num_pages = paginator.num_pages diff --git a/django_tables2/utils.py b/django_tables2/utils.py index 789834a1..d7aff0db 100644 --- a/django_tables2/utils.py +++ b/django_tables2/utils.py @@ -11,7 +11,7 @@ class Sequence(list): """ - Represents a column sequence, e.g. ``('first_name', '...', 'last_name')`` + Object to represent a column sequence, e.g. ``('first_name', '...', 'last_name')``. This is used to represent `.Table.Meta.sequence` or the `.Table` constructors's *sequence* keyword argument. @@ -24,16 +24,19 @@ class Sequence(list): def expand(self, columns): """ - Expands the ``'...'`` item in the sequence into the appropriate column - names that should be placed there. + Expand the ``'...'`` item in the sequence into the appropriate column names that should be placed there. - arguments: - columns (list): list of column names. - returns: - The current instance. + Arguments: + --------- + columns (list): list of column names. + + Returns: + ------- + The current instance. - raises: - `ValueError` if the sequence is invalid for the columns. + Raises: + ------ + `ValueError` if the sequence is invalid for the columns. """ ellipses = self.count("...") if ellipses > 1: @@ -83,14 +86,13 @@ def __new__(cls, value): @property def bare(self): """ - Returns: - `.OrderBy`: the bare form. - - The *bare form* is the non-prefixed form. Typically the bare form is - just the ascending form. + Returns + ------- + `.OrderBy`: the bare form. - Example: ``age`` is the bare form of ``-age`` + The *bare form* is the non-prefixed form. Typically the bare form is just the ascending form. + Example: ``age`` is the bare form of ``-age``. """ return OrderBy(self[1:]) if self[:1] == "-" else self @@ -99,8 +101,9 @@ def opposite(self): """ Provides the opposite of the current sorting direction. - Returns: - `.OrderBy`: object with an opposite sort influence. + Returns + ------- + `.OrderBy`: object with an opposite sort influence. Example:: @@ -113,29 +116,22 @@ def opposite(self): @property def is_descending(self): - """ - Returns `True` if this object induces *descending* ordering. - """ + """Return `True` if this object induces *descending* ordering.""" return self.startswith("-") @property def is_ascending(self): - """ - Returns `True` if this object induces *ascending* ordering. - """ + """Return `True` if this object induces *ascending* ordering.""" return not self.is_descending def for_queryset(self): - """ - Returns the current instance usable in Django QuerySet's order_by - arguments. - """ + """Return the current instance usable in Django QuerySet's order_by arguments.""" return self.replace(Accessor.LEGACY_SEPARATOR, OrderBy.QUERYSET_SEPARATOR) class OrderByTuple(tuple): """ - Stores ordering as (as `.OrderBy` objects). + Store ordering as (as `.OrderBy` objects). The `~.Table.order_by` property is always converted to an `.OrderByTuple` object. This class is essentially just a `tuple` with some useful extras. @@ -176,10 +172,12 @@ def __contains__(self, name): True Arguments: - name (str): The name of a column. (optionally prefixed) + --------- + name (str): The name of a column. (optionally prefixed) Returns: - bool: `True` if the column with `name` influences the ordering. + ------- + bool: `True` if the column with `name` influences the ordering. """ name = OrderBy(name).bare for order_by in self: @@ -189,10 +187,9 @@ def __contains__(self, name): def __getitem__(self, index): """ - Allows an `.OrderBy` object to be extracted via named or integer - based indexing. + Allow an `.OrderBy` object to be extracted via named or integer based indexing. - When using named based indexing, it's fine to used a prefixed named:: + When using name based indexing, it's fine to used a prefixed names:: >>> x = OrderByTuple(('name', '-age')) >>> x[0] @@ -203,10 +200,12 @@ def __getitem__(self, index): '-age' Arguments: - index (int): Index to query the ordering for. + --------- + index (int): Index to query the ordering for. Returns: - `.OrderBy`: for the ordering at the index. + ------- + `.OrderBy`: for the ordering at the index. """ if isinstance(index, str): for order_by in self: @@ -264,9 +263,7 @@ def __lt__(self, other): return Comparator def get(self, key, fallback): - """ - Identical to `__getitem__`, but supports fallback value. - """ + """Identical to `__getitem__`, but supports fallback value.""" try: return self[key] except (KeyError, IndexError): @@ -275,7 +272,9 @@ def get(self, key, fallback): @property def opposite(self): """ - Return version with each `.OrderBy` prefix toggled:: + Return version with each `.OrderBy` prefix toggled. + + Example:: >>> order_by = OrderByTuple(('name', '-age')) >>> order_by.opposite @@ -348,16 +347,18 @@ def resolve(self, context, safe=True, quiet=False): Arguments: - context : The root/first object to traverse. - safe (bool): Don't call anything with `alters_data = True` - quiet (bool): Smother all exceptions and instead return `None` + --------- + context : The root/first object to traverse. + safe (bool): Don't call anything with `alters_data = True` + quiet (bool): Smother all exceptions and instead return `None` Returns: - target object + ------- + target object Raises: - TypeError`, `AttributeError`, `KeyError`, `ValueError` - (unless `quiet` == `True`) + ------ + TypeError`, `AttributeError`, `KeyError`, `ValueError` (unless `quiet` == `True`) """ # Short-circuit if the context contains a key with the exact name of the accessor, # supporting list-of-dicts data returned from values_list("related_model__field") @@ -412,9 +413,7 @@ def bits(self): return self.split(self.SEPARATOR) def get_field(self, model): - """ - Return the django model field for model in context, following relations. - """ + """Return the django model field for model in context, following relations.""" if not hasattr(model, "_meta"): return @@ -435,7 +434,7 @@ def penultimate(self, context, quiet=True): """ Split the accessor on the right-most separator ('__'), return a tuple with: - the resolved left part. - - the remainder + - the remainder. Example:: @@ -473,9 +472,7 @@ def as_html(self): """ Render to HTML tag attributes. - Example: - - .. code-block:: python + Example:: >>> from django_tables2.utils import AttributeDict >>> attrs = AttributeDict({'class': 'mytable', 'id': 'someid'}) @@ -490,11 +487,10 @@ def as_html(self): def segment(sequence, aliases): """ - Translates a flat sequence of items into a set of prefixed aliases. + Translate a flat sequence of items into a set of prefixed aliases. - This allows the value set by `.QuerySet.order_by` to be translated into - a list of columns that would have the same result. These are called - "order by aliases" which are optionally prefixed column names:: + This allows the value set by `.QuerySet.order_by` to be translated into a list of columns that would have the same + result. These are called "order by aliases" which are optionally prefixed column names:: >>> list(segment(('a', '-b', 'c'), ... {'x': ('a'), @@ -527,14 +523,16 @@ def segment(sequence, aliases): def signature(fn): """ - Returns: - tuple: Returns a (arguments, kwarg_name)-tuple: - - the arguments (positional or keyword) - - the name of the ** kwarg catch all. + Return the signature of the provided function. + + Returns + ------- + tuple: Returns a (arguments, kwarg_name)-tuple: + - the arguments (positional or keyword) + - the name of the ** kwarg catch all. The self-argument for methods is always removed. """ - signature = inspect.signature(fn) args = [] @@ -552,13 +550,10 @@ def signature(fn): def call_with_appropriate(fn, kwargs): """ - Calls the function ``fn`` with the keyword arguments from ``kwargs`` it expects - - If the kwargs argument is defined, pass all arguments, else provide exactly - the arguments wanted. + Call the function ``fn`` with the keyword arguments from ``kwargs`` it expects. - If one of the arguments of ``fn`` are not contained in kwargs, ``fn`` will not - be called and ``None`` will be returned. + If the kwargs argument is defined, pass all arguments, else provide exactly the arguments wanted. + If one of the arguments of ``fn`` is missing from kwargs, ``fn`` will not be called and ``None`` will be returned. """ args, kwargs_name = signature(fn) # no catch-all defined, we need to exactly pass the arguments specified. @@ -574,7 +569,7 @@ def call_with_appropriate(fn, kwargs): def computed_values(d, kwargs=None): """ - Returns a new `dict` that has callable values replaced with the return values. + Return a new `dict` that has callable values replaced with the return values. Example:: @@ -603,11 +598,12 @@ def computed_values(d, kwargs=None): {'name': 'Brad', 'parents': {'father': 'Foo', 'mother': 'Bar'}} Arguments: - d (dict): The original dictionary. - kwargs: any extra keyword arguments will be passed to the callables, if the callable - takes an argument with such a name. + --------- + d (dict): The original dictionary. + kwargs: extra keyword arguments will be passed callables, if the callable expects an argument with such a name. Returns: + ------- dict: with callable values replaced. """ kwargs = kwargs or {} diff --git a/django_tables2/views.py b/django_tables2/views.py index 841bf901..066c6430 100644 --- a/django_tables2/views.py +++ b/django_tables2/views.py @@ -1,5 +1,5 @@ from itertools import count -from typing import Any, Dict, Optional +from typing import Any from django.core.exceptions import ImproperlyConfigured from django.views.generic.list import ListView @@ -9,25 +9,22 @@ class TableMixinBase: - """ - Base mixin for the Single- and MultiTable class based views. - """ + """Base mixin for the Single- and MultiTable class based views.""" context_table_name = "table" table_pagination = None def get_context_table_name(self, table): - """ - Get the name to use for the table's template variable. - """ + """Get the name to use for the table's template variable.""" return self.context_table_name def get_table_pagination(self, table): """ - Return pagination options passed to `.RequestConfig`: - - True for standard pagination (default), - - False for no pagination, - - a dictionary for custom pagination. + Return pagination options passed to `.RequestConfig`. + + - True for standard pagination (default), + - False for no pagination, + - a dictionary for custom pagination. `ListView`s pagination attributes are taken into account, if `table_pagination` does not define the corresponding value. @@ -61,15 +58,17 @@ def get_table_pagination(self, table): return paginate - def get_paginate_by(self, table_data) -> Optional[int]: + def get_paginate_by(self, table_data) -> int | None: """ - Determines the number of items per page, or ``None`` for no pagination. + Determine the number of items per page, or ``None`` for no pagination. Args: - table_data: The table's data. + ---- + table_data: The table's data. Returns: - Optional[int]: Items per page or ``None`` for no pagination. + ------- + Optional[int]: Items per page or ``None`` for no pagination. """ return getattr(self, "paginate_by", None) @@ -79,20 +78,18 @@ class SingleTableMixin(TableMixinBase): Adds a Table object to the context. Typically used with `.TemplateResponseMixin`. - Attributes: - table_class: subclass of `.Table` - table_data: data used to populate the table, any compatible data source. - context_table_name(str): name of the table's template variable (default: - 'table') - table_pagination (dict): controls table pagination. If a `dict`, passed as - the *paginate* keyword argument to `.RequestConfig`. As such, any - Truthy value enables pagination. (default: enable pagination). + Attributes + ---------- + table_class: subclass of `.Table` + table_data: data used to populate the table, any compatible data source. + context_table_name(str): name of the table's template variable (default: 'table') + table_pagination (dict): controls table pagination. If a `dict`, passed as the *paginate* keyword argument to + `.RequestConfig`. As such, any truthy value enables pagination. (default: enable pagination). - The `dict` can be used to specify values for arguments for the call to - `~.tables.Table.paginate`. + The `dict` can be used to specify values for arguments for the call to `~.tables.Table.paginate`. - If you want to use a non-standard paginator for example, you can add a key - `paginator_class` to the dict, containing a custom `Paginator` class. + If you want to use a non-standard paginator for example, you can add a key `paginator_class` to the dict, + containing a custom `Paginator` class. This mixin plays nice with the Django's ``.MultipleObjectMixin`` by using ``.get_queryset`` as a fall back for the table data source. @@ -102,9 +99,7 @@ class SingleTableMixin(TableMixinBase): table_data = None def get_table_class(self): - """ - Return the class to use for the table. - """ + """Return the class to use for the table.""" if self.table_class: return self.table_class if self.model: @@ -125,9 +120,7 @@ def get_table(self, **kwargs): ) def get_table_data(self): - """ - Return the table data that should be used to populate the rows. - """ + """Return the table data that should be used to populate the rows.""" if self.table_data is not None: return self.table_data elif hasattr(self, "object_list"): @@ -152,7 +145,7 @@ def get_table_kwargs(self): """ return {} - def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: """ Overridden version of `.TemplateResponseMixin` to inject the table into the template's context. @@ -182,7 +175,8 @@ class MultiTableMixin(TableMixinBase): having an entry containing the data for each table in `tables`, or by overriding this method in order to return this data. - Attributes: + Attributes + ---------- tables: list of `.Table` instances or list of `.Table` child objects. tables_data: if defined, `tables` is assumed to be a list of table classes which will be instantiated with the corresponding item from @@ -205,9 +199,7 @@ class MultiTableMixin(TableMixinBase): context_table_name = "tables" def get_tables(self): - """ - Return an array of table instances containing data. - """ + """Return an array of table instances containing data.""" if self.tables is None: view_name = type(self).__name__ raise ImproperlyConfigured(f"No tables were specified. Define {view_name}.tables") @@ -222,12 +214,10 @@ def get_tables(self): return list(Table(data[i]) for i, Table in enumerate(self.tables)) def get_tables_data(self): - """ - Return an array of table_data that should be used to populate each table - """ + """Return an array of table_data that should be used to populate each table.""" return self.tables_data - def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context = super().get_context_data(**kwargs) tables = self.get_tables() diff --git a/example/app/migrations/0001_initial.py b/example/app/migrations/0001_initial.py index 0dc7318a..e2a06d7b 100644 --- a/example/app/migrations/0001_initial.py +++ b/example/app/migrations/0001_initial.py @@ -1,7 +1,7 @@ # Generated by Django 1.11.5 on 2017-09-22 13:23 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/example/app/migrations/0002_auto_20180416_0959.py b/example/app/migrations/0002_auto_20180416_0959.py index 4662bdb4..fb791cf1 100644 --- a/example/app/migrations/0002_auto_20180416_0959.py +++ b/example/app/migrations/0002_auto_20180416_0959.py @@ -1,7 +1,7 @@ # Generated by Django 2.0.1 on 2018-04-16 09:59 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/example/app/models.py b/example/app/models.py index b1e2c93a..4ace6b54 100644 --- a/example/app/models.py +++ b/example/app/models.py @@ -11,9 +11,7 @@ def __str__(self): class Country(models.Model): - """ - Represents a geographical Country - """ + """Represents a geographical Country.""" name = models.CharField(max_length=100) population = models.PositiveIntegerField(verbose_name=_("population")) diff --git a/example/app/views.py b/example/app/views.py index 0bdcb6c9..28847b72 100644 --- a/example/app/views.py +++ b/example/app/views.py @@ -117,8 +117,7 @@ def checkbox(request): def template_example(request, version): - """Demonstrate the use of the bootstrap template""" - + """Demonstrate the use of the bootstrap template.""" versions = { "bootstrap3": (BootstrapTable, "bootstrap_template.html"), "bootstrap4": (Bootstrap4Table, "bootstrap4_template.html"), diff --git a/pyproject.toml b/pyproject.toml index aa4949aa..9d73ef71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,39 @@ [tool.black] line-length = 100 + +[tool.ruff] +fix = false +fixable = [ + "I001", # isort (sorting) + "F", # flake8 + "D", # docformatter + "UP", # pyupgrade +] +ignore = [ + "D1", # D1: Missing docstring error codes (because not every function and class has a docstring) + "D203", # D203: 1 blank line required before class docstring (conflicts with D211 and should be disabled, see https://github.com/PyCQA/pydocstyle/pull/91) + "D205", + "D212", # D212: Multi-line docstring summary should start at the first line +] +line-length = 120 +select = [ + "D", # pydocstyle + "E", # pycodestyle + "F", # flake8 + "I", # isort + "UP", # pyupgrade +] +target-version = "py311" +unfixable = [ + "F8", # names in flake8, such as defined but unused variables +] + +[tool.ruff.per-file-ignores] +"*/migrations/*" = ["D417", "E501"] + +[tool.djlint] +# Reference: https://djlint.com/djlint/rules.html +blank_line_after_tag = "load,extends,endblock,has_perm" +ignore = "H006,H008,H013,H014,H017,H019,H021,H023,T001,T002,T003" +max_line_length = 120 +profile = "django" diff --git a/tests/app/migrations/0001_initial.py b/tests/app/migrations/0001_initial.py index 3bebbd39..f8466de6 100644 --- a/tests/app/migrations/0001_initial.py +++ b/tests/app/migrations/0001_initial.py @@ -1,7 +1,7 @@ # Generated by Django 4.0a1 on 2021-09-22 18:51 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/tests/app/views.py b/tests/app/views.py index 2e39d97e..5cf2e32f 100644 --- a/tests/app/views.py +++ b/tests/app/views.py @@ -5,7 +5,7 @@ def person(request, pk): - """A really simple view to provide an endpoint for the 'person' URL.""" + """View to serve as an endpoint for the 'person' URL.""" person = get_object_or_404(Person, pk=pk) return HttpResponse(f"Person: {person}") diff --git a/tests/columns/test_booleancolumn.py b/tests/columns/test_booleancolumn.py index a61c9a94..74874688 100644 --- a/tests/columns/test_booleancolumn.py +++ b/tests/columns/test_booleancolumn.py @@ -28,9 +28,7 @@ class Meta: @skipIf(django_version < (2, 1, 0), "Feature added in django 2.1") def test_should_use_nullability_for_booloanfield(self): - """ - Django 2.1 supports null=(True|False) for BooleanField. - """ + """Django 2.1 supports null=(True|False) for BooleanField.""" class BoolModel2(models.Model): field = models.BooleanField(null=True) @@ -98,10 +96,7 @@ class Table(tables.Table): self.assertIn(table.rows[0].get_cell("col"), table.rows[0].get_cell("col_linkify")) def test_boolean_field_choices_with_real_model_instances(self): - """ - If a booleanField has choices defined, the value argument passed to - BooleanColumn.render() is the rendered value, not a bool. - """ + """With choices defined, BooleanColumn.render() should get the rendered value, not a bool.""" class BoolModelChoices(models.Model): field = models.BooleanField(choices=((True, "Yes"), (False, "No"))) @@ -119,7 +114,7 @@ class Meta: self.assertEqual(table.rows[1].get_cell("field"), '') def test_boolean_field_choices_spanning_relations(self): - "The inverse lookup voor boolean choices should also work on related models" + """The inverse lookup for boolean choices should also work on related models.""" class Table(tables.Table): boolean = tables.BooleanColumn(accessor="occupation__boolean_with_choices") @@ -141,7 +136,7 @@ class Meta: self.assertEqual(table.rows[1].get_cell("boolean"), '') def test_boolean_should_not_prevent_rendering_of_other_columns(self): - """Test for issue 360""" + """Test for issue 360.""" class Table(tables.Table): boolean = tables.BooleanColumn(yesno="waar,onwaar") diff --git a/tests/columns/test_datecolumn.py b/tests/columns/test_datecolumn.py index 73ff95d1..8ee7632b 100644 --- a/tests/columns/test_datecolumn.py +++ b/tests/columns/test_datecolumn.py @@ -15,7 +15,7 @@ class DateColumnTest(SimpleTestCase): Format string: https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date D -- Day of the week, textual, 3 letters -- 'Fri' b -- Month, textual, 3 letters, lowercase -- 'jan' - Y -- Year, 4 digits. -- '1999' + Y -- Year, 4 digits. -- '1999'. """ def test_should_handle_explicit_format(self): diff --git a/tests/columns/test_datetimecolumn.py b/tests/columns/test_datetimecolumn.py index 7539a9b3..b7afb786 100644 --- a/tests/columns/test_datetimecolumn.py +++ b/tests/columns/test_datetimecolumn.py @@ -19,7 +19,7 @@ class DateTimeColumnTest(SimpleTestCase): b -- Month, textual, 3 letters, lowercase -- 'jan' Y -- Year, 4 digits. -- '1999' A -- 'AM' or 'PM'. -- 'AM' - f -- Time, in 12-hour hours[:minutes] -- '1', '1:30' + f -- Time, in 12-hour hours[:minutes] -- '1', '1:30'. """ def dt(self): diff --git a/tests/columns/test_filecolumn.py b/tests/columns/test_filecolumn.py index 278dd3da..f7d8b6be 100644 --- a/tests/columns/test_filecolumn.py +++ b/tests/columns/test_filecolumn.py @@ -12,7 +12,7 @@ def storage(): - """Provide a storage that exposes the test templates""" + """Provide a storage that exposes the test templates.""" root = os.path.join(os.path.dirname(__file__), "..", "app", "templates") return FileSystemStorage(location=root, base_url="/baseurl/") diff --git a/tests/columns/test_general.py b/tests/columns/test_general.py index 4cd44225..44539e0c 100644 --- a/tests/columns/test_general.py +++ b/tests/columns/test_general.py @@ -156,9 +156,7 @@ class TranslationTable(tables.Table): self.assertEqual(table.columns["text"].header, "Text") def test_sequence(self): - """ - Ensures that the sequence of columns is configurable. - """ + """Ensures that the sequence of columns is configurable.""" class TestTable(tables.Table): a = tables.Column() @@ -280,8 +278,11 @@ class SimpleTable(tables.Table): table = SimpleTable([{"a": "value"}]) root = parse(table.as_html(request)) - # return classes of an element as a set - classes = lambda x: set(x.attrib.get("class", "").split()) + + def classes(x): + """Return classes of an element as a set.""" + return set(x.attrib.get("class", "").split()) + self.assertIn("orderable", classes(root.findall(".//thead/tr/th")[0])) self.assertNotIn("orderable", classes(root.findall(".//thead/tr/th")[1])) @@ -324,9 +325,7 @@ class Table(tables.Table): row[table] def test_related_fields_get_correct_type(self): - """ - Types of related fields should also lead to the correct type of column. - """ + """Types of related fields should also lead to the correct type of column.""" class PersonTable(tables.Table): class Meta: @@ -358,7 +357,7 @@ class Meta: class ColumnInheritanceTest(TestCase): def test_column_params_should_be_preserved_under_inheritance(self): """ - Github issue #337 + Github issue #337. Columns explicitly defined on MyTable get overridden by columns implicitly defined on it's child. @@ -394,9 +393,7 @@ class Meta(MyTable.Meta): def test_explicit_column_can_be_overridden_by_other_explicit_column(self): class MyTableC(MyTable): - """ - If we define a new explict item1 column, that one should be used. - """ + """If we define a new explict item1 column, that one should be used.""" item1 = tables.Column(verbose_name="New nice column name") @@ -409,7 +406,7 @@ class MyTableC(MyTable): def test_override_column_class_names(self): """ We control the output of CSS class names for a column by overriding - get_column_class_names + get_column_class_names. """ class MyTable(tables.Table): @@ -436,7 +433,7 @@ def setUp(self): Person.objects.create(first_name="Sjon", last_name="Jansen") def test_computable_td_attrs(self): - """Computable attrs for columns, using table argument""" + """Computable attrs for columns, using table argument.""" class Table(tables.Table): person = tables.Column(attrs={"cell": {"data-length": lambda table: len(table.data)}}) @@ -453,7 +450,7 @@ class Table(tables.Table): self.assertIn('', html) def test_computable_td_attrs_defined_in_column_class_attribute(self): - """Computable attrs for columns, using custom Column""" + """Computable attrs for columns, using custom Column.""" class MyColumn(tables.Column): attrs = {"td": {"data-test": lambda table: len(table.data)}} @@ -469,7 +466,7 @@ class Table(tables.Table): self.assertEqual(root.findall(".//tbody/tr/td")[1].attrib, {"data-test": "2"}) def test_computable_td_attrs_defined_in_column_class_attribute_record(self): - """Computable attrs for columns, using custom column""" + """Computable attrs for columns, using custom column.""" class PersonColumn(tables.Column): attrs = { diff --git a/tests/columns/test_linkcolumn.py b/tests/columns/test_linkcolumn.py index 01d3da83..123095d9 100644 --- a/tests/columns/test_linkcolumn.py +++ b/tests/columns/test_linkcolumn.py @@ -12,7 +12,7 @@ class LinkColumnTest(TestCase): def test_unicode(self): - """Test LinkColumn for unicode values + headings""" + """Test LinkColumn for unicode values + headings.""" class UnicodeTable(tables.Table): first_name = tables.LinkColumn("person", args=[A("pk")]) @@ -77,7 +77,7 @@ class PersonTable(tables.Table): self.assertIn("—", html) def test_linkcolumn_non_field_based(self): - """Test for issue 257, non-field based columns""" + """Test for issue 257, non-field based columns.""" class Table(tables.Table): first_name = tables.Column() @@ -141,8 +141,7 @@ class TestTable(tables.Table): self.assertEqual(table.rows[0].get_cell("col"), table.rows[0].get_cell("col_linkify")) def test_td_attrs_should_be_supported(self): - """LinkColumn should support both and attrs""" - + """LinkColumn should support both and attrs.""" person = Person.objects.create(first_name="Bob", last_name="Builder") class Table(tables.Table): diff --git a/tests/columns/test_manytomanycolumn.py b/tests/columns/test_manytomanycolumn.py index d45a8f76..804dd68e 100644 --- a/tests/columns/test_manytomanycolumn.py +++ b/tests/columns/test_manytomanycolumn.py @@ -73,9 +73,7 @@ class Table(tables.Table): self.assertIn(str(friend), friends) def test_linkify_item_different_model(self): - """ - Make sure the correct get_absolute_url() is used to linkify the items. - """ + """Make sure the correct get_absolute_url() is used to linkify the items.""" class GroupTable(tables.Table): name = tables.Column(linkify=True) diff --git a/tests/columns/test_templatecolumn.py b/tests/columns/test_templatecolumn.py index 643ab3cb..612a438a 100644 --- a/tests/columns/test_templatecolumn.py +++ b/tests/columns/test_templatecolumn.py @@ -90,9 +90,7 @@ class Table(tables.Table): col = tables.TemplateColumn() def test_should_support_value_with_curly_braces(self): - """ - https://github.com/bradleyayers/django-tables2/issues/441 - """ + """https://github.com/bradleyayers/django-tables2/issues/441.""" class Table(tables.Table): track = tables.TemplateColumn("track: {{ value }}") diff --git a/tests/columns/test_timecolumn.py b/tests/columns/test_timecolumn.py index d444ab4c..3e5dcb98 100644 --- a/tests/columns/test_timecolumn.py +++ b/tests/columns/test_timecolumn.py @@ -9,7 +9,7 @@ class TimeColumnTest(SimpleTestCase): """ Format string for TimeColumn: - https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date + https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date. """ def test_should_handle_explicit_format(self): diff --git a/tests/test_config.py b/tests/test_config.py index 5e7b8a4c..0d720489 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -66,7 +66,6 @@ def test_silent_empty_page_error(self): def test_passing_request_to_constructor(self): """Table constructor should call RequestConfig if a request is passed.""" - request = build_request("/?page=1&sort=abc") class SimpleTable(Table): diff --git a/tests/test_core.py b/tests/test_core.py index d7c64d02..70075876 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -81,7 +81,7 @@ class MayorlessCityTable(CityTable): def test_metaclass_inheritance(self): class Tweaker(type): - """Adds an attribute "tweaked" to all classes""" + """Adds an attribute "tweaked" to all classes.""" def __new__(cls, name, bases, attrs): attrs["tweaked"] = True diff --git a/tests/test_export.py b/tests/test_export.py index 5b4d9f0d..5bff2cf2 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -59,9 +59,7 @@ class View(ExportMixin, tables.SingleTableView): @skipIf(TableExport is None, "Tablib is required to run the export tests") class TableExportTest(TestCase): - """ - github issue #474: null/None values in exports - """ + """github issue #474: null/None values in exports.""" def test_None_values(self): table = Table( @@ -265,7 +263,6 @@ def test_should_work_with_foreign_keys(self): def test_datetime_xls(self): """Verify datatime objects can be exported to xls.""" - utc = pytz.timezone("UTC") class Table(tables.Table): @@ -298,7 +295,6 @@ def get_queryset(self): def test_export_invisible_columns(self): """Verify columns with visible=False *do* get exported.""" - DATA = [{"name": "Bess W. Fletcher", "website": "teammonka.com"}] class Table(tables.Table): diff --git a/tests/test_extra_columns.py b/tests/test_extra_columns.py index 015c285d..279f3952 100644 --- a/tests/test_extra_columns.py +++ b/tests/test_extra_columns.py @@ -23,7 +23,7 @@ def test_dynamically_adding_columns(self): """ When adding columns to self.base_columns, they were actually added to the class attribute `Table.base_columns`, and not to the instance - attribute, `table.base_columns` + attribute, `table.base_columns`. issue #403 """ @@ -163,11 +163,10 @@ def before_render(self, request): def test_sequence_and_extra_columns(self): """ - https://github.com/jieter/django-tables2/issues/486 + https://github.com/jieter/django-tables2/issues/486. The exact moment the '...' is expanded is crucial here. """ - add_occupation_column = True class MyTable(tables.Table): @@ -194,9 +193,7 @@ def __init__(self, data, *args, **kwargs): self.assertEqual([c.name for c in table.columns], ["first_name", "friends"]) def test_change_attributes(self): - """ - https://github.com/jieter/django-tables2/issues/574 - """ + """https://github.com/jieter/django-tables2/issues/574.""" class Table(tables.Table): mycolumn = tables.Column(orderable=False) diff --git a/tests/test_footer.py b/tests/test_footer.py index d2ea1ac2..db0c7901 100644 --- a/tests/test_footer.py +++ b/tests/test_footer.py @@ -41,7 +41,7 @@ class Table(tables.Table): def test_footer_disable_on_table(self): """ Showing the footer can be disabled using show_footer argument to the Table - constructor + constructor. """ class Table(tables.Table): diff --git a/tests/test_models.py b/tests/test_models.py index d670c1be..5b0f8dd6 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -170,7 +170,7 @@ class SimpleTable(tables.Table): def test_default_order(self): """ If orderable=False, do not sort queryset. - https://github.com/bradleyayers/django-tables2/issues/204 + https://github.com/bradleyayers/django-tables2/issues/204. """ class PersonTable(tables.Table): @@ -509,9 +509,7 @@ class PersonTable(tables.Table): self.assertEqual(Person.objects.all().count(), 10) def test_model__str__calls(self): - """ - Model.__str__ should not be called when not necessary. - """ + """Model.__str__ should not be called when not necessary.""" calls = defaultdict(int) def counting__str__(self): diff --git a/tests/test_ordering.py b/tests/test_ordering.py index 57e10190..e35e4b11 100644 --- a/tests/test_ordering.py +++ b/tests/test_ordering.py @@ -165,9 +165,8 @@ def test_ordering_by_custom_field(self): When defining a custom field in a table, as name=tables.Column() with methods to render and order render_name and order_name, sorting by this column causes an error if the custom field is not in last position. - https://github.com/jieter/django-tables2/issues/413 + https://github.com/jieter/django-tables2/issues/413. """ - Person.objects.create(first_name="Alice", last_name="Beta") Person.objects.create(first_name="Bob", last_name="Alpha") diff --git a/tests/test_paginators.py b/tests/test_paginators.py index 89177f3d..cfd1120c 100644 --- a/tests/test_paginators.py +++ b/tests/test_paginators.py @@ -72,9 +72,7 @@ def test_lookahead(self): self.assertEqual(paginator.num_pages, 100) def test_number_is_none(self): - """ - When number=None is supplied, the paginator should serve its first page. - """ + """When number=None is supplied, the paginator should serve its first page.""" objects = list(range(1, 1000)) paginator = LazyPaginator(objects, 10, look_ahead=3) self.assertEqual(paginator.page(None).object_list, list(range(1, 11))) diff --git a/tests/test_pinned_rows.py b/tests/test_pinned_rows.py index 0dbab3ba..2cce0986 100644 --- a/tests/test_pinned_rows.py +++ b/tests/test_pinned_rows.py @@ -51,9 +51,7 @@ def test_bound_rows_with_pinned_data(self): self.assertNotIn("gamma", row) def test_as_html(self): - """ - Ensure that html render correctly. - """ + """Ensure that html render correctly.""" request = build_request("/") table = SimpleTable([{"name": "Grzegorz", "age": 30, "occupation": "programmer"}]) root = parse(table.as_html(request)) @@ -94,9 +92,7 @@ def test_as_html(self): self.assertEqual(td[2].text, "130") def test_pinned_row_attrs(self): - """ - Testing attrs for pinned rows. - """ + """Testing attrs for pinned rows.""" pinned_row_attrs = {"class": "super-mega-row", "data-foo": "bar"} request = build_request("/") @@ -109,9 +105,7 @@ def test_pinned_row_attrs(self): self.assertIn("data-foo", html) def test_ordering(self): - """ - Change sorting should not change ordering pinned rows. - """ + """Change sorting should not change ordering pinned rows.""" request = build_request("/") records = [ {"name": "Alex", "age": 42, "occupation": "programmer"}, diff --git a/tests/test_rows.py b/tests/test_rows.py index 62e2c6aa..043e46cf 100644 --- a/tests/test_rows.py +++ b/tests/test_rows.py @@ -78,7 +78,7 @@ class SimpleTable(tables.Table): def test_row_attrs(self): """ If a callable returns an empty string, do not add a space to the CSS class - attribute. (#416) + attribute. (#416). """ counter = count() @@ -128,9 +128,7 @@ class Meta: self.assertEqual(row.get_cell("a"), "valA") def test_even_odd_css_class(self): - """ - Test for BoundRow.get_even_odd_css_class() method - """ + """Test for BoundRow.get_even_odd_css_class() method.""" class SimpleTable(tables.Table): foo = tables.Column() diff --git a/tests/test_tabledata.py b/tests/test_tabledata.py index 74dd7a82..8b18b5d0 100644 --- a/tests/test_tabledata.py +++ b/tests/test_tabledata.py @@ -101,7 +101,7 @@ class listlike(list): class TableQuerysetDataTest(TestCase): def test_custom_TableData(self): - """If TableQuerysetData._length is set, no count() query will be performed""" + """If TableQuerysetData._length is set, no count() query will be performed.""" for i in range(11): Person.objects.create(first_name=f"first {i}") diff --git a/tests/test_templates.py b/tests/test_templates.py index 99da44a7..0fc3b276 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -1,6 +1,7 @@ from django.template import Context, Template from django.test import SimpleTestCase, TestCase, override_settings -from django.utils.translation import gettext_lazy, override as translation_override +from django.utils.translation import gettext_lazy +from django.utils.translation import override as translation_override from lxml import etree import django_tables2 as tables @@ -118,7 +119,7 @@ def test_render_table_db_queries(self): """ Paginated tables should result in two queries: - one query for pagination: .count() - - one query for records on the current page: .all()[start:end] + - one query for records on the current page: .all()[start:end]. """ Person.objects.create(first_name="brad", last_name="ayers") Person.objects.create(first_name="davina", last_name="adisusila") @@ -147,9 +148,7 @@ class TemplateLocalizeTest(TestCase): expected_results = {None: "1234.5", False: "1234.5", True: "1 234,5"} # non-breaking space def assert_cond_localized_table(self, localizeit=None, expected=None): - """ - helper function for defining Table class conditionally - """ + """Assertin helper function to define a Table class based on ``localizeit``.""" class TestTable(tables.Table): name = tables.Column(verbose_name="my column", localize=localizeit) diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py index 7700dd12..df4afc80 100644 --- a/tests/test_templatetags.py +++ b/tests/test_templatetags.py @@ -39,7 +39,7 @@ def test_basic(self): def test_does_not_mutate_context(self): """ Make sure the tag does not change the context of the template the tag is called from - https://github.com/jieter/django-tables2/issues/547 + https://github.com/jieter/django-tables2/issues/547. """ class MyTable(Table): @@ -98,7 +98,7 @@ def test_no_data_with_empty_text(self): @override_settings(DEBUG=True) def test_missing_variable(self): - """Variable that doesn't exist (issue #8)""" + """Variable that doesn't exist (issue #8).""" template = Template("{% load django_tables2 %}{% render_table this_doesnt_exist %}") with self.assertRaisesMessage(ValueError, "Expected table or queryset, not str"): template.render(Context()) diff --git a/tests/test_views.py b/tests/test_views.py index 85de7e8c..ebad6d8e 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -1,5 +1,4 @@ from math import ceil -from typing import Optional import django_filters as filters from django.core.exceptions import ImproperlyConfigured @@ -244,7 +243,7 @@ def get_table_pagination(self, table): def test_using_get_queryset(self): """ Should not raise a value-error for a View using View.get_queryset() - (test for reverting regressing in #423) + (test for reverting regressing in #423). """ Person.objects.create(first_name="Anton", last_name="Sam") @@ -295,7 +294,7 @@ class Table(tables.SingleTableView): class SingleTableMixinTest(TestCase): def test_with_non_paginated_view(self): """ - SingleTableMixin should not assume it is mixed with a ListView + SingleTableMixin should not assume it is mixed with a ListView. Github issue #326 """ @@ -313,10 +312,7 @@ class View(tables.SingleTableMixin, TemplateView): View.as_view()(build_request()) def test_should_paginate_by_default(self): - """ - When mixing SingleTableMixin with FilterView, the table should paginate by default - """ - + """When mixing SingleTableMixin with FilterView, the table should paginate by default.""" total_records = 60 for i in range(1, total_records + 1): Region.objects.create(name=f"region {i:02d} / {total_records}") @@ -482,7 +478,7 @@ class View(tables.MultiTableMixin, TemplateView): tables = (TableB, TableB) tables_data = (Region.objects.all(), Region.objects.all()) - def get_paginate_by(self, table_data) -> Optional[int]: + def get_paginate_by(self, table_data) -> int | None: # Split data into 3 pages return ceil(len(table_data) / 3) diff --git a/tests/utils.py b/tests/utils.py index 595fbc8a..b122ecc0 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -12,9 +12,7 @@ def parse(html): def attrs(xml): - """ - Helper function that returns a dict of XML attributes, given an element. - """ + """Return a dict of XML attributes for a given XML element.""" return lxml.html.fromstring(xml).attrib diff --git a/tox.ini b/tox.ini index 2d401c23..05d2f4c9 100644 --- a/tox.ini +++ b/tox.ini @@ -54,12 +54,6 @@ ignore = E731,W503,E203 exclude = .git,__pycache__,.tox,example/app/migrations max-line-length = 120 -[testenv:isort] -basepython = python3.9 -deps = - -r requirements/common.pip - isort==5.6.4 -commands = isort --diff --check django_tables2 test [isort] multi_line_output = 3