pax_global_header 0000666 0000000 0000000 00000000064 12642271162 0014515 g ustar 00root root 0000000 0000000 52 comment=6676312f1ddec6ef544783c0895a603888dc78e1
django-tables2-1.0.7/ 0000775 0000000 0000000 00000000000 12642271162 0014316 5 ustar 00root root 0000000 0000000 django-tables2-1.0.7/.coveragerc 0000664 0000000 0000000 00000000121 12642271162 0016431 0 ustar 00root root 0000000 0000000 [run]
source =
django_tables2
tests
[html]
directory = reports/htmlcov
django-tables2-1.0.7/.gitignore 0000664 0000000 0000000 00000000307 12642271162 0016306 0 ustar 00root root 0000000 0000000 *.pyc
/.env
/reports
/*.sublime-*
/*.komodoproject
/*.tmproj
/*.egg-info/
/*.egg
/.tox
/.coverage
/MANIFEST
/dist/
/build/
/docs/_build/
/example/database.sqlite
/example/.env
/report.pylint
.cache/
django-tables2-1.0.7/.travis.yml 0000664 0000000 0000000 00000001375 12642271162 0016435 0 ustar 00root root 0000000 0000000 language: python
# django 1.9 is only supported on py27 and py35
# django 1.8 is the first realease supporting py35
matrix:
include:
- python: 2.7
env: TOXENV=py27-1.7
- python: 2.7
env: TOXENV=py27-1.8
- python: 3.3
env: TOXENV=py33-1.7
- python: 3.3
env: TOXENV=py33-1.8
- python: 3.4
env: TOXENV=py34-1.7
- python: 3.4
env: TOXENV=py34-1.8
- python: 3.4
env: TOXENV=py34-1.9
- python: 3.5
env: TOXENV=py35-1.8
- python: 3.5
env: TOXENV=py35-1.9
- python: 2.7
env: TOXENV=py27-master
- python: 3.5
env: TOXENV=py35-master
allow_failures:
- env: TOXENV=py27-master
- env: TOXENV=py35-master
install:
- pip install tox
script:
- tox
django-tables2-1.0.7/CHANGELOG.md 0000664 0000000 0000000 00000024246 12642271162 0016137 0 ustar 00root root 0000000 0000000 # Change log
## v1.0.7 (2016-01-03)
- Explicitly check if `column.verbose_name` is not None to support empty column headers (fixes #280)
- Cleanup the example project to make it work with modern Django versions.
- Do not sort queryset when orderable=False (#204 by [@bmihelac](https://github.com/bmihelac))
- `show_header` attribute on `Table` allows disabling the header (#175 by [@kviktor](https://github.com/kviktor))
- `LinkColumn` now tries to call `get_absolute_url` on a record if no `viewname` is provided (#283, fixes #231).
- Add `request` argument to `Table.as_html()` to allow passing correct request objects instead of poorly generated ones #282
- Add coverage reporting to build #282
- Drop support for python 3.2 (because of coverage), support ends feb 2016 #282
- move `build_request` from `django_table2.utils` to `tests.utils` and amend tests #282
## v1.0.6 (2015-12-29)
- Support for custom text value in LinkColumn (#277 by [@toudi](https://github.com/toudi))
- Refactor LinkColumn.render_link() to not escape twice #279
- Removed `Attrs` (wrapper for dict), deprecated on 2012-09-18
- Convert README.md to rst in setup.py to make PyPI look nice (fixes #97)
## v1.0.5 (2015-12-17)
- First version released by new maintainer [@jieter](https://github.com/jieter)
- Dropped support for django 1.5 and 1.6, add python 3.5 with django 1.8 and 1.9 to the build matrix (#273)
- Prevent `SingleTableView` from calling `get_queryset` twice. (fixes #155)
- Don't call managers when resolving accessors. (#214 by [@mbertheau](https://github.com/mbertheau), fixes #211)
## v1.0.4 (2015-05-09)
- Fix bug in retrieving `field.verbose_name` under Django 1.8.
v1.0.3
------
- Remove setup.cfg as PyPI doesn't actually support it, instead it's a distutils2 thing that's been discontinued.
v1.0.2
------
- Add setup.cfg to declare README.md for PyPI.
v1.0.1
------
- Convert README to markdown so it's formatted nicely on PyPI.
v1.0.0
------
- Travis CI builds pass.
- Added Python 3.4 support.
- Added Django 1.7 and Django 1.8 support.
- Dropped Python 2.6 and 3.2 support.
- Drop Django 1.2 support
- Convert tests to using py.test.
v0.16.0
-------
- Django 1.8 fixes
- `BoundColumn.verbose_name` now titlises only if no verbose_name was given.
`verbose_name` is used verbatim.
- Add max_length attribute to person CharField
- Add Swedish translation
- Update docs presentation on readthedocs
v0.15.0
-------
- Add UK, Russian, Spanish, Portuguese, and Polish translations
- Add support for computed table `attrs`.
v0.14.0
-------
- `querystring` and `seturlparam` template tags now require the request to
be in the context (backwards incompatible) -- #127
- Add Travis CI support
- Add support for Django 1.5
- Add L10N control for columns #120 (ignored in < Django 1.3)
- Drop Python 2.6.4 support in favour of Python 3.2 support
- Non-queryset data ordering is different between Python 3 and 2. When
comparing different types, their truth values are now compared before falling
back to string representations of their type.
v0.13.0
-------
- Add FileColumn.
v0.12.1
-------
- When resolving an accessor, *all* exceptions are smothered into `None`.
v0.12.0
-------
- Improve performance by removing unnecessary queries
- Simplified pagination:
- `Table.page` is an instance attribute (no longer `@property`)
- Exceptions raised by paginators (e.g. `EmptyPage`) are no longer
smothered by `Table.page`
- Pagination exceptions are raised by `Table.paginate`
- `RequestConfig` can handles pagination errors silently, can be disabled
by including `silent=False` in the `paginate` argument value
- Add `DateTimeColumn` and `DateColumn` to handle formatting `datetime`
and timezones.
- Add `BooleanColumn` to handle bool values
- `render_table` can now build and render a table for a queryset, rather than
needing to be passed a table instance
- Table columns created automatically from a model now use specialised columns
- `Column.render` is now skipped if the value is considered *empty*, the
default value is used instead. Empty values are specified via
`Column.empty_values`, by default is `(None, '')` (backward incompatible)
- Default values can now be specified on table instances or `Table.Meta`
- Accessor's now honor `alters_data` during resolving. Fixes issue that would
delete all your data when a column had an accessor of `delete`
- Add `default` and `value` to context of `TemplateColumn`
- Add cardinality indication to the pagination area of a table
- `Attrs` is deprecated, use `dict` instead
v0.11.0
-------
- Add `URLColumn` to render URLs in a data source into hyperlinks
- Add `EmailColumn` to render email addresses into hyperlinks
- `TemplateColumn` can now Django's template loaders to render from a file
v0.10.4
-------
- Fix more bugs on Python 2.6.4, all tests now pass.
v0.10.3
-------
- Fix issues for Python 2.6.4 -- thanks Steve Sapovits & brianmay
- Reduce Django 1.3 dependency to Table.as_html -- thanks brianmay
v0.10.2
-------
- Fix MANIFEST.in to include example templates, thanks TWAC.
- Upgrade django-attest to fix problem with tests on Django 1.3.1
v0.10.1
-------
- Fixed support for Django 1.4's paginator (thanks koledennix)
- Some juggling of internal implementation. `TableData` now supports slicing
and returns new `TableData` instances. `BoundRows` now takes a single
argument `data` (a `TableData` instance).
- Add support for `get_pagination` on `SingleTableMixin`.
- `SingleTableMixin` and `SingleTableView` are now importable directly from
`django_tables2`.
v0.10.0
-------
- Renamed `BoundColumn.order_by` to `order_by_alias` and never returns `None`
(**Backwards incompatible**). Templates are affected if they use something
like:
{% querystring table.prefixed_order_by_field=column.order_by.opposite|default:column.name %}
Which should be rewritten as:
{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}
- Added `next` shortcut to `OrderBy` returned from `BoundColumn.order_by_alias`
- Added `OrderByTuple.get()`
- Deprecated `BoundColumn.sortable`, `Column.sortable`, `Table.sortable`,
`sortable` CSS class, `BoundColumns.itersortable`, `BoundColumns.sortable`; use `orderable` instead of
`sortable`.
- Added `BoundColumn.is_ordered`
- Introduced concept of an `order by alias`, see glossary in the docs for details.
v0.9.6
------
- Fix bug that caused an ordered column's th to have no HTML attributes.
v0.9.5
------
- Updated example project to add colspan on footer cell so table border renders
correctly in Webkit.
- Fix regression that caused 'sortable' class on
.
- Table.__init__ no longer *always* calls .order_by() on querysets, fixes #55.
This does introduce a slight backwards incompatibility. `Table.order_by` now
has the possibility of returning `None`, previously it would *always* return
an `OrderByTuple`.
- DeclarativeColumnsMetaclass.__new__ now uses super()
- Testing now requires pylint and Attest >=0.5.3
v0.9.4
------
- Fix regression that caused column verbose_name values that were marked as
safe to be escaped. Now any verbose_name values that are instances of
SafeData are used unmodified.
v0.9.3
------
- Fix regression in `SingleTableMixin`.
- Remove stray `print` statement.
v0.9.2
------
- `SingleTableView` now uses `RequestConfig`. This fixes issues with
`order_by_field`, `page_field`, and `per_page_field` not being honored.
- Add `Table.Meta.per_page` and change `Table.paginate` to use it as default.
- Add `title` template filter. It differs from Django's built-in `title` filter
because it operates on an individual word basis and leaves words containing
capitals untouched. **Warning**: use `{% load ... from ... %}` to avoid
inadvertantly replacing Django's builtin `title` template filter.
- `BoundColumn.verbose_name` no longer does `capfirst`, titlising is now the
responsbility of `Column.header`.
- `BoundColumn.__unicode__` now uses `BoundColumn.header` rather than
`BoundColumn.verbose_name`.
v0.9.1
------
- Fix version in setup.py (doh)
v0.9.0
------
- Add support for column attributes (see Attrs)
- Add BoundRows.items() to yield (bound_column, cell) pairs
- Tried to make docs more concise. Much stronger promotion of using
RequestConfig and {% querystring %}
v0.8.4
------
- Removed random 'print' statements.
- Tweaked 'paleblue' theme css to be more flexible
- removed `whitespace: no-wrap`
- header background image to support more than 2 rows of text
v0.8.3
------
- Fixed stupid import mistake. Tests didn't pick it up due to them ignoring
`ImportError`.
v0.8.2
------
- `SingleTableView` now inherits from `ListView` which enables automatic
`foo_list.html` template name resolution (thanks dramon for reporting)
- `render_table` template tag no suppresses exceptions when `DEBUG=True`
v0.8.1
------
- Fixed bug in render_table when giving it a template (issue #41)
v0.8.0
------
- Added translation support in the default template via `{% trans %}`
- Removed `basic_table.html`, `Table.as_html()` now renders `table.html` but
will clobber the querystring of the current request. Use the `render_table`
template tag instead
- `render_table` now supports an optional second argument -- the template to
use when rendering the table
- `Table` now supports declaring which template to use when rendering to HTML
- Django >=1.3 is now required
- Added support for using django-haystack's `SearchQuerySet` as a data source
- The default template `table.html` now includes block tags to make it easy to
extend to change small pieces
- Fixed table template parsing problems being hidden due to a subsequent
exception being raised
- Http404 exceptions are no longer raised during a call to `Table.paginate()`,
instead it now occurs when `Table.page` is accessed
- Fixed bug where a table couldn't be rendered more than once if it was
paginated
- Accessing `Table.page` now returns a new page every time, rather than reusing
a single object
v0.7.8
------
- Tables now support using both `sequence` and `exclude` (issue #32).
- `Sequence` class moved to `django_tables2/utils.py`.
- Table instances now support modification to the `exclude` property.
- Removed `BoundColumns._spawn_columns`.
- `Table.data`, `Table.rows`, and `Table.columns` are now attributes
rather than properties.
django-tables2-1.0.7/LICENSE 0000664 0000000 0000000 00000003167 12642271162 0015332 0 ustar 00root root 0000000 0000000 All changes made to django-tables2 since forking from django-tables
are Copyright (c) 2011, Bradley Ayers
All rights reserved.
Redistribution is permitted under the same terms as the original
django-tables license. The original django-tables license is included
below.
Copyright (c) 2008, Michael Elsdörfer
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
django-tables2-1.0.7/MANIFEST.in 0000664 0000000 0000000 00000000440 12642271162 0016052 0 ustar 00root root 0000000 0000000 include README.md
include CHANGELOG.md
include LICENSE
recursive-include docs *
recursive-include django_tables2/templates *
recursive-include django_tables2/static *
recursive-include django_tables2/locale *
recursive-include example/app/fixtures *
recursive-include example/templates *
django-tables2-1.0.7/README.md 0000664 0000000 0000000 00000004011 12642271162 0015571 0 ustar 00root root 0000000 0000000 # django-tables2 - An app for creating HTML tables
[](https://travis-ci.org/bradleyayers/django-tables2)
django-tables2 simplifies the task of turning sets of data into HTML tables. It
has native support for pagination and sorting. It does for HTML tables what
`django.forms` does for HTML forms. e.g.

Its features include:
- Any iterable can be a data-source, but special support for Django querysets is included.
- The builtin UI does not rely on JavaScript.
- Support for automatic table generation based on a Django model.
- Supports custom column functionality via subclassing.
- Pagination.
- Column based table sorting.
- Template tag to enable trivial rendering to HTML.
- Generic view mixin.
# Example
Creating a table for a model `Simple` is as simple as:
```python
import django_tables2 as tables
class SimpleTable(tables.Table):
class Meta:
model = Simple
```
This would then be used in a view:
```python
def simple_list(request):
queryset = Simple.objects.all()
table = SimpleTable(queryset)
return render_to_response("simple_list.html", {"table": table},
context_instance=RequestContext(request))
```
And finally in the template:
```
{% load django_tables2 %}
{% render_table table %}
```
This example shows one of the simplest cases, but django-tables2 can do a lot
more! Check out the [documentation](http://django-tables2.readthedocs.org/en/latest/) for more details.
# Building the documentation
If you want to build the docs from within a virtualenv, and Sphinx is installed
globally, use:
make html SPHINXBUILD="python $(which sphinx-build)"
# Publishing a release
1. Bump the version in `django-tables2/__init__.py`.
2. Update `CHANGELOG.md`.
3. Create a tag `git tag -a v1.0.6 -m 'tagging v1.0.6'`
4. Run `python setup.py sdist upload --sign --identity=`.
django-tables2-1.0.7/django_tables2/ 0000775 0000000 0000000 00000000000 12642271162 0017174 5 ustar 00root root 0000000 0000000 django-tables2-1.0.7/django_tables2/__init__.py 0000664 0000000 0000000 00000000675 12642271162 0021315 0 ustar 00root root 0000000 0000000 # coding: utf-8
# pylint: disable=W0611
from .tables import Table
from .columns import (BooleanColumn, Column, CheckBoxColumn, DateColumn,
DateTimeColumn, EmailColumn, FileColumn, LinkColumn,
TemplateColumn, URLColumn, TimeColumn)
from .config import RequestConfig
from .utils import A
try:
from .views import SingleTableMixin, SingleTableView
except ImportError:
pass
__version__ = "1.0.7"
django-tables2-1.0.7/django_tables2/columns/ 0000775 0000000 0000000 00000000000 12642271162 0020654 5 ustar 00root root 0000000 0000000 django-tables2-1.0.7/django_tables2/columns/__init__.py 0000664 0000000 0000000 00000000671 12642271162 0022771 0 ustar 00root root 0000000 0000000 from .base import library, BoundColumn, BoundColumns, Column
from .booleancolumn import BooleanColumn
from .checkboxcolumn import CheckBoxColumn
from .datecolumn import DateColumn
from .datetimecolumn import DateTimeColumn
from .emailcolumn import EmailColumn
from .filecolumn import FileColumn
from .linkcolumn import LinkColumn
from .templatecolumn import TemplateColumn
from .urlcolumn import URLColumn
from .timecolumn import TimeColumn
django-tables2-1.0.7/django_tables2/columns/base.py 0000664 0000000 0000000 00000053463 12642271162 0022153 0 ustar 00root root 0000000 0000000 # coding: utf-8
from __future__ import absolute_import, unicode_literals
import warnings
from collections import OrderedDict
from itertools import islice
import six
from django import VERSION as django_version
from django.db.models.fields import FieldDoesNotExist
from django_tables2.templatetags.django_tables2 import title
from django_tables2.utils import A, AttributeDict, OrderBy, OrderByTuple
from ..utils import python_2_unicode_compatible
class Library(object):
"""
A collection of columns.
"""
def __init__(self):
self.columns = []
def register(self, column):
self.columns.append(column)
return column
def column_for_field(self, field):
"""
Return a column object suitable for model field.
:returns: column object of `None`
"""
# iterate in reverse order as columns are registered in order
# of least to most specialised (i.e. Column is registered
# first). This also allows user-registered columns to be
# favoured.
for candidate in reversed(self.columns):
if not hasattr(candidate, "from_field"):
continue
column = candidate.from_field(field)
if column is None:
continue
return column
# The library is a mechanism for announcing what columns are available. Its
# current use is to allow the table metaclass to ask columns if they're a
# suitable match for a model field, and if so to return an approach instance.
library = Library()
@library.register
class Column(object): # pylint: disable=R0902
"""
Represents a single column of a table.
`.Column` objects control the way a column (including the cells that
fall within it) are rendered.
.. attribute:: attrs
HTML attributes for elements that make up the column.
:type: `dict`
This API is extended by subclasses to allow arbitrary HTML attributes
to be added to the output.
By default `.Column` supports:
- *th* -- ``table/thead/th`` elements
- *td* -- ``table/tbody/tr/td`` elements
- *cell* -- fallback if *th* or *td* isn't defined
.. attribute:: accessor
An accessor that describes how to extract values for this column from
the :term:`table data`.
:type: string or `~.Accessor`
.. attribute:: default
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).
.. [1] The provided callable object must not expect to receive any
arguments.
.. attribute:: order_by
Allows one or more accessors to be used for ordering rather than
*accessor*.
:type: `unicode`, `tuple`, `~.Accessor`
.. attribute:: orderable
If `False`, this column will not be allowed to influence row
ordering/sorting.
:type: `bool`
.. attribute:: verbose_name
A human readable version of the column name.
:type: `unicode`
.. attribute:: visible
If `True`, this column will be included in the HTML output.
:type: `bool`
.. attribute:: localize
* If `True`, cells of this column will be localized in the HTML output
by the localize filter.
* If `False`, cells of this column will be unlocalized in the HTML output
by the unlocalize filter.
* If `None` (the default), cell will be rendered as is and localization will depend
on ``USE_L10N`` setting.
:type: `bool`
"""
#: Tracks each time a Column instance is created. Used to retain order.
creation_counter = 0
empty_values = (None, '')
def __init__(self, verbose_name=None, accessor=None, default=None,
visible=True, orderable=None, attrs=None, order_by=None,
sortable=None, empty_values=None, localize=None):
if not (accessor is None or isinstance(accessor, six.string_types) or
callable(accessor)):
raise TypeError('accessor must be a string or callable, not %s' %
type(accessor).__name__)
if callable(accessor) and default is not None:
raise TypeError('accessor must be string when default is used, not callable')
self.accessor = A(accessor) if accessor else None
self._default = default
self.verbose_name = verbose_name
self.visible = visible
if sortable is not None:
warnings.warn('`sortable` is deprecated, use `orderable` instead.',
DeprecationWarning)
# if orderable hasn't been specified, we'll use sortable's value
if orderable is None:
orderable = sortable
self.orderable = orderable
self.attrs = attrs or {}
# massage order_by into an OrderByTuple or None
order_by = (order_by, ) if isinstance(order_by, six.string_types) else order_by
self.order_by = OrderByTuple(order_by) if order_by is not None else None
if empty_values is not None:
self.empty_values = empty_values
self.localize = localize
self.creation_counter = Column.creation_counter
Column.creation_counter += 1
@property
def default(self):
# handle callables
return self._default() if callable(self._default) else self._default
@property
def header(self):
"""
The value used for the column heading (e.g. inside the ``
`` tag).
By default this returns `~.Column.verbose_name`.
:returns: `unicode` or `None`
.. note::
This property typically isn't accessed directly when a table is
rendered. Instead, `.BoundColumn.header` is accessed which in turn
accesses this property. This allows the header to fallback to the
column name (it's only available on a `.BoundColumn` object hence
accessing that first) when this property doesn't return something
useful.
"""
return self.verbose_name
def render(self, value):
"""
Returns the content for a specific cell.
This method can be overridden by :ref:`table.render_FOO` methods on the
table or by subclassing `.Column`.
:returns: `unicode`
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
@property
def sortable(self):
"""
*deprecated* -- use `.orderable` instead.
"""
warnings.warn('`sortable` is deprecated, use `orderable` instead.',
DeprecationWarning)
return self.orderable
@classmethod
def from_field(cls, field):
"""
Return a specialised column for the model field or `None`.
:param field: the field that needs a suitable column
:type field: model field instance
:returns: `.Column` object or `None`
If the column isn't specialised for the given model field, it should
return `None`. This gives other columns the opportunity to do better.
If the column is specialised, it should return an instance of itself
that's configured appropriately for the field.
"""
# Since this method is inherited by every subclass, only provide a
# column if this class was asked directly.
if cls is Column:
if hasattr(field, "get_related_field"):
verbose_name = field.get_related_field().verbose_name
else:
verbose_name = getattr(field, 'verbose_name', field.name)
return cls(verbose_name=verbose_name)
@python_2_unicode_compatible
class BoundColumn(object):
"""
A *run-time* version of `.Column`. The difference between
`.BoundColumn` and `.Column`, is that `.BoundColumn` objects include the
relationship between a `.Column` and a `.Table`. In practice, this
means that a `.BoundColumn` knows the *"variable name"* given to the
`.Column` when it was declared on the `.Table`.
For convenience, all `.Column` properties are available from thisclass.
:type table: `.Table` object
:param table: the table in which this column exists
:type column: `.Column` object
:param column: the type of column
:type name: string object
:param name: the variable name of the column used to when defining the
`.Table`. In this example the name is ``age``:
.. code-block:: python
class SimpleTable(tables.Table):
age = tables.Column()
"""
def __init__(self, table, column, name):
self.table = table
self.column = column
self.name = name
def __str__(self):
return six.text_type(self.header)
@property
def accessor(self):
"""
Returns the string used to access data for this column out of the data
source.
"""
return self.column.accessor or A(self.name)
@property
def attrs(self):
"""
Proxy to `.Column.attrs` but injects some values of our own.
A ``th`` and ``td`` are guaranteed to be defined (irrespective of
what's actually defined in the column attrs. This makes writing
templates easier.
"""
# Work on a copy of the attrs object since we're tweaking stuff
attrs = dict(self.column.attrs)
# Find the relevant th attributes (fall back to cell if th isn't
# explicitly specified).
attrs["td"] = td = AttributeDict(attrs.get('td', attrs.get('cell', {})))
attrs["th"] = th = AttributeDict(attrs.get("th", attrs.get("cell", {})))
# make set of existing classes.
th_class = set((c for c in th.get("class", "").split(" ") if c)) # pylint: disable=C0103
td_class = set((c for c in td.get("class", "").split(" ") if c)) # pylint: disable=C0103
# add classes for ordering
if self.orderable:
th_class.add("orderable")
th_class.add("sortable") # backwards compatible
if self.is_ordered:
th_class.add("desc" if self.order_by_alias.is_descending else "asc")
# Always add the column name as a class
th_class.add(self.name)
td_class.add(self.name)
if th_class:
th['class'] = " ".join(sorted(th_class))
if td_class:
td['class'] = " ".join(sorted(td_class))
return attrs
@property
def default(self):
"""
Returns the default value for this column.
"""
value = self.column.default
if value is None:
value = self.table.default
return value
@property
def header(self):
"""
The value that should be used in the header cell for this column.
"""
# favour Column.header
column_header = self.column.header
if column_header:
return column_header
# fall back to automatic best guess
return self.verbose_name
@property
def order_by(self):
"""
Returns an `.OrderByTuple` of appropriately prefixed data source
keys used to sort this column.
See `.order_by_alias` for details.
"""
if self.column.order_by is not None:
order_by = self.column.order_by
else:
# default to using column accessor as data source sort key
order_by = OrderByTuple((self.accessor, ))
return order_by.opposite if self.order_by_alias.is_descending else order_by
@property
def order_by_alias(self):
"""
Returns an `OrderBy` describing the current state of ordering for this
column.
The following attempts to explain the difference between `order_by`
and `.order_by_alias`.
`.order_by_alias` returns and `.OrderBy` instance that's based on
the *name* of the column, rather than the keys used to order the table
data. Understanding the difference is essential.
Having an alias *and* a keys version is necessary because an N-tuple
(of data source keys) can be used by the column to order the data, and
it's ambiguous when mapping from N-tuple to column (since multiple
columns could use the same N-tuple).
The solution is to use order by *aliases* (which are really just
prefixed column names) that describe the ordering *state* of the
column, rather than the specific keys in the data source should be
ordered.
e.g.::
>>> class SimpleTable(tables.Table):
... name = tables.Column(order_by=("firstname", "last_name"))
...
>>> table = SimpleTable([], order_by=("-name", ))
>>> table.columns["name"].order_by_alias
"-name"
>>> table.columns["name"].order_by
("-first_name", "-last_name")
The `OrderBy` returned has been patched to include an extra attribute
``next``, which returns a version of the alias that would be
transitioned to if the user toggles sorting on this column, e.g.::
not sorted -> ascending
ascending -> descending
descending -> ascending
This is useful otherwise in templates you'd need something like:
{% if column.is_ordered %}
{% querystring table.prefixed_order_by_field=column.order_by_alias.opposite %}
{% else %}
{% querystring table.prefixed_order_by_field=column.order_by_alias %}
{% endif %}
"""
order_by = OrderBy((self.table.order_by or {}).get(self.name, self.name))
order_by.next = order_by.opposite if self.is_ordered else order_by
return order_by
@property
def is_ordered(self):
return self.name in (self.table.order_by or ())
@property
def sortable(self):
"""
*deprecated* -- use `orderable` instead.
"""
warnings.warn('`%s.sortable` is deprecated, use `orderable`'
% type(self).__name__, DeprecationWarning)
return self.orderable
@property
def orderable(self):
"""
Return a `bool` depending on whether this column supports ordering.
"""
if self.column.orderable is not None:
return self.column.orderable
return self.table.orderable
@property
def verbose_name(self):
"""
Return the verbose name for this column, or fallback to the titlised
column name.
If the table is using queryset data, then use the corresponding model
field's `~.db.Field.verbose_name`. If it's traversing a relationship,
then get the last field in the accessor (i.e. stop when the
relationship turns from ORM relationships to object attributes [e.g.
person.upper should stop at person]).
"""
# Favor an explicit defined verbose_name
if self.column.verbose_name is not None:
return self.column.verbose_name
# This is our reasonable fallback, should the next section not result
# in anything useful.
name = title(self.name.replace('_', ' '))
# Try to use a model field's verbose_name
if hasattr(self.table.data, 'queryset') and hasattr(self.table.data.queryset, 'model'):
model = self.table.data.queryset.model
parts = self.accessor.split('.')
field = None
for part in parts:
try:
if django_version < (1, 8, 0):
field, _, _, _ = model._meta.get_field_by_name(part)
else:
field = model._meta.get_field(part)
except FieldDoesNotExist:
break
if hasattr(field, 'rel') and hasattr(field.rel, 'to'):
model = field.rel.to
continue
break
if field:
if hasattr(field, 'field'):
name = field.field.verbose_name
else:
name = getattr(field, 'verbose_name', field.name)
return name
@property
def visible(self):
"""
Returns a `bool` depending on whether this column is visible.
"""
return self.column.visible
@property
def localize(self):
'''
Returns `True`, `False` or `None` as described in ``Column.localize``
'''
return self.column.localize
class BoundColumns(object):
"""
Container for spawning `.BoundColumn` objects.
This is bound to a table and provides its `.Table.columns` property.
It provides access to those columns in different ways (iterator,
item-based, filtered and unfiltered etc), stuff that would not be possible
with a simple iterator in the table class.
A `BoundColumns` object is a container for holding `BoundColumn` objects.
It provides methods that make accessing columns easier than if they were
stored in a `list` or `dict`. `Columns` has a similar API to a `dict` (it
actually uses a `~collections.OrderedDict` interally).
At the moment you'll only come across this class when you access a
`.Table.columns` property.
:type table: `.Table` object
:param table: the table containing the columns
"""
def __init__(self, table):
self.table = table
self.columns = OrderedDict()
for name, column in six.iteritems(table.base_columns):
self.columns[name] = bc = BoundColumn(table, column, name)
bc.render = getattr(table, 'render_' + name, column.render)
def iternames(self):
return (name for name, column in self.iteritems())
def names(self):
return list(self.iternames())
def iterall(self):
"""
Return an iterator that exposes all `.BoundColumn` objects,
regardless of visiblity or sortability.
"""
return (column for name, column in self.iteritems())
def all(self):
return list(self.iterall())
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`).
"""
for name in self.table.sequence:
if name not in self.table.exclude:
yield (name, self.columns[name])
def items(self):
return list(self.iteritems())
def iterorderable(self):
"""
Same as `BoundColumns.all` but only returns orderable columns.
This is useful in templates, where iterating over the full
set and checking ``{% if column.sortable %}`` 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 itersortable(self):
warnings.warn('`itersortable` is deprecated, use `iterorderable` instead.',
DeprecationWarning)
return self.iterorderable()
def orderable(self):
return list(self.iterorderable())
def sortable(self):
warnings.warn("`sortable` is deprecated, use `orderable` instead.",
DeprecationWarning)
return self.orderable
def itervisible(self):
"""
Same as `.iterorderable` but only returns visible `.BoundColumn`
objects.
This is geared towards table rendering.
"""
return (x for x in self.iterall() if x.visible)
def visible(self):
return list(self.itervisible())
def __iter__(self):
"""
Convenience API, alias of `.itervisible`.
"""
return self.itervisible()
def __contains__(self, item):
"""
Check if a column is contained within a `Columns` object.
*item* can either be a `BoundColumn` object, or the name of a column.
"""
if isinstance(item, six.string_types):
return item in self.iternames()
else:
# let's assume we were given a column
return item in self.iterall()
def __len__(self):
"""
Return how many :class:`BoundColumn` objects are contained (and
visible).
"""
return len(self.visible())
def __getitem__(self, index):
"""
Retrieve a specific `BoundColumn` object.
*index* can either be 0-indexed or the name of a column
.. code-block:: python
columns['speed'] # returns a bound column with name 'speed'
columns[0] # returns the first column
"""
if isinstance(index, int):
try:
return next(islice(self.iterall(), index, index + 1))
except StopIteration:
raise IndexError
elif isinstance(index, six.string_types):
for column in self.iterall():
if column.name == index:
return column
raise KeyError("Column with name '%s' does not exist; "
"choices are: %s" % (index, self.names()))
else:
raise TypeError('row indices must be integers or str, not %s'
% type(index).__name__)
django-tables2-1.0.7/django_tables2/columns/booleancolumn.py 0000664 0000000 0000000 00000003355 12642271162 0024071 0 ustar 00root root 0000000 0000000 # coding: utf-8
from __future__ import absolute_import, unicode_literals
from .base import Column, library
from django.db import models
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django_tables2.utils import AttributeDict
import six
@library.register
class BooleanColumn(Column):
"""
A column suitable for rendering boolean data.
:param null: is `None` different from `False`?
:type null: `bool`
:param yesno: text to display for True/False values, comma separated
:type yesno: iterable or string
Rendered values are wrapped in a ```` to allow customisation by
themes. By default the span is given the class ``true``, ``false``.
In addition to *attrs* keys supported by `.Column`, the following are
available:
- *span* -- adds attributes to the tag
"""
def __init__(self, null=False, yesno="✔,✘", **kwargs):
self.yesno = (yesno.split(',') if isinstance(yesno, six.string_types)
else tuple(yesno))
if null:
kwargs["empty_values"] = ()
super(BooleanColumn, self).__init__(**kwargs)
def render(self, value):
value = bool(value)
text = self.yesno[int(not value)]
html = '%s'
attrs = {"class": six.text_type(value).lower()}
attrs.update(self.attrs.get("span", {}))
return mark_safe(html % (AttributeDict(attrs).as_html(), escape(text)))
@classmethod
def from_field(cls, field):
if isinstance(field, models.BooleanField):
return cls(verbose_name=field.verbose_name, null=False)
if isinstance(field, models.NullBooleanField):
return cls(verbose_name=field.verbose_name, null=True)
django-tables2-1.0.7/django_tables2/columns/checkboxcolumn.py 0000664 0000000 0000000 00000006703 12642271162 0024240 0 ustar 00root root 0000000 0000000 # coding: utf-8
from __future__ import absolute_import, unicode_literals
from django.utils.safestring import mark_safe
from django_tables2.utils import AttributeDict
import warnings
from .base import Column, library
@library.register
class CheckBoxColumn(Column):
"""
A subclass of `.Column` that renders as a checkbox form input.
This column allows a user to *select* a set of rows. The selection
information can then be used to apply some operation (e.g. "delete") onto
the set of objects that correspond to the selected rows.
The value that is extracted from the :term:`table data` for this column is
used as the value for the checkbox, i.e. ````
This class implements some sensible defaults:
- HTML input's ``name`` attribute is the :term:`column name` (can override
via *attrs* argument).
- *orderable* defaults to `False`.
.. note::
You'd expect that you could select multiple checkboxes in the rendered
table and then *do something* with that. This functionality isn't
implemented. If you want something to actually happen, you'll need to
implement that yourself.
In addition to *attrs* keys supported by `.Column`, the following are
available:
- *input* -- ```` elements in both ``
`` and ``
``.
- *th__input* -- Replaces *input* attrs in header cells.
- *td__input* -- Replaces *input* attrs in body cells.
"""
def __init__(self, attrs=None, **extra):
# For backwards compatibility, passing in a normal dict effectively
# should assign attributes to the `` tag.
valid = set(("input", "th__input", "td__input", "th", "td", "cell"))
if attrs and not set(attrs) & set(valid):
# if none of the keys in attrs are actually valid, assume it's some
# old code that should be be interpreted as {"td__input": ...}
warnings.warn('attrs keys must be one of %s, interpreting as {"td__input": %s}'
% (', '.join(valid), attrs), DeprecationWarning)
attrs = {"td__input": attrs}
# This is done for backwards compatible too, there used to be a
# ``header_attrs`` argument, but this has been deprecated. We'll
# maintain it for a while by translating it into ``head.checkbox``.
if "header_attrs" in extra:
warnings.warn('header_attrs argument is deprecated, '
'use attrs={"th__input": ...} instead',
DeprecationWarning)
attrs.setdefault('th__input', {}).update(extra.pop('header_attrs'))
kwargs = {'orderable': False, 'attrs': attrs}
kwargs.update(extra)
super(CheckBoxColumn, self).__init__(**kwargs)
@property
def header(self):
default = {'type': 'checkbox'}
general = self.attrs.get('input')
specific = self.attrs.get('th__input')
attrs = AttributeDict(default, **(specific or general or {}))
return mark_safe('' % attrs.as_html())
def render(self, value, bound_column): # pylint: disable=W0221
default = {
'type': 'checkbox',
'name': bound_column.name,
'value': value
}
general = self.attrs.get('input')
specific = self.attrs.get('td__input')
attrs = AttributeDict(default, **(specific or general or {}))
return mark_safe('' % attrs.as_html())
django-tables2-1.0.7/django_tables2/columns/datecolumn.py 0000664 0000000 0000000 00000002114 12642271162 0023357 0 ustar 00root root 0000000 0000000 # coding: utf-8
from __future__ import absolute_import, unicode_literals
from django.db import models
from .base import library
from .templatecolumn import TemplateColumn
@library.register
class DateColumn(TemplateColumn):
"""
A column that renders dates in the local timezone.
:param format: format string in same format as Django's ``date`` template
filter (optional)
:type format: `unicode`
:param short: if *format* is not specified, use Django's
``SHORT_DATE_FORMAT`` setting, otherwise use ``DATE_FORMAT``
:type short: `bool`
"""
def __init__(self, format=None, short=True, *args, **kwargs): # pylint: disable=W0622
if format is None:
format = 'SHORT_DATE_FORMAT' if short else 'DATE_FORMAT'
template = '{{ value|date:"%s"|default:default }}' % format
super(DateColumn, self).__init__(template_code=template, *args, **kwargs)
@classmethod
def from_field(cls, field):
if isinstance(field, models.DateField):
return cls(verbose_name=field.verbose_name)
django-tables2-1.0.7/django_tables2/columns/datetimecolumn.py 0000664 0000000 0000000 00000002040 12642271162 0024234 0 ustar 00root root 0000000 0000000 # coding: utf-8
from __future__ import absolute_import, unicode_literals
from django.db import models
from .base import library
from .templatecolumn import TemplateColumn
@library.register
class DateTimeColumn(TemplateColumn):
"""
A column that renders datetimes in the local timezone.
:param format: format string for datetime (optional)
:type format: `unicode`
:param short: if *format* is not specifid, use Django's
``SHORT_DATETIME_FORMAT``, else ``DATETIME_FORMAT``
:type short: `bool`
"""
def __init__(self, format=None, short=True, *args, **kwargs): # pylint: disable=W0622
if format is None:
format = 'SHORT_DATETIME_FORMAT' if short else 'DATETIME_FORMAT'
template = '{{ value|date:"%s"|default:default }}' % format
super(DateTimeColumn, self).__init__(template_code=template, *args, **kwargs)
@classmethod
def from_field(cls, field):
if isinstance(field, models.DateTimeField):
return cls(verbose_name=field.verbose_name)
django-tables2-1.0.7/django_tables2/columns/emailcolumn.py 0000664 0000000 0000000 00000002115 12642271162 0023532 0 ustar 00root root 0000000 0000000 # coding: utf-8
from __future__ import absolute_import, unicode_literals
from django.db import models
from .base import library
from .linkcolumn import BaseLinkColumn
@library.register
class EmailColumn(BaseLinkColumn):
"""
A subclass of `.BaseLinkColumn` that renders the cell value as a hyperlink.
It's common to have a email value in a row hyperlinked to other page.
:param attrs: a `dict` of HTML attributes that are added to
the rendered ``...`` tag
Example:
.. code-block:: python
# models.py
class Person(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
# tables.py
class PeopleTable(tables.Table):
name = tables.Column()
email = tables.EmailColumn()
"""
def render(self, value):
return self.render_link("mailto:%s" % value, text=value)
@classmethod
def from_field(cls, field):
if isinstance(field, models.EmailField):
return cls(verbose_name=field.verbose_name)
django-tables2-1.0.7/django_tables2/columns/filecolumn.py 0000664 0000000 0000000 00000005211 12642271162 0023362 0 ustar 00root root 0000000 0000000 # coding: utf-8
from __future__ import absolute_import, unicode_literals
from django.db import models
from django.utils.safestring import mark_safe
from django_tables2.utils import AttributeDict
import os
from .base import Column, library
@library.register
class FileColumn(Column):
"""
Attempts to render `.FieldFile` (or other storage backend `.File`) as a
hyperlink.
When the file is accessible via a URL, the file is rendered as a
hyperlink. The `.basename` is used as the text::
receipt.pdf
When unable to determine the URL, a ``span`` is used instead::
receipt.pdf
`.Column.attrs` keys ``a`` and ``span`` can be used to add additional attributes.
:type verify_exists: bool
:param verify_exists: 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.
"""
def __init__(self, verify_exists=True, **kwargs):
self.verify_exists = True
super(FileColumn, self).__init__(**kwargs)
def render(self, value):
storage = getattr(value, "storage", None)
exists = None
url = None
if storage:
# we'll assume value is a `django.db.models.fields.files.FieldFile`
if self.verify_exists:
exists = storage.exists(value.name)
url = storage.url(value.name)
else:
if self.verify_exists and hasattr(value, "name"):
# ignore negatives, perhaps the file has a name but it doesn't
# represent a local path... better to stay neutral than give a
# false negative.
exists = os.path.exists(value.name) or exists
tag = 'a' if url else 'span'
attrs = AttributeDict(self.attrs.get(tag, {}))
attrs['title'] = value.name
if url:
attrs['href'] = url
# add "exists" or "missing" to the class list
classes = [c for c in attrs.get('class', '').split(' ') if c]
if exists is True:
classes.append("exists")
elif exists is False:
classes.append("missing")
attrs['class'] = " ".join(classes)
html = '<{tag} {attrs}>{text}{tag}>'.format(
tag=tag,
attrs=attrs.as_html(),
text=os.path.basename(value.name))
return mark_safe(html)
@classmethod
def from_field(cls, field):
if isinstance(field, models.FileField):
return cls(verbose_name=field.verbose_name)
django-tables2-1.0.7/django_tables2/columns/linkcolumn.py 0000664 0000000 0000000 00000015307 12642271162 0023407 0 ustar 00root root 0000000 0000000 # coding: utf-8
from __future__ import absolute_import, unicode_literals
import warnings
from django.core.urlresolvers import reverse
from django.utils.html import format_html
from django_tables2.utils import A, AttributeDict
from .base import Column, library
class BaseLinkColumn(Column):
"""
The base for other columns that render links.
Adds support for an ``a`` key in *attrs** which is added to the rendered
```` tag.
"""
def __init__(self, attrs=None, *args, **kwargs):
valid = set(("a", "th", "td", "cell"))
if attrs and not set(attrs) & set(valid):
# if none of the keys in attrs are actually valid, assume it's some
# old code that should be be interpreted as {"a": ...}
warnings.warn('attrs keys must be one of %s, interpreting as {"a": %s}'
% (', '.join(valid), attrs), DeprecationWarning)
attrs = {"a": attrs}
kwargs['attrs'] = attrs
super(BaseLinkColumn, self).__init__(*args, **kwargs)
def render_link(self, uri, text, attrs=None):
"""
Render a hyperlink.
:param uri: URI for the hyperlink
:param text: value wrapped in ````
:param attrs: ```` tag attributes
"""
attrs = AttributeDict(attrs if attrs is not None else
self.attrs.get('a', {}))
attrs['href'] = uri
return format_html('{text}',
attrs=attrs.as_html(),
text=text)
@library.register
class LinkColumn(BaseLinkColumn):
"""
Renders a normal value as an internal hyperlink to another page.
It's common to have the primary value in a row hyperlinked to the page
dedicated to that record.
The first arguments are identical to that of
`~django.core.urlresolvers.reverse` and allows an internal URL to be
described. If this argument is `None`, then `get_absolute_url`.
(see Django references) will be used.
The last argument *attrs* allows custom HTML attributes to be added to the
rendered ```` tag.
:param viewname: See `~django.core.urlresolvers.reverse`.
Or use `None` to use Model's `get_absolute_url`
:param urlconf: See `~django.core.urlresolvers.reverse`.
:param args: See `~django.core.urlresolvers.reverse`. **
:param kwargs: See `~django.core.urlresolvers.reverse`. **
:param current_app: See `~django.core.urlresolvers.reverse`.
:param attrs: a `dict` of HTML attributes that are added to
the rendered ```` tag
:param text: Either static text, or a callable. If set, this
value will be used to render the text inside link
instead of value (default)
** In order to create a link to a URL that relies on information in the
current row, `.Accessor` objects can be used in the *args* or
*kwargs* arguments. The accessor will be resolved using the row's record
before `~django.core.urlresolvers.reverse` is called.
Example:
.. code-block:: python
# models.py
class Person(models.Model):
name = models.CharField(max_length=200)
# urls.py
urlpatterns = patterns('',
url('people/(\d+)/', views.people_detail, name='people_detail')
)
# tables.py
from django_tables2.utils import A # alias for Accessor
class PeopleTable(tables.Table):
name = tables.LinkColumn('people_detail', args=[A('pk')])
In order to override the text value (i.e. text) consider
the following example:
.. code-block:: python
# tables.py
from django_tables2.utils import A # alias for Accessor
class PeopleTable(tables.Table):
name = tables.LinkColumn('people_detail', text='static text', args=[A('pk')])
age = tables.LinkColumn('people_detail', text=lambda record: record.name, args=[A('pk')])
In the first example, a static text would be rendered ('static text')
In the second example, you can specify a callable which accepts a record object (and thus
can return anything from it)
In addition to *attrs* keys supported by `.Column`, the following are
available:
- *a* -- ```` elements in ``