Skip to content

Commit

Permalink
Added Column Class, extra column option, documentation and extra test
Browse files Browse the repository at this point in the history
  • Loading branch information
TheRealVizard authored Aug 7, 2022
1 parent 2af4af1 commit 9bfa525
Show file tree
Hide file tree
Showing 16 changed files with 345 additions and 43 deletions.
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
pip install -r requirements.txt
pip install pytest
pip install pytest-cov
pip install pytest-django
- name: Generate coverage report
run: pytest --cov=./ --cov-report=xml
- name: Upload coverage to Codecov
Expand Down
41 changes: 25 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
# Django-table-sort

[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/TheRealVizard/django-table-sort/main.svg)](https://results.pre-commit.ci/latest/github/TheRealVizard/django-table-sort/main) [![codecov](https://codecov.io/gh/TheRealVizard/django-table-sort/branch/main/graph/badge.svg?token=KGXHPZ6HOB)](https://codecov.io/gh/TheRealVizard/django-table-sort) ![django-table-sort](https://img.shields.io/pypi/v/django-table-sort?color=blue) ![python-versions](https://img.shields.io/pypi/pyversions/django-table-sort) ![django-versions](https://img.shields.io/pypi/frameworkversions/django/django-table-sort?label=django) ![license](https://img.shields.io/pypi/l/django-table-sort?color=blue) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) ![downloads](https://img.shields.io/pypi/dm/django-table-sort)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/TheRealVizard/django-table-sort/main.svg)](https://results.pre-commit.ci/latest/github/TheRealVizard/django-table-sort/main) [![Documentation Status](https://readthedocs.org/projects/django-table-sort/badge/?version=latest)](https://django-table-sort.readthedocs.io/en/latest/?badge=latest) [![codecov](https://codecov.io/gh/TheRealVizard/django-table-sort/branch/main/graph/badge.svg?token=KGXHPZ6HOB)](https://codecov.io/gh/TheRealVizard/django-table-sort) ![django-table-sort](https://img.shields.io/pypi/v/django-table-sort?color=blue) ![python-versions](https://img.shields.io/pypi/pyversions/django-table-sort) ![django-versions](https://img.shields.io/pypi/frameworkversions/django/django-table-sort?label=django) ![license](https://img.shields.io/pypi/l/django-table-sort?color=blue) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) ![downloads](https://img.shields.io/pypi/dm/django-table-sort)

Create tables with sorting on the headers in Django templates.
Create tables with sorting links on the headers in Django templates.

This is currently [WIP](https://en.wikipedia.org/wiki/Work_in_process), so many other features will come in future releases.
## Installation

**First**, install with pip:

```bash
pip install django-sort-table
pip install django-table-sort
```

**Second**, add the app to your INSTALLED_APPS setting:

```python
INSTALLED_APPS = [
...,
"django-sort-table",
"django_table_sort",
...,
]
```
Expand All @@ -41,16 +41,25 @@ INSTALLED_APPS = [
In your _view.py_ file:

```python
table = TableSort(
request,
Person.objects.all(),
column_names={"name": "Firs Name", "age": "Age in years"},
sort_key_name="o",
column_css_clases="text-center",
table_css_clases="table",
table_id="id_table",
)
return render(request, "base.html", {"table": table})
class ListViewExample(ListView):
model = Person
template_name: str = "base.html"
ordering_key = "o"

def get_ordering(self) -> tuple:
return self.request.GET.getlist(
self.ordering_key, None
) # To make Django use the order

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["table"] = TableSort(
self.request,
self.object_list,
sort_key_name=self.ordering_key,
table_css_clases="table table-light table-striped table-sm",
)
return context
```

In your _template.html_ file:
Expand All @@ -64,10 +73,10 @@ Result:
The table is render with 2 link, one to Toggle the sort direction and another to remove the sort.

<p align="center">
<img width="268" height="120" src=".\result.png">
<img width="375" height="149" src=".\result.png">
</p>

You can filter by each field you declare as a column.
<p align="center">
<img width="361" height="45" src=".\url_result.png">
<img width="375" height="45" src=".\url_result.png">
</p>
29 changes: 29 additions & 0 deletions django_table_sort/columns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from typing import Callable

from django.db.models import Model


class BaseColumn:
def __init__(self, column_field: str, column_header: str) -> None:
self.column_field = column_field
self.column_header = column_header

def get_value(self, instance: Model):
"""Return the column value for a given instance."""
return getattr(instance, self.column_field)


class TableColumn(BaseColumn):
pass


class TableExtraColumn(BaseColumn):
def __init__(
self, column_field: str, column_header: str, function: Callable
) -> None:
super().__init__(column_field, column_header)
self.function = function

def get_value(self, instance: Model):
"""Return the column value for a given instance."""
return self.function(instance)
83 changes: 60 additions & 23 deletions django_table_sort/table.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,66 @@
from typing import Union
from __future__ import annotations

from django.db.models import QuerySet
from django.http import HttpRequest
from django.utils.html import format_html

from django_table_sort.columns import TableColumn
from django_table_sort.columns import TableExtraColumn


class TableSort:
"""Class to generate the table with the sort."""
r"""
Class to generate the table with the sort.
:param request: current ``HttpRequest`` to get the url lookups to create the links.
:param object_list: ``QuerySet`` or ``list`` to fill the table.
:param column_names: ``dict`` containing the pair {field_name: field_header}, this is used to set which field will be displayed and the proper headers. If no column_names are set and the object_list is a ``Queryset``, the all the fields in the Queryset's model will be used, and as the header their verbose_name.
:param sort_key_name: ``str`` for the key name that will be used to create the sort lookup in the urls.
:param table_css_clases: class to be applied to the table.
:param table_id: ``str`` for the id of the generated tabled.
:param **kwargs:
See below
:Keyword Arguments:
* *show_primary_key* (``bool``) --
Set if the primary key of the model should be displayed, default=``False``.
* *added_columns* (``dict``) --
Extra columns to show in the table, should be a ``dict`` object having the pair {(field_identifier,field_header):callable_function}. Note that field_identifier is to mark a difference to the models fields and callable_function needs to be a function that will receive an object and return an str to print in the table column
"""

def __init__(
self,
request: HttpRequest,
object_list: Union[QuerySet, list],
column_names: Union[None, dict[str, str]] = None,
object_list: QuerySet | list,
column_names: None | dict[str, str] = None,
sort_key_name: str = "o",
column_css_clases: str = "text-center",
table_css_clases: str = "table",
table_id: Union[str, None] = None,
table_id: str = None,
**kwargs,
):
self.request = request
self.object_list = object_list
self.sort_key_name = sort_key_name
self.column_css_clases = column_css_clases
self.column_names = column_names
self.table_css_clases = table_css_clases
self.table_id = table_id
self.kwargs = kwargs
if column_names is None and isinstance(object_list, QuerySet):
self.column_names = {
field.name: field.verbose_name.title()
self.column_names = [
TableColumn(field.name, field.verbose_name.title())
for field in object_list.model._meta.fields
if not field.primary_key or kwargs.get("show_primary_key", False)
}
]
elif column_names is not None:
self.column_names = [
TableColumn(column_name, column_header)
for column_name, column_header in column_names.items()
]
else:
self.column_names = []
self.column_names += [
TableExtraColumn(column_info[0], column_info[1], column_function)
for column_info, column_function in self.kwargs.get("added_columns", [])
]

def __str__(self):
"""Returns the table in HTML format."""
Expand All @@ -52,29 +81,37 @@ def render(self) -> str:
{body}
</tbody>
</table>
""",
body=self.get_table_body(),
headers=self.get_table_headers(),
table_clases=f' class="{self.table_css_clases}"'
if self.table_css_clases is not None
else "",
table_id=f' id="{self.table_id}"' if self.table_id is not None else "",
""".format(
body=self.get_table_body(),
headers=self.get_table_headers(),
table_clases=str(f' class="{self.table_css_clases}"')
if self.table_css_clases is not None
else "",
table_id=f' id="{self.table_id}"' if self.table_id is not None else "",
)
)

def get_table_body(self) -> str:
"""Generate the body of the table."""
body_str: str = ""
for obj in self.object_list:
row_str: str = ""
for column_name in self.column_names.keys():
row_str += f"<td>{getattr(obj,column_name)}</td>"
for column in self.column_names:
if isinstance(column, TableColumn):
row_str += f"<td>{column.get_value(obj)}</td>"
if isinstance(column, TableExtraColumn):
row_str += f"<td>{column.get_value(obj)}</td>"
body_str += f"<tr>{row_str}</tr>"
return format_html(body_str)
return body_str

def get_table_headers(self) -> str:
"""Generate the column with the link to sort."""
headers_str: str = ""
for field_to_sort, column_name in self.column_names.items():
for column in self.column_names:
if isinstance(column, TableExtraColumn):
headers_str += f"<th>{column.column_header}</th>"
continue
field_to_sort, column_name = column.column_field, column.column_header
url_start = self.request.GET.urlencode()
sort_url = self.request.GET.urlencode()
order_up = True
Expand Down Expand Up @@ -131,4 +168,4 @@ def get_table_headers(self) -> str:
show_sort="show" if not first_sort else "",
sort_url=sort_url,
)
return format_html(headers_str)
return headers_str
33 changes: 33 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from __future__ import annotations

import os
import sys
from datetime import datetime

import django

sys.path.insert(0, os.path.abspath(".."))

os.environ["DJANGO_SETTINGS_MODULE"] = "tests.settings"


django.setup()


project = "django-table-sort"
author = "Eduardo Leyva"
copyright = f"{datetime.now().year}, {author}"

version = "0.2.7"


extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx"]

intersphinx_mapping = {
"python": ("https://docs.python.org/3/", None),
"sphinx": ("https://www.sphinx-doc.org/en/master/", None),
}

intersphinx_disabled_domains = ["std"]

html_theme = "sphinx_rtd_theme"
49 changes: 49 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
========
django-table-sort
========

.. image:: https://results.pre-commit.ci/badge/github/TheRealVizard/django-table-sort/main.svg
:target: https://results.pre-commit.ci/latest/github/TheRealVizard/django-table-sort/main
:alt: pre-commit.ci status

.. image:: https://readthedocs.org/projects/django-table-sort/badge/?version=latest
:target: https://django-table-sort.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status

.. image:: https://codecov.io/gh/TheRealVizard/django-table-sort/branch/main/graph/badge.svg?token=KGXHPZ6HOB
:target: https://codecov.io/gh/TheRealVizard/django-table-sort
:alt: codecov

.. image:: https://img.shields.io/pypi/v/django-table-sort?color=blue
:alt: django-table-sort

.. image:: https://img.shields.io/pypi/pyversions/django-table-sort
:alt: python-versions

.. image:: https://img.shields.io/pypi/frameworkversions/django/django-table-sort?label=django
:alt: django-versions

.. image:: https://img.shields.io/pypi/l/django-table-sort?color=blue
:alt: license

.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black
:alt: code-style-black

.. image:: https://img.shields.io/pypi/dm/django-table-sort
:alt: downloads

Create tables with sorting links on the headers in Django templates.

.. toctree::
:maxdepth: 2
:caption: User Guide

installation
usage

.. toctree::
:maxdepth: 2
:caption: API documentation

tablesort
42 changes: 42 additions & 0 deletions docs/installation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Installation
============

1. Install the package
^^^^^^^^^^^^^^^^^^^^^^

You can install *django-table-sort* via pip_ from PyPI_:

.. code:: console
$ pip install django-table-sort
Requirements
------------

* Python 3.7+
* Django 3.0+

2. Add *django-table-sort* to your ``INSTALLED_APPS``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: python
INSTALLED_APPS = [
# ...,
"django_table_sort",
# ...,
]
3. Add the ``CSS`` file to your templates
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: html

<! -- Font Awesome 6 for the table icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" integrity="sha512-1sCRPdkRXhBV2PBLUdRb4tMg1w2YPf37qatUFeS7zlBy7jJI8Lf4VHwWfZZfpXtYSLy85pkm9GaYVYMfw5BC1A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<! -- css to use the header sort icons -->
<link rel="stylesheet" href="{% static 'django_table_sort.css' %}"/>


.. _PyPI: https://pypi.org/
.. _pip: https://pip.pypa.io/
2 changes: 2 additions & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
sphinx
django
12 changes: 12 additions & 0 deletions docs/tablesort.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
=========
Tables
=========

.. _table-sort-class:

TableSort
---------


.. autoclass:: django_table_sort.table.TableSort
:members:
Loading

0 comments on commit 9bfa525

Please sign in to comment.