From 1857f85414f4514112d5b8c20a8bfc314ce275a6 Mon Sep 17 00:00:00 2001 From: Michael Elsdoerfer Date: Fri, 26 Mar 2010 12:55:07 +0100 Subject: [PATCH] Converted the readme file to a Sphinx documentation; some minor refactoring along the lines. --- .gitignore | 2 + README | 522 +---------------------------------- docs/Makefile | 89 ++++++ docs/columns.rst | 100 +++++++ docs/conf.py | 194 +++++++++++++ docs/features/index.rst | 11 + docs/features/ordering.rst | 170 ++++++++++++ docs/features/pagination.rst | 47 ++++ docs/index.rst | 145 ++++++++++ docs/installation.rst | 15 + docs/make.bat | 113 ++++++++ docs/templates.rst | 67 +++++ docs/types/index.rst | 12 + docs/types/memory.rst | 20 ++ docs/types/models.rst | 134 +++++++++ docs/types/sql.rst | 9 + requirements-dev.pip | 1 + 17 files changed, 1132 insertions(+), 519 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/columns.rst create mode 100644 docs/conf.py create mode 100644 docs/features/index.rst create mode 100644 docs/features/ordering.rst create mode 100644 docs/features/pagination.rst create mode 100644 docs/index.rst create mode 100644 docs/installation.rst create mode 100644 docs/make.bat create mode 100644 docs/templates.rst create mode 100644 docs/types/index.rst create mode 100644 docs/types/memory.rst create mode 100644 docs/types/models.rst create mode 100644 docs/types/sql.rst diff --git a/.gitignore b/.gitignore index 4a5d74c..74e43af 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,11 @@ /MANIFEST /dist +/docs/_build/* /BRANCH_TODO +# Project files /.project /.pydevproject /*.wpr diff --git a/README b/README index a3ca337..2bc8b81 100644 --- a/README +++ b/README @@ -1,520 +1,4 @@ -django-tables -============= - -A Django QuerySet renderer. - -Installation ------------- - -Adding django-tables to your INSTALLED_APPS settings is optional, it'll get -you the ability to load some template utilities via {% load tables %}, but -apart from that, ``import django_tables as tables`` should get you going. - -Running the test suite ----------------------- - -The test suite uses nose: - http://somethingaboutorange.com/mrl/projects/nose/ - -Working with Tables -------------------- - -A table class looks very much like a form: - - import django_tables as tables - class CountryTable(tables.MemoryTable): - name = tables.Column(verbose_name="Country Name") - population = tables.Column(sortable=False, visible=False) - time_zone = tables.Column(name="tz", default="UTC+1") - -Instead of fields, you declare a column for every piece of data you want to -expose to the user. - -To use the table, create an instance: - - countries = CountryTable([{'name': 'Germany', population: 80}, - {'name': 'France', population: 64}]) - -Decide how the table should be sorted: - - countries.order_by = ('name',) - assert [row.name for row in countries.row] == ['France', 'Germany'] - - countries.order_by = ('-population',) - assert [row.name for row in countries.row] == ['Germany', 'France'] - -If you pass the table object along into a template, you can do: - - {% for column in countries.columns %} - {{column}} - {% endfor %} - -Which will give you: - - Country Name - Timezone - -Note that ``population`` is skipped (as it has ``visible=False``), that the -declared verbose name for the ``name`` column is used, and that ``time_zone`` -is converted into a more beautiful string for output automatically. - -There are few requirements for the source data of a table. It should be an -iterable with dict-like objects. Values found in the source data that are -not associated with a column are ignored, missing values are replaced by -the column default or None. - -Common Workflow -~~~~~~~~~~~~~~~ - -Usually, you are going to use a table like this. Assuming ``CountryTable`` -is defined as above, your view will create an instance and pass it to the -template: - - def list_countries(request): - data = ... - countries = CountryTable(data, order_by=request.GET.get('sort')) - return render_to_response('list.html', {'table': countries}) - -Note that we are giving the incoming "sort" query string value directly to -the table, asking for a sort. All invalid column names will (by default) be -ignored. In this example, only "name" and "tz" are allowed, since: - - * "population" has sortable=False - * "time_zone" has it's name overwritten with "tz". - -Then, in the "list.html" template, write: - - - - {% for column in table.columns %} - - {% endfor %} - - {% for row in table.rows %} - - {% for value in row %} - - {% endfor %} - - {% endfor %} -
{{ column }}
{{ value }}
- -This will output the data as an HTML table. Note how the table is now fully -sortable, since our link passes along the column name via the querystring, -which in turn will be used by the server for ordering. ``order_by`` accepts -comma-separated strings as input, and "{{ table.order_by }}" will be rendered -as a such a string. - -Instead of the iterator, you can use your knowledge of the table structure to -access columns directly: - - {% if table.columns.tz.visible %} - {{ table.columns.tz }} - {% endfor %} - - -Dynamic Data -~~~~~~~~~~~~ - -If any value in the source data is a callable, it will be passed it's own -row instance and is expected to return the actual value for this particular -table cell. - -Similarily, the colunn default value may also be callable that will takes -the row instance as an argument (representing the row that the default is -needed for). - - -Table Options -------------- - -Table-specific options are implemented using the same inner ``Meta`` class -concept as known from forms and models in Django: - - class MyTable(tables.MemoryTable): - class Meta: - sortable = True - -Currently, for non-model tables, the only supported option is ``sortable``. -Per default, all columns are sortable, unless a column specifies otherwise. -This meta option allows you to overwrite the global default for the table. - - -ModelTables ------------ - -Like forms, tables can also be used with models: - - class CountryTable(tables.ModelTable): - id = tables.Column(sortable=False, visible=False) - class Meta: - model = Country - exclude = ['clicks'] - -The resulting table will have one column for each model field, with the -exception of "clicks", which is excluded. The column for "id" is overwritten -to both hide it and deny it sort capability. - -When instantiating a ModelTable, you usually pass it a queryset to provide -the table data: - - qs = Country.objects.filter(continent="europe") - countries = CountryTable(qs) - -However, you can also just do: - - countries = CountryTable() - -and all rows exposed by the default manager of the model the table is based -on will be used. - -If you are using model inheritance, then the following also works: - - countries = CountryTable(CountrySubclass) - -Note that while you can pass any model, it really only makes sense if the -model also provides fields for the columns you have defined. - -If you just want to use ModelTables, but without auto-generated columns, -you do not have to list all model fields in the ``exclude`` Meta option. -Instead, simply don't specify a model. - - -Custom Columns -~~~~~~~~~~~~~~ - -You an add custom columns to your ModelTable that are not based on actual -model fields: - - class CountryTable(tables.ModelTable): - custom = tables.Column(default="foo") - class Meta: - model = Country - -Just make sure your model objects do provide an attribute with that name. -Functions are also supported, so ``Country.custom`` could be a callable. - - -Spanning relationships -~~~~~~~~~~~~~~~~~~~~~~ - -Let's assume you have a ``Country`` model, with a foreignkey ``capital`` -pointing to the ``City`` model. While displaying a list of countries, -you might want want to link to the capital's geographic location, which is -stored in ``City.geo`` as a ``(lat, long)`` tuple, on Google Maps. - -ModelTables support relationship spanning syntax of Django's database api: - - class CountryTable(tables.ModelTable): - city__geo = tables.Column(name="geo") - -This will add a column named "geo", based on the field by the same name -from the "city" relationship. Note that the name used to define the column -is what will be used to access the data, while the name-overwrite passed to -the column constructor just defines a prettier name for us to work with. -This is to be consistent with auto-generated columns based on model fields, -where the field/column name naturally equals the source name. - -However, to make table defintions more visually appealing and easier to -read, an alternative syntax is supported: setting the column ``data`` -property to the appropriate string. - - class CountryTable(tables.ModelTable): - geo = tables.Column(data='city__geo') - -Note that you don't need to define a relationship's fields as separate -columns if you already have a column for the relationship itself, i.e.: - - class CountryTable(tables.ModelTable): - city = tables.Column() - - for country in countries.rows: - print country.city.id - print country.city.geo - print country.city.founder.name - - -ModelTable Specialities -~~~~~~~~~~~~~~~~~~~~~~~ - -ModelTables currently have some restrictions with respect to ordering: - - * Custom columns not based on a model field do not support ordering, - regardless of the ``sortable`` property (it is ignored). - - * A ModelTable column's ``default`` or ``data`` value does not affect - ordering. This differs from the non-model table behaviour. - -If a column is mapped to a method on the model, that method will be called -without arguments. This behavior differs from non-model tables, where a -row object will be passed. - -If you are using callables (e.g. for the ``default`` or ``data`` column -options), they will generally be run when a row is accessed, and -possible repeatetly when accessed more than once. This behavior differs from -non-model tables, where they would be called once, when the table is -generated. - -Columns -------- - -Columns are what defines a table. Therefore, the way you configure your -columns determines to a large extend how your table operates. - -``django_tables.columns`` currently defines three classes, ``Column``, -``TextColumn`` and ``NumberColumn``. However, the two subclasses currently -don't do anything special at all, so you can simply use the base class. -While this will likely change in the future (e.g. when grouping is added), -the base column class will continue to work by itself. - -There are no required arguments. The following is fine: - - class MyTable(tables.MemoryTable): - c = tables.Column() - -It will result in a column named "c" in the table. You can specify the -``name`` to override this: - - c = tables.Column(name="count") - -The column is now called and accessed via "count", although the table will -still use "c" to read it's values from the source. You can however modify -that as well, by specifying ``data``: - - c = tables.Column(name="count", data="count") - -For most practicual purposes, "c" is now meaningless. While in most cases -you will just define your column using the name you want it to have, the -above is useful when working with columns automatically generated from -models: - - class BookTable(tables.ModelTable): - book_name = tables.Column(name="name") - author = tables.Column(data="info__author__name") - class Meta: - model = Book - -The overwritten ``book_name`` field/column will now be exposed as the -cleaner "name", and the new "author" column retrieves it's values from -``Book.info.author.name``. - -Note: ``data`` may also be a callable which will be passed a row object. - -Apart from their internal name, you can define a string that will be used -when for display via ``verbose_name``: - - pubdate = tables.Column(verbose_name="Published") - -The verbose name will be used, for example, if you put in a template: - - {{ column }} - -If you don't want a column to be sortable by the user: - - pubdate = tables.Column(sortable=False) - -Sorting is also affected by ``direction``, which can be used to change the -*default* sort direction to descending. Note that this option only indirectly -translates to the actual direction. Normal und reverse order, the terms -django-tables exposes, now simply mean different things. - - pubdate = tables.Column(direction='desc') - -If you don't want to expose a column (but still require it to exist, for -example because it should be sortable nonetheless): - - pubdate = tables.Column(visible=False) - -The column and it's values will now be skipped when iterating through the -table, although it can still be accessed manually. - -Finally, you can specify default values for your columns: - - health_points = tables.Column(default=100) - -Note that how the default is used and when it is applied differs between -static and ModelTables. - - -The table.columns container ---------------------------- - -While you can iterate through ``columns`` and get all the currently visible -columns, it further provides features that go beyond a simple iterator. - -You can access all columns, regardless of their visibility, through -``columns.all``. - -``columns.sortable`` is a handy shortcut that exposes all columns which's -``sortable`` attribute is True. This can be very useful in templates, when -doing {% if column.sortable %} can conflict with {{ forloop.last }}. - - -Tables and Pagination ---------------------- - -If your table has a large number of rows, you probably want to paginate -the output. There are two distinct approaches. - -First, you can just paginate over ``rows`` as you would do with any other -data: - - table = MyTable(queryset) - paginator = Paginator(table.rows, 10) - page = paginator.page(1) - -You're not necessarily restricted to Django's own paginator (or subclasses) - -any paginator should work with this approach, so long it only requires -``rows`` to implement ``len()``, slicing, and, in the case of ModelTables, a -``count()`` method. The latter means that the ``QuerySetPaginator`` also -works as expected. - -Alternatively, you may use the ``paginate`` feature: - - table = MyTable(queryset) - table.paginate(Paginator, 10, page=1, orphans=2) - for row in table.rows.page(): - pass - table.paginator # new attributes - table.page - -The table will automatically create an instance of ``QuerySetPaginator``, -passing it's own data as the first argument and additionally any arguments -you have specified, except for ``page``. You may use any paginator, as long -as it follows the Django protocol: - - * Take data as first argument. - * Support a page() method returning an object with an ``object_list`` - attribute, exposing the paginated data. - -Note that due to the abstraction layer that django-tables represents, it is -not necessary to use Django's QuerySetPaginator with ModelTables. Since the -table knows that it holds a queryset, it will automatically choose to use -count() to determine the data length (which is exactly what -QuerySetPaginator would do). - -Ordering --------- - -The syntax is similar to that of the Django database API. Order may be -specified a list (or tuple) of column names. If prefixed with a hyphen, the -ordering for that particular column will be in reverse order. - -Random ordering is currently not supported. - -Interacting with order -~~~~~~~~~~~~~~~~~~~~~~ - -Letting the user change the order of a table is a common scenario. With -respect to Django, this means adding links to your table output that will -send off the appropriate arguments to the server. django-tables attempts -to help with you that. - -A bound column, that is a colum accessed through a table instance, provides -the following attributes: - - - ``name_reversed`` will simply return the column name prefixed with a - hyphen; this is useful in templates, where string concatenation can - at times be difficult. - - - ``name_toggled`` checks the tables current order, and will then - return the column either prefixed with an hyphen (for reverse ordering) - or without, giving you the exact opposite order. If the column is - currently not ordered, it will start off in non-reversed order. - -It is easy to be confused about the difference between the ``reverse`` and -``toggle`` terminology. django-tables tries to put normal/reverse-order -abstraction on top of "ascending/descending", where as normal order could -potentially mean either ascending or descending, depending on the column. - -Commonly, you see tables that indicate what columns they are currently -ordered by using little arrows. To implement this: - - - ``is_ordered``: Returns True if the column is in the current - ``order_by``, regardless of the polarity. - - - ``is_ordered_reverse``, ``is_ordered_straight``: Returns True if the - column is ordered in reverse or non-reverse, respectively, otherwise - False. - -The above is usually enough for most simple cases, where tables are only -ordered by a single column. For scenarios in which multi-column order is -used, additional attributes are available: - - - ``order_by``: Return the current order, but with the current column - set to normal ordering. If the current column is not already part of - the order, it is appended. Any existing columns in the order are - maintained as-is. - - - ``order_by_reversed``, ``order_by_toggled``: Similarly, return the - table's current ``order_by`` with the column set to reversed or toggled, - respectively. Again, it is appended if not already ordered. - -Additionally, ``table.order_by.toggle()`` may also be useful in some cases: -It will toggle all order columns and should thus give you the exact -opposite order. - -The following is a simple example of single-column ordering. It shows a list -of sortable columns, each clickable, and an up/down arrow next to the one -that is currently used to sort the table. - - Sort by: - {% for column in table.columns %} - {% if column.sortable %} - {{ column }} - {% if column.is_ordered_straight %}{% endif %} - {% if column.is_ordered_reverse %}{% endif %} - {% endif %} - {% endfor %} - - -Error handling --------------- - -Passing incoming query string values from the request directly to the -table constructor is a common thing to do. However, such data can easily -be invalid, be it that a user manually modified it, or someone put up a -broken link. In those cases, you usually would not want to raise an -exception (nor be notified by Django's error notification mechanism) - -there is nothing you could do anyway. - -Because of this, such errors will by default be silently ignored. For -example, if one out of three columns in an "order_by" is invalid, the other -two will still be applied: - - table.order_by = ('name', 'totallynotacolumn', '-date) - assert table.order_by = ('name', '-date) - -This ensures that the following table will be created regardless of the -string in "sort. - - table = MyTable(data, order_by=request.GET.get('sort')) - -However, if you want, you can disable this behaviour and have an exception -raised instead, using: - - import django_tables - django_tables.options.IGNORE_INVALID_OPTIONS = False - - -Template Utilities ------------------- - -If you want the give your users the ability to interact with your table (e.g. -change the ordering), you will need to create urls with the appropriate -queries. To simplify that process, django-tables comes with a helpful -templatetag: - - {% set_url_param sort="name" %} # ?sort=name - {% set_url_param sort="" %} # delete "sort" param - -The template library can be found in 'django_modules.app.templates.tables'. -If you add ''django_modules.app' to your INSTALLED_APPS setting, you will -be able to do: - - {% load tables %} - -Note: The tag requires the current request to be available as ``request`` -in the context (usually, this means activating the Django request context -processor). +django-tables - a Django QuerySet renderer. +Documentation: + http://elsdoerfer.name/docs/django-tables/ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..05ce818 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,89 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-tables.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-tables.qhc" + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/columns.rst b/docs/columns.rst new file mode 100644 index 0000000..d064918 --- /dev/null +++ b/docs/columns.rst @@ -0,0 +1,100 @@ +================= +All about Columns +================= + +Columns are what defines a table. Therefore, the way you configure your +columns determines to a large extend how your table operates. + +``django_tables.columns`` currently defines three classes, ``Column``, +``TextColumn`` and ``NumberColumn``. However, the two subclasses currently +don't do anything special at all, so you can simply use the base class. +While this will likely change in the future (e.g. when grouping is added), +the base column class will continue to work by itself. + +There are no required arguments. The following is fine: + +.. code-block:: python + + class MyTable(tables.MemoryTable): + c = tables.Column() + +It will result in a column named ``c`` in the table. You can specify the +``name`` to override this: + +.. code-block:: python + + c = tables.Column(name="count") + +The column is now called and accessed via "count", although the table will +still use ``c`` to read it's values from the source. You can however modify +that as well, by specifying ``data``: + +.. code-block:: python + + c = tables.Column(name="count", data="count") + +For practicual purposes, ``c`` is now meaningless. While in most cases +you will just define your column using the name you want it to have, the +above is useful when working with columns automatically generated from +models: + +.. code-block:: python + + class BookTable(tables.ModelTable): + book_name = tables.Column(name="name") + author = tables.Column(data="info__author__name") + class Meta: + model = Book + +The overwritten ``book_name`` field/column will now be exposed as the +cleaner ``name``, and the new ``author`` column retrieves it's values from +``Book.info.author.name``. + +Note: ``data`` may also be a callable which will be passed a row object. + +Apart from their internal name, you can define a string that will be used +when for display via ``verbose_name``: + +.. code-block:: python + + pubdate = tables.Column(verbose_name="Published") + +The verbose name will be used, for example, if you put in a template: + +.. code-block:: django + + {{ column }} + +If you don't want a column to be sortable by the user: + +.. code-block:: python + + pubdate = tables.Column(sortable=False) + +Sorting is also affected by ``direction``, which can be used to change the +*default* sort direction to descending. Note that this option only indirectly +translates to the actual direction. Normal und reverse order, the terms +django-tables exposes, now simply mean different things. + +.. code-block:: python + + pubdate = tables.Column(direction='desc') + +If you don't want to expose a column (but still require it to exist, for +example because it should be sortable nonetheless): + +.. code-block:: python + + pubdate = tables.Column(visible=False) + +The column and it's values will now be skipped when iterating through the +table, although it can still be accessed manually. + +Finally, you can specify default values for your columns: + +.. code-block:: python + + health_points = tables.Column(default=100) + +Note that how the default is used and when it is applied differs between +table types. diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..4a407dd --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +# +# django-tables documentation build configuration file, created by +# sphinx-quickstart on Fri Mar 26 08:40:14 2010. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'django-tables' +copyright = u'2010, Michael Elsdörfer' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'django-tablesdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index.rst', 'django-tables.tex', u'django-tables Documentation', + u'Michael Elsdörfer', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/docs/features/index.rst b/docs/features/index.rst new file mode 100644 index 0000000..abada5a --- /dev/null +++ b/docs/features/index.rst @@ -0,0 +1,11 @@ +=============== +How to do stuff +=============== + +This section will explain some specific features in more detail. + +.. toctree:: + :maxdepth: 1 + + ordering + pagination \ No newline at end of file diff --git a/docs/features/ordering.rst b/docs/features/ordering.rst new file mode 100644 index 0000000..4f81f1d --- /dev/null +++ b/docs/features/ordering.rst @@ -0,0 +1,170 @@ +================= +Sorting the table +================= + +``django-tables`` allows you to specify which column the user can sort, +and will validate and resolve an incoming query string value the the +correct ordering. + +It will also help you rendering the correct links to change the sort +order in your template. + + +Specify which columns are sortable +---------------------------------- + +Tables can take a ``sortable`` option through an inner ``Meta``, the same +concept as known from forms and models in Django: + +.. code-block:: python + + class MyTable(tables.MemoryTable): + class Meta: + sortable = True + +This will be the default value for all columns, and it defaults to ``True``. +You can override the table default for each individual column: + +.. code-block:: python + + class MyTable(tables.MemoryTable): + foo = tables.Column(sortable=False) + class Meta: + sortable = True + + +Setting the table ordering +-------------------------- + +Your table both takes a ``order_by`` argument in it's constructor, and you +can change the order by assigning to the respective attribute: + +.. code-block:: python + + table = MyTable(order_by='-foo') + table.order_by = 'foo' + +You can see that the value expected is pretty much what is used by the +Django database API: An iterable of column names, optionally using a hyphen +as a prefix to indicate reverse order. However, you may also pass a +comma-separated string: + +.. code-block:: python + + table = MyTable(order_by='column1,-column2') + +When you set ``order_by``, the value is parsed right away, and subsequent +reads will give you the normalized value: + +.. code-block:: python + + >>> table.order_by = ='column1,-column2' + >>> table.order_by + ('column1', '-column2') + +Note: Random ordering is currently not supported. + + +Error handling +~~~~~~~~~~~~~~ + +Passing incoming query string values from the request directly to the +table constructor is a common thing to do. However, such data can easily +contain invalid column names, be it that a user manually modified it, +or someone put up a broken link. In those cases, you usually would not want +to raise an exception (nor be notified by Django's error notification +mechanism) - there is nothing you could do anyway. + +Because of this, such errors will by default be silently ignored. For +example, if one out of three columns in an "order_by" is invalid, the other +two will still be applied: + +.. code-block:: python + + >>> table.order_by = ('name', 'totallynotacolumn', '-date) + >>> table.order_by + ('name', '-date) + +This ensures that the following table will be created regardless of the +value in ``sort``: + +.. code-block:: python + + table = MyTable(data, order_by=request.GET.get('sort')) + +However, if you want, you can disable this behaviour and have an exception +raised instead, using: + +.. code-block:: python + + import django_tables + django_tables.options.IGNORE_INVALID_OPTIONS = False + + +Interacting with order +---------------------- + +Letting the user change the order of a table is a common scenario. With +respect to Django, this means adding links to your table output that will +send off the appropriate arguments to the server. ``django-tables`` +attempts to help with you that. + +A bound column, that is a column accessed through a table instance, provides +the following attributes: + +- ``name_reversed`` will simply return the column name prefixed with a + hyphen; this is useful in templates, where string concatenation can + at times be difficult. + +- ``name_toggled`` checks the tables current order, and will then + return the column either prefixed with an hyphen (for reverse ordering) + or without, giving you the exact opposite order. If the column is + currently not ordered, it will start off in non-reversed order. + +It is easy to be confused about the difference between the ``reverse`` and +``toggle`` terminology. ``django-tables`` tries to put a normal/reverse-order +abstraction on top of "ascending/descending", where as normal order could +potentially mean either ascending or descending, depending on the column. + +Something you commonly see is a table that indicates which column it is +currently ordered by through little arrows. To implement this, you will +find useful: + +- ``is_ordered``: Returns ``True`` if the column is in the current + ``order_by``, regardless of the polarity. + +- ``is_ordered_reverse``, ``is_ordered_straight``: Returns ``True`` if the + column is ordered in reverse or non-reverse, respectively, otherwise + ``False``. + +The above is usually enough for most simple cases, where tables are only +ordered by a single column. For scenarios in which multi-column order is +used, additional attributes are available: + +- ``order_by``: Return the current order, but with the current column + set to normal ordering. If the current column is not already part of + the order, it is appended. Any existing columns in the order are + maintained as-is. + +- ``order_by_reversed``, ``order_by_toggled``: Similarly, return the + table's current ``order_by`` with the column set to reversed or toggled, + respectively. Again, it is appended if not already ordered. + +Additionally, ``table.order_by.toggle()`` may also be useful in some cases: +It will toggle all order columns and should thus give you the exact +opposite order. + +The following is a simple example of single-column ordering. It shows a list +of sortable columns, each clickable, and an up/down arrow next to the one +that is currently used to sort the table. + +.. code-block:: django + + Sort by: + {% for column in table.columns %} + {% if column.sortable %} + {{ column }} + {% if column.is_ordered_straight %}{% endif %} + {% if column.is_ordered_reverse %}{% endif %} + {% endif %} + {% endfor %} diff --git a/docs/features/pagination.rst b/docs/features/pagination.rst new file mode 100644 index 0000000..30217c1 --- /dev/null +++ b/docs/features/pagination.rst @@ -0,0 +1,47 @@ +---------- +Pagination +---------- + +If your table has a large number of rows, you probably want to paginate +the output. There are two distinct approaches. + +First, you can just paginate over ``rows`` as you would do with any other +data: + +.. code-block:: python + + table = MyTable(queryset) + paginator = Paginator(table.rows, 10) + page = paginator.page(1) + +You're not necessarily restricted to Django's own paginator (or subclasses) - +any paginator should work with this approach, so long it only requires +``rows`` to implement ``len()``, slicing, and, in the case of a +``ModelTable``, a ``count()`` method. The latter means that the +``QuerySetPaginator`` also works as expected. + +Alternatively, you may use the ``paginate`` feature: + +.. code-block:: python + + table = MyTable(queryset) + table.paginate(Paginator, 10, page=1, orphans=2) + for row in table.rows.page(): + pass + table.paginator # new attributes + table.page + +The table will automatically create an instance of ``Paginator``, +passing it's own data as the first argument and additionally any arguments +you have specified, except for ``page``. You may use any paginator, as long +as it follows the Django protocol: + +* Take data as first argument. +* Support a page() method returning an object with an ``object_list`` + attribute, exposing the paginated data. + +Note that due to the abstraction layer that ``django-tables`` represents, it +is not necessary to use Django's ``QuerySetPaginator`` with model tables. +Since the table knows that it holds a queryset, it will automatically choose +to use count() to determine the data length (which is exactly what +``QuerySetPaginator`` would do). \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..95ae89b --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,145 @@ +========================================== +django-tables - A Django Queryset renderer +========================================== + + +``django-tables`` wants to help you present data while allowing your user +to apply common tabular transformations on it. + +Currently, this mostly mostly means "sorting", i.e. parsing a query string +coming from the browser (while supporting multiple sort fields, restricting +the fields that may be sorted, exposing fields under different names) and +generating the proper links to allow the user to change the sort order. + +In the future, filtering and grouping will hopefully be added. + + +A simple example +---------------- + +The API looks similar to that of Django's ``ModelForms``: + +.. code-block:: python + + import django_tables as tables + + class CountryTable(tables.MemoryTable): + name = tables.Column(verbose_name="Country Name") + population = tables.Column(sortable=False, visible=False) + time_zone = tables.Column(name="tz", default="UTC+1") + +Instead of fields, you declare a column for every piece of data you want +to expose to the user. + +To use the table, create an instance: + +.. code-block:: python + + countries = CountryTable([{'name': 'Germany', population: 80}, + {'name': 'France', population: 64}]) + +Decide how the table should be sorted: + +.. code-block:: python + + countries.order_by = ('name',) + assert [row.name for row in countries.row] == ['France', 'Germany'] + + countries.order_by = ('-population',) + assert [row.name for row in countries.row] == ['Germany', 'France'] + +If you pass the table object along into a template, you can do: + +.. code-block:: django + + {% for column in countries.columns %} + {{ column }} + {% endfor %} + +Which will give you: + +.. code-block:: django + + Country Name + Timezone + +Note that ``population`` is skipped (as it has ``visible=False``), that the +declared verbose name for the ``name`` column is used, and that ``time_zone`` +is converted into a more beautiful string for output automatically. + + +Common Workflow +~~~~~~~~~~~~~~~ + +Usually, you are going to use a table like this. Assuming ``CountryTable`` +is defined as above, your view will create an instance and pass it to the +template: + +.. code-block:: python + + def list_countries(request): + data = ... + countries = CountryTable(data, order_by=request.GET.get('sort')) + return render_to_response('list.html', {'table': countries}) + +Note that we are giving the incoming ``sort`` query string value directly to +the table, asking for a sort. All invalid column names will (by default) be +ignored. In this example, only ``name`` and ``tz`` are allowed, since: + + * ``population`` has ``sortable=False`` + * ``time_zone`` has it's name overwritten with ``tz``. + +Then, in the ``list.html`` template, write: + +.. code-block:: django + + + + {% for column in table.columns %} + + {% endfor %} + + {% for row in table.rows %} + + {% for value in row %} + + {% endfor %} + + {% endfor %} +
{{ column }}
{{ value }}
+ +This will output the data as an HTML table. Note how the table is now fully +sortable, since our link passes along the column name via the querystring, +which in turn will be used by the server for ordering. ``order_by`` accepts +comma-separated strings as input, and "{{ column.name_toggled }}" will be +rendered as a such a string. + +Instead of the iterator, you can alos use your knowledge of the table +structure to access columns directly: + +.. code-block:: django + + {% if table.columns.tz.visible %} + {{ table.columns.tz }} + {% endfor %} + + +In Detail +========= + +.. toctree:: + :maxdepth: 2 + + installation + types/index + features/index + columns + templates + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..2df888b --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,15 @@ +------------ +Installation +------------ + +Adding ``django-tables`` to your ``INSTALLED_APPS`` setting is optional. +It'll get you the ability to load some template utilities via +``{% load tables %}``, but apart from that, +``import django_tables as tables`` should get you going. + + +Running the test suite +---------------------- + +The test suite uses nose: + http://somethingaboutorange.com/mrl/projects/nose/ \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..28bda72 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,113 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +set SPHINXBUILD=sphinx-build +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-tables.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-tables.ghc + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/docs/templates.rst b/docs/templates.rst new file mode 100644 index 0000000..36a2acc --- /dev/null +++ b/docs/templates.rst @@ -0,0 +1,67 @@ +================================ +Rendering the table in templates +================================ + + +A table instance bound to data has two attributes ``columns`` and ``rows``, +which can be iterate over: + +.. code-block:: django + + + + {% for column in table.columns %} + + {% endfor %} + + {% for row in table.rows %} + + {% for value in row %} + + {% endfor %} + + {% endfor %} +
{{ column }}
{{ value }}
+ +For the attributes available on a bound column, see :doc:`features/index`, +depending on what you want to accomplish. + + +The table.columns container +--------------------------- + +While you can iterate through ``columns`` and get all the currently visible +columns, it further provides features that go beyond a simple iterator. + +You can access all columns, regardless of their visibility, through +``columns.all``. + +``columns.sortable`` is a handy shortcut that exposes all columns which's +``sortable`` attribute is True. This can be very useful in templates, when +doing {% if column.sortable %} can conflict with {{ forloop.last }}. + + +Template Utilities +------------------ + +If you want the give your users the ability to interact with your table (e.g. +change the ordering), you will need to create urls with the appropriate +queries. To simplify that process, django-tables comes with a helpful +templatetag: + +.. code-block:: django + + {% set_url_param sort="name" %} # ?sort=name + {% set_url_param sort="" %} # delete "sort" param + +The template library can be found in 'django_modules.app.templates.tables'. +If you add ''django_modules.app' to your ``INSTALLED_APPS`` setting, you +will be able to do: + +.. code-block:: django + + {% load tables %} + +Note: The tag requires the current request to be available as ``request`` +in the context (usually, this means activating the Django request context +processor). diff --git a/docs/types/index.rst b/docs/types/index.rst new file mode 100644 index 0000000..02b20e0 --- /dev/null +++ b/docs/types/index.rst @@ -0,0 +1,12 @@ +=========== +Table types +=========== + +Different types of tables are available: + +.. toctree:: + :maxdepth: 1 + + MemoryTable - uses dicts as the data source + ModelTable - wraps around a Django Model + SqlTable - is based on a raw SQL query \ No newline at end of file diff --git a/docs/types/memory.rst b/docs/types/memory.rst new file mode 100644 index 0000000..d43d7a1 --- /dev/null +++ b/docs/types/memory.rst @@ -0,0 +1,20 @@ +----------- +MemoryTable +----------- + +This table expects an iterable of ``dict`` (or compatible) objects as the +data source. Values found in the data that are not associated with a column +are ignored, missing values are replaced by the column default or ``None``. + +Sorting is done in memory, in pure Python. + +Dynamic Data +~~~~~~~~~~~~ + +If any value in the source data is a callable, it will be passed it's own +row instance and is expected to return the actual value for this particular +table cell. + +Similarily, the colunn default value may also be callable that will take +the row instance as an argument (representing the row that the default is +needed for). \ No newline at end of file diff --git a/docs/types/models.rst b/docs/types/models.rst new file mode 100644 index 0000000..b4e782d --- /dev/null +++ b/docs/types/models.rst @@ -0,0 +1,134 @@ +---------- +ModelTable +---------- + +This table type is based on a Django model. It will use the Model's data, +and, like a ``ModelForm``, can automatically generate it's columns based +on the mode fields. + +.. code-block:: python + + class CountryTable(tables.ModelTable): + id = tables.Column(sortable=False, visible=False) + class Meta: + model = Country + exclude = ['clicks'] + +In this example, the table will have one column for each model field, +with the exception of ``clicks``, which is excluded. The column for ``id`` +is overwritten to both hide it by default and deny it sort capability. + +When instantiating a ``ModelTable``, you usually pass it a queryset to +provide the table data: + +.. code-block:: python + + qs = Country.objects.filter(continent="europe") + countries = CountryTable(qs) + +However, you can also just do: + +.. code-block:: python + + countries = CountryTable() + +and all rows exposed by the default manager of the model the table is based +on will be used. + +If you are using model inheritance, then the following also works: + +.. code-block:: python + + countries = CountryTable(CountrySubclass) + +Note that while you can pass any model, it really only makes sense if the +model also provides fields for the columns you have defined. + +If you just want to use a ``ModelTable``, but without auto-generated +columns, you do not have to list all model fields in the ``exclude`` +``Meta`` option. Instead, simply don't specify a model. + + +Custom Columns +~~~~~~~~~~~~~~ + +You an add custom columns to your ModelTable that are not based on actual +model fields: + +.. code-block:: python + + class CountryTable(tables.ModelTable): + custom = tables.Column(default="foo") + class Meta: + model = Country + +Just make sure your model objects do provide an attribute with that name. +Functions are also supported, so ``Country.custom`` could be a callable. + + +Spanning relationships +~~~~~~~~~~~~~~~~~~~~~~ + +Let's assume you have a ``Country`` model, with a ``ForeignKey`` ``capital`` +pointing to the ``City`` model. While displaying a list of countries, +you might want want to link to the capital's geographic location, which is +stored in ``City.geo`` as a ``(lat, long)`` tuple, on, say, a Google Map. + +``ModelTable`` supports the relationship spanning syntax of Django's +database API: + +.. code-block:: python + + class CountryTable(tables.ModelTable): + city__geo = tables.Column(name="geo") + +This will add a column named "geo", based on the field by the same name +from the "city" relationship. Note that the name used to define the column +is what will be used to access the data, while the name-overwrite passed to +the column constructor just defines a prettier name for us to work with. +This is to be consistent with auto-generated columns based on model fields, +where the field/column name naturally equals the source name. + +However, to make table defintions more visually appealing and easier to +read, an alternative syntax is supported: setting the column ``data`` +property to the appropriate string. + +.. code-block:: python + + class CountryTable(tables.ModelTable): + geo = tables.Column(data='city__geo') + +Note that you don't need to define a relationship's fields as separate +columns if you already have a column for the relationship itself, i.e.: + +.. code-block:: python + + class CountryTable(tables.ModelTable): + city = tables.Column() + + for country in countries.rows: + print country.city.id + print country.city.geo + print country.city.founder.name + + +``ModelTable`` Specialties +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``ModelTable`` currently has some restrictions with respect to ordering: + +* Custom columns not based on a model field do not support ordering, + regardless of the ``sortable`` property (it is ignored). + +* A ``ModelTable`` column's ``default`` or ``data`` value does not affect + ordering. This differs from the non-model table behaviour. + +If a column is mapped to a method on the model, that method will be called +without arguments. This behavior differs from memory tables, where a +row object will be passed. + +If you are using callables (e.g. for the ``default`` or ``data`` column +options), they will generally be run when a row is accessed, and +possible repeatedly when accessed more than once. This behavior differs from +memory tables, where they would be called once, when the table is +generated. \ No newline at end of file diff --git a/docs/types/sql.rst b/docs/types/sql.rst new file mode 100644 index 0000000..a21eb88 --- /dev/null +++ b/docs/types/sql.rst @@ -0,0 +1,9 @@ +-------- +SqlTable +-------- + +This table is backed by an SQL query that you specified. It'll help you +ensure that pagination and sorting options are properly reflected in the +query. + +**Currently not implemented yet.** \ No newline at end of file diff --git a/requirements-dev.pip b/requirements-dev.pip index 3563a73..8c5e259 100644 --- a/requirements-dev.pip +++ b/requirements-dev.pip @@ -1,2 +1,3 @@ django nose +Sphinx -- 2.26.2